嘘~ 小破站正在拼命加载中 . . .

Java中的容器大杂烩-集合


一、 集合引入

在Java中,我们经常用到数组来存储数据。

但是数组的长度是不可变的,一旦在初始化数组时指定了数组长度,这个数组长度就是不可变的了。

而大多数实际应用中,数据的数量是没法确定的,并且有些数据具有一定映射关系。

比如学生身高 <张三—170>、<李四—160>、<王五—180>,这些数据就是有有映射关系的,需要保存这样有映射关系的数据,数组就有点无能为力了。

这时候就需要我们的集合出马了

集合就像书架、抽屉、购物袋、衣柜、保险柜这样存放物体的容器。

现实生活中,这样的容器很多,当然了在Java中集合的类型也很多,用来存放不同的对象,其功能也会有所不同。

有些方便存入和取出、有的具有排序功能、有的可以保证安全性等等

二、集合框架体系

集合主要由两大接口派生而来:

  • Collection 接口,主要用于存放单一元素;

  • Map接口,主要用于存放具有映射关系的数据,以键值对的形式进行存储

Collection 接口
image-20230418220637009
注意:以上图中只列举了主要的我们常接触继承派生关系,并没有列举所有关系。
黄色背景的为接口,其他背景的为实现类

接口名称 接口作用
List 接口 存储的元素是有序的、可重复的
Set 接口 存储的元素是无序的、不可重复的
Queue 接口 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的
Map 接口 使用键值对(key-value)存储,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值

三 、Collection 接口

Collection接口作为List Set Queue 的父接口,提供了一些“公共资产”(公共方法) 给实现类(List Set Queue)使用。

在这里插入图片描述

Collection既然作为父接口,一般不直接使用了,而是通过实现类来实现对集合的基本操作

接下来,我们以Collection 的实现类ArrayList 来具体演示一下集合的常用方法

①集合的基本操作

package com.xiezhr;

import java.util.ArrayList;
import java.util.List;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class CollectionDemo &#123;

    public static void main(String[] args) &#123;
        List list = new ArrayList();

        //1 往list集合中添加元素
        list.add("xiezhr");
        list.add("xiezhrspace.cn");
        list.add(666);
        list.add(true);
        System.out.println("list中添加元素后list:"+list);

        // 2 获取list集合元素个数
        System.out.println("list集合元素个数:"+list.size());

        // 3 删除list中指定元素
        list.remove(0);
        list.remove(true);
        System.out.println("list中删除元素后list:"+list);

        // 4 查找指定元素是否存在
        System.out.println("查找list中是否存在xiezhrspace.cn  "+list.contains("xiezhrspace.cn"));

        // 5 判断list集合是否为空
        System.out.println("list集合是否为空"+list.isEmpty());

        // 6 清空list中所有元素
        list.clear();
        System.out.println("删除list集合中所有元素后list:" + list);

        // 7 往list中添加多个元素(直接将一个集合添加进去)
        List list2 = new ArrayList();
        list2.add("海贼王");
        list2.add("火影忍者");
        list2.add("名侦探柯南");

        list.add(list2);
        System.out.println("list2:" + list2);
        System.out.println("list集合中添加list2后list:" + list);

        // 8 查找多个元素是否存在
        System.out.println("list中是否存在list2中的元素"+list.containsAll(list2));

        // 9 删除多个元素

        list.removeAll(list2);

        list.add("画江湖之不良人");

        System.out.println("list:" + list);

    &#125;
&#125;
//
list中添加元素后list:[xiezhr, xiezhrspace.cn, 666, true]
list集合元素个数:4
list中删除元素后list:[xiezhrspace.cn, 666]
查找list中是否存在xiezhrspace.cn  true
list集合是否为空false
删除list集合中所有元素后list:[]
list2:[海贼王, 火影忍者, 名侦探柯南]
list集合中添加list2后list:[[海贼王, 火影忍者, 名侦探柯南]]
list中是否存在list2中的元素false
list:[[海贼王, 火影忍者, 名侦探柯南], 画江湖之不良人]

②使用Iterator(迭代器) 遍历集合

所有实现Collection接口的集合都有一个iterator()方法用于遍历集合

package com.xiezhr;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class CollectionDemo &#123;

    public static void main(String[] args) &#123;
        List list = new ArrayList();

        // 往list集合中添加元素
        list.add("个人公众号:xiezhrspace");
        list.add("个人博客:www.xiezhrspace.cn");
        list.add(666);
        list.add(true);
        System.out.println("list中添加元素后list:"+list);

        //使用Iterator(迭代器)  遍历list集合

        //①得到list集合的迭代器iterator
        Iterator iterator = list.iterator();
        //②hasNext()用于判断是否还有下一个元素
        while (iterator.hasNext())&#123;
            //③ 指针往后移动,将后移后集合上的元素返回
            System.out.println(iterator.next());
        &#125;


    &#125;
