【学习笔记】集合(四)

集合(四)

 

Map集合

image-20220725104519726

 

  • Map接口特点:

    • 用于存储任意键值对(KEY-Value)

    • 键:无序、无下标、不允许重复

    • 值:无序、无下标、允许重复

  • Map接口的方法:

    • V put(K key,V value) //将对象存入集合中,关联键值,key重复则覆盖原值

    • Object get(Object key) //根据键获取对应的值

    • Set< K > keySet () //返回所有的key

    • Collection< V > values() //返回包含所有值的Collection集合

    • <Map.Entry<K,V>> entrySet () //键值匹配的set集合

 

  • 创建集合以及添加元素

package com.collection.map.mapDemo;
​
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
​
public class Demo01 {
    public static void main(String[] args) {
        //创建Map集合
        Map<String,String> map = new HashMap<>();
        //添加元素
        map.put("cn","中国");
        map.put("uk","英国");
        map.put("usa","美国");
​
        System.out.println("元素个数:"+map.size());
        System.out.println(map);
    }
}

image-20220725142801936

当我们去添加一个重复的 key 的时候,会发现 key相同的 value 被覆盖掉了

map.put("cn","zhongguo");

image-20220725143005393

 

  • 删除元素

删除是通过 key 来删除,因为key 不能重复

map.remove("usa");
System.out.println(map);

image-20220725143221316

 

  • 遍历

    • 使用 keySet ,它是所有key 的 Set集合,然后用迭代器或foreach来遍历,

      如果也想获取 value 我们可以使用 map中的get方法,通过key去获取value

      //遍历集合
      Set<String> keySet = map.keySet();
      for (String key: keySet) {
          System.out.println(key+"--------"+map.get(key));
      }

      image-20220725144111465

       

    • 使用 entrySet 方法

    这个方法的返回值也是Set , 但是里面的类型是 Entry<K,Y>, Entry 就是映射对,或键值对

    如果想要单独获取 键或者值,可以调用 entry.getKey() 或 entry.getValue()

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

    image-20220725144950358

     

    keySet() 和 entrySet() 的图示如下:

    image-20220725150907715

 

entrySet() 的效率比 keySet() 高,原因是它一下把key 和 value全都找了出来,而 keySet 只找出了key,想要value 还要在遍历一次。

 

  • 判断

//判断
System.out.println(map.containsKey("cn"));
System.out.println(map.containsValue("中国"));

image-20220725151349243

 

Map 集合的实现类

 

HashMap

  • 存储结构:哈希表(数组+链表+红黑树)

  • JDK1.2版本加入到java中,线程不安全,运行效率快

  • 允许用null 作为key 或value

  • 它的默认初始容量是16,默认加载因子0.75

    • 默认加载因子是到容量0.75时,进行扩容

  • 也可以通过构造方法指定容量和加载因子

  • 它的方法与 map 接口的方法一样

package com.collection.map.hashMap;
​
import java.util.HashMap;
​
public class Demo01 {
    public static void main(String[] args) {
        //创建一个类型为 Student + String  的集合
        HashMap<Student,String> students = new HashMap<>();
​
        Student s1 = new Student("孙悟空",120);
        Student s2 = new Student("猪八戒",230);
        Student s3 = new Student("沙悟净",231);
​
        students.put(s1,"北京");
        students.put(s2,"上海");
        students.put(s3,"广州");
​
        System.out.println(students);
    }
}

image-20220725153405224

 

在这里,如果我们用实例化的方式添加 key 相同元素也会加进去,所以也需要重写 hashCode 和equals 方法

students.put(new Student("沙悟净",231),"广州");

image-20220725154200711

 

重写 hashCode 和equals 方法,再次运行

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return age == student.age && name.equals(student.name);
}
​
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

image-20220725154252504

 

  • 其他操作

