Javase学习12-集合

目录

Java学习12-集合

1. 集合概述

1.1 什么是集合?有什么用?

数组就是一个集合。集合就是一个能够容纳其他数据的容器。

集合常在开发时使用:

集合是一个容器,一个载体,可以容纳多个对象。在实际开发中,假设连接一个数据库,数据库中有十条数据,假设查询十个数据,那么我们的java程序就会把这十个数据封装进十个java对象,放到某个集合中,然后把集合发送到前端,遍历集合,将数据一个一个显示出来。

1.2 集合具体能存储什么?

集合不能直接存储基本数据类型,也不能直接存储java对象,集合存储的是java对象的内存地址(引用)。

list.add(11); // 自动装箱Integer

注意:

  • 集合本身也是一个对象
  • 集合在任何时候存储的都是内存地址(引用);

1.3 集合底层对应的数据结构

在java中每一个不同的集合,底层都会对应了不同的数据结构。往不同的集合中存放数据,就等于将数据存放到了不同的数据结构中。

常见的数据结构有 数组、链表、二叉树、hashMap、图等。

使用不同的集合等于使用不同的数据结构。

在java中,数据结构已经实现了,已经写好了常用的集合类,我们只需要知道在什么情况下选择使用哪一个集合即可。

new LinkedList(); 创建了一个集合对象,底层是链表

new ArrayList();创建了一个集合对象,底层是数组

1.4 集合在Java JDK中哪个包下?

java.util.*;

所有的集合类和集合接口都在java.util包下

1.5 集合继承图

在Java中集合分为两大类:

02Collection与Map.png

  1. 单个方式存储元素:超级父接口:java.util.Collection;

Collection集合继承脑图:

  1. 以键值对的方式存储元素:超级父接口:java.util.Map;

Map集合继承脑图:

1.6 总结

1.6.1 Collection:

1) List:有序可重复

  1. 有序:存入和取出的顺序相同,每一个元素都有下标

  2. 可重复:存入一个张三,还可以再存入一个张三

  3. 实现类:

    • ArrayList: 底层是数组,线程不安全

    • Linkedlist: 底层是双向链表

    • Vector: 底层是数组,线程安全,效率较低,使用较少

2) Set:无序不可重复

  1. 无序:存入和取出的顺序不一定相同,Set元素都没有下标
  2. 不可重复:不能存两个张三
  3. 实现类:
    • HashSet: 底层是HashMap,放到HashSet集合中的元素实际上都会进入HashMap的key部分
    • TreeSet: 底层是TreeMap,放到TreeSet集合中的元素实际上都会进入TreeMap的key部分

3) SortedSet(SortedMap):

继承Set,无序不可重复,但SortedSet集合中的元素是可排序的

  1. 无序:存入和取出的顺序不一定相同,Set元素都没有下标
  2. 不可重复:不能存两个张三
  3. 可排序:可以按照大小顺序排序

1.6.2 Map:

Map中的key就是一个Set集合,往Set集合中存放数据,实际放到了Map集合中的key部分

  • HashMap: 底层是哈希表
  • Hashtable: 底层是哈希表,线程安全,效率较低,使用较少
    • Properties: 继承自Hashtable线程安全,key和value只能存放String类型

SortedMap:

无序不可重复,但SortedSet集合中的元素是可排序的

  1. 无序:存入和取出的顺序不一定相同,Set元素都没有下标
  2. 不可重复:不能存两个张三
  3. 可排序:可以按照大小顺序排序
    • TreeMap: 底层是二叉树,TreeMap集合中的key可以按大小自动排序

2. 集合中的常用方法

增删改查常用英语单词:

  • 1.增:add、save、new
  • 2.删:delete、drop
  • 3.改:update、set、modify
  • 4.查:get、select、find、query

2.1 Collection

2.1.1 Collection接口常用方法:

返回值类型 方法名及传入参数类型 描述
boolean add(E e) 在集合末尾添加一个元素
int size() 返回此集合中的元素数
boolean contains(Object o) 查询集合中指定的元素,如果存在,返回true,反之返回false
boolean remove(Object o) 删除一个指定值的元素
void clear() 删除集合中的所有元素
boolean isEmpty() 判断集合是否为空,如果为空,返回true,反之返回false
Object[] toArray() 返回一个包含此集合中所有元素的数组。
Iterator iterator() 返回此集合中的元素的迭代器
  1. add(E e):

向集合中添加一个元素,集合中不能直接存放基本数据类型,也不能存放对象,只能存放对象的内存地址

05add方法.png

public class ContainsDemo01 {
    public static void main(String[] args) {
        Collection<Object> co = new ArrayList<>();
        User u1 = new User("张三");
        co.add(u1);
        String s1 = new String("李四");
        co.add(s1);
        //自动装箱 Integer integer = 11;
        co.add(11);
    }
}
class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
}
  1. contains(Object o):

查询集合中指定的元素,如果存在,返回true,反之返回false

例子:

public class ContainsDemo01 {
    public static void main(String[] args) {
        Collection<Object> co = new ArrayList<>();
        User u1 = new User("张三");
        co.add(u1);
        String s1 = new String("李四");
        co.add(s1);
        User u2 = new User("张三");
        System.out.println("集合中是否包含张三? " + co.contains(u2));
        String s2 = new String("李四");
        System.out.println("集合中是否包含李四? " + co.contains(s2));
    }
}

class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
}

结果:

集合中是否包含张三? false
集合中是否包含李四? true

为什么查询张三结果为false,李四结果为true呢?

查看ArrayList contains()方法源码:

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
public int indexOf(Object o) {
    return indexOfRange(o, 0, size);
}

int indexOfRange(Object o, int start, int end) {
    Object[] es = elementData;
    if (o == null) {
        for (int i = start; i < end; i++) {
            if (es[i] == null) {
                return i;
            }
        }
    } else {
        for (int i = start; i < end; i++) {
            if (o.equals(es[i])) {
                return i;
            }
        }
    }
    return -1;
}

o.equals(es[i])可知,contains方法调用了equals方法来比较元素。