&#125;
//执行结果
list中添加元素后list:[个人公众号:xiezhrspace, 个人博客:www.xiezhrspace.cn, 666, true]
个人公众号:xiezhrspace
个人博客:www.xiezhrspace.cn
666
true

image-20230415200108881

③使用for 循环增强 遍历集合

package com.xiezhr;

import java.util.ArrayList;
import java.util.List;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class CollectionDemo &#123;

    public static void main(String[] args) &#123;
        List list = new ArrayList();

        // 往list集合中添加元素
        list.add("个人公众号:xiezhrspace");
        list.add("个人博客:www.xiezhrspace.cn");
        list.add(666);
        list.add(true);
        System.out.println("list中添加元素后list:"+list);

        //使用for循环增强 遍历list集合
        for (Object o : list) &#123;
            System.out.println(o);
        &#125;

       
    &#125;
&#125;
// 执行结果
list中添加元素后list:[个人公众号:xiezhrspace, 个人博客:www.xiezhrspace.cn, 666, true]
个人公众号:xiezhrspace
个人博客:www.xiezhrspace.cn
666
true

四 、List集合

List 集合简介

  • List集合是一个有序、可重复的集合
  • List集合中的每一个元素都有对应的顺序索引,默认按元素的添加顺序设置元素的索引,第一个添加到 List 集合中的元素的索引为 0,第二个为 1,依此类推
  • List 集合允许使用重复元素,可以通过索引来访问指定位置的集合元素
  • List 实现了 Collection 接口,它主要有两个常用的实现类:ArrayList 类和 LinkedList

4.1 ArrayList

ArrayList有两种常见的构造方法

  • ArrayList() 构造一个初始容量为十的空列表
  • ArrayList(Collection<? extends E> c) 构造一个包含指定集合的元素的列表。

ArrayList 类除了使用 Collection 接口的所有方法之外,还有其自己独特的方法

image-20230415201707136

实践操作

package com.xiezhr;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class ArrayListDemo &#123;

    public static void main(String[] args) &#123;
        List list = new ArrayList();

        list.add("萧炎");
        list.add("美杜莎");
        list.add("纳兰嫣然");

        System.out.println("list原始值:" + list);

        // 1 在list索引为1 的地方插入一个元素
        list.add(1,"xiezhr");

        System.out.println("list在索引1处添加一个元素后:" + list);

        // 2 获取list索引为3的元素
        System.out.println(list.get(3));

        // 3 获取元素首次出现的位置
        System.out.println("返回第一次出现“美杜莎”元素的位置:"+list.indexOf("美杜莎"));

        // 4 获取元素最后一次出现的位置
        System.out.println("返回最后一次出现“美杜莎”元素的位置:"+list.lastIndexOf("美杜莎"));

        // 5 用指定元素替换list中指定位置的元素
        list.set(3,"小医仙");
        System.out.println("用“小医仙”替换list索引为3的元素后:" + list);

        // 6 返回list中从指定的 fromIndex (包括)到 toIndex之间的子集合
        List retlist = list.subList(0, 1);
        System.out.println("返回的子集合是:" + retlist);

        // 7 对list 内容进行排序
        List sortlist = new ArrayList();
        sortlist.add(34);
        sortlist.add(5);
        sortlist.add(56);
        sortlist.add(9);
        sortlist.add(-23);

        System.out.println("原始list元素:" + sortlist);
        sortlist.sort(Comparator.naturalOrder());
        System.out.println("排序后得list:" + sortlist);
    &#125;
&#125;

//结果
list原始值:[萧炎, 美杜莎, 纳兰嫣然]
list在索引1处添加一个元素后:[萧炎, xiezhr, 美杜莎, 纳兰嫣然]
纳兰嫣然
返回第一次出现“美杜莎”元素的位置:2
返回最后一次出现“美杜莎”元素的位置:2
用“小医仙”替换list索引为3的元素后:[萧炎, xiezhr, 美杜莎, 小医仙]
返回的子集合是:[萧炎]
原始list元素:[34, 5, 56, 9, -23]
排序后得list:[-23, 5, 9, 34, 56]

注意:

  • set(int index, Object element) 方法来改变 List 集合指定索引处的元素时,指定的索引必须是 List 集合的有效索引。当集合长度为 3,就不能指定替换索引为大于等于 处的元素,也就是说这个方法不会往list中添加新元素,只会替换原有元素
  • indexOf()方法和 lastIndexOf() 方法,前者是获得指定list的最小索引位置,而后者是获得指定list的最大索引位置。前提条件是指定的对象在 List 集合中有重复的对象,否则这两个方法获取的索引值相同
  • subList(1,3) 方法实际截取的是索引 1 到索引 2 的元素,并组成新的 List 集合

4.2 LinkedList

LinkedList类有两种构造方法

  • LinkedList() 构造一个空列表
  • LinkedList(Collection<? extends E> c) 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序

