Java集合之Map接口

1 - Map接口继承树

2 - Map接口概述

/*
1  MapCollection并列存在。用于保存具有映射关系的数据:key-value

2  Map 中的 keyvalue 都可以是任何引用类型的数据

3  Map 中的 key 用Set来存放无序、不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()equals()方法,value 用Collection来存放,无序、允许重复

 Map中的 entry 存储key-value对数据,用Set来存放,无序、不允许重复

5  常用String类作为Map的“键”

6  keyvalue 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value

7  Map接口的常用实现类:HashMap、TreeMap、LinkedHashMapProperties。其中,HashMapMap 接口使用频率最高的实现类
*/

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); // 有序输出
    }
}
Java集合之Map接口概述与HashMap底层实现原理

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());
        }
    }
}
Map接口中定义的方法

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 LinkedHashMapHashMap 的子类

  2 在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序

  3LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致

 

6 - Map实现类之三:TreeMap

1 TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。

2 TreeSet底层使用红黑树结构存储数据

3 TreeMapKey 的排序:

  ① 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

  ② 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 MapKey 实现 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 +
                '}';
    }
}
TreeMap的自然排序与定制排序

8 - Map实现类之四:Hashtable

1 Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。

2 Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询 速度快,很多情况下可以互用。

3 与HashMap不同,Hashtable 不允许使用 null 作为 keyvalue

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();
    }
}
Properties类使用示例

10 - Collections工具类

/*
1 Collections 是一个操作 Set、ListMap 等集合的工具类

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);
    }
}
Collections工具类常用的方法

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 }
同步控制示例

 

posted @ 2020-06-15 14:44  赖正华  阅读(199)  评论(0编辑  收藏  举报