我们知道,我们上面所写的User类并没有重写equals方法,故查找User类型的"张三"时,我们比较的是两个对象的内存地址,故为false.

而String类重写了equals方法,使得可以比较String对象的值,故为true.

故可以得出结论:

存放在集合中的类型,一定要重写equals方法

例:

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        //判断是否是User类型的元素
        if (!(obj instanceof User)) {
            return false;
        }
        //比较内存地址
        if (this == obj) {
            return true;
        }
        //强转成User类型
        User user = (User)obj;
        //比较值
        return user.name.equals(this.name);
    }
}
  1. remove(Object o):

从该集合中删除指定元素的单个实例(如果存在)

例子:

public class RemoveDemo {
    public static void main(String[] args) {
        Collection<Object> co = new ArrayList<>();
        User u1 = new User("张三");
        co.add(u1);
        System.out.println("删除前集合中元素个数:" + co.size());
        User u2 = new User("张三");
        co.remove(u2);
        System.out.println("删除后集合中元素个数:" + co.size());

    }

}
class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
}

结果:

删除前集合中元素个数:1
删除后集合中元素个数:1

显然不对。查看ArrayList remove()方法源码:

public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

可以看出,同样调用了equals方法来判断集合中是否包含要删除的元素,同上,需要重写equals方法

  1. iterator():

返回此集合中的元素的迭代器,迭代器可以对集合进行迭代/遍历

-------------对集合Collection进行迭代/遍历过程-----------------

第一步,集合对象调用iterator()方法拿到一个迭代器对象it.
迭代器对象可以调用boolean hasNext();和Object next();两个方法对集合进行迭代。

第二步,迭代器对象it调用hasNext()判断集合的下一个元素是否为空,如果为空,返回false并结束迭代,
如果不为空,返回true,然后调用next()使迭代器指向集合中的下一个元素并返回该元素。

注意:

1.迭代器一开始并不是指向集合中第一个元素
2.迭代器在所有Collection集合中是通用的

Iterator<Object> it = co.iterator();
// 判断集合中是否有元素可以进行迭代,如果有,往后走一个输出一个
while (it.hasNext()) {
    //无论存入什么数据,取出来的时候都是Object类型
    System.out.println(it.next());
}

方法调用例子(总):

package Collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @Author TSCCG
 * @Date 2021/5/24 9:58
 */

public class CollectionDemo01 {
    public static void main(String[] args) {
        /**
         * Collection collection1 = new Collection();
         * 接口是抽象的,无法实例化,故这里使用多态,new一个ArrayList对象
         * 来测试Collection中的常用方法
         */
        Collection<Object> co = new ArrayList<>();
        // 1.add 向集合中添加一个元素
        // 集合中不能直接存放基本数据类型,也不能存放对象,只能存放对象的内存地址
        User u1 = new User("张三");
        co.add(u1);
        String s1 = new String("李四");
        co.add(s1);
        //自动装箱 Integer integer = new Integer(11);
        co.add(11);
        //2.size 返回此集合中的元素数
        System.out.println("集合中元素的个数是:" + co.size());
        //3.contains 查询某一个元素
        User u2 = new User("张三");
        System.out.println("集合中是否包含张三? " + co.contains(u2));
        String s2 = new String("李四");
        System.out.println("集合中是否包含李四? " + co.contains(s2));
        //4.remove 删除此集合中指定的元素
        co.remove(u2);
        System.out.println("删除张三后,集合中元素的个数是:" + co.size());
        //判断集合是否为空 false
        System.out.println("集合此时为空吗?" + co.isEmpty());
        //5.clear 删除此集合中所有元素
        co.clear();
        System.out.println("删除全部元素后,集合中元素的个数是:" + co.size());
        //6.isEmpty 判断集合是否为空 true
        System.out.println("集合此时为空吗?" + co.isEmpty());

        //重新输入元素
        co.add("香蕉");
        co.add("野兽");
        co.add("野兽");
        co.add(new User("阿巴"));
        co.add(new Object());
        //7.toArray 将集合转换成数组
        Object[] obj = co.toArray();
        System.out.println("-----------遍历数组------------");
        for (int i = 0; i < obj.length; i++) {
            //默认调用toString方法
            System.out.println(obj[i]);
        }
        //8.iterator 迭代集合
        Iterator<Object> it = co.iterator();
        // 判断集合中是否有元素可以进行迭代
        System.out.println("-----------迭代集合------------");
        while (it.hasNext()) {
            //无论存入什么数据,取出来的时候都是Object类型
            System.out.println(it.next());
        }
    }
}

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        //判断是否是User类型的元素
        if (!(obj instanceof User)) {
            return false;
        }
        //比较内存地址
        if (this == obj) {
            return true;
        }
        //强转成User类型
        User user = (User)obj;
        //比较值
        return user.name.equals(this.name);
    }
}

结果:

集合中元素的个数是:3
集合中是否包含张三? true
集合中是否包含李四? true
删除元素11后,集合中元素的个数是:2
集合此时为空吗?false
删除全部元素后,集合中元素的个数是:0
集合此时为空吗?true
-----------遍历数组------------
香蕉
野兽
野兽
Collection.User@4fca772d
java.lang.Object@9807454
-----------迭代集合------------
香蕉
野兽
野兽
Collection.User@4fca772d
java.lang.Object@9807454

2.1.2 List

2.1.2.1 List接口特色方法:

返回值类型 方法名及传入参数类型 描述
void add(int index, E element) 将指定的元素插入此列表中的指定位置
E get(int index) 返回此列表中指定位置的元素
int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1
int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1
E remove(int index) 删除该列表中指定位置的元素
E set(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素
  1. add(int index, E element)

将指定的元素插入此列表中的指定位置.

此方法需要移动数组,效率较低,使用较少;

一般添加数据时都添加到列表末尾

ArrayList add(int index,E element)方法底层源码:

public void add(int index, E element) {
    //判断添加的下标是否越界
    rangeCheckForAdd(index);
    modCount++;
    //定义临时数组长度
    final int s;
    //定义临时数组
    Object[] elementData;
    //判断是否需要扩展数组
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    //将需要插入的位置原本的元素及其后面的元素后移一位
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    //将新元素添加到目标位置
    elementData[index] = element;
    size = s + 1;
}

测试方法:

public class ListDemo01 {
    public static void main(String[] args) {
        List<Object> list =  new ArrayList<>();
        //添加元素
        //在集合末尾添加元素
        list.add("大娃");
        list.add("二娃");
        list.add("三娃");
        list.add("四娃");
        System.out.println("---------在下标为1的位置添加指定元素前-------");
        print(list);
        //在指定位置添加元素
        list.add(1,"爷爷");
        System.out.println("---------在下标为1的位置添加指定元素后-------");
        print(list);
    }
    public static void print(List<Object> list) {
        Iterator<Object> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

结果:

---------在下标为1的位置添加指定元素前-------
大娃
二娃
三娃
四娃
---------在下标为1的位置添加指定元素后-------
大娃
爷爷
二娃
三娃
四娃
  1. get(int index)

返回此列表中指定位置的元素.

public class ListDemo01 {
    public static void main(String[] args) {
        List<Object> list =  new ArrayList<>();
        //添加元素
        //在集合末尾添加元素
        list.add("大娃");
        list.add("二娃");
        list.add("三娃");
        list.add("四娃");
        //查询下标为1的元素
        System.out.println(list.get(1));
    }
}

结果:

二娃
  1. indexOf(Object o)

返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1

public class ListDemo01 {
    public static void main(String[] args) {
        List<Object> list =  new ArrayList<>();
        //添加元素
        list.add("大娃");
        list.add(new User("二娃"));
        list.add(new User("二娃"));
        list.add("三娃");
        list.add("三娃");
        list.add("四娃");
        System.out.println("二娃第一次出现的索引为:" + list.indexOf(new User("二娃")));
        System.out.println("三娃第一次出现的索引为:" + list.indexOf("三娃"));
    }
}

class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
}

结果:

二娃第一次出现的索引为:-1
三娃第一次出现的索引为:3

查看底层得知索引时使用了equals方法,故需要在User类中重写equals方法

  1. lastIndexOf(Object o)

返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1

public class ListDemo01 {
    public static void main(String[] args) {
        List<Object> list =  new ArrayList<>();
        //添加元素
        list.add(new User("大娃"));
        list.add(new User("二娃"));
        list.add(new User("二娃"));
        list.add("三娃");
        list.add("三娃");
        list.add(new User("四娃"));
        System.out.println("二娃最后一次出现的索引为:" + list.lastIndexOf(new User("二娃")));
        System.out.println("三娃最后一次出现的索引为:" + list.lastIndexOf("三娃"));
    }
}

class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
}

结果:

二娃最后一次出现的索引为:-1
三娃最后一次出现的索引为:4

看底层ArrayList lastIndexOf方法知同样使用equals方法,需要重写equals方法

  1. remove(int index)

删除该列表中指定位置的元素

public class ListDemo01 {
    public static void main(String[] args) {
        List<Object> list =  new ArrayList<>();
        //添加元素
        //在集合末尾添加元素
        list.add("大娃");
        list.add("二娃");
        list.add("三娃");
        list.add("四娃");
        System.out.println("---------删除下标为1的元素前--------");
        print(list);
        list.remove(1);
        System.out.println("---------删除下标为1的元素后--------");
        print(list);
    }
    public static void print(List<Object> list) {
        Iterator<Object> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

结果:

---------删除下标为1的元素前--------
大娃
二娃
三娃
四娃
---------删除下标为1的元素后--------
大娃
三娃
四娃
  1. set(int index, E element)

用指定的元素(可选操作)替换此列表中指定位置的元素

public class ListDemo01 {
    public static void main(String[] args) {
        List<Object> list =  new ArrayList<>();
        //添加元素
        //在集合末尾添加元素
        list.add("大娃");
        list.add("二娃");
        list.add("三娃");
        list.add("四娃");
        System.out.println("---------替换下标为1的元素前--------");
        print(list);
        list.set(1,"六娃");
        System.out.println("---------替换下标为1的元素后--------");
        print(list);
    }
    public static void print(List<Object> list) {
        Iterator<Object> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

结果:

---------替换下标为1的元素前--------
大娃
二娃
三娃
四娃
---------替换下标为1的元素后--------
大娃
六娃
三娃
四娃

2.1.2.2 ArrayList

1. 初始化容量:

1.ArrayList集合底层是一个Object[]的数组

2.初始化容量默认为10(先创建一个长度为0的数组,当添加第一个元素的时候初始化容量为10)

3.构造方法:

  1. new ArrayList() //构造一个初始容量为十的空列表。
  2. new ArrayList(20) // 构造具有指定初始容量的空列表。
  3. new ArrayList(Collection<? extends E> c) //构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。

4.ArrayList集合不是线程安全的

package Collection.list.implements_class;

import java.util.*;

/**
 * @Author TSCCG
 * @Date 2021/5/29 10:20
 */

public class ArrayListDemo01 {
    public static void main(String[] args) {
        //默认初始化容量为10
        List<Object> list1 = new ArrayList<>();
        //指定初始化容量为100
        List<Object> list2 = new ArrayList<>(100);

        //HashSet集合
        Collection<Object> c = new HashSet<>();
        //添加元素到HashSet集合中
        c.add("张三");
        c.add("李四");
        c.add("王五");
        c.add("赵六");
        //构造一个包含HashSet集合的元素的ArrayList集合,按照它们由HashSet集合的迭代器返回的顺序存储。
        List<Object> list3 = new ArrayList<>(c);
        for (int i = 0; i < list3.size(); i++) {
            System.out.println(list3.get(i));
        }
    }
}

结果:

李四
张三
王五
赵六
2. ArrayList集合的扩容

每次扩容1.5倍 //int newCapacity = oldCapacity + (oldCapacity >> 1)

00000100:4 ----右移1位---->00000010:2

长度倍数增长为:(4 + 2)/ 4 = 1.5

ArrayList集合底层是数组,数组的扩容效率很低,在实际应用中,应尽量减少扩容来进行优化,建议在创建数组时根据数据量指定一个初始化容量。

3. ArrayList优缺点

ArrayList的优点:

  • 检索效率高。每个元素占用空间相同,内存地址是连续的,知道首元素地址,知道下标,可以通过数学表达式计算出元素的内存地址。

ArrayList的缺点:

  • 随机增删元素效率较低(向数组末尾添加元素效率较高,不受影响)
  • 数组不能存储较大的数据量,因为很难找到巨大且连续的内存空间
4. 面试常问问题:

在所有的集合中,你用哪个集合最多?

答:ArrayList集合

因为ArrayList集合的查询效率较高,在数组末尾增添元素的过程中也不会影响到过多元素的位置,效率不受很大的影响。

写程序的过程中进行检索或查找某一个元素的操作次数比较多,所以使用较多。

2.1.2.3 LinkedList

1.特点:
  1. LinkedList底层是一个双向链表

  2. 链表在内存空间中的内存地址是不连续的

  3. LinkedList集合底层也是有下标的,如:

LinkedList link = new LinkedList();
link.add("a");
link.add("b");
link.add("c");
for (int i = 0; i < link.size(); i++) {
    System.out.println(link.get(i));
}

注意:

ArrayList之所以检索效率高,不单单是因为有下标,主要还是底层数组发挥的作用。

LinkedList集合照样有下标,但是检索某个元素的效率较低,因为只能从头结点开始一个一个遍历。

2.LinkedList优缺点

LinkedList的优点:

  • 由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有过多元素发生位移,因此随机增删效率较高。
  • 在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用linkedList。

LinkedList的缺点:

  • 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以linkedList集合检索的效率较低。

更多详细内容请查看讲解链表的这篇博客

2.1.2.4 Vector

1.Vector概述

Vector特点:

  1. 底层也是一个Object数组。

  2. 初始化容量为10。

  3. 默认扩容原容量的2倍,也可以指定每次扩容的大小。

    int newCapacity = oldCapacity +
    ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
    
  4. Vector中所有方法都由synchronized关键字修饰,虽然因此而线程安全,但是线程同步使得执行效率低,使用较少。

Vector构造方法:

  • public vector() 数组默认容量为10,默认扩容2倍。
  • public vector(int initialcapacity) 指定初始容量,默认扩容两倍。
  • public vector(int initialcapacity,int capacityIncrement) 指定初始容量和每次扩容值,利用这个功能可以优化存储。
package Collection.list.implements_class;

import java.util.Iterator;
import java.util.Vector;

/**
 * @Author: TSCCG
 * @Date: 2021/07/07 15:51
 */
public class VectorDemo01 {
    public static void main(String[] args) {
        //创建Vector对象
        Vector vector = new Vector();
        Vector vector2 = new Vector(5,2);
        //添加元素
        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(4);
        vector.add(5);
        vector.add(6);
        vector.add(7);
        vector.add(8);
        vector.add(9);
        vector.add(10);
        //超出默认容量,扩容2倍 10---->20
        vector.add(11);
        //迭代集合
        Iterator it = vector.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

2.将一个线程不安全的集合转换为线程安全

使用集合工具类:java.util.Collections。

  • java.util.Collection 是集合接口。
  • java.util.Collections 是集合工具类。
List mylist = new LinkedList();//非线程安全
Collections.synchronizedList(mylist);//线程安全的
mylist.add(1);
mylist.add(2);
mylist.add(3);

2.1.3 Set

Set接口特点:无序不可重复。

1. HashSet

特点:

  • 无序不可重复。

    • 无序指的是存入的顺序和取出的顺序不一致,并且没有下标。
    • 无法存储重复的元素。
  • 放到HashSet集合中的元素实际上是放到了HashMap集合中的key部分了。

    • public boolean add(E e) {
          return map.put(e, PRESENT)==null;
      }
      
package Collection.set;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author: TSCCG
 * @Date: 2021/07/07 16:44
 * HashSet无序不可重复
 */
public class HashSetDemo01 {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("赵六");
        set.add("李四");
        set.add("李四");
        for (String s : set) {
            System.out.println(s);
        }
    }
}

结果:

李四
张三
王五
赵六

2. TreeSet

SortedSet接口:

  1. 继承Set接口。
  2. 可自动按照元素的值进行排序,称为可排序集合。

TreeSet实现SortedSet接口。

TreeSet特点:

  • 无序不可重复。

  • 可自动按照元素的值进行排序。

  • 放到TreeSet集合中的元素实际上是放到了TreeMap集合中的key部分了。

    • public boolean add(E e) {
          return m.put(e, PRESENT)==null;
      }
      
package Collection.set;

import java.util.Set;
import java.util.TreeSet;

/**
 * @Author: TSCCG
 * @Date: 2021/07/07 16:54
 */
public class TreeSetDemo01 {
    public static void main(String[] args) {
        Set<String> treeSet = new TreeSet<>();
        treeSet.add("4");
        treeSet.add("5");
        treeSet.add("2");
        treeSet.add("3");
        treeSet.add("5");
        treeSet.add("1");
        for (String s : treeSet) {
            System.out.println(s);
        }
    }
}

结果:

1
2
3
4
5

2.2 Map

2.2.1 Map结构

  1. Map和Collection之间没有继承关系。
  2. Map集合以key和value的方式存储数据:键值对。
    1. key和value都是引用数据类型。
    2. key和value都是存储对象的内存地址。
    3. key起到主导地位,value是key的一个附属品。

2.2.2 Map接口中的常用方法

返回值 方法名 作用
V put(K key,V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containKey(Object key) 判断Map集合中是否包含某个key
boolean containValue(Object value) 判断Map集合中是否包含某个value
boolean isEmpty() 判断Map集合中元素的个数是否为0
set< K > keySet() 获取Map集合中所有的key(所有的键是一个Set集合)
V remove(Object key) 通过key删除Map集合中某个键值对
int size() 获取Map集合中所有键值对的个数
Collection< V > values() 获取Map集合中所有的value,返回一个Collection集合
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合

1.V put(K key,V value)

向Map集合中添加键值对

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");

2.V remove(Object key)

通过key删除Map集合中某个键值对

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
map1.remove(2);

3.V get(Object key)

通过key获取value

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
System.out.println(map1.get(3));
王五

4.boolean isEmpty()

判断Map集合中元素的个数是否为0,是为true,否为false

Map<Integer,String> map1 = new HashMap<>();
System.out.println(map1.isEmpty());
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
System.out.println(map1.isEmpty());
true
false

5.void clear()

清空Map集合

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
System.out.println(map1.isEmpty());
map1.clear();
System.out.println(map1.isEmpty());
false
true

6.int size()

获取Map集合中所有键值对的个数

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
System.out.println(map1.size());
4

7.boolean containKey(Object key)

判断Map集合中是否包含某个key

底层调用的equals方法,自定义类型需要重写equals方法

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
System.out.println(map1.containsKey(2));
System.out.println(map1.containsKey(6));

true
false

8.boolean containValue(Object value)

判断Map集合中是否包含某个value

底层调用的equals方法,自定义类型需要重写equals方法

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
System.out.println(map1.containsValue("赵六"));
System.out.println(map1.containsValue("赵4"));
true
false

9.set< K > keySet()

获取Map集合中所有的key(所有的键是一个Set集合)

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
Set<Integer> set1 = map1.keySet();
for (Integer s : set1) {
    System.out.println(s);
}
1
2
3
4

10.Collection< V > values()

获取Map集合中所有的value,返回一个Collection集合

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
Collection<String> values = map1.values();
for (String s : values) {
    System.out.println(s);
}
张三
李四
王五
赵六

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

将Map集合转换成Set集合

现在有一个Map集合,将其转换成一个Set集合。如下:

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
Set<Map.Entry<Integer, String>> set = map1.entrySet();
for (Map.Entry<Integer,String> node : set) {
    System.out.println(node.getKey() + "=" + node.getValue());
}

打印结果:

1=张三
2=李四
3=王五
4=赵六

map1集合对象:

key value
1 张三
2 李四
3 王五
4 赵六

set集合对象:

Map.Entry<Integer,String>
node
node
node
node

注意:

Map集合通过entrySet()方法转换成的这个Set集合中,元素的类型是:Map.Entry<K,V>。

每个元素中有两个属性,分别是key和value。

Map.Entry是Map中的一个静态内部类。

静态内部类:

package map;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author: TSCCG
 * @Date: 2021/07/07 20:32
 * 静态内部类
 */
public class MyClass {
    /**
     * 静态内部类
     */
    private static class InnerClass {
        /*
        静态内部类中的静态方法
         */
        public static void m1() {
            System.out.println("静态内部类中的静态方法m1执行");
        }
        /*
        静态内部类中的实例方法
         */
        public void m2() {
            System.out.println("静态内部类中的实例方法m2执行");
        }
    }
    public static void main(String[] args) {
        //类名叫做 MyClass.InnerClass
        MyClass.InnerClass.m1();
        //创建静态内部类对象
        MyClass.InnerClass inner = new MyClass.InnerClass();
        inner.m2();
        //创建一个Set集合:
        //该Set集合中存储的对象是MyClass.InnerClass类型的
        Set<MyClass.InnerClass> set1 = new HashSet<>();
        //该Set集合中存储的对象是String类型的
        Set<String> set2 = new HashSet<>();
    }
}

2.2.3 遍历Map集合

1.通过遍历key来遍历value

获取所有的key,通过遍历key来遍历value

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
//获取所有的key,所有的key是一个Set集合
Set<Integer> set1 = map1.keySet();
/*
通过foreach
*/
//遍历key
for (Integer s : set1) {
    //通过key来获取value
    System.out.println(s + "=" + map1.get(s));
}

/*
通过迭代器
*/
//        Iterator<Integer> it = set1.iterator();
//        while (it.hasNext()) {
//            Integer key = it.next();
//            System.out.println(key + "=" + map1.get(key));
//        }
1=张三
2=李四
3=王五
4=赵六

2.把Map集合转换为Set集合再遍历

将Map集合直接转换为Set集合。

Set集合中元素的类型是:Map.Entry<K,V>

底层是Node节点,有key和value两个属性。

Map<Integer,String> map1 = new HashMap<>();
map1.put(1,"张三");
map1.put(2,"李四");
map1.put(3,"王五");
map1.put(4,"赵六");
//将Map集合转换成Set集合
Set<Map.Entry<Integer, String>> set = map1.entrySet();
//迭代器
Iterator<Map.Entry<Integer,String>> it = set.iterator();
while (it.hasNext()) {
    //遍历Set集合,每次取出一个Node
    Map.Entry<Integer,String> node = it.next();
    Integer key = node.getKey();
    String value = node.getValue();
    System.out.println(key + "=" + value);
}
//增强for
//for (Map.Entry<Integer,String> node : set) {
//    System.out.println(node.getKey() + "=" + node.getValue());
//}
1=张三
2=李四
3=王五
4=赵六

3.总结

使用第二种方式遍历的效率比较高。

  • 第一种方法获取到key后,再通过key在哈希表里找对应的元素,这是需要耗费一定时间的。
    • 这种方式适用于数据量较小的情况下。
  • 第二种方法是把key和value放一块了,对象中就有这两种属性,可以直接获取。
    • 这种方式适用于数据量较大的情况下。

2.2.3 HashMap

1.HashMap底层数据结构

  1. HashMap底层的数据结构是一个哈希表/散列表。

  2. 哈希表是一维数组和单向链表的结合体。

    • 数组:在查询方面效率很高,随机增删效率很低。
    • 单向链表:在查询方面效率很低,随机增删效率很高。
    • 哈希表将上面两种数据结构糅合到了一起,让它们充分发挥各自的优势。
    • 哈希表/散列表:实际上是一个一维数组,这个数组中每一个元素都是一个单向链表。(数组和链表的结合体)
  3. 为什么说哈希表的随机增删以及查询效率都比较高?

    • 增删是在链表上完成的。

    • 查询也不需要全部扫描,只需要局部扫描。

    • 哈希表查询的效率不如纯粹的数组。

    • 哈希表随机增删的效率不如纯粹的链表。

  4. HashMap集合的默认初始化容量是16,默认加载因子是0.75,扩容后是原容量的2倍

    • 这个默认加载因子是当HashMap集合底层数组的容量达到75%时,数组开始扩容。
    • 重点:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的。
      • 这是因为达到散列均匀,为了提高HashMap集合的存取效率所必须的。
  5. HashMap底层源码

    • public class HashMap {
          //HashMap底层是一个一维数组
          Node<K,V>[] table;
          //静态内部类HashMap.Node
          static class Node<K,V> implements Map.Entry<K,V> {
              final int hash;//哈希值,哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以返回一个下标
              final K key;//存储到Map集合中的key
              V value;//存储到Map结合中的value
              Node<K,V> next;//下一个节点的内存地址
      	}
          
      }
      

2.HashMap中元素的存取

  • 存:map.put(key,value)

    1. 首先将key和value封装到一个Node对象里。
    2. 然后底层会调用key的hashCode()方法得出key的hash值。
    3. 然后通过哈希算法/哈希函数将hash值转换成数组的下标。
    4. 如果下标位置上没有任何元素,那么就将该Node添加到该下标位置上。
    5. 如果下标对应位置上有链表,那么此时会调用equals方法将key和该链表上所有节点进行比较:
      • 如果返回结果都是false,那么就将该Node添加到该链表的末尾。
      • 如果其中有一个equals返回了true,那么该链表里的这个节点中的value将被新节点的value所覆盖。
  • 取:v = map.get(key)

    1. 先调用key的hashCode()方法得出key的hash值。
    2. 然后通过哈希算法/哈希函数将hash值转换成数组的下标。
    3. 然后通过下标快速定位到数组的某个位置上。
    4. 如果下标对应位置上没有任何元素,那么会返回null。
    5. 如果下标对应位置上有链表,那么此时会调用equals方法将key和该链表上所有节点进行比较:
      • 如果返回结果都是false,那么就返回null。
      • 如果其中有一个equals返回了true,那么该链表里的这个节点中的value就是我们要找的value。

通过以上过程,我们可以得出结论:

HashMap集合中的key,会先后调用两个方法,分别是hashCode()和equals(),这两个方法都要重写。

3.HashMap集合中key部分特点(去重)

无序,不可重复。

  • 无序:存入时不一定挂到哪个单向链表上。
  • 不可重复:equals方法来保证HashMap集合中的key不可重复,如果key重复了,value会覆盖。

放在HashMap集合中key部分的元素其实是放到HashSet集合中了,

所以HashSet集合中的元素也要同时重写hashCode()和equals()方法。

4.散列分布均匀

什么是散列分布不均匀?

  • 假设有100个元素,10条单向链表。如果每条链表上都挂10个元素,那么这种情况是最完美的,我们称之为:散列分布均匀。

同一个单向链表上所有节点的hash值相同,因为它们的数组下标是一样的。但同一个链表上的节点的key不同。

HashMap使用不当时无法发挥性能:

  1. 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成一个纯单向链表,这种情况我们称之为:散列分布不均匀。
  2. 假设将hashCode()方法所有的返回值都设定为不一样的值,这样会导致底层哈希表变成纯一维数组,也是散列分布不均匀。
  3. 散列分布均匀需要重写hashCode()方法时有一定技巧。

5.重写hashCode和equals方法

  1. 向Set集合中存元素的时候先调用hashCode方法,再调用equals方法
    •  当数组下标为null时,不需要调用equals方法
      
  2. 向Set集合中取元素的时候也先调用hashCode方法,再调用equals方法
    •  当数组下标为null时,不需要调用equals方法
      
  3. 如果两个元素的equals比较返回true,那么说明两个元素是一样的,是在同一条单向链表上比较的,而同一条单向链表上所有元素的哈希值都一样,所以hashCode和equals方法要一起重写
package map;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author: TSCCG
 * @Date: 2021/07/08 09:50
 */
public class HashMapDemo01 {
    public static void main(String[] args) {

        Student s1 = new Student("张三");
        Student s2 = new Student("张三");
        //如果此处没有重写equals方法,返回值会是false
        System.out.println(s1.equals(s2));//true
        System.out.println("s1的哈希值:" + s1.hashCode());//685325104
        System.out.println("s1的哈希值:" + s2.hashCode());//460141958
        Set<Student> set1 = new HashSet<>();
        set1.add(s1);
        set1.add(s2);
        /*
        如果此处只重写了equals没有重写hashCode,
        s1.equals(s2)结果会是true,这表示s1和s2是一样的,
        往HashSet集合中放的话,按说只能放1个(HashSet结合无序不可重复),
        但实际结果会放两个。
        这是因为s1和s2的哈希值值不相等,调用哈希算法返回的数组下标不一致,不会存放到同一条链表里,更不会检验是否重复
        */
        System.out.println(set1.size());//1
    }
}
class Student {
    private String name;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

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

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

结论:

放在HashMap集合key部分以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

6.JDK8对HashMap的改进

JDK8新特性:

当HashMap集合中单向链表长度大于8,会变成红黑树结构,当长度小于6,会变回单项链表。

单向链表过于长时,HashMap的检索效率也会降低,这样是为了提高检索效率。

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

7.哈希冲突(哈希碰撞)

对于哈希表数据结构来说:

  • 如果o1和o2的hash值相同,那么一定是放在同一条单向链表上的
  • 如果o1和o2的hash值不相同,但由于哈希算法计算结束之后转换的数组下标可能相同,此时会发生"哈希冲突(哈希碰撞)"

2.2.4 Hashtable

Hashtable和HashMap一样,底层都是哈希表。

Hashtable的初始化容量是11。

默认加载因子是:0.75f。

扩容是:原容量 * 2 + 1。

1.Hashtable和HashMap的不同之处

1) Hashtable是线程安全的

底层的方法都带有synchronized关键字,是线程同步的,是线程安全的。

但由于synchronized使得程序执行效率变低,以及目前已有其他更好的方案来解决线程安全问题,故Hashtable的使用较少。

2) Hashtable和HashMap中允许null吗?
  • HashMap集合key允许null,并且null值只能有一个。

  • Hashtable集合key和value都不允许null,不然会报空指针异常。

    • value不能为null:

      if (value == null) {
      throw new NullPointerException();
      }

    • key不能为null:int hash = key.hashCode();

HashMap:

Map map1 = new HashMap();
map1.put(null,null);
map1.put(null,200);

Set<Map.Entry> set1 = map1.entrySet();
for (Map.Entry entry : set1) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

结果:

null=200

Hashtable:

Map map1 = new Hashtable();
map1.put(null,null);
map1.put(null,200);

Set<Map.Entry> set1 = map1.entrySet();
for (Map.Entry entry : set1) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

结果:

Exception in thread "main" java.lang.NullPointerException
	at java.util.Hashtable.put(Hashtable.java:459)
	at map.HashMapDemo02.main(HashMapDemo02.java:28)

2.2.5 Properties

Properties是一个Map集合,继承Hashtable,是线程安全的。

Properties的key和value都只能存放String类型对象,被称为属性类。

package map;

import java.util.Properties;

/**
 * @Author: TSCCG
 * @Date: 2021/07/08 19:59
 */
public class PropertiesDemo01 {
    public static void main(String[] args) {
        //创建Properties对象
        Properties pro = new Properties();
        //多行编辑:alt + 鼠标拉拽
        //存放数据
        pro.setProperty("name","法外狂徒张三");
        pro.setProperty("age","31");
        pro.setProperty("sex","男");
        pro.setProperty("address","河南");
        pro.setProperty("education","本科");
        //根据key取出value
        System.out.println(pro.getProperty("name"));
        System.out.println(pro.getProperty("age"));
        System.out.println(pro.getProperty("sex"));
        System.out.println(pro.getProperty("address"));
        System.out.println(pro.getProperty("education"));

    }
}
法外狂徒张三
31
男
河南
本科

2.2.6 TreeMap

1.TreeMap的底层结构

TreeSet集合中的元素特点:无序不可重复,但可以按照元素的大小顺序自动排序,称为可排序集合。

TreeSet集合底层实际上是一个TreeMap。

放到TreeSet集合里的元素实际上是放到TreeMap集合里的key部分了。

TreeMap的底层结构是二叉树。

K key;
V value;
Entry<K,V> left; //左子树
Entry<K,V> right; //右子树
Entry<K,V> parent; //根节点

1)TreeSet/TreeMap是自平衡二叉树

遵循左小右大的原则存放,所以在存放的时候要进行比较。

存放的过程就是排序的过程。

TreeMap中添加元素时进行比较的源码:

do {
    parent = t;
    cmp = k.compareTo(t.key);//调用comparaTo方法比较新节点与二叉树中的根节点大小
    //如果新节点比根节点小,comparaTo返回一个负数,然后新节点移动到左子树上,再次与根节点进行比较,直到根节点的左子树或右子树为null
    if (cmp < 0)
        t = t.left;
    //如果新节点比根节点大,comparaTo返回一个正数,然后新节点移动到右子树上。
    else if (cmp > 0)
        t = t.right;
    //如果新节点等于根节点,comparaTo返回一个0,然后根节点被新节点覆盖
    else
        return t.setValue(value);
} while (t != null);

2)遍历二叉树的时候有三种方式:

  • 前序遍历:根左右
  • 中序遍历:左根右
  • 后序遍历:左右根

注意:

  • 前中后说的是根节点所在位置
  • 根在前面就是前序,在中间就是中序,在后面就是后序。

3)TreeSet集合/TreeMap集合采用的是:中序遍历

​ Iterator迭代器采用的是中序遍历方式。

​ 左根右。

4)将下列数字以二叉树的方式排列

70,200,20,10,40,100,60,400,50,800

5)采用中序遍历取出4)中的数据:

中序遍历遵循左根右的原则,先遍历左子树,然后遍历根节点,再然后遍历右子树。

10,20,40,50,60,70,100,200,400,800

2.TreeSet集合可以对String对象和Integer对象进行排序

String类和Integer类中都实现了Comparable接口,重写了compareTo方法,所以可以进行排序。

package map;

import java.util.TreeSet;

/**
 * @Author: TSCCG
 * @Date: 2021/07/08 20:31
 */
public class TreeSetDemo01 {
    public static void main(String[] args) {
        TreeSet<String> treeSet = new TreeSet<>();
        treeSet.add("d");
        treeSet.add("b");
        treeSet.add("e");
        treeSet.add("a");
        treeSet.add("c");
        for (String s : treeSet) {
            System.out.println(s);
        }
        System.out.println("---------------------");
        TreeSet<Object> treeSet2 = new TreeSet<>();
        treeSet2.add(3);
        treeSet2.add(1);
        treeSet2.add(5);
        treeSet2.add(2);
        treeSet2.add(4);
        for (Object o : treeSet2) {
            System.out.println(o);
        }
    }
}
a
b
c
d
e
---------------------
1
2
3
4
5

3.自定义类型实现Comparable接口实现排序

自定义类型需要实现Comparable接口,重写compareTo方法,制定排序规则,才能在TreeSet集合中实现排序。