LinkedList 除了包含 Collection 接口中的所有方法之外,还有其自己独特的方法

image-20230415221411684

实践操作

package com.xiezhr;

import java.util.LinkedList;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class LinkedListDemo &#123;

    public static void main(String[] args) &#123;
        LinkedList linkedList = new LinkedList();

        linkedList.add("海贼王");
        linkedList.add("火影忍者");


        System.out.println("linkedList原始值:" + linkedList);

        // 1 addFirst向集合开头插入一个元素
        linkedList.addFirst("名侦探柯南");
        System.out.println("linkedList使用addFirst开头插入一个值后:" + linkedList);

        // 2 addLast向集合末尾插入一个元素
        linkedList.addLast("忍者神龟");
        System.out.println("linkedList使用addLast末尾插入一个值后:" + linkedList);

        // 3 push向集合开头添加一个元素
        linkedList.push("鬼灭之刃");
        System.out.println("linkedList使用push在集合开头插入一个值后:" + linkedList);

        //4 offer向集合末尾插入一个元素
        linkedList.offer("灵笼");
        System.out.println("linkedList使用offer末尾插入一个值后:" + linkedList);

        // 5 add(int index,E element) 在指定位置插入元素
        linkedList.add(2,"无间道");
        System.out.println("linkedList在索引为2的位置插入一个值后:" + linkedList);

        // 6 offerLast 在末尾插入一个元素
        linkedList.offerLast("白蛇传");
        System.out.println("linkedList使用offerLast末尾插入一个值后:" + linkedList);

        // 7 offerFirst 在第一个位置插入一个元素
        linkedList.offerFirst("不良人");
        System.out.println("linkedList使用offerFirst开头插入一个值后:" + linkedList);

        // 8 按下标获取元素
        System.out.println("获取下标为2的元素"+linkedList.get(2));

        // 9 获取第一个元素
        System.out.println("获取集合的第一个元素为:" + linkedList.getFirst());

        // 10 获取最后一个元素
        System.out.println("获取集合最后一个元素为:" + linkedList.getLast());

        // 11 通过peek获取第一个元素,不删除元素
        System.out.println("使用peek()方法前:" + linkedList);
        System.out.println("通过peek()获取第一个元素为:" + linkedList.peek());
        System.out.println("使用peek()方法后:" + linkedList);

        // 12 通过peekFirst获取第一个元素,不删除元素
        System.out.println("使用peekFirst方法前:" + linkedList);
        System.out.println("通过peekFirst获取第一个元素为:" + linkedList.peekFirst());
        System.out.println("使用peekFirst方法后:" + linkedList);

        // 13 通过peekLast获取第一个元素,不删除元素
        System.out.println("使用peekLast方法前:" + linkedList);
        System.out.println("通过peekLast获取最后一个元素为:" + linkedList.peekLast());
        System.out.println("使用peekLast方法后:" + linkedList);

        // 14 通过poll 查询并删除第一个元素
        System.out.println("使用poll方法前:" + linkedList);
        System.out.println("通过poll获取第一个元素为:" + linkedList.poll());
        System.out.println("使用poll方法后:" + linkedList);

        // 15 通过pollFirst 查询并删除第一个元素
        System.out.println("使用pollFirst方法前:" + linkedList);
        System.out.println("通过pollFirst获取第一个元素为:" + linkedList.pollFirst());
        System.out.println("使用pollFirst方法后:" + linkedList);

        // 15 通过 pollLast 查询并删除最后元素
        System.out.println("使用pollLast方法前:" + linkedList);
        System.out.println("通过pollLast获取最后一个元素为:" + linkedList.pollLast());
        System.out.println("使用pollLast方法后:" + linkedList);

        // 16 删除集合第一个元素
        System.out.println("linkedList使用removeFirst方法前:" + linkedList);
        linkedList.removeFirst();
        System.out.println("linkedList删除第一个元素后:" + linkedList);

        // 17 删除集合最后一个元素
        System.out.println("linkedList使用removeLast方法前:" + linkedList);
        linkedList.removeLast();
        System.out.println("linkedList删除最后一个元素后:" + linkedList);

        // 18 remove(E e):移除指定元素
        System.out.println("linkedList使用remove方法前:" + linkedList);
        linkedList.remove("忍者神龟");
        System.out.println("linkedList删除指定元素后:" + linkedList);



    &#125;
