1.Map
(1)分类
|------Map:双列数据,存储key-value对的数据 -----类似于数学中函数:y=f(x)
|------HashMap:作为Map的主要实现类:线程不安全的,效率高:存储null的key和value
|------LinkedHashMap: 保证在遍历Map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构的基础上,添加了一对指针,指向了前一个元素和后一个元素。对于频繁的遍历操作。此类执行效率高于HashMap
|------TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序和定制排序
底层使用红黑树
|------Hashtable: 作为古老的实现类:线程安全的。效率低
|------Properties:常来处理配置文件,key和value都是String类型
(2)底层
1)Map结构的理解:
(1)Map的key:无序的、不可重复的,使用Set存储所有的key ----key所在的类要重写equals和hashCode方法 (以HashMap为例)
(2)Map的value:无序的、不可重复的,使用Collection存储所有的value -----所在类要重写equals()
(3)一个键值对:key-value构成了一个Entry对象。
(4)Map中的Entry:无序的,不可重复的,使用Set存储所有的Entry
2)HashMap底层:
数组+链表 (JDK7之前);数组+链表+红黑树(JDK8)
1. JDK7HashMap底层:
(1)HashMap map = new HashMap();
在实例化以后,底层创建了长度为16的一维数组Entry[] table。
(2)....可能已经执行了多次put... map.put(key1,value1);
首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算之后得到在Entry数组的存放位置:
1)如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
2) 如果此位置的数据不为空,意味着此位置上存在一个或多个数据(以链表的形式存在),比较key1和已经存在的一个或多个数据的哈希值:
2.1)如果key1的哈希值和已经存在的数据的哈希值都不相同,此时key1-value1添加成功。 ---情况2
2.2)如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在的equals()方法,比较:
2.2.1)如果equals()返回false:此时key1-value1添加成功。 -----情况3
2.2.2)如果equals()返回true:使value1替换value2.
(3)补充:关于情况2和情况3:,此时的key1-value1和原来的数据以链表的形式存储。在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来的2倍,并将原来的数据复制
2. jdk8相较于jdk7在底层方面的不同
- new HashMap(): 底层没有创建一个长度为16的数组
- jdk8底层的数组:Node[],而非Entry[]。
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7的底层结构只有数组+链表。jdk8中的底层结构:数组+链表+红黑树当数组中的某一个索引位置上的元素以链表的形式存在数据的个数 > 8 且当前数组的长度 > 64时,此时索引位置上所有数据改为使用红黑树存储。
(3)常用方法
map.put(K key.V value);//添加元素
V get(K key);//根据指定的键获取对应的value
V remove(K key);//根据指定的key把对应的key-value键值对删除,返回value
boolean containsKey(K key);//判断集合中是否包含指定的key
boolean containsValue(Object value);//是否包含指定的value
int size();//返回map中key-value的个数
boolean isEmpty();//判断当前map是否为空
boolean equals(Object obj);//判断当前map和参数对象obj是否相等
(4)遍历方式
Map<String,String> map = new HashMap<>();
map.put("key1","1");
map.put("key2","2");
//1.for循环遍历
//(1)keySet()遍历
for(String key : map.keySet()){
sout("key = " + key);
sout("value = " + map.get(key));
}
//(2)entrySet()遍历
for(Map.Entry<String,String> mm : map.entrySet()){
sout("key = " + mm.getKey());
sout("value = " + mm.getValue());
}
//2.迭代器遍历
//(1)keySet()遍历
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
String value = map.get(key);
}
//(2)entrySet()遍历
Iterator<Map.Entry<String,String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
map.Entry<String,String> mm = iterator.next();
sout("key = " + mm.getKey());
sout("value = " + mm.getValue())
}
2.Set
1)HashSet添加元素的过程
我们向Hashset中添加元素a所在类的HashCode()方法,计算a的哈希值,此哈希值接着通过某种算法计算出在Hashset底层数组中的存放位置(即为:索引位置)判断数组此位置上是否有元素:
1)如果此位置上没有其他元素:则此元素a添加成功。 ---》情况1
2)如果此位置上有其他元素b(或以链表的形式存在多个元素),则比较元素a和元素b的hash值:
2.1)如果hash值不相同,则元素a添加成功 ----》情况2
2.2) 如果hash值相同,进而需要调用元素a所在类的equals方法:
2.2.1)equals()返回true时,元素a 添加失败
2.2.2)equals()返回false时,元素a添加成功。---》情况3
说明:对于添加成功的情况2和情况3而言,元素a与已经存在指定索引位置上的数据以链表的形式存储 JDK7.0:元素a 放到数组中,指向原来的元素 JDK8.0:原来的元素在数组中,指向元素a ------------七上八下