总述:
Map用于保存具有映射关系的数据(key-value),Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。
Map用于保存具有映射关系的数据(key-value),Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。
常会使用到:
keySet(),用于返回该Map中所有key组合的Set集合。
*
*@return a set view of the keys contained in this map
*/
Set<K> keySet();
|
values(),用户返回该Map中所有value的Collection集合。
*
*@return a collection view of the values contained in this map
*/
Collection<V> values();
|
entrySet(),用于返回该Map中所有元素的Set集合。
*
*@return a set view of the mappings contained in this map
*/
Set<Map.Entry<K, V>> entrySet();
|
上面三个方法提供对Map集合的循环遍历条件,Map遍历方法:
/**Map遍历 使用keySet()获取全部key值进行遍历
* map.keySet 获取map的key列表
*/
System.out.println("----------使用keySet进行遍历-----------");
for(String key:map.keySet()){
System.out.println(key + "-" + map.get(key));
}
/**使用Iterator进行遍历
* map.entrySet() 获取map的键值列表
*/
System.out.println("----------使用entrySet().Iterator进行遍历--------");
Iterator<Map.Entry<String, Object>> i = map.entrySet().iterator();
while(i.hasNext()){
Map.Entry<String, Object> entry = i.next();
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
/** foreach循环遍历
* map.entrySet() 获取map的键值列表
*/
System.out.println("----------使用Entry进行遍历-----------");
for(Map.Entry<String, Object> entry : map.entrySet()){
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
/**通过Map.values()遍历
* map.values()获取map的值列表
*/
System.out.println("----------使用Map.values()进行遍历-----------");
for(Object v:map.values()){
System.out.println(v.toString());
}
|
对于四种遍历方法,使用value遍历不能获取key值外,另外三种遍历都能正确遍历出集合中的元素并获得key – value
Map实现:
Map接口下的实现类包括HashMap、LinkedHashMap、TreeMap、EnumMap几个常用实现。
几种实现的特点归类:
HashMap对存放元素顺序不进行记录,并且允许使用null值和null键,HashMap不保证映射的顺序恒久不变。另外HashMap非同步,如果多个线程访问某映射,而其中至少一个线程从结构上修改了该映射,则需要保持外部同步。
TreeMap对存放元素按照key值进行按照升序顺序排列排序,不允许使用null值和null键。该实现非同步。
LinkedHashMap作为HashMap的一个扩展,在HashMap的基础上对存放元素的顺序进行记录,不允许使用null值和null键。该实现非同步。
对添加顺序存放顺序以及遍历执行结果:
|
HashMap: 添加顺序 a/b/c/d ,存放顺序{d=4, b=2, c=3, a=1}
key->value :d->4
key->value :b->2
key->value :c->3
key->value :a->1
TreeMap: 添加顺序 a/c/b/d ,存放顺序{a=1, b=2, c=3, d=4}
key->value :a->1
key->value :b->2
key->value :c->3
key->value :d->4
LinkedHashMap:添加顺序 b/a/d/c ,存放顺序{b=2, a=1, d=4, c=3}
key->value :b->2
key->value :a->1
key->value :d->4
key->value :c->3
|
对于以上三个非同步实现的解决办法:
Map map = Collections.synchronizedMap(new HashMap()/TreeMap()/LinkedHashMap())
深入一些:
关于HashMap、TreeMap、LinkedHashMap存取删改的效率:
程序测试代码:
public class TestMap {
/**
* 数量级
*/
private static final intLIMIT = 1000000;
/**
* 执行主函数
*@param args
*/
public static void main(String[] args) {
Map<Integer, Integer> hashmap = Collections.synchronizedMap(new HashMap<Integer, Integer>());
Map<Integer, Integer> treemap = Collections.synchronizedMap(new TreeMap<Integer, Integer>());
Map<Integer, Integer> linkedhashmap = Collections.synchronizedMap(new LinkedHashMap<Integer, Integer>());
long create_hash =createMap(hashmap);
long create_tree =createMap(treemap);
long create_link = createMap(linkedhashmap);
System.out.println("Create HashMap " +LIMIT + " cost : " + create_hash + " ms");
System.out.println("Create TreeMap " +LIMIT + " cost : " + create_tree + " ms");
System.out.println("Create LinkedHashMap " +LIMIT + " cost : " + create_link +" ms");
long t1 = System.currentTimeMillis();
teshMap(hashmap);
long t2 = System.currentTimeMillis();
System.out.println("HashMap cost :" + (t2 - t1) + " ms");
teshMap(treemap);
t1 = System.currentTimeMillis();
System.out.println("LinkedHashMap cost :"+ (t1 - t2) + " ms");
teshMap(linkedhashmap);
t2 = System.currentTimeMillis();
System.out.println("TreeMap cost :" + (t2 - t1) +" ms");
}
/**
* 对集合赋值
*@param map
*@return
*/
public static long createMap(Map<Integer, Integer> map){
DecimalFormat df = new DecimalFormat("0000000");
Random r = new Random();
System.out.println(r.nextInt(LIMIT));
long l1 = System.currentTimeMillis();
long l2 = 0;
for (int i = 0; i <LIMIT; i++) {
map.put(i + r.nextInt(LIMIT) , Integer.parseInt(df.format(i)));
l2 = System.currentTimeMillis();
if ((l2 - l1) > 2000) {
break;
}
}
l2 = System.currentTimeMillis();
return l2-l1;
}
/**
* 对集合遍历
*@param map
*/
public static void teshMap(Map<Integer, Integer> map){
for(Iterator<Integer> i = map.values().iterator(); i.hasNext();){
i.next();
}
}
}
|
执行变量包括插入键是否有序、插入数据量级
执行结果表明: 数据插入效率 有序key TreeMap>LinkedHashMap>HashMap
无序key LinkedHashMap>HashMap>TreeMap
读取效率 有序key HashMap>LinkedHashMap>TreeMap
无序key HashMap>TreeMap>LinkedHashMap
Java
官方API
对HashMap
、TreeMap
、LinkedHashMap
的定义如下:
HashMap基于哈希表的Map接口实现,此实现假定哈希函数将元素正确分布在各个桶之间,可为基本操作(get和put)提供稳定性能,迭代集合视图所有要的时间与HashMap实例的容量(桶的数量)及大小(键值映射关系数)的和成比例,其中有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中条目数超出了加载因子与当前容量的乘积时,通过调用rehash方法将容量翻倍。
通常加载因子(0.75)在时间和空间成本上村求一种折中,加载因子过高虽然减少了空间开销,但同时也增加了查询成本。在设置初始容量时应该考虑到映射中所需要的条目数以及其加载因子,一边最大限度的降低rehash操作次数。如果吃书容量大于最大条目数除以加载因子,则不会发生rehash操作。
TreeMap 所实现接口基于红黑树的实现,此类保证了映射按照升序顺序排列关键字。
LinkedHashMap是哈希表和链接列表的实现,具有可预知的迭代顺序,此实现相较HashMap多维护了一个运行于所有条目的双重链接列表,此链接列表定义了迭代顺序,该迭代顺序即键值插入到映射中的顺序。如果某个键K重新插入到映射的时候,并不会改变K之前的插入顺序。
在实际使用中,该如何选择?
1. 如果在Map中插入、删除和定位元素,HashMap是最好的选择。
2. 如果需要按照自然顺序或者自定义顺序遍历键,TreeMap会好一些。
3. 如果需要输出和输入的顺序相同,使用LinkedHashMap可以实现,如在连接池中。
多余补充,HashMap基于哈希表的Map接口实现,hashcode是什么:
hashcode是jdk根据对象的地址或者字符串或者数字计算出来的int类型的数值
*@return a hash code value for this object.
*@see java.lang.Object#equals(java.lang.Object)
*@see java.util.Hashtable
*/
public native int hashCode();
|
可以看到hashCode调用的是native方法,具体如何调用,稍后研究。
在java程序执行期间,在对同一对象多次调用hashCode方法时,必须一致的返回相同的整数,前提是将对象进行hashcode比较时所用的信息没有被修改。两个hashCode()返回的结果相等,两个对象的equals()方法不一定相等,两个对象的equals相等,两个对象的hashCode一定相等。
为什么要有Hash算法?
Java中的集合有两类,List和Set,List中的元素时有序可以重复的,Set中的元素无序,但是不能重复。问题出来了,在Set中如何保证元素不会重复?如果简单实用equals方法来判断,每增加一个元素就拿来和集合中已有的元素进行equals,当集合中已经有很多个元素,假如1000个,那么在1001个元素加入集合时就要调用1000次equals方法, 这样的效率谁还敢用?
Hash算法原理
当Set接收一个元素时,根据该对象的内存地址计算出hashCode,看该元素属于哪个区间,在这个区间里面调用equals方法,如果两个对象的equals相等,但是不在一个区间,根本没有机会进行比较,会被认为是不同的对象,所以Java对equals方法和hashCode方法是这样规定的:
1. 如果两个对象相等,那么他们的hashCode值一定相同,也告诉我们重写equals方法一定要重写hashCode方法。
2. 如果两个对象的hashCode相同,他们并不一定相同,这里的对象相同是指用equals方法比较。