java集合简要分析

本文分析的代码为Android 7.0版本,这些类的设计与JDK的基本一致,某些细节地方可能有区别

请尊重博主劳动成果,转载请标明出处。

Map与Collection的实现类的简要类图

这里写图片描述

Map实现类

HashMap

它继承了AbstractMap,实现Map,Cloneable和Serializable接口,使用数组+链表的方式来存储数据,HashEntry数组和单向链表。
table数组,类型为HashEntry[],resize时,以table.length*2的长度增长。
基本字段:

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 4;

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * An empty table instance to share when the table is not inflated.
     */
    static final HashMapEntry<?,?>[] EMPTY_TABLE = {};

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    // If table == EMPTY_TABLE then this is the initial capacity at which the
    // table will be created when inflated.
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    // Android-Note: We always use a load factor of 0.75 and ignore any explicitly
    // selected values.
    final float loadFactor = DEFAULT_LOAD_FACTOR;

如果不设置参数使用new HashMap()方式创建,则默认的数组table长度为4,加载因子为0.75V。HashMap的最大长度不超过int的最大值。

HashEntry:

static class HashMapEntry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        HashMapEntry<K,V> next;//下一个节点
        int hash;
        ...//省略了其它代码
}

通过HashEntry的next属性,就可以形成一条单向链表。数组+链表的数据结构,既利用了数组查询速度快的优势,又得到了链表结构不需要连续的内存来存储数据的优点。

偷个懒,没有画图,使用文字描述一个长度为8的table所存储的数据结构。
(–>代表下一个,即next。使用…省略了部分HashEntry对象)
[0] = hashEntry0 –> hashEntry1–>hasEntry2..
[1] = hashEntry5 –> hashEntry6
[2] = hashEntry8 –> hashEntry9–>hasEntry11
[3] = hashEntry7
[4] = hashEntry13 –> hashEntry10–>hasEntry21…
[5] = hashEntry14 –> hashEntry17…
[6] = hashEntry20…
[7] = hashEntry27…

从这个文字图的描述,可以将HashMap内部的数据看成2个维度:纵向维度为数组,即table;横向维度为单向链表,即HashEntry对象链

在看看resize相关的代码:

    //创建新的table
   void resize(int newCapacity) {
        HashMapEntry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        HashMapEntry[] newTable = new HashMapEntry[newCapacity];
        //对新的table赋值和重新链接HashEntry对象
        transfer(newTable);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    //执行table赋值和HashEntry重链接
    void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (HashMapEntry<K,V> e : table) {
            while(null != e) {
                HashMapEntry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

   //添加
   void addEntry(int hash, K key, V value, int bucketIndex) {
        //size是否大于size*loadFactor 且table[bucketIndex]有数据
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //2倍增长
            resize(2 * table.length);
            hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

    //添加
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> e = table[bucketIndex];
        //新加入的数据在HashEntry链表的前面
        table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
        size++;
    }

resize时重新创建数组和建立对象的链接需要占用内存和CPU计算时间。使用默认构造方法创建,table数组的长度递增为4,8,16, 32,64,即2*table.length。所以使用HashMap时,最好先预估数据量,设置一个合理的初始化容量,尽量减少resize。

Hashtable

Hashtable继承Dictionary,实现Map,Cloneable和Serializable接口,也使用数组+链表的方式来存储数据,HashtableEntry数组和单向链表。
Hashtable与HashMap最大的区别为:Hashtable为线程安全。
其它小的区别:
Hashtable没有对key和value做null判断,所以传入null时会抛出空指针异常。而HashMap有判断,且可以存入。
Hashtable默认table的长度为11,增长方式为2*table.length + 1。

两者hash:
HashMap取hash和获取index

   //使用sun的类的静态方法获取
   int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);

   static int indexFor(int h, int length) {
        // 与长度-1做与运算
        return h & (length-1);
    }

Hashtable取hash和获取index

    //直接从hashCode方法获取
   private static int hash(Object k) {
        return k.hashCode();
   }
   //先与 ,再取余
   int index = (hash & 0x7FFFFFFF) % tab.length;

使用Hashtable存储自定义的类对象,如果有重写类的hashCode()方法,一定要小心谨慎。

LinkedHashMap:

LinkedHashMap是HashMap的子类,内部有一个header指向的双向循环链表,能记录数据存入的顺序和数据被访问的顺序。

看代码:

    //相当于一个指针
    private transient LinkedHashMapEntry<K,V> header;
    //true为记录访问顺序,false为只记录存入顺序
    private final boolean accessOrder;

    //初始化header,指向自己
    void init() {
        header = new LinkedHashMapEntry<>(-1, null, null, null);
        header.before = header.after = header;
    }

private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
        // before指向前一个LinkedHashMapEntry,after指向后面的
        LinkedHashMapEntry<K,V> before, after;

        LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
            super(hash, key, value, next);
        }

        //断开前面的LinkedHashMapEntry对象和后面的对自己的指向
        private void remove() {
            before.after = after;
            after.before = before;
        }

        //将自己添加到上一个添加或访问的元素后面。将上一个元素的after指向自己,
        //将header的before指向自己。同时,将自己的before指向上一个元素,
        //after指向header
        private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            //如果记录访问顺序,则自己移出,放入到header之前,即最近访问的元素位置
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }

    //添加
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> old = table[bucketIndex];
        LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
        table[bucketIndex] = e;
        //更新链表,将自己添加到最前的位置
        e.addBefore(header);
        size++;
    }

    //获取添加时间最久或最长时间未被访问的元素
    public Map.Entry<K, V> eldest() {
        Entry<K, V> eldest = header.after;
        return eldest != header ? eldest : null;
    }

