java集合--- 老韩

java集合— 老韩

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


Collection 接口 遍历原始 使用 的 是 迭代器 Iterator(迭代器)

1.Iterator 对象 称为 迭代器, 主要 用于 Collection 集合中的 元素

  1. 实现了 Collection 接口 的 集合类 都有 一个 iterator(),用于 返回 实现了 Iterator 接口的对象
    即可以返回 一个 迭代器
  2. Iterator 仅用于 遍历集合 ,本身 不存放对象

迭代器的 原理 :

Iterator iterator(); 得到 一个 集合的 迭代器

通过 hashNext() 返回 是否为 ture 来判断 是否 还有 下一个元素

如果 返回 ture , 使用 next()
next() 作用 : 1. 指针下移 2. 将 下移的 集合位置 上的元素返回

在这里插入图片描述
注意点 : 在调用 iterator.next() 方法 之前 必须 调用 iterator. hasNext()进行检查,
如果不调用的 话 ,且 下一条 记录 无效 ,直接 调用 iteator.next() 就会 抛出 NoSuchElementExection异常

col.iterator();

在这里插入图片描述

如果还想再次遍历 ,需要 重置迭代器
在这里插入图片描述


Collection 接口 使用 增强 for循环 遍历 底层的话 还是使用 迭代器原理
在底层 new 了 一个 迭代器,然后使用 hashNext() 和 next() 进行输出

在这里插入图片描述

Collection 接口 使用 for循环 遍历


List 接口

  1. List 集合中的 元素 有序 (取出和添加顺序 一致 ) ,且可以重复
  2. List 集合中的 每个元素 都有对应的顺序索引, 即支持 索引。

在这里插入图片描述

contains(Object o);   //判断当前元素是否存在

ArrayList 底层原理 源码分析

底层是使用 数组实现的 线程不安全 add方法没有使用 线程安全修饰词

源码结论:

  1. ArrayList 底层维护了 一个 Object对象的 数组 ElementData
    transient Object[] elementData; //transient 修饰的 表示短暂的, 不会被序列化
  2. 当使用 默认构造器的时候 , elementData 数组对象 默认的初始 容量为0 ,当添加 元素的 时候,扩容为 原来的 1.5 倍。
  3. 当使用 指定大小 的 构造器的 时候, 初始的 elementData 数组对象 为 指定大小的容量, 当需要扩容的时候 扩容为 原来的 1.5倍

原理文件图: ArrayList add 和 扩容原理.drawio

为什么扩容是1.5倍?

初始容量 的 话 为 10 ,第二次 才 扩容为原来的 1.5倍。

扩容操作 就是 对 原数组的容量 进行 右移 >>1 为, 即 /2 , 加上原数组容量 就为 1.5 倍

扩容操作中,使用的是 Arrays.copyOf() 操作 进行 数组的复制。


add();

每次执行 add 操作的时候 都有 一个 modCount 记录集合被修改的次数

进行add 操作的时候, 对 size(默认为0) +1 范围检查, 判断 size 是否 大于 数组的 长度, 如果 大于 则进行扩容 操作 。然后 把 加入的 元素 添加到数组中。


remove(index);

进行 index 进行判断 是否 超过 数组的长度,超过就 抛出异常。

modCount 修改次数 ++; numMoved = 数组长度 - index -1 ;移除的下标;

使用 System.arrayCopy()进行数组的 复制。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


Vector 底层原理 源码分析

底层 也是 一个 对象数组 , 使用了 protected Object[] elementData;

Vector 是 线程 同步的 ,线程安全, 因为 使用了 synchronized 关键字

public synchronized boolean add(E e) {    
    modCount++;    
    ensureCapacityHelper(elementCount + 1);    
    elementData[elementCount++] = e;    
    return true;
}

扩容:

无参构造的话, 默认 是 10 , 容量满了 之后 使用的 是 2倍 扩容

指定容量的话, 每次 直接 2倍扩容

源码解读

public Vector() {    
    this(10);
}
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

