JAVA------集合之Map

一、Map接口

Map接口实现类的特点

1、Map用于保存具有映射关系的数据:key-value

2、Map中的keyvalue可以是任何引用类型的数据,会封装到HashMap$Node对象中

3、Map中的key不允许重复(原因和HashSet一样),value允许重复

5、Mapkey可以为nullvalue也可以为null,但是keynull只能有一个,valuenull可以有多个

package com.level7.Map_;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class map01 {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("xrj", "123");
        map.put("x2", "123");
        map.put("x3", "123");
        map.put("x4", null);
        map.put(null, null  );
        map.put("xrj", "456");
        System.out.println(map);
        System.out.println(map.get("xrj"));
    }
}

Map底层存储

1、key-value实际是存放在HashMap$Node node = newNode(hash, key, value, null);

2、key-value为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型为Map.Entry<K, V>

3、entrySet中,定义的数据类型为Map.Entry<K, V>,但实际存放的数据类型还是HashMap$Node,因为HashMap$NodeMap.Entry<K, V>的子类,因此他可以存放进去

4、当把HashMap$Node对象存放到 entrySet 中就方便我们的遍历,因为Map.Entry提供了两个方法getKey()getValue(),可以得到当前Entrykeyvalue

5、在entrySet中,并没有创建新的数据,而是指向了table表中HashMap$Node的地址

6、HashMap中还有keySet方法用于返回一个Set表示当前map所有的keyvlaues方法用于返回一个Collection表示当前map所有的value

package com.level7.Map_;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class map01 {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("xrj", "123");
        map.put("x2", "123");
        map.put("x3", "123");
        map.put("x4", null);
        map.put(null, null  );
        map.put("xrj", "456");
        System.out.println(map);
        System.out.println(map.get("xrj"));

        // entrySet()
        Set set = map.entrySet();
        for (Object o : set) {
            // System.out.println(o.getClass());   // class java.util.HashMap$Node
            Map.Entry entry = (Map.Entry) o;
            // 因为Map.Node是默认访问修饰符,因此不能访问
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }

        System.out.println(map.keySet().getClass());	// class java.util.HashMap$KeySet
        System.out.println(map.keySet());
        System.out.println(map.values().getClass());	// class java.util.HashMap$Values
        System.out.println(map.values());
    }
}

Map常用方法

package com.level7.Map_;

import java.util.HashMap;
import java.util.Map;

public class MapMethod {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("xrj", "abc");
        map.put("xrj", "xyy");
        map.put("x1", "a1");
        map.put("x2", "a2");
        map.put("x3", "a3");
        System.out.println(map);

        // remove:根据键删除该键值对,也可以根据键值对删除
        map.remove("x3");
        System.out.println(map);

        // get: 根据键获取值
        System.out.println(map.get("xrj"));

        // size: 获取元素个数
        System.out.println(map.size());

        // isEmpty: 判断map是否为空
        System.out.println(map.isEmpty());

        // clear: 清空map
        // map.clear();
        // System.out.println(map);

        // containsKey: 查找键是否存在
        System.out.println(map.containsKey("xrj"));
        
        // containsValue: 查找值是否存在
        System.out.println(map.containsValue("a1"));
    }
}

Map接口遍历方法

1、containsKey 查找键是否存在

2、keySet 获取所有的键

3、entrySet 获取所有键值对关系

4、values 获取所有的值

package com.level7.Map_;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class Map_for {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("xrj", "123");
        map.put("xyy", "456");
        map.put("abc", "789");
        map.put("zs", null);
        map.put(null, "aaa");

        // 1.先获取所有的key,然后通过key遍历map
        Set key = map.keySet();
        for (Object o : key) {
            System.out.println(o + " " + map.get(o));
        }

        // 迭代器遍历
        Iterator iterator = key.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next + " " + map.get(next));
        }

        System.out.println("=============");
        // 2.通过entrySet遍历
        Set set = map.entrySet();
        for (Object o : set) {
            Map.Entry entry = (Map.Entry)o;
            System.out.println(entry.getKey() + " " + entry.getValue());
        }

        // 迭代器遍历
        Iterator iterator1 = set.iterator();
        while (iterator1.hasNext()) {
            Map.Entry entry = (Map.Entry)iterator1.next();
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}



