15.Map

Map

之前学习的Collection称为集合,是一个单列集合。

Map可以称为映射,Map中存放的是一对键值对映射数据。

Map和Collection的区别

Map存放元素是一对数据,Map键是唯一的,值可以重复,

Collection集合中存放元素是单个出现的,它的子接口List中元素是可以重复的,Set中元素是不能重复的。

Map的功能

1.添加

V put(K key, V value)

将指定的值与该映射中的指定键相关联(可选操作)。

void putAll(Map<? extends K,? extends V> m)

将指定地图的所有映射复制到此映射(可选操作)。

2.删除

V remove(Object key)

如果存在(从可选的操作),从该地图中删除一个键的映射。

default boolean remove(Object key, Object value)

仅当指定的密钥当前映射到指定的值时删除该条目。

3.判断

boolean isEmpty()

如果此地图不包含键值映射,则返回 true 。

boolean containsKey(Object key)

如果此映射包含指定键的映射,则返回 true 。

boolean containsValue(Object value)

如果此地图将一个或多个键映射到指定的值,则返回 true 。

4.获取

Set<K> keySet()

返回此地图中包含的键的Set视图。

Set<Map.Entry<K,V>> entrySet()

返回此地图中包含的映射的Set视图。

Collection<V> values()

返回此地图中包含的值的Collection视图。

V get(Object key)

返回到指定键所映射的值,或 null如果此映射包含该键的映射。

5.长度

int size()

返回此地图中键值映射的数量。

package com.day15.map1;

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

public class MapDemo {
    public static void main(String[] args) {
        //创建HashMap对象,不加指定的泛型,键值可以是任意类型
        HashMap map = new HashMap();

        //添加元素
        //        V put(K key, V value)
        //        将指定的值与该映射中的指定键相关联(可选操作)。
        //键如果重复了,后面的值会覆盖前面的值
        //Map存放是无序的,可以存放null键、null值
        //null键只有一个,null值可以有多个
        map.put(1,"hello");
        map.put("a","java");
        map.put(1,"world");
        map.put(11,"mysql");
        map.put(null,"oracle");
        map.put(null,null);
        map.put(22,"java");

        System.out.println(map);

        //删除功能
        //        V remove(Object key)
        //        如果存在(从可选的操作),从该地图中删除一个键的映射。
        Object a = map.remove("a");
        System.out.println(map);

        //判断
        //        boolean isEmpty()
        //        如果此地图不包含键值映射,则返回 true 。
        System.out.println(map.isEmpty());
        //        boolean containsKey(Object key)
        //        如果此映射包含指定键的映射,则返回 true 。
        System.out.println(map.containsKey(11));
        System.out.println(map.containsKey("hello"));//false
        //        boolean containsValue(Object value)
        //        如果此地图将一个或多个键映射到指定的值,则返回 true 。
        System.out.println(map.containsValue("hello"));//false
        System.out.println(map.containsValue("world"));//true

        //获取

//        Set<Map.Entry<K,V>> entrySet()
//        返回此地图中包含的映射的Set视图。
//        Collection<V> values()
//        返回此地图中包含的值的Collection视图。

//        V get(Object key)
//        返回到指定键所映射的值,或 null如果此映射包含该键的映射。
//        Set<K> keySet()
//        返回此地图中包含的键的Set视图。
        //1.先获取key的集合,再通过key,获取value,
        // 获取到set后,通过遍历拿到set中的每个key
        Set set = map.keySet();
        //获取到set后,可以用增强for拿到set中的每个key
        for (Object key: set){
            System.out.println("key"+key+"-- value:"+map.get(key));
        }
        System.out.println("---------------------");
        //也可以使用iterator()获取key
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Object key = iterator.next();
            System.out.println("key"+key+"-- value:"+map.get(key));
        }

