zoukankan      html  css  js  c++  java
  • (四)常用集合与原理

    1、常用List

    ArrayList:底层是数组实现 Object[],线程不安全,查询和修改⾮常快,但是增加和删除慢;查询/修改多时使用;

    LinkedList: 底层是双向链表 Node<E>,线程不安全,查询和修改速度慢,但是增加和删除速度快;删除/新增多时使用;

    Vector: 底层是数组实现 Object[],线程安全的,操作indexof/size/remove/add等的时候使⽤synchronized进⾏加锁;已经很少使用了;

    2、线程安全List

    自写list:自己写个类,继承ArrayList,每个方法都加上锁;

    Collections.synchronizedList(new ArrayList<>()):几乎所有的方法都加了synchronized;读写没什么区别;

    CopyOnWriteArrayList<>():执行修改操作时,先获取ReentrantLock锁,然后创建一个长度+1的新书组,将数据拷贝到新数组,加入新数据,然后将原集合指向新集合,最后释放锁;

     1 //CopyOnWriteArrayList 源代码
     2 public boolean add(E e) {
     3         final ReentrantLock lock = this.lock;
     4         lock.lock();
     5         try {
     6             Object[] elements = getArray();
     7             int len = elements.length;
     8             Object[] newElements = Arrays.copyOf(elements, len + 1);
     9             newElements[len] = e;
    10             setArray(newElements);
    11             return true;
    12         } finally {
    13             lock.unlock();
    14         }
    15     }

    CopyOnWriteArrayList:读没有加锁,修改操作(add、set、 remove等)加锁,写代价较高,若复制大对象有可能发生Full GC;读写分离+最终一致;适合 少写多读 的场景;

    Collections.synchronizedList:读写均加锁synchronized,写操作较多时使用;

    两者相比,写性能好-Collections.synchronizedList,读性能好-CopyOnWriteArrayList;

    3、ArrayList扩容机制

    JDK7及之前,ArrayList创建时,默认大小是10,增加第11个时扩容,扩容为原来的1.5倍,类似饿汉式;

    JDK8及之后,默认是null,无长度,增加第1个时初始化为10,类似懒汉式;

    add时判断扩容流程:

    第一次添加元素,先判断大小是否是0,如果是0,则扩容到10;

    若元素个数大于其容量,则扩容为 原始⼤⼩+原始⼤⼩/2;

    判断与扩容 实现逻辑:传入添加元素后的新容量值,原数组判空,新容量值一直取大(默认容量值、旧容量扩后值),创建新数组,拷贝数据,指针指向;

        //源码入口
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
        //判断是否是空的
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    
        //默认与传入值取大
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        //扩容后 = 原始⼤⼩+原始⼤⼩/2
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

    add方法:先 判断与扩容,数组目前位置 +1 塞新值;

    get方法:先判空,再判传入index是否超出范围,最后直接返回数据index位置数据;

    indexOf方法:先判传参数是否是null,null是循环判==null,非空是循环equals判是否相同,返回index;

    remove方法:先判空,再判传入index是否超出范围,最后 依次移动后边元素;

        System.arraycopy(Object src 原数组, int srcPos 起始位置, Object dest 目标数组, int destPos 目标位置,int length 拷贝长度);

        System.arraycopy(elementData, index, elementData, index+1,numMove);

    4、常用map

    HashMap:底层是基于数组+链表,⾮线程安全的,默认容量是16、允许有空的健和值;一般用于删除与元素定位;

    Hashtable:基于哈希表实现,线程安全的(加了synchronized),默认容量是11,不允许有 null的健和值;一般不怎么用;

    treeMap:使⽤存储结构是⼀个平衡⼆叉树->红⿊树,默认是生序;可以⾃定义排序规则,要实现Comparator接⼝;一般用于排序;

        按照添加顺序使⽤LinkedHashMap,按照⾃然排序使⽤TreeMap,⾃定义排序 TreeMap(Comparetor c,重写compare);

    ConcurrentHashMap:也是基于数组+链表,线程安全;虽然是线程安全,但是他的效率⽐Hashtable要⾼很多;

    Collections.synchronizedMap():线程安全,几乎所有的方法都加了synchronized;

    hashcode:顶级类Object的⽅法,所有类都是继承Object,返回是⼀个int类型的数 根据⼀定的hash规则(存储地址,字段,⻓长度等),映射成⼀个数组,即散列值;

    equals:顶级类Object的⽅法,所有的类都是继承Object,返回是⼀个boolean类型 根据⾃定义的匹配规则,⽤于匹配两个对象是否⼀样;引用类型/字段匹配等;

    Set,不保存重复数据,是对对应map的封装,HashSet对应的就是HashMap,treeSet对应的就是treeMap;

    //HashSet源码
    public HashSet() {
        map = new HashMap<>();
    }
    private static final Object PRESENT = new Object();
    public boolean add(E e) {
         return map.put(e, PRESENT)==null;
    }

    5、map源码-HashMap与ConcurrentHashMap

     HashMap底层是 数组+链表+红⿊树 (JDK8才有红⿊树,链表长度大于8,转红黑树)

    Node<K,V>[] table 数组,数组每个元素都是Node的首节点,Node实现了Map.Entry<K,V>接口,每个节点都是key-value的键值对,且每个节点都指向下一个节点;
    1 static class Node<K,V> implements Map.Entry<K,V> {
    2         final int hash;
    3         final K key;
    4         V value;
    5         Node<K,V> next;
    .....
    transient Node<K,V>[] table;

    hash碰撞:不同key计算得到的Hash值相同,hashmap是链表发,要放到同个bucket中;

      解决hash碰撞:链表法、开发地址法、再哈希法等

    底层结构好处:

      链表能解决hash冲突,将hash值相同的对象存在同⼀个链表中,并放在hash值对应的数组位;

      数据较少时(少于8个),遍历线性表⽐红⿊树快;

      红⿊树能提升查找数据的速度,红⿊树是平衡⼆叉树,插⼊新数据后会通过左旋,右旋、变 ⾊等操作来保持左右平衡,解决单链表查询深度的问题;

    ConcurrentHashMap,在结构上无任何区别,仅仅在方法上有区别,如取spread重哈希,加锁synchronized锁,利⽤CAS获取数据;

    JDK1.7: 扩容头插法,多线程同时扩容重新塞node时,易形成环;JDK1.8:扩容尾插法;

    允许null值(null对应哈希是0),Integer和String更适合做key(具有不可变性),顺序与插入顺序无关,且会随着扩容而发生变化

    6、map的put方法

    HashMap 流程:

    当前数组是否为空,空则扩容;

    hash值命中的数组下标,是否空,空则直接创建节点塞值;

      下标为非空,判断首节点key是否一致,一致则替换;

          否则,判树形 树形插入,链表 循环判值 替换或插入 校验转红黑树;

    统一 校验并扩容;

    HashMap 底层源码如下:

     1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     2                boolean evict) {
     3     HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
     4     //数组判空
     5     if ((tab = table) == null || (n = tab.length) == 0)
     6         //扩容
     7         n = (tab = resize()).length;
     8     //数组hash获取下标位是否为空
     9     if ((p = tab[i = (n - 1) & hash]) == null)
    10         //空直接创建节点
    11         tab[i] = newNode(hash, key, value, null);
    12     else {
    13         //非空,判断首节点是否key一致
    14         HashMap.Node<K,V> e; K k;
    15         if (p.hash == hash &&
    16                 ((k = p.key) == key || (key != null && key.equals(k))))
    17             e = p;
    18         //是否树结构
    19         else if (p instanceof HashMap.TreeNode)
    20             e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    21         else {
    22             //非树结构,循环 判一致 替换,null 新增 判长度转红黑树
    23             for (int binCount = 0; ; ++binCount) {
    24                 if ((e = p.next) == null) {
    25                     p.next = newNode(hash, key, value, null);
    26                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    27                         treeifyBin(tab, hash);
    28                     break;
    29                 }
    30                 if (e.hash == hash &&
    31                         ((k = e.key) == key || (key != null && key.equals(k))))
    32                     break;
    33                 p = e;
    34             }
    35         }
    36         if (e != null) {
    37             V oldValue = e.value;
    38             if (!onlyIfAbsent || oldValue == null)
    39                 e.value = value;
    40             afterNodeAccess(e);
    41             return oldValue;
    42         }
    43     }
    44     //扩容
    45     ++modCount;
    46     if (++size > threshold)
    47         resize();
    48     afterNodeInsertion(evict);
    49     return null;
    50 }

    ConcurrentHashMap:

      hashtable类所有的⽅法几乎都加锁synchronized,线程安全 ⾼并发效率低;

      JDK8前,ConcurrentHashMap使⽤锁分段技术,将数据分成⼀段段存储,每个数据段配置⼀把锁segment类,这个类继承ReentrantLock来保证线程安全 技术点:Segment+HashEntry;

      JKD8后取消Segment,底层也是使⽤Node数组+链表+红⿊树,CAS(读)+Synchronized(写) 技术点:Node+Cas+Synchronized;

    spread(key.hashCode()) 重哈希,减少碰撞概率;

    tabAt(i) 获取table中索引为i的Node元素;

    casTabAt(i) 利⽤CAS操作获取table中索引为i的Node元素;

    ConcurrentHashMap逻辑是:

    取重哈希,循环表,空表初始化;

    hash值命中的数组下标,是否空,空则利用cas直接创建节点塞值;

      下标为非空,判扩容 锁首节点;

          判是链表,循环链表,判key是否一致,一致则替换,null则直接插入 大于8转红黑树;

          否则,判树形 树形插入;

    统一 校验并扩容;

    ConcurrentHashMap源码如下:

     1 final V putVal(K key, V value, boolean onlyIfAbsent) {
     2     if (key == null || value == null) throw new NullPointerException();
     3     //取重哈希
     4     int hash = spread(key.hashCode());
     5     int binCount = 0;
     6     //直接无限循环数组
     7     for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
     8         ConcurrentHashMap.Node<K,V> f; int n, i, fh;
     9         //空数组,初始化
    10         if (tab == null || (n = tab.length) == 0)
    11             tab = initTable();
    12         //数组hash获取下标位是否为空
    13         else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    14             //利用cas出入节点
    15             if (casTabAt(tab, i, null, new ConcurrentHashMap.Node<K,V>(hash, key, value, null)))
    16                 break;
    17         }
    18         //判断是否需要先扩容
    19         else if ((fh = f.hash) == MOVED)
    20             tab = helpTransfer(tab, f);
    21         else {
    22             V oldVal = null;
    23             //hash冲突,加锁
    24             synchronized (f) {
    25                 if (tabAt(tab, i) == f) {
    26                     //是链表,循环
    27                     if (fh >= 0) {
    28                         binCount = 1;
    29                         for (ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
    30                             K ek;
    31                             if (e.hash == hash &&
    32                                     ((ek = e.key) == key ||
    33                                             (ek != null && key.equals(ek)))) {
    34                                 oldVal = e.val;
    35                                 if (!onlyIfAbsent)
    36                                     e.val = value;
    37                                 break;
    38                             }
    39                             ConcurrentHashMap.Node<K,V> pred = e;
    40                             if ((e = e.next) == null) {
    41                                 pred.next = new ConcurrentHashMap.Node<K,V>(hash, key,
    42                                         value, null);
    43                                 break;
    44                             }
    45                         }
    46                     }
    47                     //是树
    48                     else if (f instanceof ConcurrentHashMap.TreeBin) {
    49                         ConcurrentHashMap.Node<K,V> p;
    50                         binCount = 2;
    51                         if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
    52                                 value)) != null) {
    53                             oldVal = p.val;
    54                             if (!onlyIfAbsent)
    55                                 p.val = value;
    56                         }
    57                     }
    58                 }
    59             }
    60             if (binCount != 0) {
    61                 if (binCount >= TREEIFY_THRESHOLD)
    62                     treeifyBin(tab, i);
    63                 if (oldVal != null)
    64                     return oldVal;
    65                 break;
    66             }
    67         }
    68     }
    69     //扩容
    70     addCount(1L, binCount);
    71     return null;
    72 }
  • 相关阅读:
    LeetCode485 最大连续1的个数
    LeetCode167 两数之和 II
    js浮点数类型
    js整数类型
    js布尔类型
    js重复赋值 js数据交换 js调式方法
    JavaScript变量
    数据类型分类
    重复赋值 数据交换 查看程序执行结果
    JS注释 JS变量
  • 原文地址:https://www.cnblogs.com/huasky/p/14735422.html
Copyright © 2011-2022 走看看