&#125;
//结果
linkedList原始值:[海贼王, 火影忍者]
linkedList使用addFirst开头插入一个值后:[名侦探柯南, 海贼王, 火影忍者]
linkedList使用addLast末尾插入一个值后:[名侦探柯南, 海贼王, 火影忍者, 忍者神龟]
linkedList使用push在集合开头插入一个值后:[鬼灭之刃, 名侦探柯南, 海贼王, 火影忍者, 忍者神龟]
linkedList使用offer末尾插入一个值后:[鬼灭之刃, 名侦探柯南, 海贼王, 火影忍者, 忍者神龟, 灵笼]
linkedList在索引为2的位置插入一个值后:[鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼]
linkedList使用offerLast末尾插入一个值后:[鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
linkedList使用offerFirst开头插入一个值后:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
获取下标为2的元素名侦探柯南
获取集合的第一个元素为:不良人
获取集合最后一个元素为:白蛇传
使用peek()方法前:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
通过peek()获取第一个元素为:不良人
使用peek()方法后:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
使用peekFirst方法前:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
通过peekFirst获取第一个元素为:不良人
使用peekFirst方法后:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
使用peekLast方法前:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
通过peekLast获取最后一个元素为:白蛇传
使用peekLast方法后:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
使用poll方法前:[不良人, 鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
通过poll获取第一个元素为:不良人
使用poll方法后:[鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
使用pollFirst方法前:[鬼灭之刃, 名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
通过pollFirst获取第一个元素为:鬼灭之刃
使用pollFirst方法后:[名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
使用pollLast方法前:[名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼, 白蛇传]
通过pollLast获取最后一个元素为:白蛇传
使用pollLast方法后:[名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼]
linkedList使用removeFirst方法前:[名侦探柯南, 无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼]
linkedList删除第一个元素后:[无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼]
linkedList使用removeLast方法前:[无间道, 海贼王, 火影忍者, 忍者神龟, 灵笼]
linkedList删除最后一个元素后:[无间道, 海贼王, 火影忍者, 忍者神龟]
linkedList使用remove方法前:[无间道, 海贼王, 火影忍者, 忍者神龟]
linkedList删除指定元素后:[无间道, 海贼王, 火影忍者]

4.3 Vector

VectorList 的古老实现类,底层使用Object[] 存储,线程安全的

4.4 ArrayList LinkedListVector区别

image-20230417221547510

  • ArrayListLinkedList 都是 List 接口的实现类
  • 实现List接口方式的不同,所以对不同的操作具有不同的效率
  • ArrayListLinkedList 都是不同步的,也就是不保证线程安全
  • ArrayList 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构
  • ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作
  • LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(add(E e)addFirst(E e)addLast(E e)removeFirst()removeLast()),时间复杂度为 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element)remove(Object o)), 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入
  • LinkedList 不支持高效的随机元素访问,而 ArrayList 支持,通过get(int index) set(int index) 可以快速获取元素,修改元素
  • 创建ArrayList 会在结尾预留一定容量的空间,而LinkedList 占用的空间比ArrayList 更多(空间除了存储数据之外还需要存储直接后继和直接前驱
  • 如果需要经常插入、删除操作来改变List集合的大小,则应该使用LinkedList集合

五 、Set集合

  • 没有顺序(添加和取出元素的顺序不一致),没有索引
  • 添加的元素不允许重复,最多只能包含一个null
  • 主要有两个常用的实现类:HashSet 类和 TreeSet类

①实践操作

由于Set 属于接口,我们以Set 接口的实现类HashSet 来演示

package com.xiezhr;

import java.util.HashSet;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class SetDemo &#123;

    public static void main(String[] args) &#123;
        HashSet hashSet = new HashSet();
        hashSet.add("海贼王");
        System.out.println(hashSet.add("海贼王"));
        System.out.println("set添加元素后结果" + hashSet);
    &#125;
&#125;
// 结果
false
set添加元素后结果[海贼王]

从上面示例中,我们发现往set集合中添加重复元素是不允许的

5.1 HashSet

  • HashSetSet 接口的典型实现,大多数时候使用 Set 集合时就是使用这个实现类

  • HashSet 是按照 Hash 算法来存储集合中的元素。因此具有很好的存取和查找性能

  • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化

  • HashSet 不是同步的,如果多个线程同时访问或修改一个 HashSet,则必须通过代码来保证其同步

  • 集合元素值可以是 null

  • 当向HashSet 集合中存入一个元素时,HashSet会通过获取元素的hashcode 值来确定元素在HashSet集合中的存储位置。如果两个元素equale比较为true但是hashcode不相等,这两个元素也被当作时不同的元素,存储在不同的位置

HashSet 有常见的两种构造方法

  • HashSet() 构造一个新的空集合
  • HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合

实践操作,用HashSet 创建一个,并向集合中添加几个动漫,并通过不同方式遍历集合

package com.xiezhr;

import java.util.HashSet;
import java.util.Iterator;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class SetDemo &#123;

    public static void main(String[] args) &#123;
        HashSet hashSet = new HashSet();
        hashSet.add("海贼王");
        hashSet.add("火影忍者");
        hashSet.add("鬼灭之刃");
        hashSet.add("间谍过家家");

        System.out.println("hashSet添加元素后:" + hashSet);

        //1 通过过滤器 Iterator 遍历集合
        System.out.println("------------------通过过滤器 Iterator 遍历hashSet集合-------------------------");
        Iterator iterator = hashSet.iterator();

        while (iterator.hasNext())&#123;
            System.out.println(iterator.next());
        &#125;

        // 2 通过增强for循环遍历
        System.out.println("------------------通过增强for循环 遍历hashSet集合-------------------------");
        for (Object o : hashSet) &#123;
            System.out.println(o);
        &#125;
    &#125;
&#125;
//执行结果
hashSet添加元素后:[火影忍者, 间谍过家家, 鬼灭之刃, 海贼王]
------------------通过过滤器 Iterator 遍历hashSet集合-------------------------
火影忍者
间谍过家家
鬼灭之刃
海贼王
------------------通过增强for循环 遍历hashSet集合-------------------------
火影忍者
间谍过家家
鬼灭之刃
海贼王

5.2 TreeSet

  • TreeSet 类同时实现了 Set 接口和 SortedSet 接口。使用 TreeSet 类实现的 Set 接口默认情况下是自然排序的,这里的自然排序指的是升序排序
  • TreeSet 只能对实现了 Comparable 接口的类对象进行排序

TreeSet 类除了实现 Collection 接口的所有方法之外

image-20230416172114407

实践操作:我们将一组学生的成绩【68.0,50.5,78.6,88.0,95.0,45.5】 存入TreeSet,并进行TreeSet 集合的基本操作演示

package com.xiezhr;

import java.util.*;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class TreeSetDemo &#123;

    public static void main(String[] args) &#123;
        TreeSet<Double> scoreSet = new TreeSet<Double>();

        scoreSet.add(68.0);
        scoreSet.add(50.5);
        scoreSet.add(78.6);
        scoreSet.add(88.0);
        scoreSet.add(95.0);
        scoreSet.add(45.5);

        System.out.println("添加分数后的scoreSet:" + scoreSet);
        // 1 学生分数由低到高排序
        Iterator<Double> it = scoreSet.iterator(); // 创建 Iterator 对象
        System.out.println("学生分数从低到高的排序为:");
        while (it.hasNext()) &#123;
            System.out.print(it.next() + "\t");
        &#125;

        // 2 查找分数为90分的是否存在
        if (scoreSet.contains(90.0)) &#123;
            System.out.println("成绩为: " + 90.0 + " 存在!");
        &#125; else &#123;
            System.out.println("成绩为: " + 90.0 + " 不存在!");
        &#125;

        // 3 查找不及格分数
        System.out.println("\n不及格的有:");
        SortedSet<Double> score1 = scoreSet.headSet(60.0);
        for (int i = 0; i < score1.toArray().length; i++) &#123;
            System.out.print(score1.toArray()[i] + "\t");
        &#125;

        // 4 查找90分以上分数
        System.out.println("\n90 分以上的有:");
        SortedSet<Double> score2 = scoreSet.tailSet(90.0);
        for (int i = 0; i < score2.toArray().length; i++) &#123;
            System.out.print(score2.toArray()[i] + "\t");
        &#125;

    &#125;
&#125;
//执行结果
添加分数后的scoreSet:[45.5, 50.5, 68.0, 78.6, 88.0, 95.0]
学生分数从低到高的排序为:
45.5    50.5    68.0    78.6    88.0    95.0    成绩为: 90.0 不存在!

不及格的有:
45.5    50.5    
90 分以上的有:
95.0    

5.3 LinkedHashSet类

LinkedHashSet: LinkedHashSetHashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别

六、List和Set区别

image-20230417214245733

七 、Map集合

  • Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键(key)对象和一个值(value)对象
  • Map 用于保存具有映射关系的数据
  • keyvalue都可以是任何引用类型的数据
  • Mapkey 不允许重复,value 可以重复
  • Map 中的 keyvalue 之间存在单向一对一关系,即通过指定的 key,总能找到唯一的、确定的 value
  • Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序

Map 常用方法

image-20230417204552991

7.1 HashMap

  • Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度
  • 遍历时,取得数据的顺序是完全随机的
  • HashMap最多只允许**一条记录的键为Null;**允许多条记录的值为Null
  • HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力

7.2 LinkedHashMap

  • 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录是先插入的,也可以在构造时用带参数,按照应用次数排序,需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现
  • 遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢

7.3 HashTable

  • Hashtable 与 HashMap类似,不同的是:它不允许记录的键或者值为空
  • 支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢

7.4 HashMap和HashTable比较

image-20230418231752235

7.5 TreeMap

  • TreeMap实现SortMap接口,内部实现是红黑树
  • 能够把它保存的记录根据键排序默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的
  • TreeMap不允许key的值为null。非同步的

TreeMap 除了具有Map通用方法之外,还有自己特殊的方法

image-20230418235231297

八 、Iterator 与 ListIterator

8.1 Iterator

Iterator是一个接口,它是集合的迭代器。在前面的示例中我们已经使用过,集合可以通过Iterator去遍历集合中的元素

Iterator 常用的API主要有以下几类

boolean hasNext() 判断集合里是否存在下一个元素。如果有,hasNext()方法返回 true
Object next() 返回集合里下一个元素
void remove() 删除集合里上一次next方法返回的元素
package com.xiezhr;

import java.util.*;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class IteratorDemo &#123;

    public static void main(String[] args) &#123;
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("西游记");
        arrayList.add("红楼梦");
        arrayList.add("三国演义");
        arrayList.add("水浒传");

        System.out.println("添加元素后arrayList:" + arrayList);

        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext())&#123;
            String book = iterator.next();
            if("红楼梦".equals(book))&#123;
                iterator.remove();
            &#125;
        &#125;

        System.out.println("删除红楼梦后arrayList:" + arrayList);


    &#125;
&#125;
//执行结果
添加元素后arrayList:[西游记, 红楼梦, 三国演义, 水浒传]
删除红楼梦后arrayList:[西游记, 三国演义, 水浒传]

注意:

  • Iterator只能单向移动

  • Iterator.remove()是唯一安全的方式来在迭代过程中修改集合;如果在迭代过程中以任何其它的方式修改了基本集合将会产生未知的行为。而且每调用一次next()方法,remove()方法只能被调用一次,如果违反这个规则将抛出一个异常

8.2 ListIterator

  • ListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问
  • 可以通过调用listIterator()方法产生一个指向List开始处的ListIterator
  • 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator
  • 可以双向移动(向前/向后遍历)
  • 可以使用set()方法替换它访问过的最后一个元素
  • 可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素
package com.xiezhr;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class ListIteratorDemo &#123;

    public static void main(String[] args) &#123;
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("西游记");
        arrayList.add("红楼梦");
        arrayList.add("三国演义");
        arrayList.add("水浒传");

        System.out.println("arrayList添加元素后 : " + arrayList);
        ListIterator<String> it = arrayList.listIterator();

        // 1 获取前后索引和当前值
        System.out.println("-------------------获取前后索引和当前值--------------------");
        while (it.hasNext()) &#123;
            System.out.println(it.next() + ", " + it.previousIndex() + ", " + it.nextIndex());
        &#125;

        // 2 从后向前遍历
        System.out.println("-----------------------从后向前遍历-----------------------");
        while (it.hasPrevious()) &#123;
            System.out.print(it.previous() + " ");
        &#125;

        System.out.println();
        // 3 从指定索引开始遍历
        System.out.println("------------------从指定索引开始遍历---------------------");
        it = arrayList.listIterator(2);  //从索引2 开始遍历


        while (it.hasNext()) &#123;
            String t = it.next();
            System.out.println(t);
            // 4 迭代过程中修改集合
            if ("三国演义".equals(t)) &#123;
                it.set("少年包青天");
            &#125; else &#123;
                it.add("xiezhr");
            &#125;
        &#125;
        System.out.println("操作后的arrayList : " + arrayList);
    &#125;

&#125;
//执行结果
arrayList添加元素后 : [西游记, 红楼梦, 三国演义, 水浒传]
-------------------获取前后索引和当前值--------------------
西游记, 0, 1
红楼梦, 1, 2
三国演义, 2, 3
水浒传, 3, 4
-----------------------从后向前遍历-----------------------
水浒传 三国演义 红楼梦 西游记 
------------------从指定索引开始遍历---------------------
三国演义
水浒传
操作后的arrayList : [西游记, 红楼梦, 少年包青天, 水浒传, xiezhr]

九、集合使用场景

既然有这么多类型的集合供我们使用,那么我们应该怎么选择呢?

9.1 Collection接口选择

在这里插入图片描述

9.2 Map接口选择

image-20230419212229637

示例:

package com.xiezhr;

import java.util.*;

/**
 * @author xiezhr
 * @version 1.0
 * @description: TODO
 * @date 2023/4/15 17:07
 */
public class MapDemo &#123;

    public static void main(String[] args) &#123;
      //1 使用HashMap
        HashMap<String,String> hashMap = new HashMap<>();
        hashMap.put("4","d");
        hashMap.put("3","c");
        hashMap.put("2","b");
        hashMap.put("1","a");

        Iterator<String> iteratorHashMap = hashMap.keySet().iterator();

        System.out.println("----------------------HashMap--------------------------");
        while (iteratorHashMap.hasNext())&#123;
            Object key1 = iteratorHashMap.next();
            System.out.println(key1 + "--" + hashMap.get(key1));
        &#125;

        System.out.println("----------------------LinkedHashMap--------------------------");
        // 2 使用LinkedHashMap
        LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("4","d");
        linkedHashMap.put("3","c");
        linkedHashMap.put("2","b");
        linkedHashMap.put("1","a");

        Iterator<String> iteratorlinkedHashMap = linkedHashMap.keySet().iterator();

        while (iteratorlinkedHashMap.hasNext())&#123;
            String key2 = iteratorlinkedHashMap.next();
            System.out.println(key2 + "--" + linkedHashMap.get(key2));
        &#125;

        // 3 使用TreeMap
        TreeMap<String,String> treeMap = new TreeMap<>();
        treeMap.put("4","d");
        treeMap.put("3","c");
        treeMap.put("2","b");
        treeMap.put("1","a");

        Iterator<String> iteratorTreeMap = treeMap.keySet().iterator();

        System.out.println("----------------------treeMap--------------------------");
        while (iteratorTreeMap.hasNext())&#123;
            String key3 = iteratorTreeMap.next();
            System.out.println(key3 + "--" + treeMap.get(key3));
        &#125;


    &#125;
&#125;
//执行结果
----------------------HashMap--------------------------
1--a
2--b
3--c
4--d
----------------------LinkedHashMap--------------------------
4--d
3--c
2--b
1--a
----------------------treeMap--------------------------
1--a
2--b
3--c
4--d

十、阿里Java开发手册规约

【强制】 关于hashCodeequals的处理,遵循如下规则

1)只要复写equals,就必须覆写hashCode

2)因为Set存储的是不重复的对象,所以以及hashCodeequals进行判断,Set存储的对象必须覆写这两种方法

3)如果自定义对象作为Map的键,那么必须覆写hashCodeequals

