java集合类源码剖析

java集合类源码剖析

hashmap

底层实现

HashMap.Entry数组,数组+拉链

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;
}
  • Entry对象代表了HashMap中的一个元素(键值对)
  • hashCode相同(碰撞)的元素将分配到Entry数组的同一个桶中
  • 同一个桶中的Entry对象由next域串联起来,构成一个单向链式结构

初始化

  • 默认容量DEFAULT_INITIAL_CAPACITY是16
  • 默认负载因子DEFAULT_LOAD_FACTOR是0.75
  • 若指定的初始容量initialCapacity大于MAXIMUM_CAPACITY,会重置为MAXIMUM_CAPACITY
  • HashMap实际的容量大于等于指定的容量initialCapacity,因为hashmap的容量是2的幂,如果initialCapacity不是2的幂,实际容量将设置为比initialCapacity大的最小的2的幂值
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
  • 如果使用另一个map进行初始化,负载因子会使用DEFAULT_LOAD_FACTOR,容量会在(int) (m.size() / DEFAULT_LOAD_FACTOR) + 1DEFAULT_INITIAL_CAPACITY中取大者
  • 调用 init() 方法(空实现,LinkedHashMap会重写这个方法)

put

  • 如果键已经存在,用新值覆盖旧值并返回旧值
  • 如果键已经存在,会调用recordAccess方法,凡是访问了HashMap中元素的方法,都会调用recordAccess方法,在HashMap中是空实现,在LinkedHashMap会重写这个方法
  • 如果键不存在,会调用addEntry方法,创建一个新的Entry对象,放到Entry数组对应的桶中,成为拉链的第一个元素
  • 如果键不存在,添加了新元素之后,会判断是否需要扩容。如果size>=threshold,则进行2倍扩容(注意,如果容量已经等于最大容量MAXIMUM_CAPACITY,只把threshold改为Integer.MAX_VALUE,而不扩容)。创建新的Entry数组,并将当前数组中的元素迁移到新的数组中(重新计算每个元素在新数组中的索引)
  • 如果键不存在,modCount++size++,modCount是用来记录结构性修改次数的,凡是修改了底层结构的操作都会令modCount加1,modCount主要用于迭代器的快速失败,迭代器将根据modCount的值是否发生了变化来判断是否有其他进程对HashMap做了修改

几点注意事项

  • null的hashCode定义为0,如果key==null,将设置到table[0]中
  • hash算法(h是传入的key的hashCode)
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
  • 根据hash定位数组索引的算法
    h & (length-1)
  • 判断key是否存在的方法
    e.hash == hash && ((k = e.key) == key || key.equals(k))

