Java集合学习--HashMap、LinkedHashMap、TreeMap、HashTable

来源:互联网 发布:霸道总裁 知乎 编辑:程序博客网 时间:2024/06/05 16:45

HashMap:

概述:

基于哈希表实现,可以通过调整初始容量和加载因子进行性能调优

初始化:

HashMap中有两个因子影响其性能:初始容量和加载因子。这两个参数都可以在创建时通过构造器传入,如果不指定,默认初始容量=16,加载因子=0.75,加载因子会影响rehash操作。最大容量必须是2的幂且小于2的30次方,传入容量过大将被这个值替换。

HashMap的几个重要成员变量:

    //默认初始容量    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16    //最大容量,容量大于该值会被替换    static final int MAXIMUM_CAPACITY = 1 << 30;    //默认加载因子    static final float DEFAULT_LOAD_FACTOR = 0.75f;    //数据存储    static final Entry<?,?>[] EMPTY_TABLE = {};    //数据存储数组,HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;    //Set中元素个数    transient int size;    // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)    int threshold;    //加载因子    final float loadFactor;    //HashMap被改变的次数    transient int modCount;    //    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
构造方法:

public HashMap(int initialCapacity, float loadFactor) {        //初始容量        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal initial capacity: " +                                               initialCapacity);        if (initialCapacity > MAXIMUM_CAPACITY)            initialCapacity = MAXIMUM_CAPACITY;        //加载因子        if (loadFactor <= 0 || Float.isNaN(loadFactor))            throw new IllegalArgumentException("Illegal load factor: " +                                               loadFactor);        this.loadFactor = loadFactor;        threshold = initialCapacity;        init();    }
通常,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点,可以想想为什么)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地降低 rehash 操作次数。如果初始容量大于最大条目数除以加载因子(实际上就是最大条目数小于初始容量*加载因子),则不会发生 rehash 操作。 

如果可以预见Map中会存放很多元素,那么就应该在创建Map时人工调整Map的大小,尽量减少Map rehash的次数,而不是让Map自动去调整,当HashMap存放的元素越来越多,到达临界值(阀值)threshold时,就要对Entry数组扩容,HashMap在扩容时,新数组的容量将是原来的2倍,由于容量发生变化,原有的每个元素需要重新计算bucketIndex,再存放到新数组中去,也就是所谓的rehash。HashMap默认初始容量16,加载因子0.75,也就是说最多能放16*0.75=12个元素,当put第13个时,HashMap将发生rehash,rehash的一系列处理比较影响性能。

添加元素:

Map中key不可重复,key对象需要实现equals方法,此外,所有hash类的集合,对象都需要实现hashCode方法,此处Map中key对象也需要实现hashCode方法,value对象则不需要。

HashMap使用拉链法进行数据存储,其维护了一个数据存储数组table,table中存储了一个链表,如下图所示,HashMap中实现了了Map中的put的方法,用于向Map中新添加数据,方法如下:


public V put(K key, V value) {        if (table == EMPTY_TABLE) {            inflateTable(threshold);        }        if (key == null)            return putForNullKey(value);        int hash = hash(key);        int i = indexFor(hash, table.length);        for (Entry<K,V> e = table[i]; e != null; e = e.next) {            Object k;            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                V oldValue = e.value;                e.value = value;                e.recordAccess(this);                return oldValue;            }        }        modCount++;        addEntry(hash, key, value, i);        return null;    }
新增时首先初始化table(inflateTable(threshold))在计算hash值并确定桶编号,然后检查该桶中是否存在相同的元素,如果不存在,则调用addEntry(hash, key, value, i)方法进行添加操作:

void addEntry(int hash, K key, V value, int bucketIndex) {        if ((size >= threshold) && (null != table[bucketIndex])) {            //容量达到阈值,扩容为两倍            resize(2 * table.length);            hash = (null != key) ? hash(key) : 0;            bucketIndex = indexFor(hash, table.length);        }        createEntry(hash, key, value, bucketIndex);    }
如果Map容量达到阈值,需要扩容,扩容为原来两倍,无法自定义。方法如下:
可以看到,在扩容中会调用transfer(newTable, initHashSeedAsNeeded(newCapacity))将原table中的数据重新hash后填入newTable,这个过程会对Map性能产生影响。

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];        <span style="color:#ff0000;">//将原来table中的数据rehash之后填入newTable</span>        transfer(newTable, initHashSeedAsNeeded(newCapacity));        table = newTable;        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);    }

void transfer(Entry[] newTable, boolean rehash) {        int newCapacity = newTable.length;        for (Entry<K,V> e : table) {            while(null != e) {                Entry<K,V> next = e.next;                if (rehash) {                    e.hash = null == e.key ? 0 : hash(e.key);                }                int i = indexFor(e.hash, newCapacity);                e.next = newTable[i];                newTable[i] = e;                e = next;            }        }    }

多线程:

HashMap是非线程安全的,实现了fail-fast机制,在使用迭代器的过程中如果其他线程改变了集合内容,会抛出ConcurrentModificationException,需要捕获并进行处理。

LinkedHashMap:

LinkedHashMap继承了HashMap,覆盖了其中部分方法,实现了保存元素存入的顺序的功能。通过迭代器对集合中元素进行遍历时,会按照存入的顺序取出元素。

LinkedHashMap重写了HashMap的addEntry和createEntry方法(addEntry方法中直接调用了HashMap的addEntry方法,新增了一部分内容,但是该部分内容并没有起作用,因为removeEldestEntry方法返回false)。

void addEntry(int hash, K key, V value, int bucketIndex) {        super.addEntry(hash, key, value, bucketIndex);        // Remove eldest entry if instructed        Entry<K,V> eldest = header.after;        if (removeEldestEntry(eldest)) {            removeEntryForKey(eldest.key);        }    }
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {        return false;    }
void createEntry(int hash, K key, V value, int bucketIndex) {        HashMap.Entry<K,V> old = table[bucketIndex];        Entry<K,V> e = new Entry<>(hash, key, value, old);        table[bucketIndex] = e;        e.addBefore(header);        size++;    }
createEntry方法中,实现了向集合中添加元素,并通过维护双向列表保存了元素的存入顺序
e.addBefore(header)方法用于维护该双向列表。LinkedHashMap中的私有内部类Entry类继承了HashMap.Entry,并对其进行了扩展,主要是新增了两个成员变量before和after用于保存元素前后性质,此外还新增了几个方法用于双向列表增删数据:

private static class Entry<K,V> extends HashMap.Entry<K,V> {                Entry<K,V> before, after;                ...}
private void addBefore(Entry<K,V> existingEntry) {            after  = existingEntry;            before = existingEntry.before;            before.after = this;            after.before = this;        }private void remove() {            before.after = after;            after.before = before;        }

newEntry对象调用addBefore(existingEntry)的过程如图所示:


LinkedHashMap插入元素时,首先以新元素为单位创建新的Entry对象并放置在相应的桶位置,然后在新对象e上调用addBefore方法e.addBefore(header);,其中header为Map的头元素

新增的步骤如下图所示:



以此实现对原始数据插入的顺序进行保存。

多线程:

LinkedHashMap也是非线程安全的


TreeMap:

概述:
基于红黑树实现,没有调优选项调优,因为数总是处于平衡状态。红黑树是一种自平衡的二叉树(基本操作:左旋,右旋,着色,具体实现请参考算法导论)。TreeMap实现了排序功能。
接口:
TreeMap实现了NavigableMap接口,而接口NacigableMap是接口SortedMap的子类。
public class TreeMap<K,V>    extends AbstractMap<K,V>    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
初始化:
TreeMap提供了4种初始化方式:
(1)TreeMap():构建一个空的映像树 
(2)TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素 
(3)TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序 
(4)TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序 

为了使TreeMap能够对其中的元素进行排序,需要提供比较器Comparator。如果没有提供Comparator,默认将按照key值进行升序排序。

TreeMap的核心在于红黑树的算法实现。因为底层是用链表实现,所以不存在扩容问题,与HashMap不同。

HashTable:
HashMap的同步版本,在各方法上加上了同步修饰词。(包括get方法)
HashTable中存储的键值对的key、value都不能为null,而HashMap无此限制。
HashTable的默认初始容量是11,HashMap是16。默认加载因子都是0.75。
HashTable比HashMap多了一个hashSeed,在初始化HashTable时会初始化hashSeed,这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突(引用自:http://blog.csdn.net/chenssy/article/details/22896871 ,不了解具体原理,有谁清楚的话能否告知?谢谢)

EnumMap:
枚举类型作为键值的Map。因为键的数量相对固定,所以在内部用一个数组储存对应值。通常来说,效率要高于HashMap。

IdentityHashMap:
这是一个特殊的Map版本,它违背了一般Map的规则:它使用 “==” 来比较引用而不是调用Object.equals来判断相等。这个特性使得此集合在遍历图表的算法中非常实用——可以方便地在IdentityHashMap中存储处理过的节点以及相关的数据。

WeakHashMap:
这种Map通常用在数据缓存中。它将键存储在WeakReference中,就是说,如果没有强引用指向键对象的话,这些键就可以被垃圾回收线程回收。值被保存在强引用中。因此,你要确保没有引用从值指向键或者将值也保存在弱引用中m.put(key, new WeakReference(value))。
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 电脑用户名被停用怎么办 电脑截图不清晰怎么办 win10电脑磁盘空间不足怎么办 我的用户名忘记怎么办 12306用户名密码忘记怎么办 电脑开机要密码怎么办 三星账户忘记了怎么办 电脑账户忘记了怎么办 电脑启动要密码怎么办 电脑用户密码忘记了怎么办 微信语音听不懂怎么办 法院迟迟不立案怎么办 dns连不上网怎么办 监控显示无硬盘怎么办 电脑获取不到dns怎么办 打游戏cpu过高怎么办 注册了公司没做账怎么办 工行u盾没电了怎么办 属狗和属鸡相害怎么办 被别人说老实怎么办 牛手术后低烧怎么办 按摩把腿按肿了怎么办 吃感冒药特别困怎么办 剖腹产平躺腰疼怎么办 早餐店没生意怎么办 摆小吃摊没生意怎么办 早餐生意不好做怎么办 小吃车不让出摊怎么办 淘宝订单消失了怎么办 工厂搬迁托着不处理怎么办 电磁炉不识别锅怎么办 冰箱电线不够长怎么办 成品衣柜不到顶怎么办 安迪达斯实体店授权书怎么办 松木家具变黄怎么办 松木家具味大怎么办 摆摊卖对联刮风怎么办 买社保如果死了怎么办 黄金为什么不亮怎么办 cad指令栏不见了怎么办 cad为什么闪退怎么办