TreeMap底层在存入元素时,会将传入的对象转换成Comparable类型,如果自定义类型没有实现Comparable接口,调用会报错。

Comparable<? super K> k = (Comparable<? super K>) key;

package map;

import java.util.TreeSet;

/**
 * @Author: TSCCG
 * @Date: 2021/07/08 20:46
 */
public class TreeSetDemo02 {
    public static void main(String[] args) {
        Person p1 = new Person("cc",20);
        Person p2 = new Person("aa",20);
        Person p3 = new Person("dd",30);
        Person p4 = new Person("bb",10);
        TreeSet<Person> ts1 = new TreeSet<>();
        ts1.add(p1);
        ts1.add(p2);
        ts1.add(p3);
        ts1.add(p4);
        for (Person p : ts1) {
            System.out.println(p);
        }
    }
}

/**
 * 自定义类型需要实现Comparable接口
 */
class Person implements Comparable<Person>{
    String name;
    int age;

    public Person() {
    }

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

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

    /**
     * 按照年龄大小排序
     * 如果年龄相同,按名字的字典顺序排序
     */
    @Override
    public int compareTo(Person p) {
        if (this.age == p.age) {
            //年龄是String类型,可以调用String类中重写过的compareTo直接比较
            return this.name.compareTo(p.name);
        } else {
            /*
            p1.comepareTo(p2)
            如果p1的age大于p2的age
                返回一个正数
            如果p1的age小于p2的age
                返回一个负数
            如果p1的age等于p2的age
                返回一个0
            this就是p1,p就是p2
             */
            return this.age - p.age;
        }
    }
}
Person{name='bb', age=10}
Person{name='aa', age=20}
Person{name='cc', age=20}
Person{name='dd', age=30}