二、HashMap

1、与HashSet一样,HashMap不保证映射的顺序,因为底层是以hash表的方式来存储的(JDK8的HashMap底层是数组+链表+红黑树)

2、HashMap没有实现同步,因此是线程不安全的


HashMap底层机制

1、HashMap底层维护了Node类型的数组table,默认为null

2、当创建对象时,加载因子loadfactor初始化为0.75

3、当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素就直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key相是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。

4、第1次添加,则需要扩容table容量为16,临界值threshold为12(16*0.75)

5、以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),且table的大小>= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)


源码分析

package com.level7.Map_;

import java.util.HashMap;

public class HashMapSource {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);
        map.put("php", 10);
        map.put("java", 20);    
    }
}

1、HashMap map = new HashMap();执行这句话,就创建一个HashMaploadFactor初始化为0.75

2、map.put("java", 10);第一次添加,进入put方法,计算key的hash值,然后调用putVal方法

3、进入putVal方法,因为是第一次添加,tablenull,所以需要进入resize进入扩容,第一次扩容后大小为16。然后p = tab[i = (n - 1) & hash]通过这条语句,i 表示当前这个k-v应该插入table的位置,p 表示当前位置的对象,如果为空,表示当前位置未存放元素,可以直接插入。

map.put("java", 20); 如果执行这条语句,p = tab[i = (n - 1) & hash]执行完这条语句后,p 不为null,说明p这个位置存有元素,进入else语句,如果当前插入的元素的key的hash值和p 的hash值相等 并且 插入元素的key 要么和p的key是同一个对象,要么equal后相等,说明当前插入元素的key已经存在,那么value需要进行替换,用e 保存当前位置原来的对象,进入if (e != null)中进行value的替换。

如果if的条件不满足,先进入else if中判断当前链表是否是树结构,如果是树结构,使用树的方式查找,如果不是,那么就还是链表结构,就一个一个遍历查找,如果找到最后为空,就将当前元素插入进入,同时,元素数量到达8个,即插入第9个元素(且table容量达到64)进行树化,如果遍历过程中,找到和当前插入元素的key相同的,就进行value的替换。

如果删除元素时,红黑树上元素个数小于8,会退化为链表



三、Hashtable

1、存放的元素是键值对

2、Hashtable的键和值都不能为null,否则会抛出空指针异常

3、Hashtable使用方法和HashMap一样

4、Hashtable是线程安全的,HashMap是线程不安全的


源码分析

package com.level7.Map_;

import java.util.Hashtable;

public class HashTable01 {
    public static void main(String[] args) {
        Hashtable hashtable = new Hashtable();
        hashtable.put("x1", 123);
        hashtable.put("x2", 123);
        hashtable.put("x3", 123);
        hashtable.put("x4", 123);
        hashtable.put("x5", 123);
        hashtable.put("x6", 123);
        hashtable.put("x7", 123);
        hashtable.put("x8", 123);
        hashtable.put("x9", 123);
        hashtable.put("x0", 123);
    }
}

1、Hashtable hashtable = new Hashtable();执行这条语句。会创建一个Hashtable,初始化大小为11,加载因子为0.75

2、hashtable.put("x1", 123); 第一次执行put,如果valuenull就抛出空指针异常。然后通过int hash = key.hashCode();计算出当前key的hash值,如果key为null,这里会抛出异常,因此key也不可以为null,通过int index = (hash & 0x7FFFFFFF) % tab.length;计算出应该插入的位置。Entry<K,V> entry = (Entry<K,V>)tab[index];这条语句是得到当前要插入位置的对象,如果不为空,那么就进入for查找和当前插入元素的hash值相同并且equals后也相等(说明key是相同的),就进行value的替换,如果entry为空或者在for循环中没找到,就执行addEntry(hash, key, value, index);来添加新的元素
在这里插入图片描述

