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)
containsO(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 来说:

操作平均最好最差
xxxFisrtO(1)O(1)O(1)
xxxLastO(1)O(1)O(1)
removeFirstOccurrence(Object o)O(n)O(1)O(n)
removeLastOccurrence(Object o)O(n)O(1)O(n)
popO(1)O(1)O(1)
pushO(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)
getO(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)
removeO(1)O(1)O(n)
clearO(n)O(n)O(n)
containsValueO(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)
getO(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)
removeO(1)O(1)O(n)
clearO(n)O(n)O(n)
containsValueO(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)
getO(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)
removeO(1)O(1)O(n)
clearO(n)O(n)O(n)
containsValueO(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)
getO(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)
removeO(1)O(1)O(n)
clearO(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)


作者:于晓飞93
链接:https://www.jianshu.com/p/605fba8bcbbc
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值