集合框架详解

集合

一、集合概念

  • 对象的容器,实现了对对象常用的操作,类似数组功能。

二、集合和数组的区别

  • (1)数组长度固定,集合长度不固定

  • (2)数组可以存储基本类型和引用类型,集合只能存储引用类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ses9W2W-1638261990091)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1634776889909.png)]

1、Collection父接口

  • 特点:代表一组任意类型的对象,无序、无下标、不能重复。
  • 方法:
    • boolean add(Object obj) //添加一个对象。
    • boolean addAll(Collection c) //将一个集合中的所有对象添加到此集合中。
    • void clear() //清空此集合中的所有对象。
    • boolean contains(Object o) //比较此集合是否与指定对象相等。
    • boolean isEmpty() //判断此集合是否为空。
    • boolean remove(Object o) //在此集合中移除o对象。
    • int size() //返回此集合中的元素个数。
    • Object[] toArray() //将此集合转换成数组。

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Svjz3s8h-1638261990092)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1634779108846.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuRkq74O-1638261990092)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1634779120557.png)]

2、List子接口

  • 特点:有序、有下标、元素可以重复。

  • 方法:

    • void add(int index,Object o) //在index位置插入对象o
    • boolean addAll(int index , Collection c) // 将一个集合中元素添加到此集合的index位置。
    • Object get(int index) //返回集合中指定位置的元素
    • List subList(int fromIndex, int toIndex)//返回fromIndextoIndex之间的集合元素。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfjQaTqH-1638261990093)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1634866081810.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGQyM8nh-1638261990093)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1634864278730.png)]

3、List实现类*

ArrayList:【重点】:

  • 数组结构实现,查询快,增删慢。

  • JDK1.2版本,运行效率快、线程不安全

  • 源码分析

    • 默认容量大小:DEFAULT_CAPACITY = 10;

      • 注意*:如果没有向集合中添加任何元素时,容量为0,

        ​ 添加一个元素之后 容量是10,每次扩容是原来的1.5倍;

    • 存放元素的数组:transient Object[] elementData;

    • 实际元素个数:private int size;

    • 添加元素: .add();

      • //创建集合,size 0 容量0,扩容原来的1.5倍
        List<String,Object> list = new ArrayList<>();
        1、add方法如下:
        public boolean add(E e) {
                  
                      
        ensureCapacityInternal(size + 1);  // Increments modCount!!    elementData[size++] = e;   
        return true;
        }
        size为0。
        点击ensureCapacityInternal进入如下代码.
            
        2、ensureCapacityInternal方法:   
        private void ensureCapacityInternal(int minCapacity) {
                  
                  
                ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
            }
        minCapacity为1。
        点击calculateCapacity进入步骤3。
        点击ensureExplicitCapacity进入步骤43、calculateCapacity方法
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
                  
                  
                if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                  
                  
                    return Math.max(DEFAULT_CAPACITY, minCapacity);
                }
                return minCapacity;
            }
        其中minCapacity表示当前元素的size + 1,即为1。
        DEFAULT_CAPACITY表示默认的大小10if条件判断数组是否为空,刚构造一个list时数组是为空的,这里返回104、ensureExplicitCapacity方法
            private void ensureExplicitCapacity(int minCapacity) {
                  
                  
                modCount++;
                // overflow-conscious code
                if (minCapacity - elementData.length > 0)
                    grow(minCapacity);
            }
        这里的形参minCapacity为上面传入的DEFAULT_CAPACITY,即10。
        点击grow方法进入如下代码:
        
        5、grow方法
        private void grow(int minCapacity) {
                  
                  
                // overflow-conscious code
                int oldCapacity = elementData.length;
                int newCapacity = oldCapacity + (oldCapacity >> 1);
                if (newCapacity - minCapacity < 0)
                    newCapacity = minCapacity;
                if (newCapacity - MAX_ARRAY_SIZE > 0)
                    newCapacity = hugeCapacity(minCapacity);
                // minCapacity is usually close to size, so this is a win:
                elementData = Arrays.copyOf(elementData, newCapacity);
            }
        minCapacity为10,oldCapacity为0。扩容后newCapacity还是为0,
        因此满足下面的判断,将newCapacity赋值为10.
         if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
        
        MAX_ARRAY_SIZE 是一个非常大的数,这里我们不用管。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
               newCapacity = hugeCapacity(minCapacity);
        
        6、总结
        构造ArrayList时,数组初始为一个空数组。第一次调用add方法,会创建一个大小为10的数组。
         
            后续调用add方法
        这里以数组元素达到10触发第一次扩容为例。
        
        1、add方法如下:
        public boolean add(E e) {
                  
                  
             ensureCapacityInternal(size + 1);  // Increments modCount!!
             elementData[size++] = e;
             return true;
        }
        size为10。
        点击ensureCapacityInternal进入如下代码.
        
        2、ensureCapacityInternal方法:
        private void ensureCapacityInternal(int minCapacity) {
                  
                  
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
        
        minCapacity为11。
        点击calculateCapacity进入步骤3。
        点击ensureExplicitCapacity进入步骤43、calculateCapacity方法
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
                  
                  
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                  
                  
                 return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
        
        其中minCapacity表示当前元素的size + 1,即为11。
        elementData 现在并不是一个空数组,故if条件不满足
        直接返回minCapacity,即11.
        
        4、ensureExplicitCapacity方法
        private void ensureExplicitCapacity(int minCapacity) {
                  
                  
            modCount++;
        
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        这里的形参minCapacity为上面返回minCapacity,即11.
        点击grow方法进入如下代码:
        
        5、grow方法
        private void grow(int minCapacity) {
                  
                  
            // overflow-conscious code
              int oldCapacity = elementData.length;
              int newCapacity = oldCapacity + (oldCapacity >> 1);
              if (newCapacity - minCapacity < 0)
                  newCapacity = minCapacity;
              if (newCapacity - MAX_ARRAY_SIZE > 0)
                  newCapacity = hugeCapacity(minCapacity);
              // minCapacity is usually close to size, so this is a win:
              elementData = Arrays.copyOf(elementData, newCapacity);
        }
        minCapacity为11,oldCapacity为10(oldCapacity >> 1)相当于扩容一半得newCapacity=15。
        然后,将数据从老数组拷贝到新数组中
        
        elementData = Arrays.copyOf(elementData, newCapacity);
        
  • Vector:

    • 数组结构实现,查询快,增删慢;
  • JDK1.0版本,运行效率慢、线程安全;

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTlrhmUH-1638261990094)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635124313809.png)]