当创建LinkedHashMap传入accessOrder为true时,则会记录访问顺序。
参考了该博客:http://blog.csdn.net/ns_code/article/details/37867985

测试一把,代码如下:

    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("0", "a");
        map.put("1", "b");
        map.put("2", "c");
        map.put("3", "d");
        map.get("1");
        print(map.keySet());

        LinkedHashMap<String, String> accessOrderMap = new LinkedHashMap<String, String>(4, 0.75f, true);
        accessOrderMap.put("0", "a");
        accessOrderMap.put("1", "b");
        accessOrderMap.put("2", "c");
        accessOrderMap.put("3", "d");
        accessOrderMap.get("2");
        accessOrderMap.get("2");
        print(accessOrderMap.keySet());

    }

    static void print(Set<String> set){
        Iterator<String> iter = set.iterator();
        while(iter.hasNext()){
            System.out.print(iter.next() + ", ");
        }
        System.out.println();
    }

运行结果:

0, 1, 2, 3, 
0, 1, 3, 2, 

Android的LruCache中有使用到这个类。不过5.0及以下版本有个Bug,详情请查看博客:https://my.oschina.net/weichou/blog/735370

TreeMap

下面的博客比我讲的清楚详细,就不写了。请仔细阅读以下介绍的博客吧。

阅读这篇博客可以先了解下AVL和红黑树的基本概念,以及如何旋转失衡的树。
标题:重温数据结构:平衡二叉树(AVL树)、红黑树、TreeSet与TreeMap
网站:http://blog.csdn.net/nupt123456789/article/details/23280793

这篇博客详细的介绍了如何判断旋转类型和旋转步骤。
标题:AVL树的旋转操作详解
网站:http://www.cnblogs.com/cherryljr/p/6669489.html

这篇博客对TreeMap讲解的挺详细的。
标题:Java提高篇(二七)—–TreeMap
网站:http://www.cnblogs.com/chenssy/p/3746600.html

Android中类似Map的集合类

ArrayMap

这个是Android中的类,android.util包下,使用Object[]来存储key和value。索引为0和奇数存储的为key,偶数存储的为value。

ArrayMap内部缓存相关代码。

    //mBaseCache缓存的mArray长度
    private static final int BASE_SIZE = 4;
    //mBaseCache与mTwiceBaseCache缓存的最大次数
    private static final int CACHE_SIZE = 10;
    //缓存的长度为BASE_SIZE的mArray
    static Object[] mBaseCache;
    //mBaseCache缓存次数
    static int mBaseCacheSize;
    //缓存的长度为BASE_SIZE*2的mArray
    static Object[] mTwiceBaseCache;
    //mTwiceBaseCache缓存次数
    static int mTwiceBaseCacheSize;



    private void allocArrays(final int size) {
        if (mHashes == EMPTY_IMMUTABLE_INTS) {
            throw new UnsupportedOperationException("ArrayMap is immutable");
        }
        //符合缓存长度,则从缓存中查找,有效利用内存,防止过多的gc
        //增长的长度为8,先从缓存获取
        if (size == (BASE_SIZE*2)) {
            synchronized (ArrayMap.class) {
                if (mTwiceBaseCache != null) {
                    final Object[] array = mTwiceBaseCache;
                    mArray = array;
                    mTwiceBaseCache = (Object[])array[0];
                    mHashes = (int[])array[1];
                    array[0] = array[1] = null;
                    mTwiceBaseCacheSize--;
                    if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
                            + " now have " + mTwiceBaseCacheSize + " entries");
                    return;
                }
            }
        } else if (size == BASE_SIZE) {//增长的长度为4,先从缓存获取
            synchronized (ArrayMap.class) {
                if (mBaseCache != null) {
                    final Object[] array = mBaseCache;
                    mArray = array;
                    mBaseCache = (Object[])array[0];
                    mHashes = (int[])array[1];
                    array[0] = array[1] = null;
                    mBaseCacheSize--;
                    if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
                            + " now have " + mBaseCacheSize + " entries");
                    return;
                }
            }
        }
        //创建数组
        mHashes = new int[size];
        mArray = new Object[size<<1];
    }

    private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
        //如果释放的mArray长度符合缓存,则进行缓存
        if (hashes.length == (BASE_SIZE*2)) {//hashes长度为8,array为16
            synchronized (ArrayMap.class) {
                //如果未超过缓次数的存总数
                if (mTwiceBaseCacheSize < CACHE_SIZE) {
                    array[0] = mTwiceBaseCache;
                    array[1] = hashes;
                    for (int i=(size<<1)-1; i>=2; i--) {
                        array[i] = null;//释放数据
                    }
                    //缓存
                    mTwiceBaseCache = array;
                    mTwiceBaseCacheSize++;
                    if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
                            + " now have " + mTwiceBaseCacheSize + " entries");
                }
            }
        } else if (hashes.length == BASE_SIZE) {//hashes长度为4,array为8
            synchronized (ArrayMap.class) {
            //如果未超过缓次数的存总数
                if (mBaseCacheSize < CACHE_SIZE) {
                    array[0] = mBaseCache;
                    array[1] = hashes;
                    for (int i=(size<<1)-1; i>=2; i--) {
                        array[i] = null;//释放数据
                    }
                    //缓存
                    mBaseCache = array;
                    mBaseCacheSize++;
                    if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
                            + " now have " + mBaseCacheSize + " entries");
                }
            }
        }
    }