4.自定义类型实现Comparator比较器接口实现排序

我们在创建TreeSet对象时,可以传入一个自己写的比较器进去来实现排序。

package map;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * @Author: TSCCG
 * @Date: 2021/07/08 21:44
 * 实现比较器接口
 *
 */
public class TreeSetDemo03 {
    public static void main(String[] args) {
        //匿名内部类方式实现Comparator接口
//        TreeSet<Cat> ts = new TreeSet<>(new Comparator<Cat>(){
//            @Override
//            public int compare(Cat o1, Cat o2) {
//                return o1.age - o2.age;
//            }
//        });
        TreeSet<Cat> ts = new TreeSet<>(new CatComparator());
        ts.add(new Cat(3));
        ts.add(new Cat(2));
        ts.add(new Cat(4));
        for (Cat t : ts) {
            System.out.println(t);
        }
    }
}
class Cat {
    int age;

    public Cat(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                '}';
    }
}
class CatComparator implements Comparator<Cat> {

    @Override
    public int compare(Cat o1, Cat o2) {
        return o1.age - o2.age;
    }
}
Cat{age=2}
Cat{age=3}
Cat{age=4}

TreeMap:

public class TreeSetDemo03 {
    public static void main(String[] args) {
        TreeMap<Integer,Cat> ts2 = new TreeMap<>(new CatComparator<Integer>());
        ts2.put(2,new Cat(5));
        ts2.put(1,new Cat(5));
        ts2.put(5,new Cat(1));
        ts2.put(3,new Cat(7));
        Set<Map.Entry<Integer,Cat>> set = ts2.entrySet();
		//迭代器
        Iterator<Map.Entry<Integer,Cat>> it = set.iterator();
        while (it.hasNext()) {
            Map.Entry<Integer,Cat> entry = it.next();
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
        //System.out.println("----------------");
		//增强for
        //for (Map.Entry<Integer, Cat> entry : set) {
        //    System.out.println(entry.getKey() + "=" + entry.getValue());
        //}
    }
}
class Cat {
    int age;
    public Cat(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Cat cat = (Cat) o;
        return age == cat.age;
    }
    @Override
    public int hashCode() {
        return Objects.hash(age);
    }
}
class CatComparator<I extends Number> implements Comparator<Integer> {

    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
}
1=Cat{age=5}
2=Cat{age=5}
3=Cat{age=7}
5=Cat{age=1}

5.总结

1)TreeMap底层数据结构是二叉树,遵循左小右大原则,存入的过程就是排序的过程。