remove

  • 计算key在Entry数组中的索引,在桶中查找键相等的元素(equals
  • 如果找到了,将前一节点的next指向当前节点的下一个节点(单链表的删除操作)并返回当前节点
  • 如果找到了,modCount++size--,调用recordRemoval方法,凡是删除了HashMap中元素的方法都会调用recordRemoval,在HashMap中是空实现,在LinkedHashMap会重写这个方法
  • 最终将返回节点(Entry对象)的value值

get

  • 计算key在Entry数组中的索引(如果key==null,索引是0)
  • 遍历当前桶中的元素,如果找到key相等的元素(equals),返回元素的值

keySet

  • 返回keySet对象
  • keySet是HashMap中的一个内部类,继承了AbstractSetKeySetremoveclear方法可以直接修改HashMap本身的内容。因为,KeySet只不过是HashMap的一个视图而已
    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
  • keySetiterator方法调用了外部类(HashMap)的newKeyIterator方法,newKeyIterator将返回一个KeyIterator对象,KeyIterator继承了HashIterator。LinkedHashMap重写了newKeyIterator方法。
  • HashIterator中的域,expectedModCount初始化为modCountindex初始化为第一个不为空的桶的索引+1,next初始化为第一个不为空的桶中拉链的第一个元素。
    private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;    // next entry to return
        int expectedModCount;   // For fast-fail
        int index;      // current slot
        Entry<K,V> current; // current entry
    }
    
    HashIterator() {
        expectedModCount = modCount;
        if (size > 0) { // advance to first entry
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    }
  • next方法。迭代的方案是,按照索引从小到大(大于0,小于table.length)遍历每一个桶,如果桶不为空,则遍历这个桶中拉链的每个元素。每次被遍历到的元素将赋值给currentcurrent对于下一次操作而言,保存了上一个返回的元素。
    while (index < t.length && (next = t[index++]) == null);  //风骚的代码
  • remove方法。如果current==null,将抛出异常,这说明在调用remove方法之前必须先调用next,删除current.key对应的元素,最后expectedModCount = modCount,这是因为删除操作会使得modCount++,而这次的删除操作并不是并发操作引起的,因此重置expectedModCount,以免触发快速失败。
  • 在调用nextremove时,有个快速失败(fast-fail)机制
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

containsKey

  • 计算key在数组中的索引,遍历桶中的所有元素,如果存在key,则返回true,否则返回false

containsValue

  • 遍历所有的桶中的所有元素,如果找到值equals的,则返回true,否则返回true,否则返回false
  • 这个方法效率蛮低的

ArrayList

底层实现

Object数组

初始化

  • 如果没有指定容量,使用默认容量10

add

  • 检查容量,同时modCount++,如果size+1 > elementData.length,则需要先进行扩容,默认1.5倍扩容,如果扩容后容量还是还是比size+1小,则新的容量等于size+1,扩容使用Arrays.copyOf静态方法

    int newCapacity = (oldCapacity * 3)/2 + 1;
    elementData = Arrays.copyOf(elementData, newCapacity);
  • 添加元素
    elementData[size++] = e;

get

  • 检查参数,index必须小于size
  • 返回元素,做了类型转换
    return (E) elementData[index];

set

  • 检查参数,index必须小于size
  • 取出就元素,设置新元素,返回旧元素

clear

  • 将数组中索引小于 size 的所有元素置为null(释放引用,让垃圾回收机制工作)
  • size=0

remove(int index)

  • 检查参数,index必须小于size
  • modCount++
  • 计算需要移动的元素数量numMoved
    int numMoved = size - index - 1;
  • 如果numMoved > 0,调用System.arraycopy将从index+1开始的numMoved个元素向前移动一位,System.arraycopy是本地方法
    System.arraycopy(elementData, index+1, elementData, index, numMoved);
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
  • 为了释放引用,让垃圾回收机制工作,将移动前最后一个元素的位置设置为null
  • 返回被删除的元素
  • remove方法效率低是因为需要将被删除元素后的所有元素向前移动一个位置

remove(Object o)

  • 循环遍历整个数组,如果找到 equals 的元素,就删除这个元素,删除的方式见 remove(int index)
  • 因为在循环过程中,每找到一个符合条件的元素,都会移动一次元素,因此这个方法效率更低

add(int index, E element)

  • 检查参数,index必须大于0,小于size
  • 检查容量,同时modCount++,如果size+1 > elementData.length,则需要先进行扩容,默认1.5倍扩容,如果扩容后容量还是还是比size+1小,则新的容量等于size+1,扩容使用Arrays.copyOf静态方法
    int newCapacity = (oldCapacity * 3)/2 + 1;
    elementData = Arrays.copyOf(elementData, newCapacity);
  • 将从 index 开始的元素向后移动一位,将新元素设置到 index 的位置
  • size++
  • 同样需要在数组中移动元素,效率低

lastIndexOf(Object o)

  • 反向遍历整个数组,如果找到 equals 的元素,则返回对应的索引,否则,返回-1

HashSet

底层实现

HashMap

常用操作

  • HashSet内部封装了HashMap,其功能都是通过HashMap实现的,HashSet中的元素存储在HashMap的key中,Value使用一个静态哑变量 PRESENT 占位

    private static final Object PRESENT = new Object();
  • HashSet实现举例
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

LinkedHashMap

底层实现

LinkedHashMap.Entry数组:数组+拉链+双向循环链表

  • LinkedHashMap继承了HashMap
  • LinkedHashMap的内部类Entry继承了HashMap.Entry,多了before和after两个域,用于实现双向循环链表,而原有的next域是用于桶中拉链的单向链表的,不要搞混了。
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;
    }

初始化

  • LinkedHashMap中多了一个 accessOrder 域,表示排序的方式,如果 accessOrder 是true,按照访问顺序排序,否则,按照插入顺序排序
  • 调用父类(HashMap)的构造方法, accessOrder 默认为 false
  • 重载的构造方法可以传入 accessOrder 参数
  • 调用 init() 方法,LinkedHashMap重载了HashMap的 init() 方法,用于初始化双向循环链表头节点 header
    void init() {
        header = new Entry<K,V>(-1, null, null, null);
        header.before = header.after = header;
    }

