Java 容器---实现

“工欲善其事,必先利其器”。Java 容器就是我们开发中的利器。

然而,之前在开发中使用仅仅是容器的一小部分。这次从源码的角度进行深入的理解,一点总结分享给大家。

这里只列举了非阻塞式的容器;阻塞式的容器,会在后面的并发篇补。

如果有什么理解不对的地方,欢迎大家在评论中指正~

ArrayList


实现: 数组实现

线程安全: 非线性安全,fail-fast 机制保护

容量: 初始容量为10;随后每次增加都会变成之前的1.5倍(批量添加除外)。源码如下:

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);
}

效率:

操作 平均 最好 最差
size() O(1) O(1) O(1)
isEmpty() O(1) O(1) O(1)
contains(Object o) O(n) O(1) O(n)
indexOf(Object o) O(n) O(1) O(n)
lastIndexOf(Object o) O(n) O(1) O(n)
toArray() O(n) O(n) O(n)
get(int index) O(1) O(1) O(1)
set(int index, E e) O(1) O(1) O(1)
add(E e) O(1) O(1) O(1)
add(int index, E e) O(n) O(1) O(n)
remove(int index) O(n) O(1) O(n)
remove(Object o) O(n) O(1) O(n)
clear() O(n) O(n) O(n)
addAll(Collection c) O(m) O(m) O(m)
removeAll(Collection c) O(m*n) O(m) O(m*n)
retainAll(Collection c) O(m*n) O(m) O(m*n)

除了模型中的操作外还有一些特殊的操作:

// 缩小容器的容量到元素的数量
public void trimToSize();
// 确保容量能覆盖 minCapacity 个元素
public synchronized void ensureCapacity(int minCapacity) ;

Vector


实现: 数组

线程安全: 是;fast-fail 保护

容量: 初始容量为10;根据指定的扩容数量或者*2(批量添加除外);核心代码:

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // capacityIncrement 是构造函数中指定的值, 默认为0
    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);
}

效率:

效率与ArrayList相似,但是每一个操作都需要获取锁,时间开销要比ArrayList大。

操作 平均 最好 最差
add(E e) O(1) O(1) O(1)
add(int index, E e) O(n) O(1) O(n)
get(int index) O(1) O(1) O(1)
remove(int index) O(n) O(1) O(n)
remove(Object o) O(n) O(1) O(n)
removeAll(Collection c) O(n) O(n) O(n)
retainAll(Collection) O(n) O(n) O(n)
indexOf(Object) O(n) O(1) O(n)

Stack


实现: Vector

线程安全: 是;fast-fail保护

容量: 同Vector

效率:

操作 平均 最好 最差
push(E e) O(1) O(1) O(1)
pop() O(1) O(1) O(1)
peek() O(1) O(1) O(1)
empty() O(1) O(1) O(1)

LinkedList


实现: 双指针链表

线程安全:否;fast-fail保护

容量: 有size();无容量

效率:

作为 List 来说:

操作 平均 最好 最差
size() O(1) O(1) O(1)
isEmpty() O(1) O(1) O(1)
contains O(n) O(1) O(n)
add(E e) O(1) O(1) O(1)
remove(Object o) O(n) O(1) O(n)
containsAll(Collection c) O(m*n) O(m) O(m*n)
toArray() O(n) O(n) O(n)
removeAll(Collection c) O(m*n) O(m) O(m*n)
retainAll(Collection c) O(m*n) O(m) O(m*n)
removeAll(Collection c) O(m*n) O(m) O(m*n)
retainAll(Collection c) O(m*n) O(m) O(m*n)
clear() O(1) O(1) O(1)
get(int index) O(n) O(1) O(n)
set(int index,E e) O(n) O(1) O(n)
add(int index, E e) O(n) O(1) O(n)
remove(int index) O(n) O(1) O(n)
indexOf(Object o) O(n) O(1) O(n)
lastIndexOf(Object o) O(n) O(1) O(n)

作为 Queue 来说:

操作 平均 最好 最差
add(E e) O(1) O(1) O(1)
offer(E e) O(1) O(1) O(1)
remove() O(1) O(1) O(1)
poll() O(1) O(1) O(1)
element() O(1) O(1) O(1)
peek() O(1) O(1) O(1)

