hashmap的底层实现
hashmap是根据键的hashcode值来存储数据的,大多数情况下可以直接定位到它的值,所以它的访问速度很快,但是遍历顺序不确定。
这是因为hashmap会先用hash函数处理键值,让其能够均匀分布到map中
hashmap最多只允许一条记录的键为null,单允许多条记录的值为null。
hashmap是非线程安全的,即任一时刻可以有多个线程来写hashmap,这可能会导致数据不一致。如果需要满足线程安全,可以用Collections的SynchronizedMap方法使hashMap具有线程安全的能力,或者使用ConcurrentHashMap。
ConcurrentHashMap在写入和读取的性能都比使用SynchronizedMap更高效
hashmap的结构是数组加链表,在JAVA8中又引入了红黑树
大体来说:HashMap里面是一个数组,然后数组中每个元素是一个单向链表。链表中存储的对象是嵌套类Entry的实例
Entry包含四个属性:key,value,hash值和用于单向链表的next。
根据hash值我们能快速找到数组的具体下标,但那是在这之后,需要顺着链表一个个比对才能找到所需要的值,,时间复杂度取决于链表的长度,为O(N)。为了降低开销,在Java8中设置当链表元素超过8个以后,会将链表转化为红黑树,在这些位置查找的时候可以降低时间复杂度为O(logN)。
capacity:当前数组容量,始终保持2^n,可以扩容,扩容后数组大小为当前的2倍
loadFactor:负载因子,默认为0.75
threshould:扩容的阈值,等于capacity*loadFactor
hashmap初始化时,可以指定初始化容量和阈值,如果指定,则初始化时阈值等于容量(不指定则为0)
当放入元素后,阈值就等于容量*负载因子
当元素数量超出阈值,hashmap会扩容为原来的2倍
public class TestField {
/**输出hashmap的容量,阈值和元素数量*/
private static void getMyMap(HashMap<?,?> map) throws Exception {
Class<? extends HashMap> mapClass = map.getClass();
//数组容量
Method capacity = mapClass.getDeclaredMethod("capacity");
capacity.setAccessible(true);
//负载因子
Method loadFactor = mapClass.getDeclaredMethod("loadFactor");
loadFactor.setAccessible(true);
//扩容阈值
Field threshold = mapClass.getDeclaredField("threshold");
threshold.setAccessible(true);
System.out.println("容量:" + capacity.invoke(map) +
",负载因子:" + loadFactor.invoke(map) +
",阈值:" + threshold.get(map) + ",元素数量:" + map.size());
}
public static void main(String[] args) throws Exception{
//不指定容量,初始阈值为0
//HashMap<Integer,String> map = new HashMap<>();
//指定初始化容量,阈值等同于容量
HashMap<Integer,String> map = new HashMap<>(15);
//指定容量和负载因子
//HashMap<Integer,String> map = new HashMap<>(10,0.5f);
getMyMap(map);//容量:16,负载因子:0.75,阈值:16,元素数量:0
map.put(1,"test");
getMyMap(map);//容量:16,负载因子:0.75,阈值:12,元素数量:1
for (int i = 2; i <= 12; i++) {
map.put(i,"t");
}
getMyMap(map);//容量:16,负载因子:0.75,阈值:12,元素数量:12
map.put(13,"t");
getMyMap(map);//容量:32,负载因子:0.75,阈值:24,元素数量:13
}
}