Map双列集合
- Map接口:双列集合,用来存储一对(key - value)一对的数据 --->高中函数:y=f(x)
- HashMap:作为Map的主要实现类:线程不安全的,效率高;可以存储null的key和value
- LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
- 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个添加的元素,对于频繁的遍历操作,此类执行效率高于HashMap
- TreeMap:保证按照添加的key-value进行排序,实现排序遍历,此时考虑key的自然排序或定制排序,底层使用红黑树
- Hashtable:作为古老的实现类:线程安全的,效率低:不能存储null的key和value
- Properties:通常用来处理配置文件,key和value都是String类型
Map的底层:数组+链表(jdk 7及之前)
数组+链表+红黑树(jdk 8)
二、Map结构的理解:
Map中的kye:无序的、不可重复的,使用Set存储所有的key --> key所在的类要重写equals()和hashCode()(以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value -->value所在的类需要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
三、HashMap的底层实现原理?以jdk 7为例说明:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table
可能已经执 行官多次put
map.put(key,value):
首先,调用key所在类的hashCode()计算key哈希值;
根据传入的key,计算hash值,调用对象.hashCode()获取. 进行高16位和低16位进行扰动计算.
判断成员变量table是否为空,如果为空,说明该还没有初始化.
调用resize进行初始化,初始化默认长度为16,拓容阈值为16*0.75. 如果构造方法指定了数组的长度,按照指定的来(如果输入的长度不是2的幂次方,默认找到离传入值最近的2的幂次方的数值作为数组的长度)
使用hash & (数组长度-1) 等于求模取余 ,计算当前元素需要存放的索引位置
当前位置的元素为空
创建Node对象,将key,value,hash值存储到对象中
当前位置的元素不为空
判断当前传入的元素和数组位置上的元素是否相同元素(1.判断hash值是否相等2.判断内存地址或者equals相等),如果是相同对象,把旧的值取出,将新的值覆盖,返回旧的值
判断当前节点是否TreeNode对象,如果是,说明当前节点已经是红黑树,元素往红黑树中添加元素
遍历链表,判断链表上的元素是否和当前传入的元素时相同元素,如果是相同对象,把旧的值取出,将新的值覆盖,返回旧的值,如果不是,已经达到链表尾部,在链表尾部插入元素,返回null.
如果链表大于8,但数组长度小于64,仅仅进行拓容,不会转成红黑树
如果链表大于8,但数组长度大于64,转成红黑树
将链表变成双向链表,然后再转成红黑树.
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
如果此位置上的数据为空,此时的key1--value1添加成功(实际上是Entry) ---情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(多个数据以链表形式存在)),比较key1和已经存在的一个或者多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功 --情况2
如果key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
如果equals()返回false:此时key1-value1添加成功。 --情况3
如果equals()返回true:使用value1替换value2
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk 8相较于jdk 7在底层实现方面的不同:
1、new HashMap():底层没有创建一个长度为16的数组
2、jdk 8底层的数组是Node[],而非Entry[]
3、首次调用put()方法时,底层创建长度为16的数组
4、jdk 7底层结构只有:数组+链表,jkd 8中底层结构:数组+链表+红黑树
当数组的某一个索引位置上的元素以链表的形式存在的数据个数>8且当前数组的长度>64时,此时索引位置上的所有数据改为使用红黑树存储。
四、LinkedHashMap的底层原理
五、map的常用方法
添加、删除、修改操作:
object put(object key, object value): 将指定key-value 添加到(或修改)当前map对象中
void putALl(Map m): 将m中的所有key-value对存放到当前map中
object remove(object key):移除指定key的key-value对,并返回value
void clear(): 清空当map中的所有数据元素
static void test(){ Map map = new HashMap(); //添加 map.put("a",56); map.put(1,55); map.put("b",54); //修改 map.put("a",88); System.out.println(map); Map map1 = new HashMap(); map1.put("c",55); map1.put("d",8); map1.put("a",22); //将m中的所有key-value对存放到当前map中 map1.putAll(map); System.out.println(map1); //移除指定key的key-value对,并返回value Object value = map1.remove("c"); System.out.println(value); System.out.println(map1); //清空当map中的所有数据元素 map1.clear(); System.out.println(map1); }
查询的操作:
object get(object key):获取指定key对应的value
boolean containsKey(object key):是否包含指定的key
boolean containsValue(0bject value):是否包含指定的value
int size(): 返回map中key-value对的个数
boolean isEmpty(): 判断当前map是否为空
boolean equals(object obj): 判断当前map和参数对象obj是否相等
static void test1(){ Map map = new HashMap(); //添加 map.put("a",56); map.put(1,55); map.put("b",54); //获取指定key对应的value System.out.println(map.get("a")); //是否包含指定的key System.out.println(map.containsKey("c")); System.out.println(map.containsKey("a")); //是否包含指定的value System.out.println(map.containsKey(80)); System.out.println(map.containsValue(55)); //返回map中key-value对的个数 System.out.println(map.size()); //判断当时map是否为空 System.out.println(map.isEmpty()); }
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values(): 返回所有value构成的Collection集合
Set entrySet(): 返回所有key-value对构成的Set集合
static void test2(){ Map map = new HashMap(); //添加 map.put("a",56); map.put(1,55); map.put("b",54); //遍历所有的key集合 Set set = map.keySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } //遍历所有的value集合 Collection list = map.values(); iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } //遍历所有key-value对 set = map.entrySet(); iterator = set.iterator(); while (iterator.hasNext()){ Map.Entry entry = (Map.Entry) iterator.next(); System.out.println(entry); System.out.println(entry.getKey()); System.out.println(entry.getValue()); } }
总结:常用方法:
添加: put(Object key, Object value)
删除: remove(Object key)
修改: put(Object key, Object value)
查询: get(Object key)长度: size()
遍历: keySet() / values() / entrySet()
六、TreeMap的排序
static void test(){ TreeMap map = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { if (o1 instanceof User && o2 instanceof User){ User u1 = (User) o1; User u2 = (User) o2; //根据年龄从小到大排序,如果年龄相同按照姓名排序 int compare = Integer.compare(u1.getAge(),u2.getAge()); if (compare!=0){ return compare; } return u1.getName().compareTo(u2.getName()); } throw new RuntimeException("类型不匹配"); } }); User user1 = new User("a张三",23); User user2 = new User("b李四",23); User user3 = new User("c王五",28); User user4 = new User("d赵六",19); User user5 = new User("a张三",18); map.put(user1,80); map.put(user2,40); map.put(user3,88); map.put(user4,99); map.put(user5,89); Set set = map.entrySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()){ Map.Entry entry = (Map.Entry) iterator.next(); System.out.println(entry.getKey()+"——————"+entry.getValue()); } }
六、Properties
Properties:常用来处理配置文件。key和value都是String类型