        System.out.println("---------------------------");
        //        Set<Map.Entry<K,V>> entrySet()
        //        返回此地图中包含的映射的Set视图。
        //2.调用entrySet()方法,返回set集合中,存放的都是Entry对象
        //每个Entry对象其实就是一个键值对数据组成对象
        //Entry对象中,提供了键和值获取的方法
        Set set1 = map.entrySet();
        for (Object o :set1){
            Map.Entry entry=(Map.Entry) o;
            System.out.println(entry.getKey()+"--"+entry.getValue());
        }
        System.out.println("---------------------------");
        Iterator iterator1 = set1.iterator();
        while (iterator1.hasNext()){
            Map.Entry entry = (Map.Entry) iterator1.next();
            System.out.println(entry.getKey()+"--"+entry.getValue());
        }

        //3.key和value分开获取的
        //单独获取key
        for (Object key: map.keySet()) {
            System.out.println(key);
        }
        //单独获取value
        for (Object value : map.values()) {
            System.out.println(value);
        }

    }
}
package com.day15.map1;

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

//创建一个HashMap对象,这个HashMap对象的键只能是整数,值只能是字符串,
//在这个HashMap中存入5对数据,然后使用keySet()方法,将他们遍历出来
public class HashMapDemo01 {
    public static void main(String[] args) {
        创建HashMap对象,指定泛型,键是整数,值是字符串
        HashMap<Integer, String> hashMap = new HashMap<>();

        //添加元素
        hashMap.put(1,"java");
        hashMap.put(2,"mysql");
        hashMap.put(3,"linux");
        hashMap.put(4,"oracle");
        hashMap.put(5,"map");
        //hashMap.put("11","hash");//键类型不符,无法存入

        //keySet()遍历获取
        Set<Integer> set = hashMap.keySet();
        //增强for
        for (Integer key : set) {
            System.out.println("key"+key+"-- value:"+hashMap.get(key));

        }

        System.out.println("-------------------");
        //iterator()方法
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()){
            Integer key = iterator.next();
            System.out.println(key+"--"+hashMap.get(key));
        }

        System.out.println("-------------------");
        //entrySet()遍历
       //通过entrySet()方法 ,获取map的Entry对象的Set集合
        Set<Map.Entry<Integer, String>> set1 = hashMap.entrySet();
        //增强for
        for (Map.Entry<Integer, String> entry : set1) {
            System.out.println(entry.getKey()+"--"+entry.getValue());
        }
        System.out.println("------------------------");
        //通过iterator迭代器方法获取Set集合中的iterator对象
        Iterator<Map.Entry<Integer, String>> iterator1 = set1.iterator();
        while (iterator.hasNext()){
            Map.Entry<Integer, String> entry = iterator1.next();
            //通过Entry对象,调用getKey和getValue获取键值
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }

        HashMap<String, String> map =
                new HashMap<>(100, 0.75f);
    }
}

Map的常用子类HashMap

构造方法

HashMap()

构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。

HashMap(int initialCapacity)

构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。

HashMap(int initialCapacity, float loadFactor)

构造一个空的 HashMap具有指定的初始容量和负载因子。

HashMap遍历之后Entry对象是什么?

Entry对象其实是Map接口中的一个子接口,提供了getKey()和getValue()两个抽象方法

经过查看HashMap的源码发现,HashMap中,存在一个内部类Node,实现类Map.Entry接口。