LinkedList:【重点】:

  • 链表结构实现,增删快,查询慢;

  • 存储结构:双向链表

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p7vP0jca-1638261990094)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635124935038.png)]

  • 添加元素: .add();

    public boolean add(E e) {
          
             
        linkLast(e);   
        return true;
    }
    
     void linkLast(E e) {
          
          
            final Node<E> l = last;
            final Node<E> newNode = new Node<>(l, e, null);
            last = newNode;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }
    可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少
        
    查询方法
        public E get(int index) {
          
          
            checkElementIndex(index);
            return node(index).item;
        }
        
        Node<E> node(int index) {
          
          
            // assert isElementIndex(index);
     
            if (index < (size >> 1)) {
          
          
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
          
          
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }
    
    由此可以看出是使用二分查找来看 index 离 size 中间距离来判断是从头结点正序查还是从尾节点倒序查。
    
    node()会以O(n/2)的性能去获取一个结点 如果索引值大于链表大小的一半,那么将从尾结点开始遍历
    这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。
    
    一开始我很费解,这是要干嘛?后来我才明白,代码要做的是:判断给定的索引值,若索引值大于整个链表长度的一半,则从后往前找,若索引值小于整个链表的长度的一半,则从前往后找。这样就可以保证,不管链表长度有多大,搜索的时候最多只搜索链表长度的一半就可以找到,大大提升了效率。
    
    总结:
    
    LinkedList 插入,删除都是移动指针效率很高。
    查找需要进行遍历查询,效率较低。
    
    

ArrayListLinkedList区别:

总结 区别:

    1、二者实现结构不同arraylist是基于数组,linkedlist是基于链表,他们的特性也是由其数据结构决定的。
    2、随机遍历访问时linkedlist的性能要低于arraylist.

    3、arraylist的初始化时默认10容量,而linkedlist默认初始化为空。

    4、linkedlist的增删要优于arraylist
    
    5、linkedlist如果进行增删操作只需找到对应节点更改里面内容,当进行遍历访问时,如果用for(随机访问		  遍历)总会进行一次列表的遍历操作,遍历每个的前后节点移动指针,所以性能很低。
    
    6、所以ArrayList具有数组的优缺点就是,有利于查找修改快,而增加和删除相对慢一些。例如,			   当增加或删除某一位时,需要移动大量元素。

4、泛型

  • Java泛型是JDK1.5引入的新特性,其本质是参数化类型,把类型作为参数传递

  • 常见形式有泛型类、泛型接口、泛型方法

  • 语法:

    • <T…>T称为类型占位符,表示一种引用类型。
  • 好处:

    • (1)提高代码的重用性

    • (2)防止类型转换异常,提高代码的安全性

泛型类

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFVnFSGI-1638261990094)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635209835864.png)]

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCHc23Sh-1638261990095)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635210089655.png)]

