java学习----Map体系集合

java学习----Map体系集合,第1张

Map体系集合


特点:无序,无下标,由键值对构成(key - value)key不可重复,value可以重复。

1.主要方法介绍

详细可以查阅API

 1. clear() 移除所有的键值对
 2. entrySet()返回映射中包含的映射关系的Set视图
 3. keySet()返回所有键的Set视图
 4. put(K key,V value) 插入数据
 5. remove()移除某个映射关系
 6. size() 查看size
 7. value()获取value的Collection集合
2.简单的使用
 public static void main(String[] args) {

        Map<String, String> map = new HashMap<String, String>();

        map.put("china", "中国");
        map.put("uk", "英国");
        System.out.println(map.size());
        System.out.println(map.toString());
        System.out.println(map.remove("uk"));

        //1.打印所有的键
        Set<String> keyset = map.keySet();
        for(String str : keyset){
            System.out.println(str + "===" + map.get(str));
        }
        //2.获取所有的映射关系  :效率要高一点
        //Entry表示一对映射关系
        Set<Map.Entry<String, String>> kvset = map.entrySet();
        for(Map.Entry<String , String> entry : kvset){
            System.out.println(entry.getKey() + "==" + entry.getValue());
        }

        //3.判断
        System.out.println(map.containsKey("uk"));
        System.out.println(map.isEmpty());
    }
3.HashMap 3.1简单应用

jdk1.2版本,线程不安全,运行效率高,允许用null作为key或者是value

创建一个Student类

public class Student {

    private String name;
    private int age;

    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 }

public static void main(String[] args) {
        HashMap<Student, String> hashmap = new HashMap<Student, String>();
        //1. 添加元素
        Student stu1 = new Student("sun", 18);
        Student stu2 = new Student("chen",19);
        Student stu3 = new Student("wang",20);


        hashmap.put(stu1, "qingdao");
        hashmap.put(stu2, "shandong");
        hashmap.put(stu3,"hangzhou");

        System.out.println(hashmap.size() + "元素个数");
        System.out.println(hashmap.toString());
        //新创建 stu4
        Student stu4 = new Student("sun",18);
        hashmap.put(stu4,"nanjing");
        System.out.println(hashmap.size());
        //可以发现  能够添加stu4
        
    }

能够添加重复元素,是由于HashMap默认的评判key是否相同的依据并不是,Student中的姓名和年龄。
这时候只需要重写Student中的equals和hashCode方法就可以使得,stu4不作为一个新的key插入了,注意,这时候stu4会覆盖掉stu1。

 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(name, age);
    }
3.2源码阅读

注:

  1. HashMap加载因子为0.75,见3.2.1
  2. 添加第一个元素时,会创建一个长度为16的数组见3.2.2
  3. JDK1.7及之前,存储结构由数组+链表组成,JDK1.8之后由数组+链表+红黑树构成(当链表长度>=8 && 数组长度 >= 64,链表会自动调整为红黑树)
  4. JDK1.8当链表长度小于6的时候,存储结构会调整成为
  5. JDK1.8之前链表的插入语方式为头插入,之后尾插入方式