put

  • 如果键已经存在,用新值覆盖旧值并返回旧值
  • 如果键已经存在,会调用recordAccess方法,凡是访问了HashMap中元素的方法,都会调用recordAccess方法,在HashMap中是空实现,LinkedHashMap重写了这个方法,如果accessOrder等于truemodCount++,从双向循环链表中删除当前节点,并把当前节点插入到header之前
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }
  • 如果键不存在,会调用addEntry方法,LinkedHashMap重写了HashMap的addEntry方法,创建一个新的Entry对象,放到Entry数组对应的桶中,成为拉链的第一个元素,并在双向循环链表中把新节点插入到header之前
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }
  • 如果键不存在,添加了新元素之后,会判断是否需要扩容。如果size>=threshold,则进行2倍扩容(注意,如果容量已经等于最大容量MAXIMUM_CAPACITY,只把threshold改为Integer.MAX_VALUE,而不扩容)。创建新的Entry数组,并将当前数组中的元素迁移到新的数组中(重新计算每个元素在新数组中的索引)
  • 如果键不存在,modCount++size++,modCount是用来记录结构性修改次数的,凡是修改了底层结构的操作都会令modCount加1,modCount主要用于迭代器的快速失败,迭代器将根据modCount的值是否发生了变化来判断是否有其他进程对HashMap做了修改

get

  • 调用HashMap的get方法

    • 计算key在Entry数组中的索引(如果key==null,索引是0)
    • 遍历当前桶中的元素,如果找到key相等的元素(equals),返回元素的值
  • 如果返回是 null ,则返回 null
  • 否则在返回值之前调用 recordAccess 方法,LinkedHashMap重写了这个方法,如果accessOrder等于truemodCount++,从双向循环链表中删除当前节点,并把当前节点插入到header之前
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }

remove

  • 计算key在Entry数组中的索引,在桶中查找键相等的元素(equals
  • 如果找到了,将前一节点的next指向当前节点的下一个节点(单链表的删除操作)并返回当前节点
  • 如果找到了,modCount++size--,调用recordRemoval方法,凡是删除了HashMap中元素的方法都会调用recordRemoval,在HashMap中是空实现,在LinkedHashMap会重写这个方法,将元素从双向循环链表中移除
    void recordRemoval(HashMap<K,V> m) {
        remove();
    }
  • 最终将返回节点(Entry对象)的value值

keySet

  • 返回keySet对象
  • keySet是HashMap中的一个内部类,继承了AbstractSetKeySetremoveclear方法可以直接修改HashMap本身的内容。因为,KeySet只不过是HashMap的一个视图而已
    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
  • keySetiterator方法调用了外部类(HashMap)的newKeyIterator方法,LinkedHashMap重写了newKeyIterator方法,newKeyIterator将返回一个KeyIterator对象,KeyIterator继承了LinkedHashIterator
  • LinkedHashIterator中的域,expectedModCount初始化为modCountlastReturned保存最近一次迭代返回的元素,初始化为null,nextEntry保存下一次迭代返回的元素,初始化为header后的第一个元素,即第一个插入或者访问的元素。
    private abstract class LinkedHashIterator<T> implements Iterator<T> {
    Entry<K,V> nextEntry    = header.after;
    Entry<K,V> lastReturned = null;
    int expectedModCount = modCount;
  • next方法。迭代的方案是,从header的下一个元素开始,按照双向循环链表的正向遍历。将本次返回的元素赋值给lastReturned,对于下一次操作而言,lastReturned保存了最近一次迭代返回的元素
  • remove方法。如果lastReturned==null,将抛出异常,这说明在调用remove方法之前必须先调用next,删除lastReturned.key对应的元素,最后expectedModCount = modCount,这是因为删除操作会使得modCount++,而这次的删除操作并不是并发操作引起的,因此重置expectedModCount,以免触发快速失败。
  • 在调用nextremove时,有个快速失败(fast-fail)机制
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

ConcurrentHashMap

底层实现

ConcurrentHashMap.Segment数组 + ConcurrentHashMap.HashEntry数组

  • ConcurrentHashMap将整个map分为若干段(Segment)
  • 读写时,仅对当前段加锁,通过这种方式来减少对整个map加锁造成的效率损失
  • 每个段(Segment)都拥有自己的HashEntry数组,相当于每个段都是一个HashMap
  • 定位元素时,先根据hashCode定位所在的段,再根据hashCode定位所在的桶,最后再在拉链中定位元素(equals)。
  • Segment,注意counttable是volatile的
    transient volatile int count;
    transient int modCount;
    transient int threshold;
    transient volatile HashEntry<K,V>[] table;
    final float loadFactor;
  • HashEntry,注意keyhashnext是final的,value是volatile的
    static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;
    
        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }
    }