泛型接口

  • 第一种方式:
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2Qz6Dc6-1638261990095)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635211015939.png)]

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qa88FVrU-1638261990095)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635211049559.png)]

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kbC6yjF-1638261990096)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635211122163.png)]

  • 第二种方式:

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8ugbafq-1638261990096)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635211304578.png)]

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9zfGDw2-1638261990096)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635211339436.png)]

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tJSLg4K-1638261990097)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635211353723.png)]

泛型方法

  • 根据传入的参数类型直接进行匹配。

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K7yNYm1C-1638261990097)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635212303775.png)]

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fsrm7ox1-1638261990097)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635212316538.png)]

泛型集合

  • 概念:

    • 参数化泛型,类型安全的集合,强制集合元素的类型必须一致。
  • 特点:

    • 编译时即可检查,而非运行时抛出异常。
    • 访问时,不必类型转换(拆箱)。
    • 不同泛型之间引用不能相互赋值,泛型不存在多态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BfbKohND-1638261990098)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635295247698.png)]

5、Set集合

  • 特点:无序、无下标、元素不可重复
  • 方法:全部继承自Collection中的方法

Set实现类

HashSet :【重点】
  • 基于hashCode计算元素存放位置,元素不重复,哈希表
  • 当存入元素的哈希码相同时,会调用equals进行确认,如果为true,泽拒绝后者进入
  • 存储过程 :
    • (1)根据hashcode计算保存的位置,如果此位置为空,则直接保存,如果不为空执行第二步。
    • (2)在执行equals方法,如果equals方法为true,则任务是重复,否则,形成链表
    • 重写hashCode与equals方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdPxiyq8-1638261990098)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635297196435.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwUk53Dz-1638261990098)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635297225164.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTVGv5iQ-1638261990099)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635296390940.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-77d0RghF-1638261990099)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635296425479.png)]

TreeSet :
  • 基本排列顺序实现元素不重复。

  • 实现了SortedSet接口,对接口元素自动排序。

  • 元素对象的类型必须实现Comparable接口,指定排序规则。

  • 通过CompareTo方法确定是否为重复元素

  • 存储结构 ::红黑树

    • 红黑树就是只能有一个根节点,并且其子节点的右侧永远比左面的大,一次进行比较,红黑标记是为了保持两侧的树平衡,查找时不会因为一侧过多而浪费时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7upxPwzd-1638261990099)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635382065032.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1Fucqe9-1638261990100)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635382745768.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcfFTgRr-1638261990100)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635382959142.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VKqf3mz-1638261990100)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635383303573.png)]

6、Map集合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UElNIkI9-1638261990101)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635383936798.png)]

Map接口的特点:

  • 用于存储任意键值对(Key-value)
  • 键:无序,无下标,不允许重复(唯一)
  • 值:无序,无下标,允许重复

方法:

  • V put(K key,V value)//将对象存入到集合中,关联键值,key重复则覆盖原值。
  • Object get(Object key) //根据键获取对应的值
  • Set //返回所有key
  • Collection values //返回包含所有值的Collection集合
  • Set<Map,Entry<K,V>> //键值匹配的Set集合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-epzrBSJw-1638261990101)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635471318694.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rm6zmoXS-1638261990101)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635471343350.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ix0sj7HX-1638261990102)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635472974354.png)]