作为 Deque 来说:

操作 平均 最好 最差
xxxFisrt O(1) O(1) O(1)
xxxLast O(1) O(1) O(1)
removeFirstOccurrence(Object o) O(n) O(1) O(n)
removeLastOccurrence(Object o) O(n) O(1) O(n)
pop O(1) O(1) O(1)
push O(1) O(1) O(1)

PriorityQueue


特征: 容器中元素的 出顺序 不是元素的 插入顺序,而是一个大小顺序中的最值。

实现方式: 小顶堆

线程安全: 否;fail-fast保护

容量: 默认初始容量11;容量小的时候*2,容量大的时候+50%。核心代码如下:

private void grow(int minCapacity) {
int oldCapacity = queue.length;
// 小于64的时候增倍;大于64的时候增加50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
// queue是存放元素的数组
queue = Arrays.copyOf(queue, newCapacity);

效率:

如果只是用到确定数量个元素的情况下,它是比列表排序的效率要高的。随机数列的排序平均都要O(n*log(n))了,而它最高的复杂度仅为O(log(n))

但是,如果所有元素都要遍历一次取出来,那效率与就跟堆排序是一样的O(n*log(n))

操作 平均 最好 最差
add(E e) O(log(n)) O(1) O(log(n))
offer(E e) O(log(n)) O(1) O(log(n))
remove() O(log(n)) O(log(n)) O(log(n))
poll() O(log(n)) O(log(n)) O(log(n))
peek() O(1) O(1) O(1)
element() O(1) O(1) O(1)
toArray() O(n) O(n) O(n)
clear() O(n) O(n) O(n)

HashSet


特征: 高效率的集合容器

实现: HashMap

线程安全: 否;failfast保护

容量: 同HashMap

效率:同HashMap

LinkedHashSet


特征: 高效率的集合容器,能保存插入顺序

实现:LinkedHashMap

线程安全:否; failfast保护

容量: 同LinkedHashMap

效率: 同LinkedHashMap

TreeSet


特征: 带全排序的集合容器

实现:TreeMap

线程安全:否;failfast保护

容量:无

效率:同TreeMap

HashMap


特征:高效率的映射表

实现: 开散列

线程安全:否;fail-fast保护

是否支持null: 是

容量: 默认初始容量16;容量总是2的幂;默认加载因子0.75;扩容后的容量由当前容量和加载因子决定,这里的扩容代码不是很直接,需要联系几个方法来看:

// 添加一组映射的时候
void addEntry(int hash, K key, V value, int bucketIndex) {
    // 容量超过threshold并且对应的hash值存在在冲突的时候,开始扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        // 扩展为之前的2倍
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}

// 这个方法的作用在于将数组扩展到相应的容量,
// 并把原数组中的元素全部重新计算位置,转移到新数组中
// 重新计算下一个threshold
void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

// 这个方法在容器第一次添加元素的时候调用,使用toSize做参数的目的是避免第一次添加元素的时候多次扩容
private void inflateTable(int toSize) {
    int capacity = roundUpToPowerOf2(toSize);
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

效率:

操作 平均 最好 最差
size() O(1) O(1) O(1)
isEmpty() O(1) O(1) O(1)
get O(1) O(1) O(n)
getEntry(Object key) O(1) O(1) O(n)
put(K key, V value) O(1) O(1) O(n)
remove O(1) O(1) O(n)
clear O(n) O(n) O(n)
containsValue O(n) O(1) O(n)
putAll(Collection c) O(m) O(m) O(m*n)

LinkedHashMap


特征: 在HashMap的基础上添加了,特定规则的顺序保存

实现:开散列 + 双指针链表环

线程安全: 否;fast-fail保护

是否支持null: 是

容量: 同HashMap

特征:它在HashMap的基础上,对Entry的封装中加了两个指针记录顺序。也就是说,这里既有散列保证随机访问效率,又有链表记录顺序。

这里的顺序不是指大小顺序。而是插入顺序,或者访问次数的顺序。所以它可以用来做LRU缓存容器,不过在此之前需要对他做一些封装,需要定义缓存的体积和重写一些方法:

//这里的accessOrder要设置为true
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
}
// 缓存容量
private static final int MAX_ENTRIES = 100;
// 设置为超容量即删除
protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

TreeMap


特征: 全排序的映射表

实现:红黑树

线程安全: 否,fastfail保护

是否支持null:是

容量:无

特征:访问的时候根据二叉查找树来查找,插入,删除的时候需要根据红黑树的规则来平衡树。

效率:

操作 平均 最好 最差
size() O(1) O(1) O(1)
isEmpty() O(1) O(1) O(1)
containsKey(Object key) O(log(n)) O(1) O(log(n))
containsValue(Object value) O(n) O(1) O(n)
get(Object key) O(log(n)) O(log(n)) O(log(n))
put(K key, V value) O(log(n)) O(log(n)) O(log(n))
remove(Object key) O(log(n)) O(log(n)) O(log(n))
clear() O(1) O(1) O(1)
firstKey() O(log(n)) O(log(n)) O(log(n))
lastKey() O(log(n)) O(log(n)) O(log(n))
putAll(Map map) O(log(n)) O(log(n)) O(log(n))
firstEntry() O(log(n)) O(log(n)) O(log(n))
lastEntry() O(log(n)) O(log(n)) O(log(n))
pollFirstEntry() O(log(n)) O(log(n)) O(log(n))
pollLastEntry() O(log(n)) O(log(n)) O(log(n))
lowerXXX(K key) O(log(n)) O(log(n)) O(log(n))
floorXXX(K key) O(log(n)) O(log(n)) O(log(n))
ceilingXXX(K key) O(log(n)) O(log(n)) O(log(n))
higherXXX(K key) O(log(n)) O(log(n)) O(log(n))

Hashtable


特征:线程安全的高效映射表

实现: 开散列

线程安全: 是;fail-fast保护

是否支持null:

容量:默认初始容量11;加载因子0.75;由加载因子决定扩容时机,默认size超过容量的0.75需要扩容。核心代码如下:

public synchronized V put(K key, V value) {
    // ...
    // 容器中元素数量超过threshold时,扩容并重新计算hash,然后再添加元素
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        rehash();
        tab = table;
        hash = hash(key);
        index = (hash & 0x7FFFFFFF) % tab.length;
    }
    // ...
}