初始化

  • 默认容量DEFAULT_INITIAL_CAPACITY是16
  • 默认负载因子DEFAULT_LOAD_FACTOR是0.75
  • 默认段数组容量DEFAULT_CONCURRENCY_LEVEL是16
  • 初始化段数组(Segment<K,V>[] segments)。
    this.segments = Segment.newArray(ssize);
    • 计算段数组容量ssize。若指定的容量concurrencyLevel大于MAX_SEGMENTS,会重置为MAX_SEGMENTS,实际的容量大于等于concurrencyLevel,因为容量是2的幂,如果concurrencyLevel不是2的幂,实际容量将置为比concurrencyLevel大的最小的2的幂值

      int ssize = 1;
      while (ssize < concurrencyLevel) {
          //...
          ssize <<= 1;
      }
    • 计算segmentMask
    • 计算segmentShift
  • 为每个段初始化HashEntry数组
    for (int i = 0; i < this.segments.length; ++i)
        this.segments[i] = new Segment<K,V>(cap, loadFactor);
    • 计算HashEntry数组的容量cap。若指定的容量initialCapacity大于MAXIMUM_CAPACITY,会重置为MAXIMUM_CAPACITY,根据initialCapacity / ssize计算每个段中的容量并向上取为2的幂

      int c = initialCapacity / ssize;
      if (c * ssize < initialCapacity)
          ++c;
      int cap = 1;
      while (cap < c)
          cap <<= 1;

put

  • value不允许为null,事实上,key也不能为null,因为计算hash值时会调用key.hashCode(),如果key为null的话会抛出空指针异常。HashMap允许key和value为null,这里不一样。
  • 计算hash值
  • 根据hash值计算在段数组中的索引
    final Segment<K,V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }
  • 调用对应段对象的put方法

Segment的put

  • 判断是否需要扩容。计算count+1是否大于threshold,如果大于,进行2倍扩容(如果容量已经大于等于最大容量MAXIMUM_CAPACITY,直接返回,不扩容)。创建新的Entry数组,并将当前数组中的元素迁移到新的数组中(重新计算每个元素在新数组中的索引,具体算法太风骚,没有看懂,稍后再看)
  • 如果键已经存在,用新值覆盖旧值并返回旧值
  • 如果键不存在,创建一个新的HashEntry对象,放到HashEntry数组对应的桶中,成为拉链的第一个元素,并返回null
  • 如果键不存在,modCount++size++,modCount是用来记录结构性修改次数的,凡是修改了底层结构的操作都会令modCount加1,modCount主要用于迭代器的快速失败,迭代器将根据modCount的值是否发生了变化来判断是否有其他进程对HashMap做了修改

几点注意事项

  • hash算法(h是传入的key的hashCode)

    h += (h <<  15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h <<   3);
    h ^= (h >>>  6);
    h += (h <<   2) + (h << 14);
    return h ^ (h >>> 16);
  • 根据hash定位数组索引的算法
    int index = hash & (tab.length - 1);
  • put,remove等写方法会对当前的段加锁,这是保障并发访问的关键所在,get方法不会加锁,除非需要value等于null的特殊情况(HashEntry中只有value不是final的,因此有可能被重排序到构造函数之外进行初始化)
    lock();
    try {
        //...
    } finally {
        unlock();
    }    

get

  • key不能为null,因为计算hash值时会调用key.hashCode(),如果key为null的话会抛出空指针异常。HashMap允许key为null,这里不一样。
  • 计算hash值
  • 根据hash值计算在段数组中的索引
  • 调用对应段对象的get方法

Segment的get

  • 如果count==0,直接返回null
  • 计算key所在的索引,并取桶中的第一个节点开始遍历,如果找到key相等的元素(equals),将返回对应的value。
  • 找到key相等的元素后,会检查value是否为null,如果不为null直接返回,如果为null,将通过加锁的方式取得value并返回。正常情况下value的值不应该为null,为null说明了某些编译器对HashEntry的初始化做了重排序,通过加锁的方式取得value,将会等到新增HashEntry的操作全部结束后才去试图获得value的值。
  • get之所以不用加锁,是因为HashEntryvalue是volatile的

remove

  • key不能为null,因为计算hash值时会调用key.hashCode(),如果key为null的话会抛出空指针异常。HashMap允许key为null,这里不一样。
  • 计算hash值
  • 根据hash值计算在段数组中的索引
  • 调用对应段对象的remove方法

