java集合类知识点整理

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

Collection接口

ArrayList

ArrayList默认大小为10。add之前都会确保容量足够,不够的话扩容,扩容默认增加到原先的1.5倍大小,然后拷贝一个新数组出来。修改和查询的时候都会做越界检查,任何修改结构时都会修改modCount。ArrayList中的操作不是线程安全的。

LinkedList

jdk1.8里是由Node串联的链表,有序。存储元素的数据结构是双向链表结构,由存储元素的结点连接而成,每一个节点都包含前一个节点的引用,后一个节点的引用和节点存储的值。当一个新节点插入时,只需要修改其中保持先后关系的节点的引用即可。

transient Node<E> first;
transient Node<E> last;

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
}
CopyOnWriteArrayList

CopyOnWriteArrayList,是一个写入时复制的容器。平时查询的时候,都不需要加锁,随便访问,只有在写入/删除的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。这点要跟读写锁区分一下。

Java中JDK的源码实现,其实也是非常的简单,在add操作的时候,先使用synchronized进行加锁,保证同时只有1个线程进行变更,在变更的时候,先拷贝出来一个副本,先操作这个副本,操作完成后,再把现有的数据替换成这个副本。

  • 优点

    • 对于一些读多写少的数据,这种做法的确很不错,例如配置、黑名单、物流地址等变化非常少的数据,这是一种无锁的实现。可以帮我们实现程序更高的并发。
  • 缺点

    • 这种实现只是保证数据的最终一致性,在添加到拷贝数据而还没进行替换的时候,读到的仍然是旧数据。如果对象比较大,频繁地进行替换会消耗内存,从而引发Java的GC问题,这个时候,我们应该考虑其他的容器,例如ConcurrentHashMap。
Set

Set继承于Collection接口,是一个不允许出现重复元素,并且无序的集合,主要有HashSet和TreeSet两大实现类。

HashSet
private transient HashMap<E,Object> map;

不保证元素的顺序,而且HashSet允许使用 null 元素。

默认构造函数创建一个大小为16的容器,加载因子为0.75(容器的大小始终是2的冥,默认为16,为这个容器的临界值,当存储的元素到了这个临界值,那么容器就会自动扩容。)此实现不同步,为线程不安全的实现,如果有多个线程同时访问这个容器(HashSet),并对里面的元素进行了修改,则需要在外部同步。保证数据的冥等性(幂等是数据中得一个概念,表示N次变换和1次变换的结果相同。)
通过调用元素内部的hashCode和equals方法实现去重,首先调用hashCode方法,比较两个元素的哈希值,如果哈希值不同,直接认为是两个对象,停止比较。如果哈希值相同,再去调用equals方法,返回true,认为是一个对象。返回false,认为是两个对象。

要想按照自己制定的比较规则进行去重,必须重写hashCode和equals方法

TreeSet
private transient NavigableMap<E,Object> m;

TreeSet是一个包含有序的且没有重复元素的集合,通过TreeMap实现。非线程安全。这些元素是使用它们的可比较自然顺序排序的,是由在设置创建时提供的Comparator排序的。底层由treeMap实现。