entrySet比keySet效率更高

7、Map 实现类*

  • HashMap【重点*】:
    • JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value.
    • 使用key可使hashcode和equals作为重复

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pL2pppxe-1638261990102)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635665942637.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FZbkHWeX-1638261990102)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635665959279.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YU0x5ssO-1638261990103)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635665975551.png)]

HashMapJDK1.8中的源码

- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M2mWEwPg-1638261990103)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635671418613.png)]

//1、哈希表基于map接口的实现,这个实现提供了map所有的操作,并且提供了key和value可以为null,(HashMapHashTable大致上市一样的除了hashmap是异步的和允许key和value为null),
这个类不确定map中元素的位置,特别要提的是,这个类也不确定元素的位置随着时间会不会保持不变。//假设哈希函数将元素合适的分到了每个桶(其实就是指的数组中位置上的链表)中,则这个实现为基本的操作(get、put)提供了稳定的性能,迭代这个集合视图需要的时间跟hashMap实例(key-value映射的数量)的容量(在桶中)
成正比,因此,如果迭代的性能很重要的话,就不要将初始容量设置的太高或者loadfactor设置的太低,【这里的桶,相当于在数组中每个位置上放一个桶装元素】

//HashMap的实例有两个参数影响性能,初始化容量(initialCapacity)和loadFactor加载因子,在哈希表中这个容量是桶的数量【也就是数组的长度】,一个初始化容量仅仅是在哈希表被创建时容量,在
容量自动增长之前加载因子是衡量哈希表被允许达到的多少的。当entry的数量在哈希表中超过了加载因子乘以当前的容量,那么哈希表被修改(内部的数据结构会被重新建立)所以哈希表有大约两倍的桶的数量

//通常来讲,默认的加载因子(0.75)能够在时间和空间上提供一个好的平衡,更高的值会减少空间上的开支但是会增加查询花费的时间(体现在HashMap类中get、put方法上),当设置初始化容量时,应该考虑到map中会存放
entry的数量和加载因子,以便最少次数的进行rehash操作,如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

//如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

  • HashMap的继承关系

public class HashMap<K,V> extendsAbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
}

HashMap继承自AbstractMap实现了Map,Cloneable,Serializable接口。

  • HashMap的属性

    /默认容量,1向左移位4个,00000001变成00010000,也就是2的4次方为16,使用移位是因为移位是计算机基础运算,效率比加减乘除快。/
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

  • //最大容量,2的30次方。

    static final int MAXIMUM_CAPACITY = 1 << 30;

  • //加载因子,用于扩容使用。

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

  • //当某个桶节点数量大于8时,会转换为红黑树。

    static final int TREEIFY_THRESHOLD = 8;

  • //当某个桶节点数量小于6时,会转换为链表,前提是它当前是红黑树结构。

    static final intUNTREEIFY_THRESHOLD = 6;

  • //当整个hashMap中元素数量大于64时,也会进行转为红黑树结构。

    static final int MIN_TREEIFY_CAPACITY = 64;

  • //存储元素的数组,transient关键字表示该属性不能被序列化

    transient Node<K,V>[] table;

  • //将数据转换成set的另一种存储形式,这个变量主要用于迭代功能。

    transient Set<Map.Entry<K,V>> entrySet;

  • //元素数量

    transient int size;

  • //统计该map修改的次数

    transient int modCount;

  • //临界值,也就是元素数量达到临界值时,会进行扩容。

    int threshold;

  • //也是加载因子,只不过这个是变量。

    final float loadFactor;

