java—HashMap

1.hashmap底层结构是什么,为什么用这个?


底层数据结构是table数组加链接,当时同一个table数组为出现hash冲突则会使用链表来存储数据。在java1.8版本中,当链表长度大于8时链表转为红黑树。

分析为什么使用数组加链表的形式和为什么换成红黑树?

首先数组可以快速的获取每个元素的位置,然后链表在添加和删除元素的时候也是只需要知道指定元素的前后节点就可以,这两种结构结合在一起可以大大提高hashmap得增删改查的 效率。

使用红黑树代替链表,是因为红黑树的查找效率能达到logN,红黑树其实是一种特别的二叉查找树,保证有序,查询效率很高。为什么不直接使用红黑树代替链表,当元素个数少的时候,直接使用链表比较简单方便,构造红黑树比较复杂,消耗比较大,入不敷出的感觉。为什么不是直接使用二叉查找树,在插入一个元素造成不平衡,红黑树最多需要三次旋转就可以达到平衡。为什么不使用B树和B+树,B和B+树对于大数量级数据,减少io次数的,hashmap的链接元素不会很多,使用红黑树就可以,且红黑树时间复杂度低(longN)。

 

2.负载因子过大对hashMap的影响

负载因子是指散列表空间的使用程度,负载因子过大会影响查找的效率,因为链表在查找的时候,会一个一个元素进行比对,当元素过多的时候查找效率就会降低。

因此在一个链表的元素大于8时,链表就会转化为红黑树。红黑树的查找效率极高。

 

3.hashmap是线程安全的吗?为什么不安全?

首先要介绍一下put元素和get元素以及扩容

3.1 hashmap put元素的过程

put

代码块

addEntry(hash, key, value, i); public V put(K key, V value) {
14 if (table == EMPTY_TABLE) {
15 inflateTable(threshold);
16 }
17 if (key == null)
18 return putForNullKey(value);
19 int hash = hash(key);
20 int i = indexFor(hash, table.length);
21 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
22 Object k;
23 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
24 V oldValue = e.value;
25 e.value = value;
26 e.recordAccess(this);
27 return oldValue;
28 }
29 }
30
31 modCount++;//
32 addEntry(hash, key, value, i);
33 return null;
34 }
解析:首先判断table数组,判断数组是否为空,为空的话就去使用inflateTable的方法初始化hashmap。

   如果table不为空的话,就判断key是否为空,为空的话就将放到数组的index=0的位置,如果value不为空则返回value值。

   如果key不为空的话,就通过key获取hash值,通过hash值和table的长度与运算获取hashCode值。

   通过hashCode的遍历entry<K,V>的键值对,如果key的hash值相等 并且key.equals(e.key)也相等的话,就将新的value替换掉旧的,返回旧值。

如果key的hash值相等 并且key.equals(e.key)不相等的话,则添加元素addEntry(hash, key, value, i);,添加的元素则放在第一个位置上。

get

代码块

public V get(Object key) {
19 if (key == null)
20 return getForNullKey();
21 Entry<K,V> entry = getEntry(key);
22
23 return null == entry ? null : entry.getValue();
24 }
25
33 private V getForNullKey() {
34 if (size == 0) {
35 return null;
36 }
37 for (Entry<K,V> e = table[0]; e != null; e = e.next) {
38 if (e.key == null)
39 return e.value;
40 }
41 return null;
42 }
 首先get的时候,如果key==null,判断map的长度也为空的话,则返回null,如果map长度不为空,则e也不空,遍历table[0],返回e.value

 然后getEntry的时候,首先要获取hash(key)的值,通过hash&table.length获取到的hashCode值得到entry在桶中存放的位置,判断如果如果传入的key的hash与要获得key的hash相等的话并且 key.equals(e.key)y也相等,则返回entry,如果返回的entry不为空的话,则getValue的值。

 

扩容

代码块

void transfer(Entry[] newTable) {
Entry[] src = table; //src引用了旧的Entry数组
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
Entry<K, V> e = src[j]; //取得旧Entry数组的每个元素
if (e != null) {
src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
do {
Entry<K, V> next = e.next;
int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
e.next = newTable[i]; //标记[1]
newTable[i] = e; //将元素放在数组上
e = next; //访问下一个Entry链上的元素
} while (e != null);
}
}
}
当负载因子大于0.75f时,进行扩容。扩容时相当于新建一个table数组,逐一将旧table数组中的所有的元素复制到新的数组中。

 

两个线程同时进行put,又遇上扩容复制元素,这种情况下容易形成链表上的闭环,导致get元素的时候造成死循环。

4.treemap是跟据什么排序的,怎么实现根据value排序?

treeMap是跟key排序的,如果要实现根据value值进行排序。重新定义一个类实现Comparator,重写compare方法,根据传入的key获取value值,比较value值,返回比较结果进行排序。

代码块


public class ValueComparator implements Comparator<Object>{

 
Map<Object, Object> base;

 
public ValueComparator(Map<Object, Object> map){
this.base = map;
}

 
@Override
public int compare(Object o1, Object o2) {
Integer value1 = Integer.parseInt(base.get(o1.toString()).toString());
Integer value2 = Integer.parseInt(base.get(o2.toString()).toString());
if ((value2 - value1) > 0) {
return 1;
}else if (value2 - value1 ==0) {
return o2.toString().compareTo(o1.toString());
}else {
return -1;
}
}
}

posted @ 2018-11-16 10:06  Teenager  阅读(243)  评论(0编辑  收藏  举报