static class Node<K,V> implements Map.Entry<K,V> {

final int hash;

final K key;

V value;

Node<K,V> next;

说明HashMap中的Entry对象其实就是Node对象。

经过查看源码又发现,每个Node对象,其实就是一个节点,这个节点中,存放了key键和value这两个属性,

每次调用put方法去添加key和value,其实就是在new Node,并把key和value放入Node对象中,

并且它重写了getKey()和getValue()方法,将来可以通过这两个方法找到key和value。

所以,当我们调用HashMap的entrySet()方法的时候,返回值是Set<Map.Entry<K,V>>,

也就是一个Entry对象的Set集合,从这个Set集合中,获取每个Entry对象,

也就是Node对象,它具有getKey()和getValue()方法,从而能拿到key和value。

散列表介绍

概念

散列表其实是结合了数组和链表的优点,组合成的一个数据结构,

散列表不在意元素的顺序,也能够快速地找到元素中的数据。

散列表工作原理

散列表为每个键,计算一个数值,这个数值称为散列码,根据计算出来的散列码,去判断应该保存在哪个位置上。

Java中散列表,是通过数组+链表实现的

每个Java中的散列表,称为一个桶

一个桶中,可能会遇到散列码相同的情况(hash冲突)

如果散列码相同,那这时候,多个元素就存在同一个位置,这种情况无法避免。

如果出现了hash冲突,解决方案有2个:

1.判断要存进来的新的键和已经存在的键是否是同一个对象,

如果是同一个对象,就直接将原来的值覆盖。

2.如果不是同一个对象,那么就将新的对象和之前的对象,形成链表的形式,放在桶中。

HashMap详解【面试题】

1.允许null值,null键,键不能重复。

2.存取数据不能保证有序,存取无序。

3.创建的时候可以设定初始容量和负载因子,负载因子设定的太高或者太低都不太好,默认值0.75是一个折中选择

4.扩容是在容量达到了 初始容量*负载因子 的值,开始扩容。

5.扩容每次扩容2倍。

HashMap属性

默认初始容量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

最大空间

static final int MAXIMUM_CAPACITY = 1 << 30;

默认加载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

链表转树的最少链表数量

static final int TREEIFY_THRESHOLD = 8;

树退为链表的数量

static final int UNTREEIFY_THRESHOLD = 6;

转树同时满足最低的空间要

static final int MIN_TREEIFY_CAPACITY = 64;

HashMap方法

无参构造

以0.75为默认加载因子,构造一个HashMap,默认容量会在放入元素后,扩容到默认的16

public HashMap() {

this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

}

有参构造

根据传入的初始容量和加载因子,来创建对象

加载因子就是自己定义的,容量要经过tableSizeFor方法计算

public HashMap(int initialCapacity, float loadFactor) {

this.loadFactor = loadFactor;

this.threshold = tableSizeFor(initialCapacity);

}

tableSizeFor这个方法,会将容量计算为离传入的数值最近的,比数值大的2^n

put方法(提问)

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

key进入put方法后先经过hash方法,将key的hash()值,计算到后,再去传入到putVal方法中,

hash()方法中,会将传进来的key,调用hashCode()方法后返回一个hash值,

再拿这个hash值,和hash值的高位右移16位的数值做位异或计算,得到一个新的值,

(h = key.hashCode()) ^ (h >>> 16)

再传入putVal()方法中,最终再拿这个新的值和hashMap的容量-1的值,做位与计算,得到下标索引,

(p = tab[i = (n - 1) & hash])

这么复杂的计算,主要是为了减少hash冲突

putVal()方法(提问)

方法参数:key的hash值、key值、value值、

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

boolean evict) {

//声明空数组tab、 空节点对象p、变量n 表示容量、变量i 表示下标索引

Node<K,V>[] tab; Node<K,V> p; int n, i;

//table表示当前hashMap对象

//将当前的hashMap对象赋值给tab,判断是否为null,或者获取当前数组的长度,是否为0

//满足则将调用当前的 resize()方法后,赋值给tab对象,并且把最新的数组长度赋值给n = 16

if ((tab = table) == null || (n = tab.length) == 0)

n = (tab = resize()).length;

//把新的容量-1和 key的hash值,做位与计算,计算出它的下标

//把下标对应的值,取出来赋值给p,并且判断p是否为null

if ((p = tab[i = (n - 1) & hash]) == null)

//如果为null,数组下标不存在元素的情况

//将new一个新的Node对象,将key、value放进去,赋值给

//数组的下标的值

tab[i] = newNode(hash, key, value, null);

else {

//数组下标存在元素的情况,创建一个新几点对象e

Node<K,V> e; K k;

//拿到已经存在的元素的key的hash值,和当前要存放元素的hash比较,并且取出已存在元素的key赋值给k,之后和要存放元素的key比较值是否相等。或者用equals比较 k 和 key 是否是同一个对象,

//如果相同,就把p赋值给e,也就是将旧的节点对象,赋值给e变量

if (p.hash == hash &&

((k = p.key) == key || (key != null && key.equals(k))))

e = p;

//判断该链表是否是红黑树

//如果hash值不能,key不能,是一个红黑树节点

else if (p instanceof TreeNode)

//将p强转为树节点,调用putTreeVal()方法,将key、value存到红黑树节点中

e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

//该链是一个链表

else {

//遍历链表,在链表的最后,插入节点

for (int binCount = 0; ; ++binCount) {

//判断节点的next属性是否为null

if ((e = p.next) == null) {

//为null表示没有下一个节点,直接创建一个新节点,赋值给p的next属性

p.next = newNode(hash, key, value, null);

//链表的长度大于等于7,

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

//转树

treeifyBin(tab, hash);

break;

}

//判断链表中节点的key和插入元素的key是否存在相同的情况

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

//如果相同,就跳出循环

break;

//将e节点在复制给p,表示继续往下遍历

p = e;

}

}

//判断e是否为null

//进入到这个if,就表示是key完全相同 情况

if (e != null) { // existing mapping for key

//记录e的值

V oldValue = e.value;

if (!onlyIfAbsent || oldValue == null)

//把新的value值覆盖就的value值

e.value = value;

afterNodeAccess(e);

//返回旧值

return oldValue;

}

}

++modCount;

//如果size大小超过了扩容的临界值

if (++size > threshold)

//扩容

resize();

afterNodeInsertion(evict);

return null;

}

