HashMap:
首先,HashMap的底层数据结构是数组+链表,它的底层是由一个Entry数组组成的。Entry对象是一个链式结构,它里面有四个属性,分别是:key、value、next(链式结构中的下一个对象)、Hash值。
HashMap里面有几个成员变量,分别定义了一些默认的数值,比如定义了默认的长度是1<<4也就是16,还定义了默认的加载因子是0.75f,HashMap他有一个扩容机制,当数组中的元素达到加载因子所定义的密度时,为了保证每条链上的对象不会过多而影响查询速度,HashMap中的Entry数组会进行扩容,每次扩大为之前的2倍,实际上这是一种空间换时间的做法。
HashMap中有很多构造方法,但是无论我们通过哪个构造方法创建HashMap最终调用的都是两个参的有参构造。在源码里对传入的参数做了一些安全性验证,但是此时HashMap中的Entry数组并没有开辟空间。而是在第一次调用put方法存值的时候开辟空间。
当调用put方法存值时候,HashMap是允许键为null的,当键为null时因为无法计算Hash值,所以将键为null的键值对默认放入索引值为0的Entry对象中。首先遍历索引值为0的Entry对象,判断是否存在键为null的Entry对象,如果存在则用新的值覆盖旧值,并将旧值返回(put结束)。如果当前链中没有键为null的对象,则需要追加一个新的Entry对象。调用addEntry,首先判断当前map中的元素个数是否大于临界值,如果大于临界值并且当前需要追加的索引所对应的位置已经有Entry对象,为了避免链过长,则会触发扩容机制。如果当前数组已经达到最大值(Max)则直接扩容到Integer的最大值,否则扩大为原来的两倍。每次扩容时都是创建一个新的数组,然后底层通过C将旧数组中的数据复制到新创建的数组中。然后将新数组赋予成员变量。扩容结束之后,通过键计算hash值,如果键为null则hash为0,再通过Hash做位运算求出存放数据的索引值,位运算相当于是用hash相对于数组长度求模。根据计算出的索引值,将原Entry对象取出。创建新的Entry对象,将原对象放入到新对象的next属性中。再将新对象赋予数组(put结束)。当键不为空时计算键的hash值。然后判断是否存在,存在则覆盖,不存在则创建新的Entry对象追加到数组。追加前要判断是否需要扩容。原理和键为null时相同。
Hashtable
Hashtable和HashMap的底层数据结构相同,都是由Entry数组构成(数组+链表),数组默认长度是11,默认加载因子0.75f。它是线程安全的,因为所有的公共方法都有synchronized修饰,一个类中由synchronized修饰的所有方法同时只能有一个线程操作,所以HashTale可以保证每个人再操作数据时不受他人干扰,保证了数据的安全性,但是,会影响效率。
当向Hashtable插入键为null的值的时候,会抛空指针异常,即Hashtable中不允许有null的键存在。
Vector
数据结构是一个Object数组,这个数组的默认长度是10,可以通过有参构造方法创建不同长度的Vector集合。Vector集合每次默认扩容为原来的2倍。也可以通过两个参数的有参构造来设置扩容时的增量。当扩容之后的值大于Integer最大值-8时,扩容到Integer最大值。Vector也是线程安全的一个类,因为所有公共方法都有synchronized修饰。