Java集合

163 阅读16分钟

Java集合

1、概述

集合类是Java数据结构的实现。Java的集合类是java.util包中的重要内容

2、区别于数组

  • 数组:
    • 创建时长度固定
    • 存放基本数据类型和引用类型
  • 集合:
    • 长度动态变化的
    • 存放引用数据类型

3、分类

单列集合:Collection

image-20230719123201322

双列集合:Map

image-20230719123226911

4、Java集合体系

4.1、迭代器 Iterator

它是Java集合的顶层接口,Iterator 接口提供遍历任何 Collection 的接口。

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());// 1 2 3
            iterator.remove();//用来删除元素
        }
        System.out.println(list.size());//0
    }
}

其他遍历方式:

public class ListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");

        //下标法
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        //增强for
        for (String s : list) {
            System.out.println(s);
        }

        //foreach
        list.forEach(s-> System.out.println(s));


        //listIterator
        ListIterator<String> listIterator = list.listIterator();
        //从前向后遍历
        while (listIterator.hasNext()) {
            int nextIndex = listIterator.nextIndex();
            String next = listIterator.next();
            System.out.println(nextIndex + "=" + next);
        }

        //从后向前遍历
        while (listIterator.hasPrevious()) {
            int previousIndex = listIterator.previousIndex();
            String previous = listIterator.previous();
            System.out.println(previousIndex + "=" + previous);
        }
    }
}

4.2、Collection接口

4.2.1、常见方法
方法描述
int size();返回元素数量
boolean isEmpty();判断是否为空
void clear();清空集合元素
boolean add(E e);添加一个元素
boolean addAll(Collection<? extends E> c);添加一个集合
boolean remove(Object o);删除一个元素
boolean remove(Object o);删除一个元素
boolean removeAll(Collection<?> c);删除一个集合
default boolean removeIf(Predicate<? super E> filter)根据条件删除
boolean retainAll(Collection<?> c);返回交集部分
Object[] toArray();返回一个包含此集合中所有元素的数组。 toArray(new Object[0])的功能与toArray()的相同。
<T> T[] toArray(T[] a);返回一个包含此集合中所有元素的数组。 返回的数组的运行时类型是指定数组的运行时类型。
4.2.2、List
4.2.2.1、常见方法
方法描述
void add(int index, E element);在指定索引处添加元素
E remove(int index);删除指定位置处的元素
E get(int index);获取指定位置处的元素
E set(int index, E element);设置指定位置处的元素
int indexOf(Object o);从前开始查找,返回该元素第一次出现的索引
int lastIndexOf(Object o);从后开始查找,返回该元素第一次出现的索引
default void replaceAll(UnaryOperator<E> operator)指定替换方式,替换集合中每个元素为新的值
default void sort(Comparator<? super E> c)指定排序方式,更改集合中每个元素排列位置
List subList(int fromIndex, int toIndex);根据前闭后开,重新获取集合中的一个子集合
4.2.2.2、ArrayList、LinkedList、Vector对比
  • 相同点:元素的存取是有序的,可以重复,可以存取null,元素有下标

  • 不同点:

    底层结构线程安全集合特点扩容机制版本
    ArrayList数组查询快、增删慢如果有参构造,满后每次1.5倍 如果无参构造,默认0,第一次10,满后每次1.5倍1.2
    LinkedList双向链表查询慢、增删快无扩容机制1.2
    Vector数组查询快、增删慢如果有参构造,满后每次2倍 如果无参构造,默认10,满后每次2倍1.0
4.2.2.3、Stack栈

后进先出(LIFO),继承自Vector,也是数组,线程安全的栈。

但作为栈数据类型,不建议使用Vector中与栈无关的方法,尽量只用Stack中的定义的栈相关方法,这样不会破坏栈数据类型。

4.2.2.4、ArrayList源码分析:

核心步骤:

  1. 创建ArrayList对象的时候,他在底层先创建了一个长度为0的数组。

    数组名字:elementDate,定义变量size。

    size这个变量有两层含义: ①:元素的个数,也就是集合的长度 ②:下一个元素的存入位置

  2. 添加元素,添加完毕后,size++

扩容时机一:

  1. 当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15.再把所有的元素,全拷贝到新数组中。如果继续添加数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍。

扩容时机二:

  1. 一次性添加多个数据,扩容1.5倍不够,怎么办呀?

    如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准。

举个例子: 在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,

怎么办?

此时新数组的长度,就以实际情况为准,就是110

具体分析过程可以参见视频讲解。

添加一个元素时的扩容:

第一次添加数据

添加多个元素时的扩容:

第11次添加数据

4.2.2.5、LinkedList源码分析:

底层是双向链表结构

核心步骤如下:

  1. 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
  2. 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
  3. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值

具体分析过程可以参见视频讲解。

LinkedList源码分析

4.2.3、 Set
4.2.3.1、子类对比
  • 相同点:元素的存取是无序的,不能重复,可以存取null,元素无下标

  • 不同点:

    底层结构线程安全集合特点扩容机制版本
    HashSetHashMap (数组+单向链表+红黑树)存取无序同HashMap1.2
    TreeSetTreeMap (红黑树)支持排序无扩容机制1.2
    LinkedHashSetLinkedHashMap (数组+双向链表+红黑树)存取有序同HashMap1.4
    ConcurrentSkipListSetConcurrentSkipListMap (跳跃表)支持排序无扩容机制1.6
4.2.3.2、HashSet添加元素原理
  • 底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了
  • 向HashSet中添加元素的过程:
    • 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值判断元素是否存在
    • 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素
    • 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
4.2.3.3、TreeSet实现排序原理
  • 底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了,红黑树(自平衡的排序二叉树)
  • TreeSet的有序存储,存储元素时会判断他是否重复,并且自动排序,判断重复元素由compareTo()方法来实现。因此自定义类要使用TreeSet必须覆写Comparable接口。具体的排序规则可以由我们自己来定
4.2.4、 Queue队列
4.2.4.1、PriorityQueue
  • 优先队列,保存队列元素的顺序并不是按照加入的顺序,而是按照队列元素的大小进行排序的,数组实现的二叉树,完全二叉树实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)。
  • 不允许插入null元素。
4.2.4.2、Deque

接口是Queue接口的子接口,它代表一个双端队列

ArrayDeque:

  • 数组实现的双端队列,可以在队列两端插入和删除元素,数组队列,先进先出
  • 和LinkedList一样也是双向链表

4.3、Map

4.3.1、常见方法
方法描述
int size();返回元素数量
boolean isEmpty();判断是否为空
void clear();清空集合元素
V put(K key, V value);添加一个键值对
void putAll(Map<? extends K, ? extends V> m);添加集合键值对
default V putIfAbsent(K key, V value)如果指定键尚未与某个值相关联, 则将其与给定值关联并返回null , 否则返回当前值
V remove(Object key);删除键所指对象
default boolean remove(Object key, Object value)删除指定键值对
V get(Object key);根据键来获取值
default V getOrDefault(Object key, V defaultValue)根据键来获取值, 如果值为空则返回defaultValue
efault V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)根据键来设置值, 如果值为空则置value,否则算新值
default V replace(K key, V value)替换指定键值对
default boolean replace(K key, V oldValue, V newValue)替换指定键值对
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)函数式替换所有
Set<K> keySet();获取所有键集合
Collection<V> values();获取所有值集合
Set<Map.Entry<K, V>> entrySet();获取键值对集合
boolean containsKey(Object key);是否包含指定键
boolean containsValue(Object value);是否包含指定值
default void forEach(BiConsumer<? super K, ? super V> action)循环遍历键值对
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)如果该键存在, 则使用函数式重新计算新值
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)如果该键存在,且值为null, 则使用函数式重新计算新值
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)如果该键存在,且值不null, 则使用函数式重新计算新值
4.3.2、遍历方法
public class MapDemo {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "张三");
        map.put(2, "李四");
        map.put(3, "王五");
        map.put(4, "赵六");

        //foreach
        map.forEach((key,value)->{
            System.out.println(key+"="+value);
        });

        //entrySet获取
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        entries.forEach((Map.Entry<Integer, String> entry) -> {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        });

        //遍历key获取value
        Set<Integer> keySets = map.keySet();
        keySets.forEach((key) -> {
            String value = map.get(key);
            System.out.println(key+"="+value);
        });

        //遍历所有值集合
        Collection<String> values = map.values();
        values.forEach((value) -> {
            System.out.println(value);
        });

    }
}
4.3.3、子类对比
  • 相同点::以键值对的形式添加元素,键不能重复,值可以重复

  • 不同点:

    底层结构线程安全集合特点扩容机制版本
    Hashtable数组键和值不可null 存取无序如果无参构造,加载因子为0.75 默认11,阈值8,大于阈值扩容2倍+11.0
    HashMap数组+单向链表+红黑树键和值可以null 存取无序如果无参构造,加载因子为0.75 默认16,阈值12,大于阈值扩容2倍 何时进行树化?链表大小>=8、数组大小>=641.2
    LinkedHashMap继承自HashMap (数组+双向链表+红黑树)键和值可以null 存取有序同HashMap1.4
    ConcurrentHashMap数组+链表/红⿊⼆叉树是,效率比Hashtable高键和值不可null 存取无序1.7
    TreeMap红黑树键不可null 值可以null 支持排序无扩容机制1.2
    ConcurrentSkipListMap跳跃表键和值不可null 支持排序无扩容机制1.6