package com.collection.map.hashMap;
​
import java.util.HashMap;
import java.util.Map;
​
public class Demo01 {
    public static void main(String[] args) {
        //创建一个类型为 Student + String  的集合
        HashMap<Student,String> students = new HashMap<>();
​
        Student s1 = new Student("孙悟空",120);
        Student s2 = new Student("猪八戒",230);
        Student s3 = new Student("沙悟净",231);
        //添加元素
        students.put(s1,"北京");
        students.put(s2,"上海");
        students.put(s3,"广州");
        System.out.println(students);
​
        //删除元素
        //students.remove(s1);
        
        //遍历
        //1.使用 ketSet
        System.out.println("-------ketSet-------");
        for (Student stu:
             students.keySet()) {
            System.out.println(stu);
        }
        //2.使用 entrySet
        System.out.println("-------entrySet-------");
        for (Map.Entry<Student,String> stu:
             students.entrySet()) {
            System.out.println(stu);
        }
​
        //判断
        System.out.println(students.containsKey(new Student("孙悟空", 100)));
        System.out.println(students.containsValue("北京"));
    }
}

image-20220725155323150

 

HashMap 源码

  • 常量:

    • 默认初始容量 1<< 4 = 16 是数组的初始容量大小

    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;

    加入我们数组中的容量是100,当数组元素的数量大于75 即最大容量的75% ,就要进行扩容

    • 红黑树与链表数组的转换

    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    static final int MIN_TREEIFY_CAPACITY = 64;

在JDK1.8之后,存储结构中加入了红黑树,一开始是没有用红黑树的结构,还是使用数组加链表的结构。当链表的长度大于8,并且数组的长度大于等于64,就把数组链表结构转换成红黑树,这时候查找效率会高一些。当红黑树的元素个数小于6时,在把红黑树转换成数组链表

 

  • Node 类

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

我们往集合中添加的元素都是键值对,其实就是一个个的Node

它有 key ,value 还有next 用来在链表中指向下一个元素

 

transient Node<K,V>[] table;

table 是我们创建HashMap 之后,在table 里面保存的数组,就是数组加链表中的数组

transient int size;

元素的个数

当我们刚创建 HashMap 时,table = null size = 0;

 

  • 构造方法

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

一创建 HashMap ,只是把默认加载因子赋值给了loadFactor,没有对数组进行初始化,原因是为了节省空间。

 

  • put 方法

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

put 方法调用 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;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);

首先用Node 创建数组 tab 以及对象p

把table 赋值给tab ,并且判断是否为空,为空就调用resize() 并且将返回值赋值给tab,在获取tab的长度

Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
 else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;

创建新的变量newCap,把默认初始容量(16)赋值给newCap,

创建新的Node 数组 长度为newCap,在把新数组赋值给table

if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);

这句话就是把元素放到数组里

 

总结:

1.HashMap刚刚创建时,table是null,为了节省空间,当添加第一个元素时,table容量调整为16

2.当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的二倍,目的是减少调整元素的个数

3.jdk1.8 当每个链表长度大于8 并且数组元素个数大于等于64时,会调整为红黑树,目的提高执行效率

4.jdk1.8 当链表长度小于6时,调整成链表

5.jdk1.8以前,链表是头插入,jdk1.8以后链表是尾插入

 

HashSet 与 HashMap 的关系:

HashSet 里面用的就是 HashMap

HashSet 的add方法调用的是HashMap 的put方法

public HashSet() {
    map = new HashMap<>();
}
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

 

HsahMap

  • JDK1.0版本加入,现在较少使用这个集合

  • 线程安全,运行效率慢,不允许使用null作为key或value

子类 Properties

  • Hashtable 的子类,要求key 和 value 都是String 。通常用于配置文件的读取

  • 使用较多

 

TreeMap

  • 存储结构:红黑树

  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序

 

添加元素时,如果要添加的对象是自己创建的类,也需要实现comparable接口,重写comparator接口,给他定义一个比较的规则,否则就会报类型转换异常

package com.collection.map.treeMap;
​
import com.collection.map.hashMap.Student;
​
import java.util.TreeMap;
​
public class Demo01 {
    public static void main(String[] args) {
        //创建集合
        TreeMap<Student,String> students = new TreeMap();
        Student s1 = new Student("孙悟空",120);
        Student s2 = new Student("猪八戒",230);
        Student s3 = new Student("沙悟净",231);
        //添加元素
        students.put(s1,"北京");
        students.put(s2,"上海");
        students.put(s3,"广州");
        System.out.println(students);
    }
}