3、addEntry方法中,首先判断当前容量是否达到了临界值,如果达到临界值,就执行rehash()方法进行扩容,否则,就创建新的entry然后添加进去。

tab[index] = new Entry<>(hash, key, value, e);这里传入的e是和当前插入元素hash值相同的链表的第一个节点,相当于头插法。

在这里插入图片描述

4、rehash()方法,每次扩容为原始大小的2倍+1。Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];然后创建新的table数组。同时进入for循环,将旧的table中的数据复制到新的里边。



Hashtable和HashMap对比

版本线程安全效率允许null键null值
HashMap1.2不安全可以
Hahstable1.0安全较低不可以



四、Properties

1、Properties类继承自Hashtable类并实现了Map接口,也是使用键值对保存数据。他的put源码和Hashtable相同

2、Properties可以用于从XXX.properties文件中,加载数据到Properties类对象,进行读取和修改

3、Properties也不可以有空键和空值

package com.level7.Map_;

import java.util.Properties;

public class Properties_ {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("x1", 1);
        properties.put("x2", 2);
        properties.put("x3", 3);
        properties.put("x4", "400");
        properties.put(123, 4);
        System.out.println(properties);

        // 修改
        properties.put("x1", 123);
        System.out.println(properties);

        // 删除
        properties.remove("x3");
        System.out.println(properties);

        // 查找
        System.out.println(properties.get("x1"));
        System.out.println(properties.get(123));
        System.out.println(properties.getProperty("x4"));   // getProperty获取的key和value必须都是String
    }
}

五、TreeMap

可以将键按指定规则进行排序


无参构造

package com.level7.Map_;

import java.util.TreeMap;

public class TreeMap_ {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap();
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("xrj", "徐睿杰");
    }
}

1、TreeMap treeMap = new TreeMap(); 执行后会创建一个TreeMap,比较器为空

在这里插入图片描述

2、treeMap.put("jack", "杰克"); 执行后,直接进入put方法,因此第一次添加,所以根节点rootnull,然后进入默认的compare方法来比较key的大小,然后创建节点添加进去

在这里插入图片描述

3、第二次添加,进入put方法后,根节点不为空,但是传入的比较器为空,因此进入下边代码中,比较key的大小,如果相等,就进行值的替换。和TreeSet一样


有参构造

package com.level7.Map_;

import java.util.Comparator;
import java.util.TreeMap;

public class TreeMap_ {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap(new Comparator(){
            @Override
            public int compare(Object o1, Object o2){
                // return ((String)o1).compareTo((String)o2);
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("xrj", "徐睿杰");
        System.out.println(treeMap);
    }
}

1、TreeMap treeMap = new TreeMap(new Comparator(){ 执行后会创建一个TreeMap 传入的有比较器

在这里插入图片描述

2、之后每次add调用的比较器就是我们传入的。

3、treeMap.put("xrj", "徐睿杰");当执行到这里时,因为我们传入的比较器是比较字符串长度大小,因此这个传入后,key比较后是相等的,无法插入,因此会进行value的替换



六、集合的选择

1、Collection接口(单列对象)

​ 允许重复:List

​ 增删多:LinkedList【底层维护了一个双向链表】

​ 改查多:ArrayList【底层维护了一个Object类型的可变数组】

​ 不允许重复:Set

​ 无序:HashSet【底层是HashMap,数组+链表+红黑树】

​ 排序:TreeSet

​ 插入和取出顺序一致:LinkedHashSet【底层是LinkedHashMap,数组+链表+双向链表】

2、Map(键值对)

​ 键无序:HashMap【底层是:哈希表 jdk7:数组+链表 jdk8:数组+链表+红黑树】

​ 键排序:TreeMap

​ 键插入和取出顺序一致:LinkedHashMap

​ 读取文件:Properties

不管是TreeSet还是TreeMap,他们传入值时,key的类型必须相同,因为要进行key值的比较,如果类型不同无法比较会抛出异常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值