protected void rehash() {
    int oldCapacity = table.length;
    Entry<K,V>[] oldMap = table;
    // 新容量是旧容量的2倍
    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    Entry<K,V>[] newMap = new Entry[newCapacity];
    modCount++;
    // 这里计算新的阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    boolean rehash = initHashSeedAsNeeded(newCapacity);
    table = newMap;
    // 然后重新计算hash对应的位置
    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;
            if (rehash) {
                e.hash = hash(e.key);
            }
            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = newMap[index];
            newMap[index] = e;
        }
    }
}


效率:

操作 平均 最好 最差
size() O(1) O(1) O(1)
isEmpty() O(1) O(1) O(1)
get O(1) O(1) O(n)
getEntry(Object key) O(1) O(1) O(n)
put(K key, V value) O(1) O(1) O(n)
remove O(1) O(1) O(n)
clear O(n) O(n) O(n)
containsValue O(n) O(1) O(n)
putAll(Collection c) O(m) O(m) O(m*n)

WeakHashMap


特征:
它的Map.Entry类设计的比较特殊继承了弱引用类。key是作为弱引用持有的对象。这意味着容器中的对象在没有外部引用持有的时候随时都有可能被GC回收。所以它可以被用来做缓存。

    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            // key是弱引用持有的对象
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
    }

实现: 开散列 + WeakReference + ReferenceQueue

线程安全: 否; fast-fail保护

是否支持null:是

容量:默认初始容量16;默认加载因子0.75;容量总是2的幂,每次扩容都*2;如果在扩容过程中发现大量的元素被回收,可能会终止扩容,继续使用之前的容量。核心代码如下:

public V put(K key, V value) {
    // ...
    tab[i] = new Entry<>(k, value, queue, h, e);
    if (++size >= threshold)
        resize(tab.length * 2);
}