Segment的remove

  • 计算key在HashEntry数组中的索引,在桶中查找键相等的元素(equals
  • 如果找到了,链表中该节点之后的节点不用动,该节点之前的每个元素拷贝一份。为什么不直接将节点从链表中摘除呢?这是因为HashEntry中的keynexthash都是final的,一经初始化,不能修改,因此需要把节点之前的元素全部拷贝一份新的。(那么问题来了,为什么设计成final的呢?参见JMM-final)
    HashEntry<K,V> newFirst = e.next;
    for (HashEntry<K,V> p = first; p != e; p = p.next)
        newFirst = new HashEntry<K,V>(p.key, p.hash,
                                      newFirst, p.value);
    tab[index] = newFirst;
    1 -> 2 -> 3 -> 4 -> 5 - > 6
    移除4后将变为
    3 -> 2 -> 1 -> 5 -> 6
  • 如果找到了,modCount++count--
  • 返回被删除节点(HashEntry对象)的value值

size

  • 不加锁的计算size,第一次循环,对每个段的count求和sum,并把每个段的modCount保存下来。第二次循环,再次对每个段的count求和check,并检查这次的modCount是否和上次的一样。如果每个段的modCount都没有变,且check=sum,则返回sum,否则,重试。(最多进行RETRIES_BEFORE_LOCK次)
  • 对所有段加锁,循环,对每个段的count求和sum,对所有段解锁,返回sum
    sum = 0;
    for (int i = 0; i < segments.length; ++i)
        segments[i].lock();
    for (int i = 0; i < segments.length; ++i)
        sum += segments[i].count;
    for (int i = 0; i < segments.length; ++i)
        segments[i].unlock();

keySet

  • 返回keySet对象
  • keySet是ConcurrentHashMap中的一个内部类,继承了AbstractSetKeySetremoveclear方法可以直接修改HashMap本身的内容。因为,KeySet只不过是HashMap的一个视图而已
    final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return new KeyIterator();
        }
        public int size() {
            return ConcurrentHashMap.this.size();
        }
        public boolean contains(Object o) {
            return ConcurrentHashMap.this.containsKey(o);
        }
        public boolean remove(Object o) {
            return ConcurrentHashMap.this.remove(o) != null;
        }
        public void clear() {
            ConcurrentHashMap.this.clear();
        }
    }
  • keySetiterator方法返回一个KeyIterator对象,KeyIterator继承了HashIterator
  • 遍历的顺序,按照Segment数组从后向前,HashEntry数组从后向前,拉链从头向为的顺序遍历
  • ConcurrentHashMap的迭代器没有使用快速失败机制,在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。(参见 弱一致性迭代器)

思考

  • 为什么key和value不允许为null?

HashTable

与HashMap对比

  • 继承不同

    public class Hashtable extends Dictionary implements Map
    public class HashMap  extends AbstractMap implements Map
  • Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
  • Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
  • 两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
  • 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
  • Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

与ConcurrentHashMap对比

  • HashTable锁定整个map,而ConcurrentHashMap仅锁定所在的段,ConcurrentHashMap并发的效率高
  • HashTable提供强一致性迭代器,而ConcurrentHashMap提供弱一致性迭代器,HashTable迭代时的一致性好
时间: 2024-10-06 01:57:33

java集合类源码剖析的相关文章

【Java集合源码剖析】HashMap源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap. HashMap 实现了Serializable接口,因此它支持序列化,

【Java集合源码剖析】ArrayList源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/35568011 ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的C

【Java集合源码剖析】Vector源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/35793865 Vector简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. LinkedList是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是相对安全,有些时候还是要加入同步语句来保证线程的安全),可以用于多线程环境. LinkedList没有丝线Serializable接口,因此它不支持序列化,实现了Cloneable接口,

【Java集合源码剖析】LinkedList源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/35787253 LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全的,只在单线程下适合使用. LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆. Linked

【Java集合源码剖析】HashMap源码剖析(转)

HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap. HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆. HashMap源码剖析 HashMap的源码如下(加入了比较详细的注释): [ja

Java ArrayList源码剖析

转自: Java ArrayList源码剖析 总体介绍 ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现.除该类未实现同步外,其余跟Vector大致相同.每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量.当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小.前面已经提过,Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组

【Java集合源码剖析】LinkedHashmap源码剖析

LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同. LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析). LinkedHashMap同样是非线程安全的,只在单线程环境下使用. LinkedHashMap源码剖析 LinkedHashM

转:【Java集合源码剖析】LinkedHashmap源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985   前言:有网友建议分析下LinkedHashMap的源码,于是花了一晚上时间研究了下,分享出此文(这个系列的最后一篇博文了),希望大家相互学习.LinkedHashMap的源码理解起来也不难(当然,要建立在对HashMap源码有较好理解的基础上). LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加

转:【Java集合源码剖析】Vector源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/35793865   Vector简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是相对安全,有些时候还是要加入同步语句来保证线程的安全),可以用于多线程环境. Vector没有丝线Serializable接口,因此它不支持序列化,实现了Cloneable接口,能被克隆,实