add()方法

  1. 会进行判断 (minCapacity - elementData.length > 0 当前容量 是否满足,如果满足的话, 直接添加元素
  2. 如果 容量不满足 ,则进行扩容操作,为原来的2倍
  3. 扩容的时候 : 根据三元运算符进行判断 , 如果 capacityIncrement 是否大于0 ,如果不大于0,变为原来的2倍,默认为0
1.public synchronized boolean add(E e) {    
modCount++;    //集合修改次数
//调用此方法 判断 数组容量是否足够    
ensureCapacityHelper(elementCount + 1);   
elementData[elementCount++] = e;   
return true;
}
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    //如果 >0 则 进行扩容操作 扩容为原来的2倍
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
  private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
      	//重点在于 这个 三元运算符  capacityIncrement默认为 0   原数组的2倍
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

LinkedList 底层原理 源码分析

  1. LinkedList 底层实现了 双向链表 和 双端队列的 特点

  2. 可以 添加 任意 元素 (元素 可以 重复 ), 包括 null

  3. 线程 不安全, 没有 实现同步

    first 头节点 last 尾节点

底层机制:

  1. LinkedList 底层 维护 了 一个 双向链表

  2. LinkedList 中 维护了 两个元素 first 和 last 分别 指向 头节点 和 尾节点

  3. 每个节点 (Node 对象) , 里面 维护了 prev next item 三个属性,

    其中通过 prev 指向前一个,通过 next 指向 后 一个 节点 ,item 存放数据, 最终 实现 双向链表

  4. 所以 LinkedList 元素 的 添加 和 删除, 不是 通过 数组完成的 ,相对 效率高

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

public LinkedList() {} // 使用无参构造器 创建一个空的链表  first =0  last =0

LinkedList add() 解读

  1. 使用无参构造器 的话,使用add方法
  2. 调用linkLast() 进行 尾插法 添加元素
  3. 创建一个新节点,加入到双向链表的尾部,使得 last first 都指向 新节点
  4. 当添加第二个节点的时候,使得last 节点 指向 第二个新节点, 第一个节点的 next 指向 第二个新节点
//进行装箱操作
public static Integer valueOf(int i) {   
if (i >= IntegerCache.low && i <= IntegerCache.high)      
return IntegerCache.cache[i + (-IntegerCache.low)]; 
return new Integer(i);}
//执行add方法 调用linkLast() 进行添加元素
public boolean add(E e) {    
    linkLast(e);
    return true;
}
//创建一个新节点,加入到双向链表的尾部,使得 last first 都指向 新节点
//当添加第二个节点的时候,使得last 节点 指向 第二个新节点, 第一个节点的 next 指向 第二个新节点
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++;
}

remove() 源码分析

//如果调用 remove() 方法 没有参数的话, 默认删除头结点,也就是调用removeFirst()方法
public E remove() {   
return removeFirst();
}
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
//把头节点置为null, first 节点执行 下一个节点 下一个节点的 prev 置为null
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

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

ArrayList 和 LinkedList 的 比较

  1. 都是 线程不安全的
  2. ArrayList 底层 是 对象数组 , LinkedList 底层 是双向链表
  3. ArrayList 查找 是通过 元素的序号 查找 快, LinkedList 插入和删除 不受 元素位置影响 快
  4. ArrayList 需要预留空间 LinkedList 需要存放 prev 和 next 数据

Set 接口

无序 (添加和取出的顺序不一致,没有索引) 不允许重复元素 ,最多一个null

1、可以使用迭代器

2、增强for

3、不能使用 索引的方式 获取

1636103919079

HashSet 底层原理 源码分析

  1. 底层是 HashMap

    public HashSet() {   
    map = new HashMap<>();
    }
    
  2. 可以存放 null, 但只能有一个

  3. Haset 不保证 元素 是 有序的 ,取决于 hash后, 在确定 索引的结果

    (不保证 存放元素的顺序和取出顺序一致)

  4. 不能有 重复元素对象

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