void resize(int newCapacity) {
    Entry<K,V>[] oldTable = getTable();
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    Entry<K,V>[] newTable = newTable(newCapacity);
    boolean oldAltHashing = useAltHashing;
    useAltHashing |= sun.misc.VM.isBooted() &&
            (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean rehash = oldAltHashing ^ useAltHashing;
    transfer(oldTable, newTable, rehash);
    table = newTable;
    /*
     * 如果在扩容过程中删除的元素非常的多,填充数不到阈值的一半的时候
     * 使用回之前容量的数组
     */
    if (size >= threshold / 2) {
        threshold = (int)(newCapacity * loadFactor);
    } else {
        expungeStaleEntries();
        transfer(newTable, oldTable, false);
        table = oldTable;
    }
}

/** Transfers all entries from src to dest tables */
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest, boolean rehash) {
    for (int j = 0; j < src.length; ++j) {
        Entry<K,V> e = src[j];
        src[j] = null;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object key = e.get();
            // 注意,这里在转移元素的过程中会删除一些已经被垃圾回收的元素
            if (key == null) {
                e.next = null;  // Help GC
                e.value = null; //  "   "
                size--;
            } else {
                if (rehash) {
                    e.hash = hash(key);
                }
                int i = indexFor(e.hash, dest.length);
                e.next = dest[i];
                dest[i] = e;
            }
            e = next;
        }
    }
}

效率:

操作 平均 最好 最差
size() O(n) O(n) O(n)
isEmpty() O(n) O(n) O(n)
get O(1) O(1) O(n)
getEntry(Object key) O(1) O(1) O(n)
put(K key, V value) O(1) O(1) O(n)
remove O(1) O(1) O(n)
clear O(n) O(n) O(n)
containsValue O(n) O(1) O(n)
putAll(Collection c) O(m) O(m) O(m*n)

IdentityHashMap


特征: 使用“引用相等”而非equals相等。只有两个 key 完全是同一个对象的时候才判定为相等。

实现: 闭散列;没有用Map.Entry类,数组偶数位置放 key,奇数位置放 value

线程安全: 否;fast-fail保护

是否支持null:是

容量:默认初始容量32;加载因子2/3;容量一定为2的幂;核心代码:

    public V put(K key, V value) {
        // ...
        // 超过阈值,扩容
        if (++size >= threshold)
            resize(len); // len == 2 * current capacity.
        // ...
    }

    private void resize(int newCapacity) {
        // assert (newCapacity & -newCapacity) == newCapacity; // power of 2
        // 原容量*2
        int newLength = newCapacity * 2;

        Object[] oldTable = table;
        int oldLength = oldTable.length;
        // 这里重新计算阈值
        if (oldLength == 2*MAXIMUM_CAPACITY) { // can't expand any further
            if (threshold == MAXIMUM_CAPACITY-1)
                throw new IllegalStateException("Capacity exhausted.");
            threshold = MAXIMUM_CAPACITY-1;  // Gigantic map!
            return;
        }
        if (oldLength >= newLength)
            return;

        Object[] newTable = new Object[newLength];
        threshold = newLength / 3;
        // 重新计算位置, 由于这是闭散列,需要对所有元素重新计算
        for (int j = 0; j < oldLength; j += 2) {
            Object key = oldTable[j];
            if (key != null) {
                Object value = oldTable[j+1];
                oldTable[j] = null;
                oldTable[j+1] = null;
                int i = hash(key, newLength);
                while (newTable[i] != null)
                    i = nextKeyIndex(i, newLength);
                newTable[i] = key;
                newTable[i + 1] = value;
            }
        }
        table = newTable;
    }

效率:

操作 平均 最好 最差
size() O(1) O(1) O(1)
isEmpty() O(1) O(1) O(1)
get O(1) O(1) O(n)
getEntry(Object key) O(1) O(1) O(n)
put(K key, V value) O(1) O(1) O(n)
remove O(1) O(1) O(n)
clear O(n) O(n) O(n)
containsKey(Object key) O(1) O(1) O(n)
containsValue(Object value) O(n) O(1) O(n)
putAll(Collection c) O(m) O(m) O(m*n)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容