说明:String因为覆写了hashCode和equals方法,所以可以愉快的将对象作为key使用

【强制】 判断所有集合内部元素是否为空,应该用isEmpty()方法,而不是使用size()==0的方式

说明:在某些集合中,前者的时间复杂度为O(1),可读性更好

正例:

Map<String,Object> map = new HashMap<>(16);
if(map.isEmpty())&#123;
    System.out.println("no element in this map.")
&#125;

【强制】 在使用 java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要使用含以后参数类型为BinaryOperator、参数名为mergeFunction的方法,否则出现相同 key值时,会抛出IllegalStateException异常。

说明: 参数mergeFunction 的作用是当出现key重复时,自定义对value的处理策略。

正例:

List<Pair<String,Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version",6.19));
pairArrayList.add(new Pair<>("version",10.24));
pairArrayList.add(new Pair<>("version",13.14));
//在生成的Map集合中,只有一个键值对:&#123;version=13.14&#125;
Map<String,Double> map = pairArrayList.stream().collect(Collectors.toMap(Pair::getKey,pair::getValue,(v1,v2)->v2));

反例:

String[] words = new String[]&#123;"w","w","x"&#125;
//抛出IllegStateException 异常
Map<Integer,String> map = Arrays.stream(words).collect(Collectors.toMap(String::hashCode,v->v))