底层机制:

  1. HashSet 底层 是 HashMap
  2. 添加一个元素 时, 先 得到 hash值 , 会转成 --> 索引值
  3. 找到 存储 数据表 table , 看 这个 索引 位置 是否 已经 存放 元素
  4. 如果没有, 直接 加入
  5. 如果有 , 调用 equals 比较 ,如果 相同 , 就 放弃 添加, 不同 则 添加到 尾部
  6. java 8 中 ,如果 链表的 元素 个数 超过 8 , 并且 table 大小 大于 64, 就会进行树化(红黑树)
扩容机制:

​ 1.HashSet 底层是 HashMap, 第一次添加 时, table 数组 扩容 到16, << 1 左移 一位 *2

​ 临界值 (threshold) 16 * 加载因子(loadFactor) 0.75 =12

  1. 如果 table 数组 使用 达到了临界值 12 , 就扩容 到 162 =32 ,新临界值: 32 0.75 =24
  2. 当 链表长度达到 8,并且 数组长度 达到 64, 就会 进行 树化(红黑树 ),否则 采用 数组 扩容 两倍

源码解读:

//创建一个HashMap对象
public HashSet() {    
map = new HashMap<>();
}

add() 解读

  1. 调用 一个put()方法 调用的时候传入两个参数 其中 PRESENT 用于占位 ,没有实际意义 ,一个静态最终static final的常量

    private static final Object PRESENT = new Object();
    
public boolean add(E e) {   
return map.put(e, PRESENT)==null;
}
  1. put()方法 调用 putval方法,其中 可以得到 key对应的 哈希值 ,

哈希值 是通过 key的哈希方法 异或(^) h无符号右移 16位后的值 ,目的是为了 降低哈希冲突

public V put(K key, V value) {
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);
}
  1. 执行 putval()方法

    1. if 判断 table 和 长度 是否为 null 和 0 , 如果 成立 调用 resize() 创建 新的table

      数组最大长度n (16) -1 与 hash值 进行 与(& )运算 获取到 索引值

    ​ p = tab[i = (n - 1) & hash

    1. 如果当前位置没有 元素,构造一个 Node节点 进行加入

    2. 如果 当前 索引位置 对应的 链表 的 第一个元素 和 准备添加的 key 的 hash值 一样
      并且 满足 下面 两个 条件之一:

    3.1 准备加入的 key 和 p 指向的 Node 节点的 key 是 同一个对象
    3.2 指向的 Node 节点 的 key 的 equals() 和 准备 加入的key 比较后 相同

    满足就不能加入

  2. 在判断 p 是否 是一个 红黑树 ,如果 是 一颗 红黑树, 就调用putTreeVal 来进行添加

  3. 如果table 对应的 索引位置, 已经是一个 链表, 使用for进行死循环
    (1) 依次 和 该链表的 每一个元素 比较后,都不相同, 则加入到 该链表的最后
    注意 在 元素 加入到 链表后, 立即判断, 该链表长度是否 达到 8 个节点
    达到调用treeifyBin() 对 当前节点 进行 树化(转成红黑树)
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)) resize();
    注意 转成 红黑树 之前 进行 判断 数组长度 是否 达到64 ,没有 先扩容,有转红黑树
    (2) 依次和 该链表的 每一个元素比较过程中 ,如果有相同的情况, 就直接 break

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i; //定义辅助变量
    //tab 就是 HashMap 底层的 Node[] 数组
    
    //if 判断 table 和 长度 是否为 null 和 0 , 如果 成立 调用   resize() 创建 新的table
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 数组最大长度n (16) -1  与 hash值 进行 与运算
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
     else {
          Node<K,V> e; K k;
         // 如果 当前 索引位置 对应的 链表 的 第一个元素 和 准备添加的 key 的 hash值 一样
         // 并且 满足  下面 两个 条件之一:
         //1. 准备加入的 key 和 p 指向的 Node  节点的 key 是 同一个对象
         //2. p 指向的 Node 节点  的 key  的 equals() 和 准备 加入的key 比较后 相同
         // 就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
         //在判断 p  是否 是一个 红黑树
         //如果 是 一颗 红黑树, 就调用putTreeVal 来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
         //如果table 对应的 索引位置, 已经是一个 链表, 使用for进行死循环
         //(1) 依次 和 该链表的 每一个元素 比较后,都不相同, 则加入到 该链表的最后
         //     注意 在 元素 加入到 链表后, 立即判断,  该链表长度是否 达到 8 个节点
         //     达到调用treeifyBin() 对 当前节点 进行 树化(转成红黑树)         
   //   if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))   resize();
         //     注意 转成 红黑树 之前 进行 判断  数组长度 是否 达到64 ,没有 先扩容,有转红黑树
         
         //(2)  依次和 该链表的 每一个元素比较过程中 ,如果有相同的情况, 就直接 break
         else {
             	//如果 找到的 节点 , 后面 是 链表 循环比较
                for (int binCount = 0; ; ++binCount) { //死循环
                    if ((e = p.next) == null) {// 如果整个 链表 没有相同的,则加到 链表末尾
                        p.next = newNode(hash, key, value, null);
                        //判断 链表 是否 大于 8  ,大就树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //循环 过程中 发现  hash值 和 key的eqauals 相同 直接跳出循环 后面 替换value
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
         //当 key哈希值相同  和  (对象相等 或者  key的eqalus相同 ) 2选一 的 话  进行 替换操作
         if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value; //进行替换操作
                afterNodeAccess(e);
                return oldValue;
            }
        }
      ++modCount; //修改次数加1
        if (++size > threshold) //临界值
            // 判断 节点数 是否大于 临界值 
            //如果大于 则进行扩容操作,数组容量左移 一位 ,变为原来的 2倍 临界值=0.75*新数组容量
            resize();
        afterNodeInsertion(evict);
        return null;
    }


resize()创建table也就是HashMap的数组的代码:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults 首次创建
            newCap = DEFAULT_INITIAL_CAPACITY; //默认值 为 16
            //负载因子  0.75 * 默认值 16
            //临界值扩容,预判达到原先的0.75倍准备扩容,以防止多线程的大量涌入
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            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 = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        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;
                        }
                    }
                }
            }
        }
        return newTab;
    }