5、Collections(集合工具类)

常用方法:

方法描述
public static void reverse(List<?> list)反转排列集合元素
public static void shuffle(List<?> list)随机排序集合元素
public static <T extends Comparable<? super T>> void sort(List<T> list)默认排序集合元素
public static void swap(List<?> list, int i, int j)交换指定下标元素
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)替换集合指定元素的值
public static <T> void fill(List<? super T> list, T obj)填充集合元素为指定值
public static int frequency(Collection<?> c, Object o)返回指定元素出现次数
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)根据自然顺序,获取集合中的最大值
public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)根据自然顺序,获取集合中的最小值
public static <T> List<T> singletonList(T o)创建单例集合列表
public static <K,V> Map<K,V> singletonMap(K key, V value)创建单例集合映射

6、数据结构

6.1、栈和队列【记忆】

  • 栈结构

    ​ 先进后出

  • 队列结构

    ​ 先进先出

6.2、数组和链表【记忆】

  • 数组结构

    ​ 查询快、增删慢

  • 队列结构

    ​ 查询慢、增删快

6.6、二叉树【理解】

  • 二叉树的特点

    • 二叉树中,任意一个节点的度要小于等于2
      • 节点: 在树结构中,每一个元素称之为节点
      • 度: 每一个节点的子节点数量称之为度
  • 二叉树结构图

    01_二叉树结构图

6.7、二叉查找树【理解】

  • 二叉查找树的特点

    • 二叉查找树,又称二叉排序树或者二叉搜索树
    • 每一个节点上最多有两个子节点
    • 左子树上所有节点的值都小于根节点的值
    • 右子树上所有节点的值都大于根节点的值
  • 二叉查找树结构图

    02_二叉查找树结构图

  • 二叉查找树和二叉树对比结构图

    03_二叉查找树和二叉树对比结构图

  • 二叉查找树添加节点规则

    • 小的存左边
    • 大的存右边
    • 一样的不存

    04_二叉查找树添加节点规则

6.8、平衡二叉树【理解】

  • 平衡二叉树的特点

    • 二叉树左右两个子树的高度差不超过1
    • 任意节点的左右两个子树都是一颗平衡二叉树
  • 平衡二叉树旋转

    • 旋转触发时机

      • 当添加一个节点之后,该树不再是一颗平衡二叉树
    • 左旋

      • 就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点

      05_平衡二叉树左旋01

      05_平衡二叉树左旋02

    • 右旋

      • 就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点

        06_平衡二叉树右旋01

        06_平衡二叉树右旋02

  • 平衡二叉树和二叉查找树对比结构图

    07_平衡二叉树和二叉查找树对比结构图

  • 平衡二叉树旋转的四种情况

    • 左左

      • 左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡

      • 如何旋转: 直接对整体进行右旋即可

        08_平衡二叉树左左

    • 左右

      • 左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡

      • 如何旋转: 先在左子树对应的节点位置进行左旋,在对整体进行右旋

        09_平衡二叉树左右

    • 右右

      • 右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡

      • 如何旋转: 直接对整体进行左旋即可

        10_平衡二叉树右右

    • 右左

      • 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡

      • 如何旋转: 先在右子树对应的节点位置进行右旋,在对整体进行左旋

        11_平衡二叉树右左

6.9、红黑树【理解】

  • 红黑树的特点

    • 平衡二叉B树
    • 每一个节点可以是红或者黑
    • 红黑树不是高度平衡的,它的平衡是通过"自己的红黑规则"进行实现的
  • 红黑树的红黑规则有哪些

    1. 每一个节点或是红色的,或者是黑色的

    2. 根节点必须是黑色

    3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的

    4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)

    5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

      12_红黑树结构图

  • 红黑树添加节点的默认颜色

    • 添加节点时,默认为红色,效率高

  • 红黑树添加节点后如何保持红黑规则

    • 根节点位置
      • 直接变为黑色
    • 非根节点位置
      • 父节点为黑色
        • 不需要任何操作,默认红色即可
      • 父节点为红色
        • 叔叔节点为红色
          1. 将"父节点"设为黑色,将"叔叔节点"设为黑色
          2. 将"祖父节点"设为红色
          3. 如果"祖父节点"为根节点,则将根节点再次变成黑色
        • 叔叔节点为黑色
          1. 将"父节点"设为黑色
          2. 将"祖父节点"设为红色
          3. 以"祖父节点"为支点进行旋转