LinkedHashSet
    /**
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    final boolean accessOrder;

LinkedHashSet存储结构是一个双向链表,因此它存储的元素是有序的,此处有序指按添加顺序的顺序。

LinkedHashSet继承自HashSet,初始化为一个容器为16大小,加载因子为0.75的Map容器。源码更少、更简单,唯一的区别是LinkedHashSet内部使用的是LinkHashMap。这样做的意义或者好处就是LinkedHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。

Queue

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

Map接口

HashMap

源码分析(转载)

  • HashMap存储键值对实现快速存取,允许为null。key值不可重复,若key值重复则覆盖。非同步,线程不安全。底层是hash表,不保证有序(比如插入的顺序)
  • 基于hashing的原理,jdk8后采用数组+链表+红黑树的数据结构。我们通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后在返回值对象。
  • hashMap中put的实现方法
    • 计算关于key的hashcode值(与Key.hashCode的高16位做异或运算)
    • 如果散列表为空时,调用resize()初始化散列表
    • 如果没有发生碰撞,直接添加元素到散列表中去
    • 如果发生了碰撞(hashCode值相同),进行三种判断
      • 若key地址相同或者equals后内容相同,则替换旧值
      • 如果是红黑树结构,就调用树的插入方法
      • 链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
    • 如果桶满了大于阀值,则resize进行扩容
  • 扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。
  • hash是对key的hashCode做hash操作,与高16位做异或运算。因为数组位置的确定用的是与运算,仅仅最后四位有效,设计者将key的哈希值与高16为做异或运算使得在做&运算确定数组的插入位置时,此时的低位实际是高位与低位的结合,增加了随机性,减少了哈希碰撞的次数。
  • HashMap默认初始化长度为16,并且每次自动扩展或者是手动初始化容量时,必须是2的幂。
    • 为了数据的均匀分布,减少哈希碰撞。因为确定数组位置是用的位运算,若数据不是2的次幂则会增加哈希碰撞的次数和浪费数组空间。(PS:其实若不考虑效率,求余也可以就不用位运算了也不用长度必需为2的幂次)
    • 输入数据若不是2的幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数最近的数字
    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  • 当两个对象的hashCode相等时会产生哈希碰撞,若key值相同则替换旧值,不然链接到链表后面,链表长度超过阙值8就转为红黑树存储
  • HashMap和HashTable
    • 相同点:都是存储key-value键值对的
    • 不同点:
      • HashMap允许Key-value为null,hashTable不允许;
      • hashMap没有考虑同步,是线程不安全的。hashTable是线程安全的,给api套上了一层synchronized修饰;
      • HashMap继承于AbstractMap类,hashTable继承与Dictionary类。
      • HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),会抛ConcurrentModificationException。
      • 容量的初始值和增加方式都不一样
        • HashMap默认的容量大小是16;增加容量时,每次将容量变为"原始容量x2"。
        • Hashtable默认的容量大小是11;增加容量时,每次将容量变为"原始容量x2 + 1";
      • 添加key-value时的hash值算法不同
        • HashMap添加元素时,是使用自定义的哈希算法。
        • Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
  • loadFactor表示HashMap的拥挤程度,影响hash操作到同一个数组位置的概率。默认loadFactor等于0.75,当HashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,在HashMap的构造器中可以定制loadFactor。
  • 选择Integer,String这种不可变的类型的元素作为Key,像对String的一切操作都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的覆写了hashCode()以及equals()方法。作为不可变类天生是线程安全的,
LinkedHashMap
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    ......

LinkedHashMap是有序的,且默认为插入顺序。共有两种:插入顺序和访问顺序。

这里accessOrder设置为false,表示不是访问顺序而是插入顺序存储的,这也是默认值,表示LinkedHashMap中存储的顺序是按照调用put方法插入的顺序进行排序的。

LinkedHashMap就是HashMap+双向链表。

transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;

LinkeedList源码

TreeMap
    private transient Entry<K,V> root;

    /**
     * The number of entries in the tree
     */
    private transient int size = 0;

    /**
     * The number of structural modifications to the tree.
     */
    private transient int modCount = 0;
static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
        ......

TreeMap可以实现存储元素的自动排序。在TreeMap中,键值对之间按键有序,TreeMap的实现基础是平衡二叉树。
TreeMap源码(转载)

ConcurrentHashMap
  • HashMap线程不安全,数组+链表+红黑树
  • Hashtable线程安全,锁住整个对象,数组+链表
  • ConccurentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
  • HashMap的key,value均可为null,其他两个不行。

JDK1.8中的改进

  • 不采用segment而采用node,锁住node来实现减小锁粒度。
  • 设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
  • 使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
  • sizeCtl的不同值来代表不同含义,起到了控制的作用。
  • 采用synchronized而不是ReentrantLock
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值