乐趣区

03.Java数据结构问题

目录介绍

3.0.0.1 在 arrayList 中 System.arraycopy()和 Arrays.copyOf()方法区别联系?System.arraycopy()和 Arrays.copyOf()代码说明?
3.0.0.2 SparseArray 基本介绍,相比 HashMap 为什么性能会好?
3.0.0.3 Arrays 和 Collections 对于 sort 的不同实现原理?说一说它们的区别……
3.0.0.4 Java 集合框架中有哪些类?都有什么特点?Java 集合的快速失败机制“fail-fast”?
3.0.0.5 ArrayList,Vector 和 LinkList 的区别,底层分别是怎么实现的,存储空间是如何扩容的?什么是加载因子?
3.0.0.6 如何理解 ArrayList 的扩容消耗?Arrays.asList 方法后的 List 可以扩容吗?ArrayList 如何序列化?
3.0.0.7 如何理解 list 集合读写机制和读写效率?什么是 CopyOnWriteArrayList,它与 ArrayList 有何不同?
3.0.1.0 HashSet 和 TreeSet 的区别?是如何保证唯一值的,底层怎么做到的?
3.0.1.5 HashMap 和 Hashtable 的区别?HashMap 在 put、get 元素的过程?体现了什么数据结构?
3.0.1.6 如何保证 HashMap 线程安全?底层怎么实现的?HashMap 是有序的吗?如何实现有序?
3.0.1.7 HashMap 存储两个对象的 hashcode 相同会发生什么?如果两个键的 hashcode 相同,你如何获取值对象?
3.0.1.8 HashMap 为什么不直接使用 hashCode()处理后的哈希值直接作为 table 的下标?
3.0.1.9 为什么 HashMap 中 String、Integer 这样的包装类适合作为 K?为啥不用其他作 key 值?
3.0.2.0 HashMap 是如何扩容的?如何理解 HashMap 的大小超过了负载因子定义的容量?重新调整 HashMap 大小存在什么问题吗?

好消息

博客笔记大汇总【15 年 10 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 500 篇[近 100 万字],将会陆续发表到网上,转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有博客将陆续开源到 GitHub!

3.0.0.1 在 arrayList 中 System.arraycopy()和 Arrays.copyOf()方法区别联系?System.arraycopy()和 Arrays.copyOf()代码说明?

System.arraycopy()和 Arrays.copyOf()方法区别?

