ArrayMap、SparseArray
HashMap原理
- HashMap 基于 Hash 算法实现的,用于存储key、value键值对。当传入 key 时,获取key的hash 值,根据 hash 值计算出散列地址,将 value 保存在该地址里。当计算出的散列地址相同时,我们称之为 hash冲突,HashMap的做法是用链表和红黑树存储相同散列地址的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
- 取值的时候如果对比节点的哈希值和要查找的哈希值相等,就会判断 key 是否相等(链表和红黑树情况下),相等就直接返回;不相等就从子树中递归查找。
- 当一个链表上的元素个数大于8时,会将链表转为红黑树,主要是为了在数据量大的情况下,加快数据的查找效率。毕竟一个红黑树的插入和查找的时间复杂度为O(logn),远比链表的插入和查询的时间复杂度O(n)要好的多,而且数据量越大,越明显。
- 平衡二叉树的插入和查找时间复杂度为O(logn)
- 参考资料
HashMap的构造方法
- public HashMap(int initialCapacity, float loadFactor)
默认初始容量是16,initialCapacity
默认的负载因子是0.75,loadFactor- 关于initialCapacity:
例如new HashMap(3)会创建一个大于3的2的n次方的长度的HashMap,为4
例如new HashMap(5)会创建一个大于5的2的n次方的长度的HashMap,为8
- 关于initialCapacity:
- 扩容:
当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),哈希表扩容一倍。rehash消耗性能,所以可以通过设置合理的初始容量来减少rehash次数。 - 问题:new HashMap(65,0.75f)要录入95条数据,会产生扩容吗?
解答:创建的HashMap的长度为,2的6次方=64,不够申请的65,所以需要申请2的7次方=128,触发扩容的大小为128*0.75=96条数据,所以不会进行扩容。
LinkedHashMap
-
继承自HashMap,里面构造类似于双向链表原理。Android中的缓存算法LRUCache就是基于LinkedHashMap实现的。
-
特点:
1.是有序的(录入顺序,使用顺序)。而HashMap是无序的。
2.一次性取大量数据的情况下会快(例如遍历)。 -
对于录入顺序(最新添加的排在后面)
public static Map<String, Integer> getMap() {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("zhangyu1", 1);
map.put("zhangyu2", 2);
map.put("zhangyu3", 3);
map.put("zhangyu4", 4);
map.put("zhangyu5", 5);
return map;
}
//对于录入顺序
{zhangyu1=1, zhangyu2=2, zhangyu3=3, zhangyu4=4, zhangyu5=5}
- 对于使用顺序(最近添加或访问的排在后面)
第三个参数,accessOrder = true
public static Map<String, Integer> getMap() {
//对于使用顺序,要修改构造方法,true
Map<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("zhangyu1", 1);
map.put("zhangyu2", 2);
map.put("zhangyu3", 3);
map.put("zhangyu4", 4);
map.put("zhangyu5", 5);
map.get("zhangyu1");//LinkedHashMap使用后,排在最后
return map;
}
//使用顺序,输出结果
{zhangyu2=2, zhangyu3=3, zhangyu4=4, zhangyu5=5, zhangyu1=1}
- LRU是Least Recently Used的缩写,即最近最少使用
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUMap<K, V> extends LinkedHashMap<K, V> {
private int maxSize;
public LRUMap(int maxSize) {
super(16, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > this.maxSize;
}
}
public class LRUMapTest {
public static void main(String[] args) {
Map<String,Integer> lruMap = new LRUMap(3);
lruMap.put("zhangyu1",1);
lruMap.put("zhangyu2",2);
lruMap.put("zhangyu3",3);
lruMap.put("zhangyu4",4);
lruMap.put("zhangyu5",5);
System.out.println(lruMap);
}
}
//结果:{zhangyu3=3, zhangyu4=4, zhangyu5=5}
public class LRUMapTest {
public static void main(String[] args) {
Map<String,Integer> lruMap = new LRUMap(3);
lruMap.put("zhangyu1",1);
lruMap.put("zhangyu2",2);
lruMap.put("zhangyu3",3);
System.out.println(lruMap);
lruMap.get("zhangyu1");
System.out.println(lruMap);
lruMap.put("zhangyu4",4);
lruMap.put("zhangyu5",5);
System.out.println(lruMap);
}
}
//结果
//{zhangyu1=1, zhangyu2=2, zhangyu3=3}
//{zhangyu2=2, zhangyu3=3, zhangyu1=1}
//{zhangyu1=1, zhangyu4=4, zhangyu5=5}
TreeMap
- TreeMap按照key的值排序,因为有排序所以速度会慢
public class TreeMapTest {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("aaa",1);
map.put("lkj",4);
map.put("abc",2);
map.put("bcc",3);
map.put("sdf",4);
System.out.println(map);
}
}
//{aaa=1, abc=2, bcc=3, lkj=4, sdf=4}
public class TreeMapTest {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
map.put("aaa",1);
map.put("lkj",4);
map.put("abc",2);
map.put("bcc",3);
map.put("sdf",4);
System.out.println(map);
}
}
//{sdf=4, lkj=4, bcc=3, abc=2, aaa=1}
HashTable
- public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable - 线程安全
- 完全依靠synchronized导致性能低(全部加锁)
大锁:对HashTable对象加锁
长锁:直接对方法加锁
读写锁公用:只有一把锁,从头锁到尾
ConcurrentHashMap
- public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable - 线程安全
- 减少synchronized的使用,多用volatile
- 发展历程
JDK5:分段锁,必要时加锁(缺陷segment分布不均匀)
JDK6:优化二次Hash算法(使其均匀分布,高位找segment低位找table)
JDK7:段懒加载,volatile&cas
JDK8:摒弃段,基于HashMap原理的并发实现(尽量使用volatile和小范围加锁),引入CounterCell,本质上也是分段计数。 - 弱一致性
添加元素后不一定能马上读到
清空元素后可能仍然会有元素
遍历之前段元素的变化会读到
遍历之后段元素变化读不到
遍历时元素发生变化不抛出异常
ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable一样了。
Map的遍历
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map<String, Integer> map = getMap();
System.out.println(map);
showMap1(map);
showMap2(map);
showMap3(map);
showMap4(map);
}
/**
* 利用迭代器遍历。
* 同时获取到key,value
* 速度方面,values,entrySet,Iterator速度相近
* @param map
*/
public static void showMap4(Map<String, Integer> map) {
System.out.println("--------------------------");
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
/**
* 利用entrySet进行遍历
* 同时获取到key,value
* 速度方面,values,entrySet,Iterator速度相近
* @param map
*/
public static void showMap3(Map<String, Integer> map) {
System.out.println("--------------------------");
for (Map.Entry<String,Integer> entry : map.entrySet()) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
/**
* 利用values进行遍历
* 缺点:只能获取到value
* 速度方面,values,entrySet,Iterator速度相近
* @param map
*/
public static void showMap2(Map<String, Integer> map) {
System.out.println("--------------------------");
for (Integer v : map.values()) {
System.out.println(v);
}
}
/**
* 利用keySet进行遍历
* 优势:既可以得到key,又可以得到value
* 缺点:速度最慢,通常不用
* @param map
*/
public static void showMap1(Map<String, Integer> map) {
System.out.println("--------------------------");
for (String key : map.keySet()) {
System.out.println(key+"="+map.get(key));
}
}
/**
* 初始化一个map
*
* @return
*/
public static Map<String, Integer> getMap() {
Map<String, Integer> map = new HashMap<>();
map.put("zhangyu1", 1);
map.put("zhangyu2", 2);
map.put("zhangyu3", 3);
map.put("zhangyu4", 4);
map.put("zhangyu5", 5);
return map;
}
}
输出结果
{zhangyu4=4, zhangyu5=5, zhangyu1=1, zhangyu2=2, zhangyu3=3}
--------------------------
zhangyu4=4
zhangyu5=5
zhangyu1=1
zhangyu2=2
zhangyu3=3
如何避免put会修改之前的值
- 由于Map中key值是唯一的,所以put一个相同的key,value会被修改。
有两种方法:- 1.if (map.containKey(key))
- 2.使用putIfAbsent(key,value)