image-20220725173738461

 

实现comparable接口,重写compareTo方法,定义先根据年龄排,再根据姓名排

@Override
public int compareTo(Student s) {
    int n1 = this.age - s.getAge();
    int n2 = this.name.compareTo(s.getName());
    return n1 == 0 ? n2:n1;
}

在运行添加元素的代码

image-20220725174221878

 

如果通过实例化对象的方式添加属性相同的元素时,不会添加进来,key相同就会覆盖其对应的value

students.put(new Student("沙悟净",231),"深圳");

image-20220725174446443

 

  • 其他操作

package com.collection.map.treeMap;
​
import com.collection.map.hashMap.Student;
​
import java.util.Map;
import java.util.TreeMap;
​
public class Demo01 {
    public static void main(String[] args) {
        //创建集合
        TreeMap<Student,String> students = new TreeMap();
        Student s1 = new Student("孙悟空",120);
        Student s2 = new Student("猪八戒",230);
        Student s3 = new Student("沙悟净",231);
        //添加元素
        students.put(s1,"北京");
        students.put(s2,"上海");
        students.put(s3,"广州");
        System.out.println(students);
        //删除
        //students.remove(s1);
        //遍历
        //1.keySet
        System.out.println("-------keySet------");
        for (Student stu:
             students.keySet()) {
            System.out.println(stu+"-------"+ students.get(stu));
        }
        //2.entrySet
        System.out.println("-------entrySet------");
        for (Map.Entry<Student,String> stu:
             students.entrySet()) {
            System.out.println(stu);
        }
        //判断
        System.out.println(students.containsKey(new Student("孙悟空", 120)));
        System.out.println(students.containsValue("广州"));
    }
}

image-20220725175220393

 

如果没有实现comparable 接口的话,我们可以采用比较器的方式

TreeMap<Student,String> students = new TreeMap(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 的关系

在TreeSet 里面用的就是TreeMap

 

Collections 工具类

  • 概念:集合工具类,定义了除了存取以外的集合常用方法

  • 方法:

    • public static void reverse(List<?> list) //反转集合中元素的顺序

    • public static void shuffle(List<?> list) //随机重置集合元素的顺序

    • public static void sort(List<?> list) //升序排序(元素类型必须实现Comparable 接口)

package com.collection.comllections;
​
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
​
public class Demo01 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(56);
        list.add(23);
        list.add(78);
        list.add(12);
        list.add(6);
        //sort排序
        System.out.println("排序之前:"+list);
        Collections.sort(list);
        System.out.println("排序之后:"+list);
​
    }
}

image-20220725181017586

 

  • binarySearch 方法 二分查找,找到返回元素位置,找不到返回小于0的数

System.out.println(Collections.binarySearch(list, 12));
System.out.println(Collections.binarySearch(list, 13));

image-20220725181255068

 

  • copy 复制

//copy
List<Integer> dest = new ArrayList<>();
Collections.copy(dest,list);
System.out.println(dest);

image-20220725181706291

如果直接复制的话,会报下标越界异常,因为它要求目的集合与源集合的长度必须相等,我们可以先把目标数组里面放上东西,再去复制

//copy
List<Integer> dest = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
    dest.add(0);
}
Collections.copy(dest,list);
System.out.println(dest);

 

  • reverse 反转

//reverse 反转
Collections.reverse(list);
System.out.println("反转之后:"+list);

image-20220725182205625

 

  • shuffle 打乱

//reverse 反转
Collections.reverse(list);
System.out.println("反转之后:"+list);

image-20220725182441325

 

  • 集合与数组的转换

//集合转数组
System.out.println("----------集合转数组---------");
Integer[] arr = list.toArray(new Integer[10]);
System.out.println(Arrays.toString(arr));
//数组转集合
System.out.println("----------数组转集合---------");
String[] names = {"张三","李四","王五"};
List<String> list2 = Arrays.asList(names);
System.out.println(list2);

image-20220725183519493

需要注意的是,由集合转成的数组是受限的,无法进行添加和删除的操作。

把基本类型转成集合时,需要修改为包装类型

posted @ 2022-07-25 18:41  GrowthRoad  阅读(23)  评论(0编辑  收藏  举报