zoukankan      html  css  js  c++  java
  • list 、set 、map 粗浅性能对比分析

     list 、set 、map 粗浅性能对比分析


     

    不知道有多少同学和我一样,工作五年了还没有仔细看过list、set的源码可怜,一直停留在老师教导的:“LinkedList插入性能比ArrayList好,LinkedList顺序遍历性能比ArrayList好”的世界里。可是真是如此么?本文很“肤浅”的对比和分析了几种常用的集合,“高手”可以就此打住不必往下阅读了。。。


    本文分开介绍了List、Map、Set:

    (测试环境:win7、jdk、4G、i3;文章示例为了节省篇幅,只会列出测试大体形式和遍历次数)

    第一部分:List

    1.add(E e)

    ==============================性能测试=================================

    (1)表格统计的差异在于new的方式不同:

    [java] view plaincopy
    1. for (int i = 0; i < 10000; i++) {  
    2.     List<Integer> list = new LinkedList<Integer>();  
    3.     for (int j = 0; j < 10000; j++) {  
    4.         list.add(j);  
    5.     }  
    6.     list = null;  
    7. }  

    new方式 消耗时间(毫秒)
    new LinkedList<Integer>() 3420
    new ArrayList<Integer>() 3048
    new ArrayList<Integer>(10000) 2280
    new Vector<Integer>()  4045
    new Vector<Integer>(10000) 3859

    (2)我们变化一下集合的长度和遍历次数:

    [java] view plaincopy
    1. for (int i = 0; i < 1000000; i++) {  
    2.     List<Integer> list = new LinkedList<Integer>();  
    3.     for (int j = 0; j < 100; j++) {  
    4.         list.add(j);  
    5.     }  
    6.     list = null;  
    7. }  
    new方式 消耗时间(毫秒)
    new LinkedList<Integer>() 2286
    new ArrayList<Integer>() 2832
    new ArrayList<Integer>(10000) 1714
    new Vector<Integer>()  3937
    new Vector<Integer>(10000) 3184


    *************************************************源码分析*************************************************

    似乎并没有看到期待的差异,若非“冒充下大牛”纠结一下区别:

    1、若集合长度比较小(小于百位),LinkedList比ArrayList的插入性能稍胜一筹。

    2、但是当长度达到五位数时,ArrayList的插入性能反而会比LinkedList好一些。

    3、若你能预知ArrayList的长度,可以完胜LinkedList。(原因后面会说)

    4、Vector的作用不在一个纬度上,不过也没有想象中的那么差。


    LinkedList.add:

    [java] view plaincopy
    1.    先调用了自身的add:  
    2.    public boolean add(E e) {  
    3. addBefore(e, header);  
    4.        return true;  
    5.    }  
    6.    然后又调用了addBefore,表示插入在谁之前(有点绕,因为是双项链):  
    7.    private Entry<E> addBefore(E e, Entry<E> entry) {  
    8. Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);  
    9. newEntry.previous.next = newEntry;  
    10. newEntry.next.previous = newEntry;  
    11. size++;  
    12. modCount++;  
    13. return newEntry;  
    14.    }  
    15.    用于维护指定前后关系:  
    16.    private static class Entry<E> {  
    17. E element;  
    18. Entry<E> next;  
    19. Entry<E> previous;  
    20.   
    21. Entry(E element, Entry<E> next, Entry<E> previous) {  
    22.     this.element = element;  
    23.     this.next = next;  
    24.     this.previous = previous;  
    25. }  
    26.    }  
    也就是说LinkedList的上下级维护关系是借助了指针,若任意位置插入,则是先查找到before前的entry,然后重指原有位置前后元素的指针。


    ArrayList.add:

    首先判断下是否超过了最大长度数组长度,若超过会重新扩展拷贝(这也就说明了,为什么当我们在newArrayList时指定合适的长度会提升性能)

    [java] view plaincopy
    1.    相比LinkedList来说就简单了狠多:  
    2.    public boolean add(E e) {  
    3. ensureCapacity(size + 1);  // Increments modCount!!  
    4. elementData[size++] = e;  
    5. return true;  
    6.    }  
    7.    //首先判断下是否超过了最大长度数组长度,若超过会重新扩展拷贝(这也就说明了,为什么当我们在newArrayList时指定合适的长度会提升性能)  
    8.    public void ensureCapacity(int minCapacity) {  
    9. modCount++;  
    10. int oldCapacity = elementData.length;  
    11. if (minCapacity > oldCapacity) {  
    12.     Object oldData[] = elementData;  
    13.     int newCapacity = (oldCapacity * 3)/2 + 1;  
    14.         if (newCapacity < minCapacity)  
    15.     newCapacity = minCapacity;  
    16.            // minCapacity is usually close to size, so this is a win:  
    17.            elementData = Arrays.copyOf(elementData, newCapacity);  
    18. }  
    19.    }  
    20.    //默认长度10  
    21.    public ArrayList() {  
    22. this(10);  
    23.    }  

    通过上面的源码可以理解出为什么当集合的长度会左右LinkedList和ArrayList的插入性能PK。影响ArrayList性能损耗的一大元凶就是数组的不断增长拷贝,LinkedList由于自身的特性当数据插入时需要借助特殊的内部类“Entry”来维护插入数据的上下级链。


    2.add(int index, E element)

    通过上面的分析出现了一个想法,如果是

    随机插入位置呢?

    ==============================性能测试=================================

    (1)

    [java] view plaincopy
    1. for (int i = 0; i < 100; i++) {  
    2.     List<Integer> list = new LinkedList<Integer>();  
    3.     for (int j = 0; j < 10000; j++) {  
    4.         list.add(list.size() / 2, j);  
    5.     }  
    6.     list = null;  
    7. }  
    new方式 消耗时间
    new LinkedList<Integer>() 15782
    new ArrayList<Integer>() 3513
    new ArrayList<Integer>(10000) 3490

    (2)我们变化一下集合的长度和遍历次数:

    [java] view plaincopy
    1. for (int i = 0; i < 100000; i++) {  
    2.     List<Integer> list = new LinkedList<Integer>();  
    3.     for (int j = 0; j < 100; j++) {  
    4.         list.add(list.size() / 2, j);  
    5.     }  
    6.     list = null;  
    7. }  
    new方式 消耗时间
    new LinkedList<Integer>() 880
    new ArrayList<Integer>() 1262
    new ArrayList<Integer>(10000) 2308

    *************************************************源码分析*************************************************

    从纯插入性能上来说,随机插入LikedList要比ArrayList性能好:(至于为什么当LinkedList集合长度为10000时性能变化会如此之大,我们后面的get会有介绍)

    LikedList.add(int index, E element) :

    大体分为两个步骤

    [java] view plaincopy
    1. public void add(int index, E element) {  
    2.     addBefore(element, (index==size ? header : entry(index)));  
    3. }   
    4. //第一步获取到addBefore需要的插入位置(entry),第二步如上  
    5. private Entry<E> entry(int index) {  
    6.     if (index < 0 || index >= size)  
    7.         throw new IndexOutOfBoundsException("Index: "+index+  
    8.                                             ", Size: "+size);  
    9.     Entry<E> e = header;  
    10.     if (index < (size >> 1)) {  
    11.         for (int i = 0; i <= index; i++)  
    12.             e = e.next;  
    13.     } else {  
    14.         for (int i = size; i > index; i--)  
    15.             e = e.previous;  
    16.     }  
    17.     return e;  
    18. }  
    所以从代码上看,LikedList.add(int index, E element) 没有理由在集合长度为10000时会有这么大的差异,除非entry(int index)存在问题(我们后面可能要说的hash也正是为了避免遍历)。


    ArrayList.add(int index, E element) :

    大体分为三步

    第二步暴漏了为什么现在初始指定了集合长度反而不一定是好事

    [java] view plaincopy
    1.    public void add(int index, E element) {  
    2. if (index > size || index < 0)  
    3.     throw new IndexOutOfBoundsException(  
    4.     "Index: "+index+", Size: "+size);  
    5. //第一步:扩充长度  
    6. ensureCapacity(size+1);  // Increments modCount!!  
    7. //第二步:挪动数据(这一步决定了为什么现在初始指定了集合长度反而不一定是好事)  
    8. System.arraycopy(elementData, index, elementData, index + 1,  
    9.          size - index);  
    10. //第三步:插入  
    11. elementData[index] = element;  
    12. size++;  
    13.    }  

    3.get(int index)

    ==============================性能测试=================================

    [java] view plaincopy
    1. List<Integer> list = new LinkedList<Integer>();  
    2. for (int j = 0; j < 10000; j++) {  
    3.     list.add(j);  
    4. }  
    5.   
    6. for (int i = 0; i < 100; i++) {  
    7.     for (int j = 0; j < 10000; j++) {  
    8.         list.get(j);  
    9.     }  
    10. }  
    new方式 消耗时间
    new LinkedList<Integer>() 8349
    new ArrayList<Integer>(10000) 15
    *************************************************源码分析*************************************************

    通过这个表首先能够知道如果直接或间接(如模板语言)调用for循环遍历,LikedList的性能要差很多。

    LinkedList.get(int index):

    [java] view plaincopy
    1.     public E get(int index) {  
    2.         return entry(index).element;  
    3.     }  
    4.     //性能损耗在这里,每次都需要线性搜索(遍历链定位位置)  
    5.     private Entry<E> entry(int index) {  
    6.         if (index < 0 || index >= size)  
    7.             throw new IndexOutOfBoundsException("Index: "+index+  
    8.                                                 ", Size: "+size);  
    9.         Entry<E> e = header;  
    10.         if (index < (size >> 1)) {  
    11.             for (int i = 0; i <= index; i++)  
    12.                 e = e.next;  
    13.         } else {  
    14.             for (int i = size; i > index; i--)  
    15.                 e = e.previous;  
    16.         }  
    17.         return e;  
    18.     }  

    ArrayList.get(int index):

    [java] view plaincopy
    1.    public E get(int index) {  
    2. //检查是否越界  
    3. RangeCheck(index);  
    4. //直接从数组中取出对应index的数据  
    5. return (E) elementData[index];  
    6.    }  


    4.迭代器

    ==============================性能测试=================================

    [java] view plaincopy
    1. List<Integer> list = new LinkedList<Integer>();  
    2. for (int j = 0; j < 100; j++) {  
    3.     list.add(j);  
    4. }  
    5.   
    6. for (int i = 0; i < 1000000; i++) {  
    7.     Iterator<Integer> it = list.iterator();  
    8.     while (it.hasNext()) {  
    9.         it.next();  
    10.     }  
    11.     it = null;  
    12. }  
    new方式 消耗时间
    new LinkedList<Integer>() 1335
    new ArrayList<Integer>(); 3940


    *************************************************源码分析*************************************************

    LinkedList.iterator():

    [java] view plaincopy
    1.    private class ListItr implements ListIterator<E> {  
    2. //header为LikedList的成员变量Entry  
    3. private Entry<E> lastReturned = header;  
    4. private Entry<E> next;  
    5. private int nextIndex;  
    6. private int expectedModCount = modCount;  
    7.   
    8. //这里我们会默认传入0,  
    9. ListItr(int index) {  
    10.     if (index < 0 || index > size)  
    11.     throw new IndexOutOfBoundsException("Index: "+index+  
    12.                             ", Size: "+size);  
    13.   
    14.         //下面的if和else很有意思,为了实现“所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。”  
    15.     //size为外部LikedList的成员变量  
    16.     if (index < (size >> 1)) {  
    17.     next = header.next;  
    18.     for (nextIndex=0; nextIndex<index; nextIndex++)  
    19.         next = next.next;  
    20.     } else {  
    21.     next = header;  
    22.     for (nextIndex=size; nextIndex>index; nextIndex--)  
    23.         next = next.previous;  
    24.     }  
    25. }  
    26.   
    27. //判断是否已经超过长度  
    28. public boolean hasNext() {  
    29.     return nextIndex != size;  
    30. }  
    31.   
    32. //便宜至下一个元素并返回element  
    33. public E next() {  
    34.     checkForComodification();  
    35.     if (nextIndex == size)  
    36.     throw new NoSuchElementException();  
    37.   
    38.     lastReturned = next;  
    39.     next = next.next;  
    40.     nextIndex++;  
    41.     return lastReturned.element;  
    42. }  
    43.   
    44. ...  
    45.   
    46. final void checkForComodification() {  
    47.     if (modCount != expectedModCount)  
    48.     throw new ConcurrentModificationException();  
    49. }  
    50.    }  
    也就是说我们的LinkedList的Iterator性能损耗主要在变换指针,所以对比for遍历方式来说性能提升了很多。


    ArrayList.iterator():

    [java] view plaincopy
    1.    public Iterator<E> iterator() {  
    2. return new Itr();  
    3.    }  
    4.   
    5.    private class Itr implements Iterator<E> {  
    6. /** 
    7.  * Index of element to be returned by subsequent call to next. 
    8.  */  
    9. int cursor = 0;  
    10.   
    11. /** 
    12.  * Index of element returned by most recent call to next or 
    13.  * previous.  Reset to -1 if this element is deleted by a call 
    14.  * to remove. 
    15.  */  
    16. int lastRet = -1;  
    17.   
    18. /** 
    19.  * The modCount value that the iterator believes that the backing 
    20.  * List should have.  If this expectation is violated, the iterator 
    21.  * has detected concurrent modification. 
    22.  */  
    23. int expectedModCount = modCount;  
    24.   
    25. public boolean hasNext() {  
    26.            return cursor != size();  
    27. }  
    28.   
    29. public E next() {  
    30.            checkForComodification();  
    31.     try {  
    32.     //主要内容在这里,又绕回了ArrayList的获取方式  
    33.     E next = get(cursor);  
    34.     lastRet = cursor++;  
    35.     return next;  
    36.     } catch (IndexOutOfBoundsException e) {  
    37.     checkForComodification();  
    38.     throw new NoSuchElementException();  
    39.     }  
    40. }  
    41.   
    42.    }  
    ArrayList“很懒”的没有做什么,主要借助了父类AbstractList,获取方式采用了模板方法设计模式(Template Method Pattern)。

    总结:无论你遍历ArrayList还是LinkedList都可以尽量采用迭代器。


    通过add和get的分析,我们最常用的场景就是单页数据获取,然后利用jstl或velocity等遍历展示:

    1、如果能确定遍历采用的for循环建议使用ArrayList并采用带参的构造器指定集合长度(也就是每页的个数);

    2、若遍历采用迭代器建议采用LinkedList,因为我们还会有时对dao获取的数据采用缓存策略。

    (jstl用的迭代器,可看源码org.apache.taglibs.standard.tag.common.core.ForEachSupport.toForEachIterator方法)


    第二部分:Map


    1.put(K key, V value)

    ==============================性能测试=================================

    表格统计的差异在于new的方式不同:

    [java] view plaincopy
    1. for (int i = 0; i < 100000; i++) {  
    2.     Map<Integer, String> map = new TreeMap<Integer, String>();  
    3.     for (int j = 0; j < 100; j++) {  
    4.         map.put(j, "s" + j);  
    5.     }  
    6.     map = null;  
    7. }  
    new方式 运行时间
    new TreeMap<Integer, String>() 2945
    new HashMap<Integer, String>() 2029
    new HashMap<Integer, String>(100) 1845
    *************************************************源码分析*************************************************

    TreeMap.put(K key, V value):

    首先确认一点TreeMap采用的“红黑树”二叉查找方式。(具体算法请自行google)

    该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。此实现为 containsKey、get、put 和 remove 操作提供受保证的 log(n) 时间开销。

    插入大体分为四步:

    1、判断是否为初次;

    2、获取指定Comparator,若没有指定获取key的父类的实现方式。

    3、利用Comparator确定数据存放位置;

    4、树的旋转。

    [java] view plaincopy
    1.    public V put(K key, V value) {  
    2.        Entry<K,V> t = root;  
    3. //判断是否存在根节点,或者集合是否存在了数据  
    4.        if (t == null) {  
    5.            root = new Entry<K,V>(key, value, null);  
    6.            size = 1;  
    7.            modCount++;  
    8.            return null;  
    9.        }  
    10.        int cmp;  
    11.        Entry<K,V> parent;  
    12.        // split comparator and comparable paths  
    13. //这点是获取构造器传入的Comparator  
    14.        Comparator<? super K> cpr = comparator;  
    15.        if (cpr != null) {  
    16.            do {  
    17.                parent = t;  
    18.                cmp = cpr.compare(key, t.key);  
    19.                if (cmp < 0)  
    20.                    t = t.left;  
    21.                else if (cmp > 0)  
    22.                    t = t.right;  
    23.                else  
    24.                    return t.setValue(value);  
    25.            } while (t != null);  
    26.        }  
    27. //若没有指定Comparator,查找父类实现  
    28.        else {  
    29.            if (key == null)  
    30.                throw new NullPointerException();  
    31.            Comparable<? super K> k = (Comparable<? super K>) key;  
    32.     //遍历放入确切位置  
    33.            do {  
    34.                parent = t;  
    35.                cmp = k.compareTo(t.key);  
    36.                if (cmp < 0)  
    37.                    t = t.left;  
    38.                else if (cmp > 0)  
    39.                    t = t.right;  
    40.                else  
    41.                    return t.setValue(value);  
    42.            } while (t != null);  
    43.        }  
    44.        Entry<K,V> e = new Entry<K,V>(key, value, parent);  
    45.        if (cmp < 0)  
    46.            parent.left = e;  
    47.        else  
    48.            parent.right = e;  
    49. //树的旋转  
    50.        fixAfterInsertion(e);  
    51.        size++;  
    52.        modCount++;  
    53.        return null;  
    54.    }  

    HashMap.put(K key, V value):

    1、HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数;

    2、key的hashcode至关重要。

    [java] view plaincopy
    1.    //构造一个带指定初始容量和加载因子的空 HashMap  
    2.    public HashMap(int initialCapacity, float loadFactor) {  
    3.        if (initialCapacity < 0)  
    4.            throw new IllegalArgumentException("Illegal initial capacity: " +  
    5.                                               initialCapacity);  
    6.        if (initialCapacity > MAXIMUM_CAPACITY)  
    7.            initialCapacity = MAXIMUM_CAPACITY;  
    8.        if (loadFactor <= 0 || Float.isNaN(loadFactor))  
    9.            throw new IllegalArgumentException("Illegal load factor: " +  
    10.                                               loadFactor);  
    11.   
    12.        // Find a power of 2 >= initialCapacity  
    13.        int capacity = 1;  
    14. //取大于等于初始容量最近的2的倍数  
    15.        while (capacity < initialCapacity)  
    16.            capacity <<= 1;  
    17.   
    18.        this.loadFactor = loadFactor;  
    19.        threshold = (int)(capacity * loadFactor);  
    20.        table = new Entry[capacity];  
    21.        init();  
    22.    }  
    23.    public V put(K key, V value) {  
    24.        if (key == null)  
    25.            return putForNullKey(value);  
    26. //计算key的hash值  
    27.        int hash = hash(key.hashCode());  
    28. //计算出在第几个“水桶”,计算方式是&与了table.length-1,这应该也是水桶数据是2的倍数的原因  
    29.        int i = indexFor(hash, table.length);  
    30. //通过这步可以理解为什么看到好多文档要求key的hashCode“均匀分布”便于均匀落入各个水桶  
    31.        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
    32.            Object k;  
    33.            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
    34.                V oldValue = e.value;  
    35.                e.value = value;  
    36.                e.recordAccess(this);  
    37.                return oldValue;  
    38.            }  
    39.        }  
    40.   
    41.        modCount++;  
    42.        addEntry(hash, key, value, i);  
    43.        return null;  
    44.    }  

    2.get

    ==============================性能测试=================================

    (1)表格统计的差异在于new的方式不同:

    [java] view plaincopy
    1. Map<Integer, String> map = new TreeMap<Integer, String>();  
    2. for (int j = 0; j < 100; j++) {  
    3.     map.put(j, "s" + j);  
    4. }  
    5.   
    6. for (int i = 0; i < 100000; i++) {  
    7.     for (int j = 0; j < 100; j++) {  
    8.         map.get(j);  
    9.     }  
    10. }  
    new方式 运行时间
    new TreeMap<Integer, String>() 542
    new HashMap<Integer, String>(100) 280

    (2)变化下集合的大小(有时我们使用Map存放上万条乃至几十万条数据用于小数据本地快速缓存):

    [java] view plaincopy
    1. Map<Integer, String> map = new HashMap<Integer, String>(300000);  
    2. for (int j = 0; j < 300000; j++) {  
    3.     map.put(j, "sasd");  
    4. }  
    5. beginTime = System.currentTimeMillis();  
    6. for (int i = 0; i < 100; i++) {  
    7.     for (int j = 0; j < 300000; j++) {  
    8.         map.get(j);  
    9.     }  
    10. }  
    new方式 运行时间
    new TreeMap<Integer, String>() 5100
    new HashMap<Integer, String>(300000) 1400

    (3)当hashcode为极端情况下:

    [java] view plaincopy
    1. Map<HashKeyTest, String> map = new TreeMap<HashKeyTest, String>();  
    2. for (int j = 0; j < 10000; j++) {  
    3.     map.put(new HashKeyTest(j), "sasd");  
    4. }  
    5. beginTime = System.currentTimeMillis();  
    6. for (int i = 0; i < 10; i++) {  
    7.     for (int j = 0; j < 10000; j++) {  
    8.         map.get(new HashKeyTest(j));  
    9.     }  
    10. }  
    11.   
    12. public class HashKeyTest implements Comparable<HashKeyTest> {  
    13.   
    14.     private int value;  
    15.     ...  
    16.       
    17.     @Override  
    18.     public boolean equals(Object obj) {  
    19.         if (this == obj)  
    20.             return true;  
    21.         if (obj == null)  
    22.             return false;  
    23.         if (getClass() != obj.getClass())  
    24.             return false;  
    25.         HashKeyTest other = (HashKeyTest) obj;  
    26.         if (value != other.value)  
    27.             return false;  
    28.         return true;  
    29.     }  
    30.     //模拟hashCode的极端情况,返回相同的值  
    31.     @Override  
    32.     public int hashCode() {  
    33.         return 345345435;  
    34.     }  
    35.     //用于TreeMap的二叉树对比排序  
    36.     @Override  
    37.     public int compareTo(HashKeyTest o) {  
    38.         int thisVal = this.value;  
    39.         int anotherVal = o.getValue();  
    40.         return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));  
    41.     }  
    42.   
    43. }  
    new方式 消耗时间
    new TreeMap<HashKeyTest, String>() 18
    new HashMap<HashKeyTest, String>() 3480

    *************************************************源码分析*************************************************

    TreeMap.get(Object key):

    基于红黑树查找

    [java] view plaincopy
    1.    public V get(Object key) {  
    2.        Entry<K,V> p = getEntry(key);  
    3.        return (p==null ? null : p.value);  
    4.    }  
    5.    final Entry<K,V> getEntry(Object key) {  
    6.        // Offload comparator-based version for sake of performance  
    7.        if (comparator != null)  
    8.            return getEntryUsingComparator(key);  
    9.        if (key == null)  
    10.            throw new NullPointerException();  
    11. Comparable<? super K> k = (Comparable<? super K>) key;  
    12.        Entry<K,V> p = root;  
    13.        while (p != null) {  
    14.            int cmp = k.compareTo(p.key);  
    15.            if (cmp < 0)  
    16.                p = p.left;  
    17.            else if (cmp > 0)  
    18.                p = p.right;  
    19.            else  
    20.                return p;  
    21.        }  
    22.        return null;  
    23.    }  

    HashMap.get(Object key):

    代码中很简明的可以看出,如果hashCode不均为出现的问题

    [java] view plaincopy
    1.    public V get(Object key) {  
    2.        if (key == null)  
    3.            return getForNullKey();  
    4.        int hash = hash(key.hashCode());  
    5. //若hashCode相同,会导致查找方式就是这里的不停的遍历  
    6.        for (Entry<K,V> e = table[indexFor(hash, table.length)];  
    7.             e != null;  
    8.             e = e.next) {  
    9.            Object k;  
    10.            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
    11.                return e.value;  
    12.        }  
    13.        return null;  
    14.    }  

    总结:

    1、key如果是自定义对象,一定要有效地重载hashCode(),可参考《覆盖equals时总要覆盖hashCode》;

    2、尽量保证hashCode“均为分布”,以便于均匀填充“水桶”;

    3、当我们确定Map存放的数据比较多且hashCode分配严重不均匀,切记不要使用HashMap,建议使用TreeMap;

    4、正常情况下,也就是我们常把表id作为key时,建议使用HashMap。


    Set留给同学自己学习吧。(HashSet其实就是使用的HashMap作为容器)

  • 相关阅读:
    Numpy
    啊大大阿达
    asda
    啊大大
    初识python
    初识python
    初识python
    初识python
    初识python
    初识python
  • 原文地址:https://www.cnblogs.com/new0801/p/6175879.html
Copyright © 2011-2022 走看看