hashMap的添加流程【面试】

1.判断当前的hashMap对象数组是否空或者null,如果是空或者null,调用resize()方法扩容,扩容到16。

2.根据键值key的hash和当前长度-1,计算到元素索引要存放的位置,判断索引位置的元素是否为null,如果是null,直接创建新节点,放入到索引位置下。

3.如果不为null,表示产生hash冲突,继续判断hash表的第一个元素的key值是否跟要添加的key值是==或者 equals相等,如果相等则后续直接把值覆盖。

4,存在hash冲突,key也不相同的情况,判断当前的hash表是否是一个treeNode,如果是树,则往树中添加数据。

5.存在hash冲突,hash表也不是树,那就是链表的情况。遍历链表,看看长度是否超过8,

超过的话,转红黑树,在红黑树中执行插入,不超过就是执行链表的插入操作,

插入过程中判断key是否存在完全相同的情况,如果相同,就把旧值覆盖。

6.插入成功后,再判断空间的size值是否超过 临界值,如果超过,进行扩容。

hashMap的扩容

如果是无参构造创建的hashMap,再放入元素后,扩容到默认值16,

如果hashMap中已经有元素,在容量达到 当前容量*负载因子 的值的时候,开始扩容,扩容两倍,

默认16的话,达到 16*0.75=12 开始扩容。

hashMap总结

hashMap存放数据无序,可以存放null值,null键,键不能重复,线程不安全,

1. JDK8中的hashMap的底层结构是 数组+单向链表+红黑树

2. hashMap对象在构造的时候,默认需要一个初始容量值和负载因子, 当存入的元素超过 初始容量*负载因子的值后,会扩容2倍。

3. 负载因子默认是0.75,太大太小都不太好。

4. 初始容量默认是16,我们也可以自己定义初始容量,初始容量一定是2的n次方。

5. HashMap中如果出现hash冲突,会转为链表,链表节点数量超过8,并且当前hash表的容量大于64,会转红黑树

LinkedHashMap

LinkedHashMap是HashMap的子类,它就在HashMap的基础上,

加了一个可以维护的双向链表,保证数据存取的有序性。

可以存放null,线程不安全,声明的时候,也可以指定初始容量值和负载因子。

LinkedHashMap中存在一个accessOrder属性