LinkedHashSet 底层原理 源码分析

总结:

  • LinkedHashSet 是 HashSet 的子类

  • 底层是一个 LinkedHashMap ,底层维护了一个 数组 +双向链表

  • LinkedHashSet 根据元素的 hashCode 值来都决定元素的存储位置,

    同时使用链表维护元素的次序,着使得元素看起来是以插入顺序来保存的。

  • LinkedHashSet 不允许添加重复元素

底层分析:

  1. 在 LinkedHashSet 中 维护了 一个 hash 表 和 双向链表 (LinkedHashSet 有 head 和 tail)

  2. 每一个节点 有 pre 和 next 属性 ,这样 可以 形成 双向链表

  3. 在 添加 一个 元素 时, 先 求 hash值 , 再求出 索引。 确定 该元素在 hashtable 的 位置, 然后

    将 添加的 元素 加入到 双向链表( 如果 已经存在 ,不添加)【原则上 和 hashset 一致】

    1. 这样的话, 遍历 LinkedHashSet 也能 确保 插入顺序 和 遍历顺序 一致

add()

  1. 添加第一次的时候 ,直接 将 数组 table 扩容到 16, 存放的是 LinkedHashMap$Entry

  2. 数组 HashMap $ Node[ ]

    ​ 存放的元素/数据 是 LinkedHashMap $Enrty

  3. static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    static class Node<K,V> implements Map.Entry<K,V> {
    
  4. 唯一的变化就是节点为Entry继承于HashMap的Node,变成了双向链表,其他的扩容,创建,比较全部沿用HashMap的


TreeSet (排序) 底层是 TreeMap

  1. 当 使用 无参 构造器 , TreeSet 仍然 是 无序的

  2. 使用 TreeSet 提供的 一个 构造器 , 可以 传入 一个 比较器

  3. public TreeSet(Comparator<? super E> comparator) {   
        this(new TreeMap<>(comparator));
    
    @FunctionalInterface
    public interface Comparator<T> {
    
  4. 源码分析

    public TreeSet(Comparator<? super E> comparator) {  
    this(new TreeMap<>(comparator));
    }
    
    // 1.构造器 把 传入 的 比较器 对象 赋给了 底层的 TreeMap 的 属性 this.comparator
    public TreeMap(Comparator<? super K> comparator) {   
        this.comparator = comparator;
    }
    
  5. add 方法

    public boolean add(E e) {    return m.put(e, PRESENT)==null;}
    
public V put(K key, V value) {
    Entry<K,V> t = root;//获取树的头节点
    if (t == null) {//头节点为null的情况直接添加
        compare(key, key); // type (and possibly null) check防止空值

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;

if (cpr != null) {    // cpr 匿名内部类(对象)
do {       
parent = t;        
    //动态绑定 到 我们 匿名内部类(对象) compare方法 中
    cmp = cpr.compare(key, t.key);      
    if (cmp < 0)          
        t = t.left;        
    else if (cmp > 0)        
        t = t.right;        
    else   //如果 相等 , 即 返回 0 , 这个key数据 加入不了        
        return t.setValue(value);   
} 
    while (t != null);
}
     Entry<K,V> e = new Entry<>(key, value, parent);//创建新元素的节点
    if (cmp < 0)//如果
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

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


Map接口

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

Map 接口实现类的特点

  • Map 用于 保存 具有 映射关系的 数据 key-value

  • Map 中的 key 和 value 可以 是 任何 引用类型 的 数据 , 会 封装到 HashMap$Node对象中

  • Map 的 key 可以 为 null ,value 也可以 为 null , 注意 key 为null ,只能有一个, value 为 null ,可以多个

  • 常用String 类 作为 Map 的 key

  • key 和 value 之间 存在 单向 一对一的 关系 , 即 通过 指定的 key 总能 找到 对应的 value

    map.get(key) 返回 对应的 value

  • Map 存放的数据 的 key -value ,一对K 是放在 一个HashMap$ Node 中的 ,因为 Node 实现了 Entry 接口

  • static class Node<K,V> implements Map.Entry<K,V> {  
        final int hash;    final K key;    V value;    Node<K,V> next; 
    

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

源码解读

  1. k-v 最后 是 HashMap$Node node=newNode(hash, key, value, null);

  2. k-v 为了 方便 程序员 的 遍历, 还会 创建 EntrySet 集合, 该集合 存放的 元素 类型 是 Entry

  3. 而 一个 Entry 对象 就有 K V, EntrySet <Entry <K,V> >

  4. transient Set<Map.Entry<K,V>> entrySet;
    
  5. entrySet 中的 K 只是 指向 Node节点中的 key ,V 指向 Node节点的 V 为了遍历方便

    1. entrySet 中 ,定义的类型 是 Map.Entry 但是 实际上 存放的 还是 HashMap$Node

    2. static class Node<K,V> implements Map.Entry<K,V>  //实现了接口  ,多态调用
      
    3. 当把 HashMap$Node 对象 存放到 entrySet 方便遍历 , 因为 Map.Entry 提供了

      • K getKey(); V getValue();
HashMap<String,Integer> map=new HashMap();
for (int i = 0; i <10 ; i++) 
{    
map.put("Value"+i,i);
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {    System.out.println(entry.getKey() +"--"+ entry.getValue());
}

        final Set<String> strings = map.keySet();
        final Collection<Integer> values = map.values();
        System.out.println(strings.getClass()); //HashMap$KeySet
        System.out.println(values.getClass()); // HashMap$Values
1636185300783

Map 接口常用方法

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


Map 六大遍历

用到的基本方法

  • containsKey : 查找 键 是否存在
  • KeySet : 获取所有的 键
  • entrySet : 获取 所有关系 k-v
  • values : 获取所有的值

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

 HashMap<String,Integer>  map=new HashMap();
 map.put("邓超",1); map.put("孙俪",2); 
 map.put(null,3); map.put("孙晓",null); 
 //第一组:  取出所有的 key  ,通过key 取出 values Set<String> keySet = map.keySet();
 //1. 增强for for(String  key:keySet){    
 System.out.println(key+"---"+map.get(key));
 }
 System.out.println("-----"); 
 //2. 迭代器 
 Iterator<String> iterator = keySet.iterator(); 
 while (iterator.hasNext()){     
 String next = iterator.next();   
 System.out.println(next+"---"+map.get(next)); }
 //第二组 : 取出所有的 values 
 Collection<Integer> values = map.values(); 
 //1. 增强for for (Integer value : values) { 
 System.out.println(value); } 
 System.out.println("-----"); 
 //2. 迭代器 
 Iterator<Integer> iterator2 = values.iterator(); 
 while (iterator2.hasNext()){    
 final Integer next = iterator2.next(); 
 System.out.println(next); }     
 //第三组  使用EntrySet 来获取 k-v 
 //1. 增强for Set<Map.Entry<String,Integer>> entries = map.entrySet(); 
 for (Map.Entry<String,Integer> entry: entries) {     System.out.println(entry.getKey()+"---"+entry.getValue()); } 
 //2. 迭代器 
 Iterator<Map.Entry<String, Integer>> iterator3 = entries.iterator(); 
 while (iterator3.hasNext()){    
 Map.Entry<String,Integer> next=iterator3.next();     System.out.println(next.getKey()+"---"+next.getValue()); 
 }

HashMap 底层机制 以及 源码 剖析

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

  1. HashMap 底层 维护了 Node 类型的 数组 table ,默认 为 null

  2. 当创建 对象时 , 将 加载因子 初始化为 0.75

  3. 当添加 key-value 时, 通过 key的 哈希值

    (哈希值 是通过 key的哈希方法 异或(^) h无符号右移 16位后的值 ,目的是为了 降低哈希冲突)

    得到 table的 索引值 ( 数组的长度-1 & 与 哈希值)

  4. 根据 索引值 判断 索引处 是否 有 元素 ,没有 则 直接 添加 ,

    如果 table 的索引位置 的 key 的hash相同 和新的key的 hash值相同,

    并满足 (table 现有的 节点 key 和 准备添加的 key 是 同一个对象 或者 通过 key的eqalus()相等,

    直接 替换值 ;

  5. 不相等 ,判断 是否为 树形结构 , 是 调用putTreeVal 来进行添加

  6. 为 链表 则 依次 和 该链表的 每一个元素 比较后,都不相同, 则加入到 该链表的最后

    注意 在 元素 加入到 链表后, 立即判断, 该链表长度是否 达到 8 个节点
    达到调用treeifyBin() 对 当前节点 进行 树化(转成红黑树)

  7. 注意 转成 红黑树 之前 进行 判断 数组长度 是否 达到64 ,没有 先扩容,有转红黑树

  8. 第一次添加 ,则 需要扩容 table 的 容量 为 16, 临界值 为 12 16*0.75

  9. 以后的 扩容中 ,扩容 为 原来的 2倍 , 数组容量 左移 一位 ,当 链表个数达到 8 和 数组长度 达到 64 树化

源码解读

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

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


HashTable 底层原理 源码分析

  1. 存放的是 元素 是 键值对 k-v
  2. hashtable 的 键 和 值 都不能 为 null
  3. 线程安全 加了 修饰词 synchronized
  4. 使用方法 与 hashmap 差不多

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

HashTable 底层

private static class Entry<K,V> implements Map.Entry<K,V> {
  1. 底层 是一个 数组 HashTable $Entry [ ] 初始化 为 11 Entry 为静态的

  2. 临界值为 8 数组容量11 * 负载因子 0.75 = 8

  3. 执行 put的时候 ,判断 值是否 为 null ,null抛出异常 ,addEntry() 添加 k-v 封装到 entry

    • addEntry(hash, key, value, index);
      
  4. 当满足 if (count >= threshold) 即 添加的元素 总数 大于 临界值 进行扩容

private void addEntry(int hash, K key, V value, int index) {    
modCount++;   
Entry<?,?> tab[] = table;   
if (count >= threshold) {      
// Rehash the table if the threshold is exceeded       
rehash();      
tab = table;        
hash = key.hashCode();       
index = (hash & 0x7FFFFFFF) % tab.length;    }   
// Creates the new entry.
@SuppressWarnings("unchecked")  
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;}
  1. rehash(); 进行 扩容 机制

数组容量 左移一位 变为原来的两倍 +1 (2n+1)

int newCapacity = (oldCapacity << 1) + 1;

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


Properties 分析

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

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

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


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


TreeMap 底层是 Entry

static final class Entry<K,V> implements Map.Entry<K,V> {
  1. 当 使用 无参 构造器 , TreeSet 仍然 是 无序的
  2. 使用 TreeMap提供的 一个 构造器 , 可以 传入 一个 比较器
public TreeMap(Comparator<? super K> comparator) {  
this.comparator = comparator;
}

Collection工具类

介绍:

  • Collection 是一个操作Set、List 和 Map 等集合的工具类
  • Collection 中提供了一系列静态方法对集合元素进行排序、查询和修改等操作

排序方法:

  • reverse(List):反转 List 中元素的顺序
  • shuffle(List):对 List 集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定的 List 集合元素按升序排序
  • sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
  • swap(List, int, int):将指定 List 集合中的 i 处元素和 j 处元素进行交换
package com.zhang.CollectionsDemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Collections_ {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("A");
        arrayList.add("B");
        arrayList.add("C");
        arrayList.add("D");
        arrayList.add("E");
        arrayList.add("F");
        //        - reverse(List):反转 List 中元素的顺序
        Collections.reverse(arrayList);
        System.out.println(arrayList);
        //        - shuffle(List):对 List 集合元素进行随机排序
        Collections.shuffle(arrayList);
        System.out.println(arrayList);
        //        - sort(List):根据元素的自然顺序对指定的 List 集合元素按升序排序
        Collections.sort(arrayList);
        System.out.println(arrayList);
        //        - sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
        Collections.sort(arrayList, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o2).compareTo((String)o1);
            }
        });
        System.out.println(arrayList);
        //        - swap(List, int, int):将指定 List 集合中的 i 处元素和 j 处元素进行交换
        Collections.swap(arrayList,0,1);
        System.out.println(arrayList);
    }
}

查找、替换:

  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中最大元素
  • Object min(Collection)
  • Object min(Collection,Comparator)
  • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
  • void copry(List dest,List src):将src中的内容赋值到dest中
  • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
package com.zhang.CollectionsDemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Collections_ {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("A");
        arrayList.add("A");
        arrayList.add("B");
        arrayList.add("C");
        arrayList.add("D");
        arrayList.add("E");
        arrayList.add("F");
        //        - Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
        System.out.println(Collections.max(arrayList));
        //        - Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中最大元素
        System.out.println(Collections.max(arrayList,new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o2).compareTo((String)o1);
            }
        }));
        //        - Object min(Collection)
        System.out.println(Collections.min(arrayList));
        //        - Object min(Collection,Comparator)
        System.out.println(Collections.max(arrayList,new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String)o2);
            }
        }));
        //        - int frequency(Collection,Comparator):返回指定集合中指定元素的出现次数
        System.out.println(Collections.frequency(arrayList,"A"));
        //        - void copry(List dest,List src):将src中的内容赋值到dest中
        ArrayList arrayList1 = new ArrayList();
        for (int i = 0; i < arrayList.size(); i++) {
            arrayList1.add("");
        }
        Collections.copy(arrayList1,arrayList);
        System.out.println(arrayList1);
        //        - boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
        Collections.replaceAll(arrayList,"A","hahahah");
        System.out.println(arrayList);
    }
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值