【强制】 在使用java.util.stream.Collectors 类的toMap()方法转为Map集合时,一定要注意当valuenull时,会抛出异常。

说明: 在java.util.HashMapmerge方法中,会进行如下判断:

if(value == null ||remappingFunction ==null)

throw new NullPointerException();

反例:

List<Pair<String,Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1",4.22));
pairArrayList.add(new Pair<>("version2",null));
Map<String,Double> map = pairArrayList.stream().collect(
    //抛出异常
    Collectors.toMap(Pair::getKey,Pair::getValue,(v1,v2)->v2);
)

【强制】 ArrayListsubList结果不可强转成ArrayList,否则会抛出ClassCastException异常,即java.util.RandomAccessSubList canot be cast to java.util.ArrayList.

说明:subList() 返回的时ArrayList的内部类SubList,并不是ArrayList本身,而是ArrayList的一个试图,对应SubList的所有操作最终会反映到原列表上

【强制】 使用Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其添加元素,否则会抛出UnsupportedOperationException异常

【强制】 Collections类返回的对象,如:emptyList()/singletonList() 等都是immutable list,不可对其添加或者删除元素。

反例:

如果查询无结果,返回Collections.emptyList()空集合对象,调用方一旦进行了添加元素的操作,就会触发UnsupportedOperationException异常