创建对象的时候,可以传入这个属性值,默认情况下是false,

如果传入ture,会改变调用后的数据的顺序结构,也就是调用get()方法后,原有的结构顺序会被改变

package com.day15.map2;

import java.util.LinkedHashMap;
import java.util.Set;

public class MapDemo02 {
    public static void main(String[] args) {
        //创建LinkedHashMap对象
        LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>();

        linkedHashMap.put(1,"hello");
        linkedHashMap.put(3,"java");
        linkedHashMap.put(4,"world");
        linkedHashMap.put(5,"mysql");
        linkedHashMap.put(2,"oracle");
        System.out.println(linkedHashMap);

        Set<Integer> keySet = linkedHashMap.keySet();
        for (Integer key : keySet) {
            System.out.println(key+"--"+linkedHashMap.get(key));
        }
    }
}
package com.day15.map2;

import java.util.LinkedHashMap;

public class MapDemo03 {
    public static void main(String[] args) {
        LinkedHashMap<Integer, String> linkedHashMap =
        new LinkedHashMap<>(16, 0.75f, false);

        linkedHashMap.put(1,"hello");
        linkedHashMap.put(3,"java");
        linkedHashMap.put(4,"world");
        linkedHashMap.put(5,"mysql");
        linkedHashMap.put(2,"oracle");
        System.out.println(linkedHashMap);

        System.out.println(linkedHashMap.get(3));
        System.out.println();
    }
}

TreeMap

package map3;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class TreeMapDemo01 {
    public static void main(String[] args) {
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(1,"hello");
        treeMap.put(3,"java");
        treeMap.put(4,"world");
        treeMap.put(5,"mysql");
        treeMap.put(2,"oracle");

        System.out.println(treeMap);

        String s = treeMap.get(1);

        new HashSet<>();
        System.out.println(treeMap);

        Integer firstKey = treeMap.firstKey();
        System.out.println(firstKey);

        Map.Entry<Integer, String> firstEntry = treeMap.firstEntry();
        System.out.println(firstEntry.getKey());
        System.out.println(firstEntry.getValue());

        Set<Map.Entry<Integer, String>> entries = treeMap.entrySet();
        for (Map.Entry<Integer, String> entry : entries) {
            System.out.println(entry.getKey() + "--" + entry.getValue());
        }

    }
}
package map3;

public class Person {
    //public class Person implements Comparable{
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
    }

    //    @Override
    //    public int compareTo(Object o) {
    //        Person p = (Person) o;
    //        if (this.age>p.age){
    //            return 1;
    //        }else if (this.age<p.age){
    //            return -1;
    //        }
    //        return 0;
    //    }
}
package map3;

import map3.Person;

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

/*
在TreeMap中存入对象,按照对象的年龄排序
 */

public class TreeMapDemo02 {
    public static void main(String[] args) {
        Person p1 = new Person("jack", 20);
        Person p2 = new Person("tom", 21);
        Person p3 = new Person("lucy", 18);
        Person p4 = new Person("zhangsan", 22);
        Person p5 = new Person("lisi", 23);

        //        //把Person对象放到TreeMap中
        //        TreeMap<Person, String> treeMap = new TreeMap<>();
        //        //调用put()方法,存入对象和值
        //        treeMap.put(p1,"p1");
        //        treeMap.put(p2,"p2");
        //        treeMap.put(p3,"p3");
        //        treeMap.put(p4,"p4");
        //        treeMap.put(p5,"p5");
        //        System.out.println(treeMap);

        TreeMap<Person, String> treeMap1 = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                if (o1.age>o2.age){
                    return 1;
                }else if (o1.age<o2.age){
                    return -1;
                }
                return 0;
            }
        });
        treeMap1.put(p1,"p1");
        treeMap1.put(p2,"p2");
        treeMap1.put(p3,"p3");
        treeMap1.put(p4,"p4");
        treeMap1.put(p5,"p5");
        System.out.println(treeMap1);

    }
}
  • 44
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值