3.2.1首先是构造函数
/**
     * Constructs an empty HashMap with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

loadFactor为hashmap的加载因子,这里的加载因子是0.75,就是说,当存放元素超过,当前总容量的75%。就会扩容。
具体这个值的来源,来自于开发时的合理选取,综合了诸多因素,若太小就会频繁扩容,若太大后期可能会频繁冲突之类的。

3.2.2初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;

初始容量16,1左移四位。最大容量2^30。

3.2.3存储结构,数组加链表结构

数组的定义

  transient Node<K,V>[] table;

Node的定义

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;    
        //后面省略
  }

显然 Node是一个单向链表

3.2.4put方法

put()函数内部内容

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

putVal()

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

先不管内部内容如何,我们可以看到 putVal()一共由五个参数,第一个为哈希值。哈希值的计算来自于hash()函数,找到hash()函数,内容如下所示。

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

如果key是空直接返回0,如果不是,h = key.hashCode()) ^ (h >>> 16),此处。


计算完成后得出的值即为hashcode的值。相当于是 低16位和高16位来一个异或运算。通常16位已经足够满足我们的需要。工程师们(开发者)认为,这样做会使得哈希表分布更加的均匀,减少散列冲突。

回到putVal()

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;  //第一次添加要扩容  扩到16

		if ((p = tab[i = (n - 1) & hash]) == null) //根据哈希值计算位置
           tab[i] = newNode(hash, key, value, null);

如果我当前是第一次添加数据,则 我的第一个i的计算就是 (16 - 1)& hash也就是:
此处,i = (n - 1) & hash一定是0-15之间,如果计算出的位置是空的,就直接插入。如果不是null

 else {
            Node<K,V> e; K k;
            //此处就要开始看equals了
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
             //再往下就要上链表了
             //再往下就上红黑树了
              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;
            }
        }
            

关于扩容,初始的数组大小为16,当进行到13个数据的时候就会进行扩容处理。

        ++modCount;
        if (++size > threshold)    //即将搞到13,马上resize()
            resize();    
        afterNodeInsertion(evict);
        return null;
    }

会进入到 resize() 内

		if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {  //判断是不是抄过最大
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)  //新的容量时旧的容量左移一位,成为32
                newThr = oldThr << 1; // double threshold
        }

  //之后会创建新的数组,容量为32,然后再传回去使用
4.TreeMap

存储结构,是一个红黑树。
实现了SortedMap接口(Map的子接口),可以对key自动排序,key需要实现Comparable接口。

4.1简单的应用

注意的点主要是,“key”需要继承Comparable接口
拿上述,Student举例

package main.java.day19;

import java.util.Objects;

public class Student implements Comparable<Student>{

    private String name;
    private int age;

    public Student() {
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public int compareTo(Student o) {
        //先比较姓名
        int n1 = this.getName().compareTo(o.getName());
        //比较年龄
        int n2 = this.getAge() - o.getAge();
        return n1 == 0 ? n2 : n1;
    }
}

主函数代码

public static void main(String[] args) {
        TreeMap<Student, String> treeMap = new TreeMap<Student, String>();
        Student stu1 = new Student("sun", 18);
        Student stu2 = new Student("chen",19);
        Student stu3 = new Student("wang",20);

        treeMap.put(stu1, "beijing");
        treeMap.put(stu2, "shanghai");
        treeMap.put(stu3, "hangzhou");

        System.out.println(treeMap.size() + "这是元素个数");
        System.out.println(treeMap.toString());

        // 删除
        treeMap.remove(stu1);
        System.out.println(treeMap.size());
        //遍历
        //使用KeySet
        Set<Student> students = treeMap.keySet();

        for (Student student : students){
            System.out.println(student.toString() + "-----" + treeMap.get(student));
        }
        
        //使用entry
        Set<Map.Entry<Student, String>> entrySet = treeMap.entrySet();
        for (Map.Entry entry : entrySet){
            System.out.println(entry.getKey() + "----" + entry.getValue());
        }

利用比较器更换比较规则,比如我们现在想先比较年龄再比较姓名
注意比较器的优先级是高于元素自然排序的优先级的。
示例代码如下:

public static void main(String[] args) {
		//定义比较器
        Comparator<Student> comparator = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int n1 = o1.getAge() - o2.getAge();
                int n2 = o1.getName().compareTo(o2.getName());
                return n1 == 0 ? n2 : n1;

            }
        };
        
		//这里将定义好的比较器,传给我们定义的TreeSet
        TreeMap<Student, String> treeMap = new TreeMap<Student, String>(comparator);
        Student stu1 = new Student("sun", 18);
        Student stu2 = new Student("chen",19);
        Student stu3 = new Student("wang",20);

        treeMap.put(stu1, "beijing");
        treeMap.put(stu2, "shanghai");
        treeMap.put(stu3, "hangzhou");

        System.out.println(treeMap.size() + "这是元素个数");
        System.out.println(treeMap.toString());

        // 删除
        treeMap.remove(stu1);
        System.out.println(treeMap.size());
        //遍历
        //使用KeySet
        Set<Student> students = treeMap.keySet();

        for (Student student : students){
            System.out.println(student.toString() + "-----" + treeMap.get(student));
        }

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


    }
5.Properties
  1. 存储属性名或属性值
  2. 属性名和属性值都是字符串类型
  3. 没有泛型
  4. 和流有关系

这里与流相关的内容之后再进行补充

5.1简单的应用
public static void main(String[] args) {
        //创建集合
        Properties properties = new Properties();
        //添加元素
        properties.setProperty("name", "sun");
        properties.setProperty("age", "20");

        System.out.println(properties.size());
        System.out.println(properties);
        properties.remove("age");
        System.out.println(properties);

        //遍历 使用keySet和EntrySet
        //还可以使用stringPropertyNames,返回所有的属性名
        Set<String> proStrings = properties.stringPropertyNames();

        for (String str : proStrings){
            System.out.println(str + "--" + properties.get(str));
        }

        System.out.println(properties.containsKey("age"));
 }
5.2补充System.getProperties()函数
        //补充System.getProperties();
        //此函数为获取与系统相关的所有属性
        Properties properties1 = System.getProperties();
        Set<String> proTrings1 = properties1.stringPropertyNames();

        for(String string : proTrings1){
            System.out.println(string + "-----" + properties1.getProperty(string));
        }

代码执行完之后的结果,如下所示:

会输出系统的相关属性。

6.总结

Map的主要内容基本上就这么多。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/langs/721994.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-04-26
下一篇2022-04-26

发表评论

登录后才能评论

评论列表(0条)

    保存