2)放到TreeSet或TreeMap集合key部分的元素如果想要做到排序,包括两种方式:

  1. 放在集合中的元素实现Comparable接口。
  2. 在构造TreeSet或者TreeMap集合的时候给它传入一个比较器对象。

3)sComparable和Comparator该怎么选择呢?

  • 当比较规则不会发生改变时,或者说比较规则只有一个时,建议实现Comparable接口。
  • 当比较规则有多个,并且需要在多个比较规则之间频繁切换的时候,将以实现Comparator接口。
    • Comparator接口的设计符合OCP原则。

2.3 集合工具类

集合工具类Collections

2.3.1 将非线程安全的集合转换为线程安全的

Collections.synchronizedList(集合对象) ;

将一个非线程安全的List集合转换为一个线程安全的集合。

例:

//ArrayList集合是非线程安全的
List<Integer> list1 = new ArrayList<>();
//将ArrayList集合转换为线程安全的
Collections.synchronizedList(list1);
list1.add(3);
list1.add(4);
list1.add(1);
list1.add(2);

2.3.2 对集合进行排序

Collections.sort(集合对象);

对集合中的元素进行排序。

如果是自定义类型,则需要实现Comparable接口,重写CompareTo方法,制定比较规则。

public class CollectionsDemo01 {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        list1.add(3);
        list1.add(4);
        list1.add(1);
        list1.add(2);
        //将ArrayList排序
        Collections.sort(list1);
        for (Object o : list1) {
            System.out.println(o);
        }
       
        System.out.println("----Cat----");
        List<Cat> list2 = new ArrayList<>();
        list2.add(new Cat(2));
        list2.add(new Cat(4));
        list2.add(new Cat(1));
        list2.add(new Cat(3));
        //如果是自定义类型,必须要继承Comparable接口,重写CompareTo方法
        Collections.sort(list2);
        for (Cat cat : list2) {
            System.out.println(cat);
        }

    }
}
class Cat implements Comparable<Cat>{
    int age;

    public Cat(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                '}';
    }
    @Override
    public int compareTo(Cat o) {
        return this.age -  o.age;
    }
}
1
2
3
4
----Cat----
Cat{age=1}
Cat{age=2}
Cat{age=3}
Cat{age=4}

参考博客:

https://blog.csdn.net/lijock/article/details/80410202

https://blog.csdn.net/Star______/article/details/108011055

posted @ 2021-05-23 21:04  TSCCG  阅读(59)  评论(0编辑  收藏  举报