当前位置 博文首页 > 邱天的henry的博客:Java集合List,Set和Map史上最详细讲解

    邱天的henry的博客:Java集合List,Set和Map史上最详细讲解

    作者:[db:作者] 时间:2021-07-05 22:09

    1.集合概念

    集合是java中提供的一种容器,可以用来存储多个数据。

    2.集合与数组的区别(集合和数组都是容器,它们有啥区别呢?)

    • 数组的长度是固定的。集合的长度是可变的
    • 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

    3.集合框架

    • 集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map
    • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.Listjava.util.Set。其中,List的特点是元素有序、元素可重复。Set的特点是元素无序,而且不可重复。List接口的主要实现类有java.util.ArrayListjava.util.LinkedListSet接口的主要实现类有java.util.HashSetjava.util.TreeSet
    • 集合本身是一个工具,它存放在java.util包中。在Collection接口定义着单列集合框架中最最共性的内容。

    4.List接口介绍

    • ArrayList
      优点: 底层数据结构是数组,查询快,增删慢。
      缺点: 线程不安全,效率高

    • Vector
      优点: 底层数据结构是数组,查询快,增删慢。
      缺点: 线程安全,效率低

    • LinkedList
      优点: 底层数据结构是链表(双向链表),查询慢,增删快。
      缺点: 线程不安全,效率高

    5.Set接口介绍

    • HashSet(底层实现是HashMap)
      底层数据结构是哈希表。(无序,唯一)
      如何来保证元素唯一性?
      1.依赖两个方法:hashCode()和equals()
    • LinkedHashSet
      底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
      1.由链表保证元素有序
      2.由哈希表保证元素唯一
    • TreeSet
      底层数据结构是红黑树。(唯一,有序)
      1.如何保证元素排序的呢?
      自然排序
      比较器排序
      2.如何保证元素唯一性的呢?
      根据比较的返回值是否是0来决定

    ps:什么是哈希表呢?

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
    在这里插入图片描述
    从上图中可以看出,哈希表实则是数组+链表的形式组成,数组指的上图中的01233456…,链表则指的是在这里插入图片描述

    ps:哈希表实现:
    1.在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
    2.简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
    在这里插入图片描述
    以下是存储流程图:
    在这里插入图片描述

    6.针对Collection集合我们到底使用谁?(掌握)

    唯一吗?

    是:Set

    排序吗?

    是:TreeSet或LinkedHashSet
    否:HashSet
    如果你知道是Set,但是不知道是哪个Set,就用HashSet。

    否:List

    要安全吗?

    是:Vector
    否:ArrayList或者LinkedList

    查询多:ArrayList
    增删多:LinkedList
    如果你知道是List,但是不知道是哪个List,就用ArrayList。

    1.如果你知道是Collection集合,但是不知道使用谁,就用ArrayList。
    2.如果你知道用集合,就用ArrayList。

    7.Map接口介绍
    1.现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即 java.util.Map 接口。
    2.我们通过查看 Map 接口描述,发现 Map 接口下的集合与 Collection 接口下的集合,它们存储数据的形式不同,如下图。
    在这里插入图片描述

    • Collection 中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
    • Map 中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的
      值。
    • Collection 中的集合称为单列集合, Map 中的集合称为双列集合。
    • 需要注意的是, Map 中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

    Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
    1.TreeMap是有序的,HashMap和HashTable是无序的。
    2.Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。
    这就意味着:
    1.Hashtable是线程安全的,HashMap不是线程安全的。
    2.HashMap效率较高,Hashtable效率较低。如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
    3.Hashtable不允许null值,HashMap允许null值(key和value都允许)
    4.父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap

    8.TreeSet, LinkedHashSet and HashSet 的区别
    1.介绍

    • TreeSet的主要功能用于排序
    • LinkedHashSet的主要功能用于保证FIFO即有序的集合(先进先出)
    • HashSet只是通用的存储数据的集合

    2.相同点

    • Duplicates elements: 因为三者都实现Set interface,所以三者都不包含duplicate elements
    • Thread safety: 三者都不是线程安全的,如果要使用线程安全可以Collections.synchronizedSet()

    3.不同点

    • Performance and Speed: HashSet插入数据最快,其次LinkHashSet,最慢的是TreeSet因为内部实现排序
    • Ordering: HashSet不保证有序,LinkHashSet保证FIFO即按插入顺序排序,TreeSet安装内部实现排序,也可以自定义排序规则
    • null:HashSet和LinkHashSet允许存在null数据,但是TreeSet中插入null数据时会报NullPointerException

    9.ArrayList、和*hashset扩容问题

    ArrayList 默认初始容量为10
    线程不安全,查询速度快
    底层数据结构是数组结构
    扩容增量:原容量的 0.5倍
    如 ArrayList的容量为10,一次扩容后是容量为15

    Vector:线程安全,但速度慢
    底层数据结构是数组结构
    加载因子为1:即当 元素个数 超过 容量长度 时,进行扩容
    扩容增量:原容量的 1倍
    如 Vector的容量为10,一次扩容后是容量为20

    HashSet:线程不安全,存取速度快
    底层实现是一个HashMap(保存数据),实现Set接口
    默认初始容量为16(为何是16,见下方对HashMap的描述)
    加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
    扩容增量:原容量的 1 倍
    如 HashSet的容量为16,一次扩容后是容量为32

    10.案例
    按照斗地主的规则,完成洗牌发牌的动作。
    具体规则:

    1. 组装54张扑克牌将
    2. 54张牌顺序打乱
    3. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
    4. 查看三人各自手中的牌(按照牌的大小排序)、底牌
      规则:手中扑克牌从大到小的摆放顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3

    案例分析:

    1. 准备牌:
      完成数字与纸牌的映射关系:
      使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。
    2. 洗牌:
      通过数字完成洗牌发牌
    3. 发牌:
      将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
      存放的过程中要求数字大小与斗地主规则的大小对应。
      将代表不同纸牌的数字分配给不同的玩家与底牌。
    4. 看牌:
      通过Map集合找到对应字符展示。
      通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示。
    public class Poker {
    public static void main(String[] args) {
    /*
    * 1组装54张扑克牌
    */
    // 1.1 创建Map集合存储
    HashMap<Integer, String> pokerMap = new HashMap<Integer, String>();
    // 1.2 创建 花色集合 与 数字集合
    ArrayList<String> colors = new ArrayList<String>();
    ArrayList<String> numbers = new ArrayList<String>();
    // 1.3 存储 花色 与数字
    Collections.addAll(colors, "?", "?", "?", "?");
    Collections.addAll(numbers, "2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4",
    "3");
    // 设置 存储编号变量
    int count = 1;
    pokerMap.put(count++, "大王");
    pokerMap.put(count++, "小王");
    // 1.4 创建牌 存储到map集合中
    for (String number : numbers) {
    for (String color : colors) {
    String card = color + number;
    pokerMap.put(count++, card);
    }
    }
    /*
    * 2 将54张牌顺序打乱
    */
    // 取出编号 集合
    Set<Integer> numberSet = pokerMap.keySet();
    // 因为要将编号打乱顺序 所以 应该先进行转换到 list集合中
    ArrayList<Integer> numberList = new ArrayList<Integer>();
    numberList.addAll(numberSet);
    // 打乱顺序
    Collections.shuffle(numberList);
    // 3 完成三个玩家交替摸牌,每人17张牌,最后三张留作底牌
    // 3.1 发牌的编号
    // 创建三个玩家编号集合 和一个 底牌编号集合
    ArrayList<Integer> noP1 = new ArrayList<Integer>();
    ArrayList<Integer> noP2 = new ArrayList<Integer>();
    ArrayList<Integer> noP3 = new ArrayList<Integer>();
    ArrayList<Integer> dipaiNo = new ArrayList<Integer>();
    // 3.2发牌的编号
    for (int i = 0; i < numberList.size(); i++) {
    // 获取该编号
    Integer no = numberList.get(i);
    // 发牌
    // 留出底牌
    if (i >= 51) {
    dipaiNo.add(no);
    } else {
    if (i % 3 == 0) {
    noP1.add(no);
    } else if (i % 3 == 1) {
    noP2.add(no);
    } else {
    noP3.add(no);
    }
    }
    }
    // 4 查看三人各自手中的牌(按照牌的大小排序)、底牌
    // 4.1 对手中编号进行排序
    Collections.sort(noP1);
    Collections.sort(noP2);
    Collections.sort(noP3);
    Collections.sort(dipaiNo);
    // 4.2 进行牌面的转换
    // 创建三个玩家牌面集合 以及底牌牌面集合
    ArrayList<String> player1 = new ArrayList<String>();
    ArrayList<String> player2 = new ArrayList<String>();
    ArrayList<String> player3 = new ArrayList<String>();
    ArrayList<String> dipai = new ArrayList<String>();
    // 4.3转换
    for (Integer i : noP1) {
    // 4.4 根据编号找到 牌面 pokerMap
    String card = pokerMap.get(i);
    // 添加到对应的 牌面集合中
    player1.add(card);
    }
    for (Integer i : noP2) {
    String card = pokerMap.get(i);
    player2.add(card);
    }
    for (Integer i : noP3) {
    String card = pokerMap.get(i);
    player3.add(card);
    }
    for (Integer i : dipaiNo) {
    String card = pokerMap.get(i);
    dipai.add(card);
    }
    //4.5 查看
    System.out.println("令狐冲:"+player1);
    System.out.println("石破天:"+player2);
    System.out.println("鸠摩智:"+player3);
    System.out.println("底牌:"