6.10、哈希值【理解】

  • 哈希值简介

    ​ 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

  • 如何获取哈希值

    ​ Object类中的public int hashCode():返回对象的哈希码值

  • 哈希值的特点

    • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
    • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

6.11、哈希表结构【理解】

  • JDK1.8以前

    ​ 数组 + 链表

  • JDK1.8以后

    • 节点个数少于等于8个

      ​ 数组 + 链表

    • 节点个数多于8个

      ​ 数组 + 红黑树

7、不可变集合

7.1、什么是不可变集合

​ 是一个长度不可变,内容也无法修改的集合

7.2、使用场景

​ 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

​ 当集合对象被不可信的库调用时,不可变形式是安全的。

简单理解:

​ 不想让别人修改集合中的内容

比如说:

1,斗地主的54张牌,是不能添加,不能删除,不能修改的

2,斗地主的打牌规则:单张,对子,三张,顺子等,也是不能修改的

3,用代码获取的操作系统硬件信息,也是不能被修改的

7.3、不可变集合分类

  • 不可变的list集合
  • 不可变的set集合
  • 不可变的map集合

7.4、不可变的list集合

public class ImmutableDemo1 {
    public static void main(String[] args) {
        /*
            创建不可变的List集合
            "张三", "李四", "王五", "赵六"
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        List<String> list = List.of("张三", "李四", "王五", "赵六");

        System.out.println(list.get(0));
        System.out.println(list.get(1));
        System.out.println(list.get(2));
        System.out.println(list.get(3));

        System.out.println("---------------------------");

        for (String s : list) {
            System.out.println(s);
        }

        System.out.println("---------------------------");


        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("---------------------------");

        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        System.out.println("---------------------------");

        //list.remove("李四");
        //list.add("aaa");
        list.set(0,"aaa");
    }
}

7.5、不可变的Set集合

public class ImmutableDemo2 {
    public static void main(String[] args) {
        /*
           创建不可变的Set集合
           "张三", "李四", "王五", "赵六"


           细节:
                当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");

        for (String s : set) {
            System.out.println(s);
        }

        System.out.println("-----------------------");

        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        System.out.println("-----------------------");
        //set.remove("王五");
    }
}

7.6、不可变的Map集合

7.6.1、键值对个数小于等于10
public class ImmutableDemo3 {
    public static void main(String[] args) {
       /*
        创建Map的不可变集合
            细节1:
                键是不能重复的
            细节2:
                Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
            细节3:
                如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        Map<String, String> map = Map.of("张三", "南京", "张三", "北京", "王五", "上海",
                "赵六", "广州", "孙七", "深圳", "周八", "杭州",
                "吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
                "陈二", "嘉兴");

        Set<String> keys = map.keySet();
        for (String key : keys) {
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }

        System.out.println("--------------------------");

        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "=" + value);
        }
        System.out.println("--------------------------");
    }
}
7.6.2、键值对个数大于10
public class ImmutableDemo4 {
    public static void main(String[] args) {

        /*
            创建Map的不可变集合,键值对的数量超过10个
        */

        //1.创建一个普通的Map集合
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "南京");
        hm.put("李四", "北京");
        hm.put("王五", "上海");
        hm.put("赵六", "北京");
        hm.put("孙七", "深圳");
        hm.put("周八", "杭州");
        hm.put("吴九", "宁波");
        hm.put("郑十", "苏州");
        hm.put("刘一", "无锡");
        hm.put("陈二", "嘉兴");
        hm.put("aaa", "111");

        //2.利用上面的数据来获取一个不可变的集合
/*
        //获取到所有的键值对对象(Entry对象)
        Set<Map.Entry<String, String>> entries = hm.entrySet();
        //把entries变成一个数组
        Map.Entry[] arr1 = new Map.Entry[0];
        //toArray方法在底层会比较集合的长度跟数组的长度两者的大小
        //如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
        //如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
        Map.Entry[] arr2 = entries.toArray(arr1);
        //不可变的map集合
        Map map = Map.ofEntries(arr2);
        map.put("bbb","222");*/


        //Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

        Map<String, String> map = Map.copyOf(hm);
        map.put("bbb","222");
    }
}