【强制】subList场景中,高度注意对父集合元素的增加火删除,它们均会导致子列表的遍历、增加、删除产生ConcurrentModificationException异常。

【强制】 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一致、长度为0的空数组。

反例:

直接使用toArray无参方法存在问题,此方法返回值只能是Object[] 类,若强转成其他类型数组,将出现ClassCastException 异常

正例:

List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String [] array = list.toArray(new String[0]);

说明: 使用toArray带参方法,数组空间大小为length

1)等于0,动态创建于size相同的数组,性能最好

2) 大于0但小于size,重新创建大小等于size的数组,增加GC负担

3)等于size,在高并发情况下,在数组创建完成之后,size正在变大的情况下,负面影响与第二条相同

4)大于size,空间浪费,且在size处插入null值,存在NPE隐患

【强制】 在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。

说明:ArrayList#addAll方法的第一行代码即Object[] a = c.toArray();

其中,c为输入集合参数,如果为null,则直接抛出异常

【强制】 当使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupporedOperationException异常。

说明:asList的返回对象时一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的时适配器模式,知识转换接口,后台的数据认识数组。

String[] str = new String[] &#123;"yang",“hao”&#125;;

List list =Arrays.asList(str);

第一种情况:list.add("yangguanbao");运行时异常。

第二种情况:str[0] = "changed"; 也会随之修改,反之亦然