public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false) }

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    
    
    //获取该map的实际长度
    int s = m.size();
    if (s > 0) {
    
    
        //判断table是否初始化,如果没有初始化
        if (table == null) {
    
     // pre-size
            /**求出需要的容量,因为实际使用的长度=容量*0.75得来的,+1是因为小数相除,基本都不会是整数,容量大小不能为小数的,后面转换为int,多余的小数就要被丢掉,所以+1,例如,map实际长度22,22/0.75=29.3,所需要的容量肯定为30,有人会问如果刚刚好除得整数呢,除得整数的话,容量大小多1也没什么影响**/
            float ft = ((float)s / loadFactor) + 1.0F;
            //判断该容量大小是否超出上限。
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            /**对临界值进行初始化,tableSizeFor(t)这个方法会返回大于t值的,且离其最近的2次幂,例如t为29,则返回的值是32**/
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        //如果table已经初始化,则进行扩容操作,resize()就是扩容。
        else if (s > threshold)
            resize();
        //遍历,把map中的数据转到hashMap中。
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
    
    
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}
  • HashMap的put方法

​ public V put(K key, V value) {
​ /四个参数,第一个hash值,第四个参数表示如果该key存在值,如果为null的话,则插入新的value,最后一个参数,在hashMap中没有用,可以不用管,使用默认的即可/
​ return putVal(hash(key), key, value, false, true);
​ }

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h =key.hashCode()) ^ (h >>> 16);
}

   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                  boolean evict) {
       //tab 哈希数组,p 该哈希桶的首节点,n hashMap的长度,i 计算出的数组下标
       Node<K,V>[] tab; Node<K,V> p; int n, i;
       //获取长度并进行扩容,使用的是懒加载,table一开始是没有加载的,等put后才开始加载
       if ((tab = table) == null || (n = tab.length) == 0)
           n = (tab = resize()).length;
       /**如果计算出的该哈希桶的位置没有值,则把新插入的key-value放到此处,此处就算没有插入成功,也就是发生哈希冲突时也会把哈希桶的首节点赋予p**/
       if ((p = tab[i = (n - 1) & hash]) == null)
           tab[i] = newNode(hash, key, value, null);
       //发生哈希冲突的几种情况
       else {
           // e 临时节点的作用, k 存放该当前节点的key 
           Node<K,V> e; K k;
           //第一种,插入的key-value的hash值,key都与当前节点的相等,e = p,则表示为首节点
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           //第二种,hash值不等于首节点,判断该p是否属于红黑树的节点
           else if (p instanceof TreeNode)
               /**为红黑树的节点,则在红黑树中进行添加,如果该节点已经存在,则返回该节点(不为null),该值很重要,用来判断put操作是否成功,如果添加成功返回null**/
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           //第三种,hash值不等于首节点,不为红黑树的节点,则为链表的节点
           else {
               //遍历该链表
               for (int binCount = 0; ; ++binCount) {
                   //如果找到尾部,则表明添加的key-value没有重复,在尾部进行添加
                   if ((e = p.next) == null) {
                       p.next = newNode(hash, key, value, null);
                       //判断是否要转换为红黑树结构
                       if (binCount >= TREEIFY_THRESHOLD - 1) 
                           treeifyBin(tab, hash);
                       break;
                   }
                   //如果链表中有重复的key,e则为当前重复的节点,结束循环
                   if (e.hash == hash &&
                       ((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                   p = e;
               }
           }
           //有重复的key,则用待插入值进行覆盖,返回旧值。
           if (e != null) { 
               V oldValue = e.value;
               if (!onlyIfAbsent || oldValue == null)
                   e.value = value;
               afterNodeAccess(e);
               return oldValue;
           }
       }
       //到了此步骤,则表明待插入的key-value是没有key的重复,因为插入成功e节点的值为null
       //修改次数+1
       ++modCount;
       //实际长度+1,判断是否大于临界值,大于则扩容
       if (++size > threshold)
           resize();
       afterNodeInsertion(evict);
       //添加成功
       return null;
   }

可以看到这里主要有以下几步:
1、根据key计算出在数组中存储的下标
2、根据使用的大小,判断是否需要扩容。
3、根据数组下标判断是否当前下标已存储数据,如果没有则直接插入。
4、如果存储了则存在哈希冲突,判断当前entry的key是否相等,如果相等则替换,否则判断下一个节点是否为空,为空则直接插入,否则取下一节点重复上述步骤。
5、判断链表长度是否大于8当达到8时转换为红黑树。
下面我们看下HashMap的扩容函数resize()

final Node<K,V>[] resize() {
    
    
    //把没插入之前的哈希数组做我诶oldTal
    Node<K,V>[] oldTab = table;
    //old的长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //old的临界值
    int oldThr = threshold;
    //初始化new的长度和临界值
    int newCap, newThr = 0;
    //oldCap > 0也就是说不是首次初始化,因为hashMap用的是懒加载
    if (oldCap > 0) {
    
    
        //大于最大值
        if (oldCap >= MAXIMUM_CAPACITY) {
    
    
            //临界值为整数的最大值
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //标记##,其它情况,扩容两倍,并且扩容后的长度要小于最大值,old长度也要大于16
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //临界值也扩容为old的临界值2倍
            newThr = oldThr << 1; 
    }
    /**如果oldCap<0,但是已经初始化了,像把元素删除完之后的情况,那么它的临界值肯定还存在,        
       如果是首次初始化,它的临界值则为0
    **/
    else if (oldThr > 0) 
        newCap = oldThr;
    //首次初始化,给与默认的值
    else {
    
                   
        newCap = DEFAULT_INITIAL_CAPACITY;
        //临界值等于容量*加载因子
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //此处的if为上面标记##的补充,也就是初始化时容量小于默认值16的,此时newThr没有赋值
    if (newThr == 0) {
    
    
        //new的临界值
        float ft = (float)newCap * loadFactor;
        //判断是否new容量是否大于最大值,临界值是否大于最大值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //把上面各种情况分析出的临界值,在此处真正进行改变,也就是容量和临界值都改变了。
    threshold = newThr;
    //表示忽略该警告
    @SuppressWarnings({
    
    "rawtypes","unchecked"})
        //初始化
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //赋予当前的table
    table = newTab;
    //此处自然是把old中的元素,遍历到new中
    if (oldTab != null) {
    
    
        for (int j = 0; j < oldCap; ++j) {
    
    
            //临时变量
            Node<K,V> e;
            //当前哈希桶的位置值不为null,也就是数组下标处有值,因为有值表示可能会发生冲突
            if ((e = oldTab[j]) != null) {
    
    
                //把已经赋值之后的变量置位null,当然是为了好回收,释放内存
                oldTab[j] = null;
                //如果下标处的节点没有下一个元素
                if (e.next == null)
                    //把该变量的值存入newCap中,e.hash & (newCap - 1)并不等于j
                    newTab[e.hash & (newCap - 1)] = e;
                //该节点为红黑树结构,也就是存在哈希冲突,该哈希桶中有多个元素
                else if (e instanceof TreeNode)
                    //✨✨✨把此树进行转移到newCap中✨✨✨
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else {
    
     /**此处表示为链表结构,同样把链表转移到newCap中,就是把链表遍历后,把值转过去,在置位null**/
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
    
    
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
    
    
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
    
    
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
    
    
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
    
    
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    //返回扩容后的hashMap
    return newTab;
}

前面主要介绍了, HashMap的结构为数组+ 链表(红黑树)。
总结一下上面的逻辑就是:
1、对数组进行扩容,
2、扩容后重新计算hashCode也就是key的下标,将原数据塞到新扩容后的数据结构中。
3、当存在hash冲突时,在数组后面以链表的形式追加到后面,当链表长度达到8时,就会将链表转换为红黑树。
那么对于红黑树新增一个节点 ,我们考虑到前面所说的红黑树的性质。就需要对红黑树做调整,是红黑树达到平衡。

  • Hashtable:
  • JDK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。
  • Properties:
    • Hashtables的子类,要求key和value都是String.常用于配置文件的读取。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCLyXMTF-1638261990104)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635671874579.png)]

TreeMap:

  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。
  • 存储结构:红黑树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8jdvgLuq-1638261990104)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635672579257.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1CO0Cbj-1638261990105)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635672616647.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpscRY81-1638261990105)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1635672666291.png)]

Colletions工具类

  • 概念:集合工具类,定义了除了存取以外的集合常用方法。

  • 方法:

    • public static void reverse(List<?> list) //反转集合中元素的顺序

    • public static void shuffle(List<?> list) //随机重置集合元素的顺序

    • public static void sort(List<?> list) //升序排序(元素类型必须实现Comparable接口)

猜你喜欢

转载自blog.csdn.net/qq_42023701/article/details/121636917