比如下面 <font color=”red”>add(int index, E element)</font> 方法就很巧妙的用到了 <font color=”red”>arraycopy()方法 </font> 让数组自己复制自己实现让 index 开始之后的所有成员后移一个位置:
/**
* 在此列表中的指定位置插入指定的元素。
* 先调用 rangeCheckForAdd 对 index 进行界限检查;然后调用 ensureCapacityInternal 方法保证 capacity 足够大;

*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
//arraycopy()方法实现数组自己复制自己
//elementData: 源数组;index: 源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置;size – index:要复制的数组元素的数量;
System.arraycopy(elementData, index, elementData, index + 1, size – index);
elementData[index] = element;
size++;
}
“`
– 如 toArray()方法中用到了 copyOf()方法
“`
/**
* 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
* 返回的数组将是“安全的”,因为该列表不保留对它的引用。(换句话说,这个方法必须分配一个新的数组)。
* 因此,调用者可以自由地修改返回的数组。此方法充当基于阵列和基于集合的 API 之间的桥梁。
*/
public Object[] toArray() {
//elementData:要复制的数组;size:要复制的长度
return Arrays.copyOf(elementData, size);
}
“`
– 两者联系与区别
– 看了上面的两者源代码可以发现 `copyOf()` 内部调用了 `System.arraycopy()` 方法
– [技术博客大总结](https://github.com/yangchong211/YCBlogs)
– 区别:
– 1.arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置
– 2.copyOf()是系统自动在内部新建一个数组,并返回该数组。

System.arraycopy()和 Arrays.copyOf()代码说明?

使用 System.arraycopy()方法
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = new int[10];
a[0] = 0;
a[1] = 1;
a[2] = 2;
a[3] = 3;
System.arraycopy(a, 2, a, 3, 3);
a[2]=99;
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}

// 结果:
//0 1 99 2 3 0 0 0 0 0

使用 Arrays.copyOf()方法。技术博客大总结
public static void main(String[] args) {
int[] a = new int[3];
a[0] = 0;
a[1] = 1;
a[2] = 2;
int[] b = Arrays.copyOf(a, 10);
System.out.println(“b.length”+b.length);
// 结果:
//10
}

得出结论

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组。

3.0.0.2 SparseArray 基本介绍,相比 HashMap 为什么性能会好?

位于 android.util,Android 中的数据结构,针对移动端做了优化,在数据量比较少的情况下,性能会好过 HashMap,类似于 HashMap,key:int ,value:object。
1.key 和 value 采用数组进行存储。存储 key 的数组是 int 类型,不需要进行装箱操作。提供了速度。
2. 采用二分查找法,在插入进行了排序,所以两个数组是按照从小到大进行排序的。
3. 在查找的时候,进行二分查找,数据量少的情况下,速度比较快。

3.0.0.3 Arrays 和 Collections 对于 sort 的不同实现原理?说一说它们的区别……

1、Arrays.sort()
该算法是一个经过调优的快速排序,此算法在很多数据集上提供 N *log(N)的性能,这导致其他快速排序会降低二次型性能。

2、Collections.sort()
该算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素效益高子列表中的最低元素,则忽略合并)。此算法可提供保证的 N *log(N)的性能,此实现将指定列表转储到一个数组中,然后再对数组进行排序,在重置数组中相应位置处每个元素的列表上进行迭代。

它们的区别
技术博客大总结

3.0.0.4 Java 集合框架中有哪些类?都有什么特点?Java 集合的快速失败机制“fail-fast”?

可将 Java 集合框架大致可分为 Set、List、Queue 和 Map 四种体系

Set:代表无序、不可重复的集合,常见的类如 HashSet、TreeSet
List:代表有序、可重复的集合,常见的类如动态数组 ArrayList、双向链表 LinkedList、可变数组 Vector
Map:代表具有映射关系的集合,常见的类如 HashMap、LinkedHashMap、TreeMap
Queue:代表一种队列集合

Java 集合的快速失败机制“fail-fast”

技术博客大总结

java 集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程 1、线程 2),线程 1 通过 Iterator 在遍历集合 A 中的元素,在某个时候线程 2 修改了集合 A 的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生 fail-fast 机制。

原因:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next()遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:

1. 在遍历过程中,所有涉及到改变 modCount 值得地方全部加上 synchronized。
2. 使用 CopyOnWriteArrayList 来替换 ArrayList

3.0.0.5 ArrayList,Vector 和 LinkList 的区别,底层分别是怎么实现的?存储空间是如何扩容的?什么是加载因子?

ArrayList

ArrayList 的底层结构是数组,可用索引实现快速查找;是动态数组,相比于数组容量可实现动态增长。
ArrayList 非线程安全,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList;默认初始容量为 10,每次扩容为原来的 1.5 倍

Vector
和 ArrayList 几乎是一样的,Vector 使用了 synchronized 关键字,是线程安全的,比 ArrayList 开销更大,访问更慢;默认初始容量为 10,默认每次扩容为原来的 2 倍,可通过 capacityIncrement 属性设置

LinkList
LinkedList 底层结构是链表,增删速度快;是一个双向循环链表,也可以被当作堆栈、队列或双端队列

3.0.0.6 如何理解 ArrayList 的扩容消耗?Arrays.asList 方法后的 List 可以扩容吗?ArrayList 如何序列化?

如何理解 ArrayList 的扩容消耗
由于 ArrayList 使用 elementData = Arrays.copyOf(elementData, newCapacity); 进行扩容,而每次都会重新创建一个 newLength 长度的数组,所以扩容的空间复杂度为 O(n), 时间复杂度为 O(n)
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

Arrays.asList 方法后的 List 可以扩容吗?
不能,asList 返回的 List 为只读的。其原因为:asList 方法返回的 ArrayList 是 Arrays 的一个内部类,并且没有实现 add,remove 等操作

List 怎么实现排序?

实现排序,可以使用自定义排序:list.sort(new Comparator(){…})
或者使用 Collections 进行快速排序:Collections.sort(list)

ArrayList 如何序列化?

ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
技术博客大总结
保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。

transient Object[] elementData; // non-private to simplify nested class access
ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt(); // ignored
if (size > 0) {
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。
ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);

3.0.0.7 如何理解 list 集合读写机制和读写效率?什么是 CopyOnWriteArrayList,它与 ArrayList 有何不同?

读写机制

ArrayList 在执行插入元素是超过当前数组预定义的最大值时,数组需要扩容,扩容过程需要调用底层 System.arraycopy()方法进行大量的数组复制操作;在删除元素时并不会减少数组的容量(如果需要缩小数组容量,可以调用 trimToSize()方法);在查找元素时要遍历数组,对于非 null 的元素采取 equals 的方式寻找。
LinkedList 在插入元素时,须创建一个新的 Entry 对象,并更新相应元素的前后元素的引用;在查找元素时,需遍历链表;在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将此元素删除即可。
Vector 与 ArrayList 仅在插入元素时容量扩充机制不一致。对于 Vector,默认创建一个大小为 10 的 Object 数组,并将 capacityIncrement 设置为 0;当插入元素数组大小不够时,如果 capacityIncrement 大于 0,则将 Object 数组的大小扩大为现有 size+capacityIncrement;如果 capacityIncrement<=0, 则将 Object 数组的大小扩大为现有大小的 2 倍。

读写效率

ArrayList 对元素的增加和删除都会引起数组的内存分配空间动态发生变化。因此,对其进行插入和删除速度较慢,但检索速度很快。
LinkedList 由于基于链表方式存放数据,增加和删除元素的速度较快,但是检索速度较慢。

什么是 CopyOnWriteArrayList,它与 ArrayList 有何不同?

CopyOnWriteArrayList 是 ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。相比较于 ArrayList 它的写操作要慢一些,因为它需要实例的快照。
CopyOnWriteArrayList 中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的 ”=” 将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用,绝对不会发生 ConcurrentModificationException,因此 CopyOnWriteArrayList 适合使用在读操作远远大于写操作的场景里,比如缓存。
技术博客大总结

3.0.1.0 HashSet 和 TreeSet 的区别?是如何保证唯一值的,底层怎么做到的?

HashSet
不能保证元素的排列顺序;使用 Hash 算法来存储集合中的元素,有良好的存取和查找性能;通过 equal()判断两个元素是否相等,并两个元素的 hashCode()返回值也相等

TreeSet
是 SortedSet 接口的实现类,根据元素实际值的大小进行排序;采用红黑树的数据结构来存储集合元素;支持两种排序方法:自然排序(默认情况)和定制排序。前者通过实现 Comparable 接口中的 compareTo()比较两个元素之间大小关系,然后按升序排列;后者通过实现 Comparator 接口中的 compare()比较两个元素之间大小关系,实现定制排列

3.0.1.5 HashMap 和 Hashtable 的区别?HashMap 在 put、get 元素的过程?体现了什么数据结构?

HashMap
基于 AbstractMap 类,实现了 Map、Cloneable(能被克隆)、Serializable(支持序列化)接口;非线程安全;允许存在一个为 null 的 key 和任意个为 null 的 value;采用链表散列的数据结构,即数组和链表的结合;初始容量为 16,填充因子默认为 0.75,扩容时是当前容量翻倍,即 2capacity

Hashtable

基于 Map 接口和 Dictionary 类;线程安全,开销比 HashMap 大,如果多线程访问一个 Map 对象,使用 Hashtable 更好;不允许使用 null 作为 key 和 value;底层基于哈希表结构;初始容量为 11,填充因子默认为 0.75,扩容时是容量翻倍 +1,即 2capacity+1
HashTable 里使用的是 synchronized 关键字,这其实是对对象加锁,锁住的都是对象整体,当 Hashtable 的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。

HashMap 在 put、get 元素的过程

向 Hashmap 中 put 元素时,首先判断 key 是否为空,为空则直接调用 putForNullKey(),不为空则计算 key 的 hash 值得到该元素在数组中的下标值;如果数组在该位置处没有元素,就直接保存;如果有,还要比较是否存在相同的 key,存在的话就覆盖原来 key 的 value,否则将该元素保存在链头,先保存的在链尾。
从 Hashmap 中 get 元素时,计算 key 的 hash 值找到在数组中的对应的下标值,返回该 key 对应的 value 即可,如果有冲突就遍历该位置链表寻找 key 相同的元素并返回对应的 value

体现了什么数据结构

HashMap 采用链表散列的数据结构,即数组和链表的结合,在 Java8 后又结合了红黑树,当链表元素超过 8 个将链表转换为红黑树
技术博客大总结

3.0.1.6 如何保证 HashMap 线程安全?底层怎么实现的?HashMap 是有序的吗?如何实现有序?

使用 ConcurrentHashMap 可保证线程安全

ConcurrentHashMap 是线程安全的 HashMap,它采取锁分段技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。在 JDK1.8 中对 ConcurrentHashmap 做了两个改进:

取消 segments 字段,直接采用 transient volatile HashEntry<K,V>[] table 保存数据,将数组元素作为锁,对每一行数据进行加锁,可减少并发冲突的概率
数据结构由“数组+单向链表”变为“数组+单向链表+红黑树”,使得查询的时间复杂度可以降低到 O(logN),改进一定的性能。

通俗一点解释:ConcurrentHashMap 引入了分割 (Segment),可以理解为把一个大的 Map 拆分成 N 个小的 HashTable,在 put 方法中,会根据 hash(paramK.hashCode()) 来决定具体存放进哪个 Segment,如果查看 Segment 的 put 操作,我们会发现内部使用的同步机制是基于 lock 操作的,这样就可以对 Map 的一部分(Segment)进行上锁,这样影响的只是将要放入同一个 Segment 的元素的 put 操作,保证同步的时候,锁住的不是整个 Map(HashTable 就是这么做的),相对于 HashTable 提高了多线程环境下的性能,因此 HashTable 已经被淘汰了。技术博客大总结

使用 LinkedHashMap 可实现有序
HashMap 是无序的,而 LinkedHashMap 是有序的 HashMap,默认为插入顺序,还可以是访问顺序,基本原理是其内部通过 Entry 维护了一个双向链表,负责维护 Map 的迭代顺序

3.0.1.7 HashMap 存储两个对象的 hashcode 相同会发生什么?如果两个键的 hashcode 相同,你如何获取值对象?

HashMap 存储两个对象的 hashcode 相同会发生什么?

错误回答:因为 hashcode 相同,所以两个对象是相等的,HashMap 将会抛出异常,或者不会存储它们。
正确回答:两个对象就算 hashcode 相同,但是它们可能并不相等。如果不明白,可以先看看我的这篇博客:Hash 和 HashCode 深入理解。回答“因为 hashcode 相同,所以它们的 bucket 位置相同,‘碰撞’会发生。因为 HashMap 使用链表存储对象,这个 Entry(包含有键值对的 Map.Entry 对象)会存储在链表中。

HashMap1.7 和 1.8 的区别

在 JDK1.6,JDK1.7 中,HashMap 采用数组 + 链表实现,即使用链表处理冲突,同一 hash 值的链表都存储在一个链表里。但是当位于一个链表中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。
JDK1.8 中,HashMap 采用位数组 + 链表 + 红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

如果两个键的 hashcode 相同,你如何获取值对象?

当调用 get()方法,HashMap 会使用键对象的 hashcode 找到 bucket 位置,然后获取值对象。当然如果有两个值对象储存在同一个 bucket,将会遍历链表直到找到值对象。
在没有值对象去比较,如何确定确定找到值对象的?因为 HashMap 在链表中存储的是键值对,找到 bucket 位置之后,会调用 keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
技术博客大总结

3.0.1.8 HashMap 为什么不直接使用 hashCode()处理后的哈希值直接作为 table 的下标?

不直接使用 hashCode()处理后的哈希值
hashCode()方法返回的是 int 整数类型,其范围为 -(2^31)~(2^31-1),约有 40 亿个映射空间,而 HashMap 的容量范围是在 16(初始化默认值)~2 ^ 30,HashMap 通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过 hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;

HashMap 是使用了哪些方法来有效解决哈希冲突的

1. 使用链地址法(使用散列表)来链接拥有相同 hash 值的数据;
2. 使用 2 次扰动函数(hash 函数)来降低哈希冲突的概率,使得数据分布更平均;
3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

如何解决匹配存储位置问题

HashMap 自己实现了自己的 hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
在保证数组长度为 2 的幂次方的时候,使用 hash()运算之后的值与运算(&)(数组长度 – 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为 2 的幂次方时,h&(length-1)才等价于 h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;

为什么数组长度要保证为 2 的幂次方呢?

只有当数组长度为 2 的幂次方时,h&(length-1)才等价于 h%length,即实现了 key 的定位,2 的幂次方也可以减少冲突次数,提高 HashMap 的查询效率;
技术博客大总结
如果 length 为 2 的次幂 则 length-1 转化为二进制必定是 11111……的形式,在于 h 的二进制与操作效率会非常的快,而且空间不浪费;如果 length 不是 2 的次幂,比如 length 为 15,则 length – 1 为 14,对应的二进制为 1110,在于 h 与操作,最后一位都为 0,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。

3.0.1.9 为什么 HashMap 中 String、Integer 这样的包装类适合作为 K?为啥不用其他作 key 值?

为什么 HashMap 中 String、Integer 这样的包装类适合作为 K?

String、Integer 等包装类的特性能够保证 Hash 值的不可更改性和计算准确性,能够有效的减少 Hash 碰撞的几率

都是 final 类型,即不可变性,保证 key 的不可更改性,不会存在获取 hash 值不同的情况
内部已重写了 equals()、hashCode()等方法,遵守了 HashMap 内部的规范(不清楚可以去上面看看 putValue 的过程),不容易出现 Hash 值计算错误的情况;

想要让自己的 Object 作为 K 应该怎么办呢?

重写 hashCode()和 equals()方法

重写 hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的 Hash 碰撞;
重写 equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非 null 的引用值 x,x.equals(null)必须返回 false 的这几个特性,目的是为了保证 key 在哈希表中的唯一性;

总结

采用合适的 equals()和 hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的 hashcode,这将提高整个获取对象的速度,使用 String,Interger 这样的 wrapper 类作为键是非常好的选择。
技术博客大总结

3.0.2.0 HashMap 是如何扩容的?如何理解 HashMap 的大小超过了负载因子定义的容量?重新调整 HashMap 大小存在什么问题吗?

HashMap 是为啥要扩容
当链表数组的容量超过初始容量 * 加载因子(默认 0.75)时,再散列将链表数组扩大 2 倍,把原链表数组的搬移到新的数组中。为什么需要使用加载因子?为什么需要扩容呢?因为如果填充比很大,说明利用的空间很多,如果一直不进行扩容的话,链表就会越来越长,这样查找的效率很低,扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率。

如何理解 HashMap 的大小超过了负载因子 (load factor) 定义的容量?
默认的负载因子大小为 0.75,也就是说,当一个 map 填满了 75% 的 bucket 时候,和其它集合类 (如 ArrayList 等) 一样,将会创建原来 HashMap 大小的两倍的 bucket 数组,来重新调整 map 的大小,并将原来的对象放入新的 bucket 数组中。这个过程叫作 rehashing,因为它调用 hash 方法找到新的 bucket 位置。

重新调整 HashMap 大小存在什么问题吗?技术博客大总结
当多线程的情况下,可能产生条件竞争。当重新调整 HashMap 大小的时候,确实存在条件竞争,因为如果两个线程都发现 HashMap 需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的 bucket 位置的时候,HashMap 并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。

其他介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

退出移动版