【强制】 泛型通配符<? extends T> 用来接收返回的数据,此写法的泛型集合不能使用add方法,而<? supper T> 不能使用get方法,因为两者在接口调用赋值的场景中容易出错。

说明: 扩展介绍一下PECS(Producer Extends Consumer Super)

​ 原则:第一,频繁往外读取内容的,适合用<? extends T> ;

​ 第二,经常往里插入的,适合用<? super T>

【强制】 在无泛型限制定义的集合赋值给泛型限制的集合中,当使用集合元素时,需要镜像instanceof判断,避免抛出ClassCastException异常。

说明:比较泛型是在JDK5后才出现的,考虑到向前兼容,编译器允许非泛型集合与泛型集合相互赋值。

反例:

    List<String> generics = null;
    List notGenerics = new ArrayList(10);
    notGenerics.add(new Object());
    notGenerics.add(new Integer(1));
    generics = notGenerics;
    //此处抛出 ClassCastException 异常
    String string = generics.get(0);

【强制】 不要再foreach循环中对元素进行remove/add操作。当进行remove操作时,请使用Iterator方式。如果时并发操作,需要对Iterator对象加锁。

正例:

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext())&#123;
    String item = iterator.next();
    if(删除元素条件)&#123;
        iterator.remove();
    &#125;
&#125;

反例:

for(String item : list)&#123;
    if("1".equals(item))&#123;
        list.remove(item);
    &#125;
&#125;

说明:执行结果肯定会出乎大家的意料,试一下把“1”换成“2”,会是同样的结果吗?

【强制】 再JDK7及以上版本中,Comparator实现类要满足三个条件,否则Arrays.sortCollections.sort 会抛出IllegalArgumentException异常。

说明: 三个条件如下

​ 1)x,y的比较结果和y,x的比较结果相反。

​ 2)若x>y,y>z , 则x>z。

​ 3)若x=y,则x,z的比较结果和y,z的比较结果相同。

反例:下列中没有处理相等的情况,交换两个对象判断结果并不相反,不符合第一个条件,在实际使用中可能会出现异常

new Comparator<Student>()&#123;
    @Override
    public int compare(Student o1,Student o2)&#123;
        return o1.getId()>o2.getId() ? 1:-1;
    &#125;
&#125;

【推荐】 当使用泛型集合定义时,在 JDK7及以上版本中,使用diamond语法或全省略。

说明: 菱形泛型即diamond,直接使用<>指代前边已经指定的类型

【推荐】 当集合初始化时,指定集合初始值大小

说明: HashMap 使用HashMap(int initialCapacity) 初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。

正例:initialCapacity =(需要存储的元素个数/负载因子)+1.

​ 注意负载因子(即 loader factor)默认为0.75,如果暂时无法确定初始值大小,则设置为16(即默认值)。

反例:HashMap 需要放置1024 个元素,由于没有设置容量初始大小,则随着元素的增加而被迫不断扩容,resize()方法一给一共会调用8次,反复重建哈希表和数据迁移。当放置的集合元素规模达到千万时,会影响程序性能

⓲ 【推荐】使用entrySet 遍历Map类集合K/V,而不是用keySet方式遍历。

说明: keySet 方式其实遍历了两次,一次时转为Iterator对象,另一次时从hashMap中取出Key所对应的Value。而entrySet只遍历了一次九八KeyValue都放到了entery中,效率更高。如果是JDK8、则使用Map.forEach方法。

正例: values()返回的V值集合,是一个list集合对象;keySet()返回的是K值集合,是一个Set集合对象;entrySet()返回的是K-V值组合集合。

【推荐】 高度注意Map类集合K/V能否存储null值,如表1-1所示。

​ 表1-1 Map类集合K/V存储

集合类 Key Value Super 说明
Hashtable 不允许为null 不允许为null Dictionary 线程安全
Concurrent HashMap 不允许为null 不允许为null AbstractMap 锁分段技术
TreeMap 不允许为null 允许为null AbstractMap 线程不安全
HashMap 允许为null 允许为null AbstractMap 线程不安全

反例:由于HashMap的干扰,很多人认为ConcurrentHashMap可以置入null值,而事实上,在存储null值时,会抛出NPE异常。

【参考】 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。

说明:有序性指遍历的结果按某中比较规则依次排列。稳定性指集合每次遍历的元素次序时一定的。如:ArrayListorder/unsort;

HashMapunorder/unsort; TreeSetorder/sort

以上就是本期全部内容,希望对你有所帮助,我们下期再见 (●’◡’●)


文章作者: xiezhr
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 xiezhr !
评论
  目录