这种缓存数据设计,在储存少量数据上有非常大的优势。当如果数据很多,则就无法利用到缓存。

SparseArray

SparseArray类的基本属性:

    //标记mValues中被移除的元素
    private static final Object DELETED = new Object();
    //是否整理mKeys和mValues
    private boolean mGarbage = false;

    //key数组。与HashMap和ArrayMap相比,省去了将int转换为Integer的装箱步骤
    private int[] mKeys;
    //value数组
    private Object[] mValues;
    //长度
    private int mSize;

delete和remove方法代码:

    public void delete(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }

    public void removeAt(int index) {
        if (mValues[index] != DELETED) {
            mValues[index] = DELETED;
            mGarbage = true;
        }
    }

从SparseArray中删除元素并不会立即更新mKeys和mValues数组,只是使用DELETED标记和更改mGarbage。在调用szie(), keyAt(),valueAt(),setValueAt(),indexOfKey(),indexOfValue(),以及put()和append()方法时才调用到gc()方法来整理mKeys和mValues数组和更新mSize。
gc()方法代码:

    private void gc() {
        // Log.e("SparseArray", "gc start with " + mSize);

        int n = mSize;
        int o = 0;
        int[] keys = mKeys;
        Object[] values = mValues;

        for (int i = 0; i < n; i++) {
            Object val = values[i];

            if (val != DELETED) {
                if (i != o) {
                    keys[o] = keys[i];
                    values[o] = val;
                    values[i] = null;
                }

                o++;
            }
        }

        mGarbage = false;
        mSize = o;

        // Log.e("SparseArray", "gc end with " + mSize);
    }

如果有元素被删除,在for循环内,会将mKeys和mValues内的往前移动到被删除的位置,原位置被置为null。经过gc()方法后,mKeys和mValues内的数据会跟紧凑。

在看看put()方法代码:

    public void put(int key, E value) {
        //二分查找获取被存入的位置
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {//找到则更新
            mValues[i] = value;
        } else {
            i = ~i;//两次取反运算,即原来值。(binarySearch返回值,和此处取反)

            if (i < mSize && mValues[i] == DELETED) {/找到则更新
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

            if (mGarbage && mSize >= mKeys.length) {
                gc();//整理mKeys和mValues

                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }

            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

看看GrowingArrayUtils.insert()代码:

    public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }

        @SuppressWarnings("unchecked")
        T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
                growSize(currentSize));
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }

其实这个存入数据的过程与ArrayMap的put过程一样。数组长度足够,则将index及后面的数据后移一个位置,然后将新数据插入。数组容量不足,则创建新的,将原数组中0至index复制到新数组,再存入,然后将原数组剩余的再复制。ArrayUtils.newUnpaddedArray()方法中直接调用了VMRuntime.getRuntime().newUnpaddedArray()创建对象数组,效率上会更高些。

Collection实现类

Set

HashSet

继承AbstractSet,实现Set,Cloneable和Serializable,内存使用HashMap来存储数据
部分代码:

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
TreeSet

继承AbstractSet,实现Set,Cloneable和Serializable,内存使用TreeMap来存储数据
部分代码:

    private transient NavigableMap<E,Object> m;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }

List

ArrayList

继承AbstractList,实现List,RandomAccess,Cloneable和Serializable,采用Object[]存储数据。它需要使用连续内存空间来存储数据,数组结构让查询非常方便,而删除和添加操作则比较麻烦。

LinkedList

继承AbstractSequentialList,实现List, Deque, Cloneable, Serializable,采用双向链表来存储数据。
代码:

    //数据节点
    private static class Node<E> {
        E item;//存储的数据
        Node<E> next;//上一个节点
        Node<E> prev;//下一个节点

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    //指向链表的头
    transient Node<E> first;
    //指向链表的尾部
    transient Node<E> last;

这种数据结构使的LinkedList添加和删除数据非常方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值