Android 对HashMap深度分析和应用
来源:互联网 发布:小加索尔数据 编辑:程序博客网 时间:2024/06/05 01:06
java.util.HashMap是很常见的类,前段时间公司系统由于对HashMap使用不当,导致cpu百分之百,在并发环境下使用HashMap 而没有做同步,可能会引起死循环,关于这一点,sun的官方网站上已有阐述,这并非是bug。
HashMap的数据结构
transient Entry[] table;
Entry就是HashMap存储数据所用的类,它拥有的属性如下
final K key;
V value;
final int hash;
Entry<K,V> next;
看 到next了吗?next就是为了哈希冲突而存在的。比如通过哈希运算,一个新元素应该在数组的第10个位置,但是第10个位置已经有Entry,那么好 吧,将新加的元素也放到第10个位置,将第10个位置的原有Entry赋值给当前新加的 Entry的next属性。数组存储的是链表,链表是为了解决哈希冲突的,这一点要注意。
几个关键的属性
存储数据的数组
transient Entry[] table;
默认容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
当实际数据大小超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子
int threshold;
加载因子
final float loadFactor;
构造函数1
- public
HashMap( intinitialCapacity, floatloadFactor) { -
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); -
-
// Find a power of 2 >= initialCapacity -
int capacity 1;= -
while (capacity < initialCapacity) -
capacity <<= 1; -
-
this.loadFactor = loadFactor; -
threshold = (int)(capacity * loadFactor); -
table = new Entry[capacity]; -
init(); - }
- while
(capacity < initialCapacity) -
capacity <<= 1;
capacity才是初始容量,而不是initialCapacity,这个要特别注意,如果执行new HashMap(9,0.75);那么HashMap的初始容量是16,而不是9,想想为什么吧。
构造函数2
- public
HashMap( intinitialCapacity) { -
this(initialCapacity, DEFAULT_LOAD_FACTOR); -
}
- public
HashMap() { -
this.loadFactor = DEFAULT_LOAD_FACTOR; -
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); -
table = new Entry[DEFAULT_INITIAL_CAPACITY]; -
init(); -
}
- public
HashMap(Map<? extendsK, extends? V> m) { -
this(Math.max((int) (m.size() 1,/ DEFAULT_LOAD_FACTOR) + -
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); -
putAllForCreate(m); -
}
int hash = hash(key.hashCode());
hash函数如下:
- static
int hash( inth) { -
return useNewHash ? newHash(h) : oldHash(h); -
}
- private
static final boolean useNewHash; -
static { false;useNewHash = }
- private
static int oldHash( inth) { -
h += ~(h << 9); -
h ^= (h >>> 14); -
h += (h << 4); -
h ^= (h >>> 10); -
return h; - }
-
- private
static int newHash( inth) { -
// This function ensures that hashCodes that differ only by -
// constant multiples at each bit position have a bounded -
// number of collisions (approximately 8 at default load factor). -
h ^= (h >>> 20) ^ 12);(h >>> -
return h 7)^ (h >>> ^ 4);(h >>> - }
如果确定数据的位置
看下面两行
- int
hash = hash(k.hashCode()); - int
i = indexFor(hash, table.length);
第一行,上面讲过了,是得到哈希值,第二行,则是根据哈希指计算元素在数组中的位置了,位置的计算是将哈希值和数组长度按位与运算。
- static
int indexFor( inth, intlength) { -
return h 1);& (length- -
}
“h & (length-1)”
首先我们要确定一下,HashMap的数组长度永远都是偶数,即使你在初始化的时候是这样的new HashMap(15,0.75);因为在构造函数内部,上面也讲过,有这样的一段代码:
- while
(capacity < initialCapacity) -
capacity <<= 1;
所以length-1一定是个奇数,假设现在长度为16,减去1后就是15,对应的二进制是:1111。
假设有两个元素,一个哈希值是8,二进制是1000,一个哈希值是9,二进制是1001。和1111与运算后,分别还是1000和1001,它们被分配在了数组的不同位置,这样,哈希的分布非常均匀。
那么,如果数组长度是奇数,减去1后就是偶数了,偶数对应的二进制最低位一定是 0了,例如14二进制1110。对上面两个数子分别与运算,得到1000和1000。看到了吗?都是一样的值,哈希值8和9的元素多被存储在数组同一个位 置的链表中。在操作的时候,链表中的元素越多,效率越低,因为要不停的对链表循环比较。所以,一定要哈希均匀分布,尽量减少哈希冲突,减少了哈希冲突,就 减少了链表循环,就提高了效率。
put方法到底作了什么?
- public
V put(K key, V value) { -
if (key null)== -
return putForNullKey(value); -
int hash = hash(key.hashCode()); -
int i = indexFor(hash, table.length); -
for (Entry<K,V> null;e = table[i]; e != 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; -
}
- private
V putForNullKey(V value) { -
int hash = hash(NULL_KEY.hashCode()); -
int i = indexFor(hash, table.length); -
-
for (Entry<K,V> null;e = table[i]; e != e = e.next) { -
if (e.key == NULL_KEY) { -
V oldValue = e.value; -
e.value = value; -
e.recordAccess(this); -
return oldValue; -
} -
} -
-
modCount++; -
addEntry(hash, (K) NULL_KEY, value, i); -
return null; -
}
这一段代码是处理哈希冲突的,就是说,在数组某个位置的对象可能并不是唯一的,它是一个链表结构,根据哈希值找到链表后,还要对链表遍历,找出key相等的对象,替换它,并且返回旧的值。
- for
(Entry<K,V> null;e = table[i]; e != e = e.next) { -
if (e.key == NULL_KEY) { -
V oldValue = e.value; -
e.value = value; -
e.recordAccess(this); -
return oldValue; -
} -
}
- modCount++;
- addEntry(hash,
(K) NULL_KEY, value, i); - return
null;
- void
addEntry( inthash, intK key, V value, bucketIndex) { - Entry<K,V>
e = table[bucketIndex]; -
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); -
if (size++ >= threshold) -
resize(2 * table.length); - }
- Entry(int
h, K k, V v, Entry<K,V> n) { -
value = v; -
next = n; -
key = k; -
hash = h; -
}
上面的put方法里有这样的一段:
- if
(size++ >= threshold) -
resize(2 * table.length);
- void
resize( intnewCapacity) { -
Entry[] oldTable = table; -
int oldCapacity = oldTable.length; -
if (oldCapacity == MAXIMUM_CAPACITY) { -
threshold = Integer.MAX_VALUE; -
return; -
} -
-
Entry[] newTable = new Entry[newCapacity]; -
transfer(newTable); -
table = newTable; -
threshold = (int)(newCapacity * loadFactor); -
}
- void
transfer(Entry[] newTable) { -
Entry[] src = table; -
int newCapacity = newTable.length; -
for ( intj 0;= j < src.length; j++) { -
Entry<K,V> e = src[j]; -
if (e null)!= { -
src[j] = null; -
do { -
Entry<K,V> next = e.next; -
int i = indexFor(e.hash, newCapacity); -
e.next = newTable[i]; -
newTable[i] = e; -
e = next; -
} while (e null);!= -
} -
} -
}
如何寻找元素
根据key的hasocde()得到哈希值
根据哈希值确定
找到指定位置 的链表,循环比较,先“==”比较,如果不等,再“equals”比较,如果有一个比较相等,就说明找到元素了。
所以说到这里,我想大家也明白了,为什么要把一个对象放进HashMap的时候,最好是重写hashcode()方法和equals 方法呢?根据前面的分析,hashcode()可以确定元素在数组中的位置,而equals方法在链表的比较时要用到。
正确的使用HashMap
1:不要在并发场景中使用HashMap
看看get方法的红色部分
- public
V get(Object key) { -
if (key null)== -
return getForNullKey(); -
int hash = hash(key.hashCode()); -
for (Entry<K,V> e = table[indexFor(hash, table.length)]; -
e != null; -
e = e.next) { -
Object k; -
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) -
return e.value; -
} -
return null; -
}
- Android 对HashMap深度分析和应用
- Android 对HashMap深度分析…
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析
- Java中对HashMap的深度分析与比较
- Java中对HashMap的深度分析与比较
- Java中对HashMap的深度分析与比较
- Java中对HashMap的深度分析与比较
- Java语言中对HashMap的深度分析与比较
- Java中对HashMap的深度分析与比较
- Java中对HashMap的深度分析与比较
- JSP页面中的pageEncoding和contentType两种属性
- HDU1043:Eight(A*+康托)
- 深入浅出java原子操作
- Android实战—实现自动读取通讯录和通话记录切换,ViewPage滑屏效果,可拨打电话
- eclipse+NDK r9 配置
- Android 对HashMap深度分析和应用
- Python 代码调试技巧(图文详解)
- tomcat 调优
- 关于struct占用内存大小的几点分析
- 移动硬盘不见数据怎么恢复
- LVM扩容/
- 第六周上机项目4-静态成员应用
- 《程序员,你伤不起》读书笔记 (二) :2005年我的第一次软件行业创业,烧掉30万、2年时间打水漂的惨痛教训 读书笔记
- 你不懂事的岁月