JavaEE - 11集合Map
(6)集合Map
(6.1)Map概述
Map: 双列数据, 存储key-value对的数据
- HashMap:作为Map的主要实现类;线程不安全,效率高;可以存储null的key和null
- LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。在HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
- 对于频繁的遍历操作,LinkedHashMap执行效率高于HashMap。
- TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。底层使用红黑树。
- Hashtable: 作为古老的实现类;线程安全,效率低;不能存储null的key和value
- Properties:常用来处理配置文件。key和value都是String类型。
Map结构:
- Map中的key: 无序的、不可重复的,使用Set存储所有key --> key所在的类要重写equals() 和 hashCode()
- Map中的value: 无序的、可重复的,使用Collection存储所有的value --> value所在的类要重写 equals()
- 一个键值对: key-value构成一个Entry对象。
- Map中的entry: 无序的、不可重复的,使用Set存储所有的entry
(6.2)HashMap
底层结构: 数组+链表(JDK 7及之前) 数组+链表+红黑树(JDK 8)
(6.2.1)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。
- 如果key2的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较,调用key1所在类的equals(key2)
- 如果equals()返回false: 此时key1-value1 添加成功 --> 成功情况3
- 如果equals()返回true: 使用value1替换value2。
- 关于情况2和情况3: 此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,默认扩容方式:扩容为原来的2倍,并将原来的数据复制过来。
(6.2.2)HashMap底层实现原理(JDK8)
- new HashMap(): 底层没有创建一个长度为16的数组
- JDK8底层的数组是: Node[],而非Entry[]
- 首次调用put()方法时,底层才创建长度为16的数组。
- 底层结构: 数组+链表(JDK 7及之前) 数组+链表+红黑树(JDK 8)
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。
(6.2.3)HahsMap源码中的重要常量
- DEFAULT_INITIAL_CAPACITY: HashMap的默认容量,16
- MAXIMUM_CAPACITY: HashMap的最大支持容量,2^30
- DEFAULT_LOAD_FACTOR: HashMap的默认加载因子, 0.75f
- TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转换为红黑树, 8
- UNTREEIFY_THRESHOLD: Bucket中红黑树存储的Node数量小于该默认值时转换为链表, 6
- MIN_TREEIFY_CAPACITY: 桶中的Node被树化时最小的hash表容量。 64
- 当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时执行resize扩容操作。
- 这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。
- table: 存储元素的数组,总是2的n次幂; transient Node<K,V>[] table;
- entrySet: 存储具体元素的集; transient Set<Map.Entry<K,V>> entrySet;
- size: HashMap中存储的键值对的数量
- modCount: HashMap 扩容和结构改变的次数
- threshold: 扩容的临界值, = 容量*填充因子
- loadFactor: 填充因子
(6.2.4)源码解析
获取元素get
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
添加元素put
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
扩容resize()
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
节点Node<K,V>
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
(6.3)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); } }
void reinitialize() { super.reinitialize(); head = tail = null; } Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; LinkedHashMap.Entry<K,V> t = new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next); transferLinks(q, t); return t; }
(6.4)Map常用方法
- 添加、删除、修改操作
- Object put(Object key, Object value): 将指定key-value添加到(或修改)当前map对象中
- voide putAll(Map m): 将m中所有key-value对存放到当前map对象中
- Object remove(Object key): 移除指定key的key-value对,并返回value
- void clear(): 清空当前map中的所有数据
- 元素查询的操作
- 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是否相等
- 元视图操作的方法
- Set keySet(): 返回所有key构成的Set集合
- Collection values(): 返回所有value构成的Collection集合
- Set entrySet(): 返回所有key-value对构成的Set集合
@Test public void test1(){ HashMap map = new HashMap(); map.put("zhangsan",new Person("zhangsan",12)); map.put("lisi",new Person("lisi",14)); map.put("wangwu",new Person("wangwu",64)); map.put("aa",123); map.put("bb",23); map.put(123,12); //{aa=123, bb=23, lisi=Person{name='lisi', age=14}, zhangsan=Person{name='zhangsan', age=12}, wangwu=Person{name='wangwu', age=64}, 123=12} System.out.println(map); System.out.println(map.size()); //6 Object value = map.remove("aa"); System.out.println(value); //123 System.out.println(map.get("zhangsan")); //Person{name='zhangsan', age=12} boolean cc = map.containsKey("cc"); System.out.println(cc); //false map.clear(); System.out.println(map.isEmpty()); //true }
@Test public void test3(){ HashMap map = new HashMap(); map.put("zhangsan",new Person("zhangsan",12)); map.put("aa",123); map.put(123,12); Set set = map.keySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } Set entrySet = map.entrySet(); Iterator it = entrySet.iterator(); while (it.hasNext()){ Object obj = it.next(); Map.Entry entry = (Map.Entry) obj; // entrySet集合中的元素是entry System.out.println(entry.getKey() + "-->" + entry.getValue()); } }
Collection values = map.values(); Iterator it2 = values.iterator(); while (it2.hasNext()){ System.out.println(it2.next()); }
(6.5)TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象。按照key进行排序:自然排序、定制排序。
(6.5.1)自然排序
@Test public void test4(){ TreeMap map = new TreeMap(); Person p1 = new Person("Tom",23); Person p2 = new Person("Marr",24); Person p3 = new Person("Jack",11); Person p4 = new Person("Rose",33); map.put(p1,98); map.put(p2,90); map.put(p3,68); map.put(p4,78); //{Person{name='Tom', age=23}=98, Person{name='Rose', age=33}=78, Person{name='Marr', age=24}=90, Person{name='Jack', age=11}=68} System.out.println(map); }
(6.5.2)定制排序
@Test public void test4(){ Comparator com = new Comparator() { // 按照年龄从小到大 @Override public int compare(Object o1, Object o2) { if(o1 instanceof Person && o2 instanceof Person){ Person p1 = (Person) o1; Person p2 = (Person) o2; return Integer.compare(p1.getAge(), p2.getAge()); }else { throw new RuntimeException("输入的类型不匹配"); } } }; TreeMap map = new TreeMap(com); Person p1 = new Person("Tom",23); Person p2 = new Person("Marr",24); Person p3 = new Person("Jack",11); Person p4 = new Person("Rose",33); map.put(p1,98); map.put(p2,90); map.put(p3,68); map.put(p4,78); //{Person{name='Jack', age=11}=68, Person{name='Tom', age=23}=98, Person{name='Marr', age=24}=90, Person{name='Rose', age=33}=78} System.out.println(map); }
(6.6)Properties
- Properties 类是Hashtable 的子类, 该对象用于处理属性文件
- 由于属性文件里的key、value都是字符串类型, 所以Properties里的key和value都是字符串类型
- 存取数据时,使用setProperty(String key, String value)方法 和 getProperty(String key)方法
@Test public void test5() throws IOException { Properties properties = new Properties(); properties.load(new FileInputStream("E:\\javaProgramLearn\\11Collection\\src\\jdbc.properties")); String user = (String) properties.get("jdbc.user"); String password = properties.getProperty("jdbc.password"); System.out.println(user + "-->" + password); // root-->Mypass }
关于路径: properties.load(new FileInputStream("src/jdbc.properties")); 文件读取相对于项目根目录
(7)Collections工具类
- Collections是一个操作Set、List和Map等集合的工具类。操作数组的工具类:Arrays。
- Collections提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
- 排序操作:(均为static方法)
- reverse(List): 反转List中元素的顺序
- shuffle(List): 对List集合元素进行随机排序
- sort(List): 根据元素的自然排序对指定List集合元素按升序排序
- sort(List, Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
- swap(List, int, int): 将指定List集合中 i 处元素和 j 处元素进行交换
- 查找、替换方法
- Object max(Collection): 根据元素的自然排序,返回给定集合中的最大元素
- Object max(Collection, Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection, Comparator):
- int frequency(Collection, Object): 返回指定集合中指定元素出现的次数
- void copy(List dest, List src): 将src中的内容复制到dest中
- boolean replaceAll(List list, Object oldVal, Object newVal): 使用新值替换List对象的所有旧值
- 同步控制: Collections 类中提供了多个synchronizedXxx()方法,该方法可将指定集合包装成线程安全的集合,从而解决多线程并发访问集合时的线程安全问题。
- static <T> Collection<T> synchronizedCollection(Collection<T> c)
- static <T> Set<T> synchronizedSet(Set<T> s)
- static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
- static <T> List<T> synchronizedList(List<T> list)
- static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
- static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
@Test public void test1(){ List list = new ArrayList<>(); list.add(1); list.add(2); list.add(-9); // List dest = new ArrayList(); // Collections.copy(dest,list); //java.lang.IndexOutOfBoundsException: Source does not fit in dest
List dest = Arrays.asList(new Object[list.size()]); Collections.copy(dest, list); System.out.println(dest); }
@Test public void test1(){ List list = new ArrayList<>(); list.add(1); list.add(2); list.add(-9); // 返回的list1 即为线程安全的List List list1 = Collections.synchronizedList(list); }
浙公网安备 33010602011771号