Map
首先观察Map接口的定义
public interface Map<K,V>
在Map接口中有如下常用方法:
Map本事是一个接口,要使用Map需要通过子类进行对象实例化。
Map接口的常用子类有如下四个:HashMap、HashTable、TreeMap、ConcurrentHashMap。
HashTable子类
JDK1.0提供有三大主要类:Vector、Enumeration、Hashtable。Hashtable是最早实现这种二元偶对象数据结构,后期 的设计也让其与Vector一样多实现了Map接口而已。
观察HashTable
public class Test {
public static void main(String[] args) {
Map<Integer,String> map = new Hashtable<>();
map.put(1,"Hello");
//key重复
map.put(1,"Hello");
map.put(3,"秋招");
map.put(2,"Future");
System.out.println(map);
}
}
{3=秋招, 2=Future, 1=Hello}
Process finished with exit code 0
Hashtable的原理和HashMap基本一致。
HashMap和Hashtable的区别
一、Hashtable是线程安全的,方法时synchronized,适合在多线程环境中使用,效率较低
HashMap是线程不安全的,方法不是synchronized,适合在单线程环境中使用,效率较高
所以如果要在多线程下使用的话,需要手动同步HashMap,Collections.synchronizedMap()
PS:Hashtable的效率较低的原因?
在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问HashTable的同步方法时,访问其它同步方法的线程就可能 会进入阻塞状态或者轮训状态。如果线程1使用put方法进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法 来获取元素,所以竞争越激烈效率越低。
二、HashMap的Key和Value都可以为null值,而HashTable的Key和Value都不允许有Null值。
三、HashMap中数组的默认大小是16,而且一定是2的倍数,扩容后的数组长度是之前数组长度的2倍。HashTable中数组默认大小是11, 扩容后的数组是之前数组长度的2倍+1。
四、哈希值的使用不同。
//HashMap重新计算hash值,而且用&代替求模:
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
static int hash(Object x){
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static int indexFor(int h, int length){
return h & (length - 1);//HashMap的表长永远是2 ^n
}
//HashTable直接使用对象的HashCode值
int hahs = key.hashCode();//注意区分二者的hash值
int index = (hash & 0x7FFFFFFF) % tab.length;
五、判断是否还有某个键
在HashMap中,null可以作为键,这样的键就只有一个;可以有一个或者多个键所对应的值为null。当get()方法返回null值时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能用get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。Hashtable的键值都不能为null,所以可以用get()方法来判断是否含有某个键。
个人建议:多考虑使用HashMap,Hashtable不常用。
Map集合使用Iterator输出
Map接口与Collection接口不同,Collection接口有iterator()方法可以很方便的取得Iterator对象来输出,而Map接口本 身并没有此方法。下面我们首先来观察Collection接口与Map接口数据保存的区别:
在Map接口里面有一个重要的方法,将Map集合转为Set集合:
public Set<Map.Entry<K, V>> entrySet();
Map要想调用Iterator接口输出,走的是一个间接使用的模式,如下图:
通过Iterator输出Map集合
public class Test {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "Hello");
map.put(2, "Future");
map.put(3, "秋招,I am coming!");
//1.将Map集合转为Set集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
//2.获取Iterator对象
Iterator<Map.Entry<Integer,String>> iterator = set.iterator();
//3.输出
while (iterator.hasNext()){
Map.Entry<Integer,String> entry = iterator.next();
System.out.println(entry.getKey()+" = " + entry.getValue());
}
}
}
1 = Hello
2 = Future
3 = 秋招,I am coming!
Process finished with exit code 0
关于Map中Key的说明
在之前使用Map集合的时候使用的都是系统类作为key(Integer,String等)。实际上也可采用自定义类作为key。这 个时候一定要记得覆写Object类的hashCode()与equals()方法。
TreeMap子类
TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的。
观察TreeMap的使用
public class Test {
public static void main(String[] args) {
Map<Integer,String> map = new TreeMap<>();
map.put(2,"C");
map.put(1,"A");
map.put(3,"B");
System.out.println(map);
}
}
{1=A, 2=C, 3=B}
Process finished with exit code 0
这个时候的排序处理依然按照的是Comparable接口完成的。
结论:有Comparable出现的地方,判断数据就依靠compareTo()方法完成,不再需要equals()与hashCode()
HashMap和TreeMap的区别
- 实现方式的区别:HashMap基于哈希表实现。TreeMap基于红黑树实现。
- TreeMap能够把它保存的记录根据键排序。
- HashMap:适用于在Map中插入、删除和查找元素。
- TreeMap:适用于按自然顺序或自定义顺序遍历键(Key)
HashMap通常比TreeMap快一点。
Map集合小结:
-
Collection保存数据的目的一般用于输出(Iterator),Map保存数据的目的是为了根据key查找,找不到返回 null。
-
Map使用Iterator输出(Map.Entry的作用)
-
HashMap数据结构一定要理解(链表与红黑树)、HashMap与Hashtable区别
讲一下集合中的Fail-Fast机制
举个栗子:
假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候,线程2修改了集合A中的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出一个ConcurrentModificationException异常,从而产生Fail-Fast机制。
产生的原因:
当调用容器的iterator()方法返回Iterator对象时,把容器中包含对象的个数赋值给了一个变量expectedModCount,在调用next()方法时,会比较expectedModCount与容器中实际对象的个数是否相等,若二者不相等,则会抛出ConcurrentModification异常。
如果在遍历集合的同时,需要删除元素的话,可以用iterator里面的remove()方法删除元素。