Java集合之Map接口
1 - Map接口继承树
2 - Map接口概述
/*
1 Map与Collection并列存在。用于保存具有映射关系的数据:key-value
2 Map 中的 key 和 value 都可以是任何引用类型的数据
3 Map 中的 key 用Set来存放,无序、不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法,value 用Collection来存放,无序、允许重复
4 Map中的 entry 存储key-value对数据,用Set来存放,无序、不允许重复
5 常用String类作为Map的“键”
6 key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value
7 Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类
*/
package com.lzh.java1; import org.junit.Test; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.Map; /* 1 Java集合之Map接口概述: /----Map:双列数据,存储key - value对的数据 ---类似于高中的函数:y = f(x) /----HashMap:作为Map的主要实现类;线程不安全,效率高;存储null的key和value HashMap的数据结构:jdk8 -> 数组+链表+红黑树 jdk8 -> 数组+链表 /----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。 对于频繁的遍历操作,此类知晓效率高于HashMap /----TreeMap:保证按照添加的key - value对进行排序,实现排序遍历;此时考虑key的自然排序或定制排序;底层使用 [红黑树] /----Hashtable(jdk1.0):作为古老的实现类;线程安全,效率低;不能存储null的key和value /----Properties:常用来处理配置文件。key和value都是String类型 2 Map结构的理解: Map中的key:无序的、不可重复的,使用Set存储所有的key --> key所在的类要重写equals()和hashCode() 以HashMap为例 Map中的value:无序、可重复的,使用Collection存储所有的value --> value所在的类要重写equals() 一个键值对:key-value构成了一个Entry对象 Map中的entry:无序、不可重复的,使用Set存储所有的entry 2 (重点)面试题:HashMap的底层实现原理? JDK7中 HashMap map = new HashMap(); // 在实例化以后,底层创建了长度是16的一维数组Entry[] table。 ...可能已经执行过多次put... map.put(key1,value1); ① 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。 如果此位置上的数据为空,此时的key1-value1就添加成功 --> 情况1 如果此位置上的数据不为空,意味着此位置上存在一个或多个数据(以链表形式存在),比较key1和已经存在的一个或多个数据的哈希值: 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功 --> 情况2 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较,调用key1所在类的equal(key2)方法 如果equals()返回false,此时key1-value1添加成功 --> 情况3 如果equals()返回true,将value1替换value2 补充情况2和情况3:此时key1-value1和原来的数据已链表的方式存储 ② 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式,扩容为原来的2倍,并将原有的数据复制过来 JDK8中 相较于JDK7在底层实现方面的不同: ① new HashMap(); // 底层没有创建一个长度为16的数组 ② JDK8底层的数组是:Node[] 而非Entry[] ③ 首次调用put()方法时,底层创建长度为16的数组 ④ JDK7底层结构只有:数组+链表。JDK8中底层结构:数组+链表+红黑树 ⑤ 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储 HashMap源码中的重要常量: DEFAULT_INITIAL_CAPACITY:HashMap的默认容量 --> 16 DEFAULT_LOAD_FACTOR:HashMap的默认加载因子 --> 0.75 threshold:扩容的临界值 = 容量*填充因子 --> 16 * 0.75 = 12 TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 --> 8 MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的 数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行 resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4 倍。) --> 64 3 面试题:HashMap 与 Hashtable 的异同? 4 LinkedHashMap的底层实现原理(了解) 源码中: static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; // 能够记录元素添加的先后顺序 Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } */ public class MapTest { @Test public void test1(){ Map mapList = new HashMap(); Hashtable hashtable = new Hashtable(); // hashtable.put(null,null); 编译不通过 mapList.put("name","alex"); mapList.put(null,null); } @Test public void test2(){ HashMap map = new HashMap(); map = new LinkedHashMap(); for(int i = 0;i < 5;i++){ map.put("list"+i,i); } System.out.println(map); // 有序输出 } }
3 - Map接口中定义的方法
package com.lzh.java1; import org.junit.Test; import java.util.*; /* 1 添加、删除、修改操作: 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中的所有数据 2 元素查询的操作: Object get(Object key):获取指定key对应的value boolean containsKey(Object key):是否包含指定的key boolean containsValue(Object value):是否包含指定的value int size():返回map中key-value对的个数 boolean isEmpty():判断当前map是否为空 boolean equals(Object obj):判断当前map和参数对象obj是否相等 3 元视图操作的方法: Set keySet():返回所有key构成的Set集合 Collection values():返回所有value构成的Collection集合 Set entrySet():返回所有key-value对构成的Set集合 */ public class MapMethodTest { @Test public void test1(){ Map map = new HashMap(); // 添加 map.put("AA",123); map.put("45",123); map.put("BB",123); // 修改 map.put("AA",23); // System.out.println(map); {AA=23, BB=123, 45=123} Map map1 = new HashMap(); map1.put("CC",123); map1.put("DD",null); map.putAll(map1); System.out.println(map); // {AA=23, BB=123, CC=123, DD=null, 45=123} // 移除 Object value = map.remove("AA"); System.out.println(value); System.out.println(map); // clear map.clear(); // 与map = null;操作不同 System.out.println(map.size()); // 0 System.out.println(map); // {} } @Test public void test2(){ Map map = new HashMap(); map.put("a",1); map.put("b",2); map.put("c",3); map.put("d",4); // Object get(Object key):获取指定key对应的value System.out.println(map.get("a")); // 1 System.out.println(map.get(123)); // null // boolean containsKey(Object key):是否包含指定的key // boolean containsValue(Object value):是否包含指定的value boolean isExist = map.containsKey("a"); boolean isValue = map.containsValue(4); System.out.println(isExist); // true System.out.println(isValue); // false // int size():返回map中key-value对的个数 // boolean isEmpty():判断当前map是否为空 // boolean equals(Object obj):判断当前map和参数对象obj是否相等 } @Test public void test3(){ // 3 元视图操作的方法: Map map = new HashMap(); map.put("a",1); map.put("b",2); map.put("c",3); map.put("d",4); // Set keySet():返回所有key构成的Set集合 Set set = map.keySet(); Iterator iterator = set.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } // Collection values():返回所有value构成的Collection集合 Collection values = map.values(); for(Object i:values){ System.out.println(i); } // Set entrySet():返回所有key-value对构成的Set集合 Set entrySets = map.entrySet(); for(Object set1:entrySets){ // System.out.println(set1); // entrySet集合中的元素都是entry Map.Entry entry = (Map.Entry)set1; System.out.println(entry.getKey()+":"+entry.getValue()); } } }
4 - Map实现类之一:HashMap
1 HashMap是 Map 接口使用频率最高的实现类。
2 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
3 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写: equals()和hashCode()
4 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类 要重写:equals()
5 一个key-value构成一个entry
6 所有的entry构成的集合是Set:无序的、不可重复的
7 HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true, hashCode 值也相等。
8 HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true
HashMap的存储结构
详细内容在上面Map接口概述里
5 - Map实现类之二:LinkedHashMap
1 LinkedHashMap 是 HashMap 的子类
2 在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
3 与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致
6 - Map实现类之三:TreeMap
1 TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。
2 TreeSet底层使用红黑树结构存储数据
3 TreeMap 的 Key 的排序:
① 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
② 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
4 TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0。
7 - TreeMap的排序实现
package com.lzh.java1; import org.junit.Test; import java.util.*; /* 向TreeMap中添加key-value,要求可以必须是由同一个类创建的对象 因为要按照key进行排序:自然排序、定制排序 */ public class TreeMapTest { @Test // 自然排序 (姓名从大到小。年龄从小到大) public void test1(){ TreeMap treeMap = new TreeMap(); User u1 = new User("A",22); User u2 = new User("B",20); User u3 = new User("C",13); User u4 = new User("D",25); treeMap.put(u1,98); treeMap.put(u2,90); treeMap.put(u3,80); treeMap.put(u4,60); // 遍历 Set entrySet = treeMap.entrySet(); Iterator iterator = entrySet.iterator(); while(iterator.hasNext()){ Object object = iterator.next(); // 再转成EntrySet集合 Map.Entry entry = (Map.Entry) object; System.out.println(entry.getKey()+":"+entry.getValue()); } } @Test // 定制排序 (按照年龄从小到大进行比较) public void test2(){ TreeMap treeMap = new TreeMap(new Comparator(){ @Override public int compare(Object o1, Object o2) { if(o1 instanceof User && o2 instanceof User){ User user1 = (User)o1; User user2 = (User)o2; // return user1.compareTo(user2); return Integer.compare(user1.getAge(),user2.getAge()); } throw new RuntimeException("传入的数据不匹配"); } }); User u1 = new User("A",22); User u2 = new User("B",20); User u3 = new User("C",13); User u4 = new User("D",25); treeMap.put(u1,98); treeMap.put(u2,90); treeMap.put(u3,80); treeMap.put(u4,60); // 遍历测试 for(Object obj:treeMap.entrySet()){ Map.Entry entry = (Map.Entry)obj; System.out.println(entry.getKey()+":"+entry.getValue()); } } } class User implements Comparable{ private String name; private int age; public User(){} public User(String name,int age){ this.name = name; this.age = age; } public String getName(){ return this.name; } public int getAge(){ return this.age; } @Override public int compareTo(Object obj){ if(obj instanceof User){ User user = (User)obj; // 按照姓名从大到小排序,年龄从小到大排 int compare = -this.name.compareTo(user.name); if(compare != 0){ return compare; }else{ return Integer.compare(this.age,user.age); } } throw new RuntimeException("输入的数据不一致"); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
8 - Map实现类之四:Hashtable
1 Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。
2 Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询 速度快,很多情况下可以互用。
3 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
4 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
5 Hashtable判断两个key相等、两个value相等的标准,与HashMap一致
9 - Hashtable的子类Properties
/*
1 Properties 类是 Hashtable 的子类,该对象用于处理属性文件
2 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
3 存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法
*/
package com.lzh.java1; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; /* jdbc.properties配置文件 name=howie password=123 */ public class PropertiesTest { public static void main(String[] args) throws IOException { // Properties是Hashtable的子类,常用来处理配置文件。key和value都是String类型 Properties properties = new Properties(); FileInputStream files = new FileInputStream("jdbc.properties"); properties.load(files); // 加载流对应的文件 String name = properties.getProperty("name"); String pwd = properties.getProperty("password"); System.out.println("用户名:"+name); System.out.println("密码:"+pwd); files.close(); } }
10 - Collections工具类
/*
1 Collections 是一个操作 Set、List 和 Map 等集合的工具类
2 Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
3 排序操作:(均为static方法)
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
*/
package com.lzh.java1; /* Collection:操作Collection、Map的工具类 查找、替换 1 Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 2 Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回 给定集合中的最大元素 3 Object min(Collection) 4 Object min(Collection,Comparator) 5 int frequency(Collection,Object):返回指定集合中指定元素的出现次数 6 void copy(List dest,List src):将src中的内容复制到dest中 7 boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值 排序操作:(均为static方法) 1 reverse(List):反转 List 中元素的顺序 2 shuffle(List):对 List 集合元素进行随机排序 3 sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序 4 sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 5 wap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换 */ import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class CollectionTest { @Test public void test1(){ List list = new ArrayList(); for(int i = 0;i < 5;i++){ list.add("list"+i); // 有序存储 } // 1 reverse(List):反转 List 中元素的顺序 System.out.println(list); Collections.reverse(list); System.out.println(list); // 2 shuffle(List):对 List 集合元素进行随机排序 Collections.shuffle(list); // 6 void copy(List dest,List src):将src中的内容复制到dest中 Collections.sort(list); System.out.println(list); // [list0, list1, list2, list3, list4] // 报异常:java.lang.IndexOutOfBoundsException: Source does not fit in dest // List dest = new ArrayList(); // Collections.copy(dest,list); List dest = Arrays.asList(new Object[list.size()]); Collections.copy(dest,list); System.out.println(dest); } }
11 - Collections常用方法之 同步控制
/*
1 Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集 合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
*/
1 package com.lzh.java1; 2 3 import org.junit.Test; 4 5 import java.util.ArrayList; 6 import java.util.Collections; 7 import java.util.List; 8 9 /* 10 Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集 合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题 11 */ 12 public class CollectionsTest { 13 @Test 14 public void test1(){ 15 List list = new ArrayList(); 16 for(int i = 0;i < 5;i++){ 17 list.add("list"+i); 18 } 19 20 List newList = Collections.synchronizedList(list); // 返回线程安全的list 21 } 22 }