03.Java数据结构问题

1,372

目录介绍

  • 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万字],将会陆续发表到网上,转载请注明出处,谢谢!
  • 链接地址:github.com/yangchong21…
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有博客将陆续开源到GitHub!

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

  • System.arraycopy()和Arrays.copyOf()方法区别?
    • 比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法让数组自己复制自己实现让index开始之后的所有成员后移一个位置:
      /**
       * 在此列表中的指定位置插入指定的元素。 
       * 先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
       * 再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
       */
      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()方法
      • 技术博客大总结
      • 区别:
        • 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.关于博客汇总链接

02.关于我的博客