【JavaSE---11】集合 「ArrayList | Vector | LinkedList | HashSet | TreeSet | HashMap | HashTable |TreeMap」

前言:在这里插入图片描述
因此引入集合。集合能够存放不同的对象,同时能够使用方法进行CRUD,灵活度高。

1. 集合

集合分类:(必须记住)

  1. 单列集合:直接存放元素。
    在这里插入图片描述
  2. 双列集合:以键值对的形式存放元素。
    在这里插入图片描述

注意:以上单列集合、双列集合中都只显示常用的实现类,实际上还有其他的实现类

2. 单列集合

2.1 Collection接口

  1. Collection接口的常用方法:即单列集合都有的方法
    在这里插入图片描述

    注意:

    1. toArray()方法只能将集合转为Object类型的数组,如果要将集合要转为指定类型的数组,只能使用toArray(T[] a)方法。
      在这里插入图片描述
    2. 上面黄色背景的方法使用default修饰,故不被子类继承。但是由于是接口,如果不被重写将无意义,所以接口中default修饰的方法都会在子类中被重写。事实上,这就是要告诉子类,想用该方法就得重写。
      ①关于removeIf方法:https://blog.csdn.net/czydream/article/details/81585069
      ②关于spliterator() 、stream() 、parallelStream() 方法:https://blog.csdn.net/starexplode/article/details/80567758/
  2. 遍历集合
    1. 使用迭代器:Collection接口继承了Iterable接口,故Collection对象使用.iterator()方法可获取迭代器对象,再使用迭代器对象的next()方法可不断获取元素对象。
      在这里插入图片描述
    2. 使用for-each循环:(for-each的底层依旧使用的是迭代器实现)

2.2 List接口

2.2.1 List接口介绍

  1. 实现List接口的特点:
    1. 元素有序,且可重复 (故可重复添加多个null)
    2. 支持索引。【使用.get(index)方法进行索引】
      3. 每一个元素都对应一个整型的序号记载其在容器中的位置,故也可根据序号访问
  2. 常用方法:
    1. Collection接口的常用方法
    2. List接口的常用方法:
      在这里插入图片描述

    replace()方法:https://vimsky.com/examples/usage/java-programming_library_arraylist_replaceall.html#:~:text=replaceAll%20%28%29%20%E6%96%B9%E6%B3%95%E4%B8%8D%E8%BF%94%E5%9B%9E%E4%BB%BB%E4%BD%95%E5%80%BC%E3%80%82%20%E7%9B%B8%E5%8F%8D%EF%BC%8C%E5%AE%83%E5%B0%86%20arraylist,%E7%9A%84%E6%89%80%E6%9C%89%E5%80%BC%E6%9B%BF%E6%8D%A2%E4%B8%BA%20operator%20%E4%B8%AD%E7%9A%84%E6%96%B0%E5%80%BC%E3%80%82%20%E7%A4%BA%E4%BE%8B%201%EF%BC%9A%E5%B0%86%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E6%9B%B4%E6%94%B9%E4%B8%BA%E5%A4%A7%E5%86%99

  3. 遍历List:三种方法。 在Collection的两种遍历方式在基础上,加上使用 fori 当作数组遍历.【迭代遍历效率优于索引遍历】
    在这里插入图片描述
  4. 细节:
    1. List对象具有特殊的迭代器ListIterator
      1. 该迭代器除了正常迭代器操作外(如查找),还允许①元素插入、②删除、③替换、④双向访问(next方法、previous方法)
        点击进入详细情况
      2. 两个重载方法
        在这里插入图片描述

2.2.2 排序

  1. 使用list对象的sort()方法。使用方法和Array.sort()类似。
    在这里插入图片描述
  2. 1
  3. 3

2.2.3 List接口的三个实现类

  1. ArrayList:① 底层使用的是数组结构改查很快,但增删较慢;② 线程不同步,即不是线程安全,但效率高
  2. LinkedList:① 底层使用的是链表结构,改查较慢,增删较快线程不同步
  3. Vector:① 查改、增删都慢。②但是线程安全,故效率低
2.2.3.1 ArrayList类
  1. ArrayList具有一个成员字段:transient Object[] elementData;。ArrayList的底层就是用数组elementData存放数据的。 transient 关键字详细介绍
  2. 扩容机制:ArrayList有两个构造器。
    ① 使用无参构造器:elementData的容量为0;第一次添加数据,会自动扩容到10;存满后自动再次扩容时,为原来的1.5倍。
    0,10,15,22(向下取整),...
    ② 使用指定大小的构造器:elementData的容量为指定大小;再次扩容时为原来的1.5倍。
    在这里插入图片描述
  3. 特点:① 改查很快,但增删较慢;② 线程不同步,即不是线程安全,但效率高
  4. 特有方法:查API

扩容机制案例:

import java.util.ArrayList;

public class Person{
    public static void main(String[] args) {
        ArrayList list = new ArrayList();

        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }

        for (int i = 11; i <= 15; i++) {
            list.add(i);
        }
        list.add(100);
    }
}

使用以上代码进行调试,可以得到扩容的流程:
在这里插入图片描述

2.2.3.2 Vector类
  1. Vector存放也是使用elementData数组存放数据。但是该数组是protected Object[] elementData;,故可以被序列化。
  2. 扩容机制
    在这里插入图片描述
  3. 特点:① 查询、增删都慢。②但是线程安全,可以看到其每个方法都使用synchronized修饰。
  4. 特有方法:查API
2.2.3.3 LinkedList类
  1. LinkedList使用的双向链表存放数据。
    1. LinkedList有两个属性:
      transient Node<E> first; 指向首结点
      transient Node<E> last; 指向尾结点
    2. Node类是LinkedList的一个静态内部类。
      在这里插入图片描述
  2. 特点:① 改查较慢,增删较快线程不同步
  3. 特有方法:查API

2.3 Set接口

  1. 实现Set接口的特点:
    元素无序:指添加顺序与取出顺序可能不一致。但是要注意每次取出的顺序是一致的。
    元素不重复(故只能有一个null):故使用add()方法添加元素时可能添加失败。(返回值为false就是添加失败)

    注意:Set的本质是使用Map的key。将value设为固定值。

  2. 常用方法:
    1. Collection接口的常用方法
    2. Set接口的常用方法:
      在这里插入图片描述
  3. 遍历Set:遍历Collection的2种方法。即 迭代器foreach

2.3.1 HashSet类

2.3.1.1 HashSet介绍
  1. 类定义:实现了Set接口。
    在这里插入图片描述
  2. 构造器:【对应HashMap的4个构造器】
    1. HashSet():table初始容量为0,默认第一次扩容为16;默认加载因子为0.75
    2. HashSet(int initialCapacity):table初始容量为0,指定第一次扩容大小;默认加载因子为0.75
    3. HashSet(int initialCapacity, float loadFactor):table初始容量为0,指定第一次扩容大小;指定加载因子
    4. HashSet(Map<? extends K, ? extends V> m):将t的键值对复制到新HashTable中。
  3. 方法:
    1. Collection接口和Set接口的方法。
    2. HashSet特有的方法
      在这里插入图片描述
  4. 细节:
    1. HashSet底层是HashMap。将结点中的value置为固定值。
      在这里插入图片描述
    2. 元素重复的定义:满足以下两个条件则元素重复
      1. 两者的hash值相同
      2. 使用 == 比较为True;或者使用.equal比较为True
        在这里插入图片描述
        在这里插入图片描述

      注意:在Object类中讲过,无论是否重写hashCode和equals方法,都保持一个特性equals相等,则hashCode必相等

2.3.1.2 HashSet底层结构

HashSet底层使用的是HashMap。使用其key,将其value设置为固定值。
在这里插入图片描述

可跳转到3.1.1.2小节。

2.3.1.4 LinkedHashSet类

LinkedHashSet底层是LinkedHashMap。可跳转到3.1.1.4小节。

2.3.2 TreeSet类

  1. 类定义:继承了AbstractSet类。
    在这里插入图片描述
  2. 构造器:【对应TreeMap的4个构造器】
    1. TreeSet() :TreeSet中的元素自然排序。
    2. TreeSet(Collection<? extends E> c) :将集合c中元素复制到TreeSet集合中。并自然排序。
    3. TreeSet(Comparator<? super E> comparator) :TreeSet中的元素根据指定的比较器进行排序。【通常使用匿名内部类做为形参】
      在这里插入图片描述
    4. TreeSet(SortedSet<E> s) :将集合s中元素复制到TreeSet集合中。并TreeSet元素顺序与集合s元素顺序一致。
  3. 方法:
    1. Collection接口、Set接口方法
    2. TreeSet的方法
      在这里插入图片描述
  4. 细节:
    1. TreeSet底层使用的TreeMap。
    2. 元素是否重复:
      ① 排序规则不同,调用的比较方法不同。
        (1)若采用自然排序:则以添加元素对象实现的Compareable接口的compareTo方法去比较。【若要添加元素没有实现Compareable接口,则会报异常,如下图所示:】
      在这里插入图片描述
        (2)若采用比较器:则以比较器中的compare方法去比较。
      ② 根据返回结果判断是否重复: 将要添加元素与TreeSet中元素逐个传入 Compareable接口的compareTo方法比较器compare方法 中。若结果为0,则重复,不添加元素;若所有比较结果都不为0,则不重复并添加改元素。
      在这里插入图片描述

3. 双列集合

3.1 Map接口

  1. 实现Map接口的特点:
    1. 以键值对的方式存放数据。
      ① 键值对存放在Map.Entry或其子类实例中。若键值对存放在Entry的子类中,可利用多态向上转型为Entry。
      ② Map.Entry是Map的一个内部类
      ③ Entry类中有两个重要方法:getKey()getValue()
    2. key不允许重复,value可以重复。【故至多有一个key = null,可以有多个value = null】
    3. key和value都是Object类型,但常用String类型的实参作为key。
  2. Map接口的常用方法:即双列集合都有的方法
    在这里插入图片描述

    注意:CRUD中,Collection的增改为add、set;Map的增改为put、replace。

  3. 遍历集合:4种方法
    1. 获取键值对遍历:使用entrySet方法获取Set<Entry<K, v>>,而遍历set有两种方式。
      在这里插入图片描述
    2. 获取所有键,再根据键获取值:使用keySet方法获取Set<k>,而遍历set有两种方式。
  4. 细节:
    1. 使用put方法,如果要添加的元素在Map中已经存在,则会覆盖原来的value,等同于修改。 看源码可知replace方法底层调用了put(key, value)方法,可知put方法包含了替换。事实上确实是这样,从HashMap的源码中就可以看到。
      在这里插入图片描述
      在这里插入图片描述
    2. entrySet包含Map中所有的键值对元素,但是其不是复制元素,而是指向元素。故通过entrySet可以修改Map中元素的value。
      在这里插入图片描述

3.1.1 HashMap类

3.1.1.1 HashMap介绍
  1. 类定义:继承了AbstractMap类,实现了Map接口。
    在这里插入图片描述
  2. 构造器:
    1. HashMap():table初始容量为0,默认第一次扩容为16;默认加载因子为0.75
    2. HashMap(int initialCapacity):table初始容量为0,指定第一次扩容大小;默认加载因子为0.75
    3. HashMap(int initialCapacity, float loadFactor):table初始容量为0,指定第一次扩容大小;指定加载因子
    4. HashMap(Map<? extends K, ? extends V> m):将t的键值对复制到新HashTable中。
  3. 重要字段:
    1. table:为一个 Node[ ] 数组类型【而HashMap中是Node[ ]】。Node是一个内部类,键值对是放在Node类实例中。Node实现了Entry接口,故Node类型可向上转型为Entry。
      在这里插入图片描述
    2. size:包含Node键值对的数量。
    3. threshold:HashMap的阈值
    4. loadFactor:加载因子,默认为0.75。
    5. modCount:用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,某线程进行迭代操作时,若有其他线程对其进行结构性的修改(是结构上面的修改,而不是简单的修改集合元素的内容),这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你已经出错了。【https://blog.csdn.net/a745233700/article/details/83387688】
    6. transient Set<Map.Entry<K,V>> entrySet:存放键值对的Node向上转型为Entry后组成的集合。
  4. 方法:
    1. Map接口的方法
    2. HashMap的方法
      在这里插入图片描述
  5. 细节:增删改查时间复杂度都为 O(1)
3.1.1.2 HashMap底层结构

HashMap底层数据结构:
① 初始时是拉链法(数组 + 链表) 的散列结构,
② 当添加元素达到某条件后链表会转为红黑树,即转为(数组 + 红黑树)

当删除元素达到某条件后红黑树会转回为链表,该操作叫做剪枝
在这里插入图片描述

  1. (数组 + 链表) 阶段添加元素:
    1. 根据key的hashCode经过某算法得到该key的hash值—>hash值经过某算法得到该元素要存放在table的索引值index。

      注意两点:

      1. hash值并不是hashCode值,而是由hashCode经过某算法得到。
      2. 只要hashCode值相同,则对应的hash值、index值都相同。
    2. 找到table,看table[index]处是否已经存放元素。
      ① 若没有,则直接添加;
      ② 若有,则看table[index]处遍历此处的链表,逐个与之比较是否key重复。如果遍历完链表都不重复,则添加到链表尾部【TreeMap是采用头插法】若重复,则将value值替换
    3. 若添加元素成功,则还需要判断是否将链表转为红黑树。

    注意:

    1. 本节中的链表指:不包含table[index]处的结点,将其后续的结点叫做链表
    2. 元素重复的定义:比较两元素的key,满足以下两个条件则元素重复
      1. . hash值相同
      2. . 使用 == 比较为True;或者使用.equal比较为True
        在这里插入图片描述
    3. . 对于正常的Integer,如果值为-128 ~ 127, 是按值传递。但是如果HashMap的key为Integer ,那么尽管值在-128 ~ 127,还是当做引用数据类型。
  2. 数组table的扩容机制:
    1. 初始为0;
    2. 第一次扩容为16;
    3. ① 后续每次添加元素后,再判断是否扩容
      (1)是否达到阈值:hashMap中元素个数(包括table 和 链表中的元素) > 阈值(0.75 * n) 时会再次扩容【如果等到快满时再扩容会导致在高并发时效率很低】
      (2)链表长度是否过长:table[index]处的链表长度 >= 8时,会再扩容。
      ② table扩容为原来的两倍。
  3. (数组 + 链表) 转为 (数组 + 红黑树) 的时机:添加元素后
    ① 当旧table容量 >= 64(旧table指:添加元素后,若此次要扩容,是使用扩容前的table),
    ② 且table[index]处链表长度 >= 8时,将该链表转为红黑树结构。
  4. (数组 + 红黑树) 转为 (数组 + 链表) 的时机:删除元素后
    ① 如果红黑树根 root 为空
    ② 或者 root 的左子树或右子树为空,
    ③ 或者 root.left.left(根的左子树的左子树)为空
    都会发生红黑树退化成链表。
  5. (数组 + 红黑树) 阶段删除元素:
    1. 根据要删除键值对的key的hashCode经过某算法得到该key的hash值—>hash值经过某算法得到索引值index。
    2. 找到table,看table[index]处是否已经存放元素。
      ① 若无元素,则该键值对不存在,删除失败。
      ② 若有元素,则看table[index]处遍历此处的树,逐个与之比较是否相同。相同则删除成功。
      【remove(key)只看key是否相同,remove(key, value)还要看value是否相同】
      【是否相同的条件和添加元素时是否重复的条件一样】
      在这里插入图片描述
    3. 若删除该键值对成功,再判断是否要将红黑树转为链表结构。
  6. 关于删除元素时的坑:若更改了s中的内容删除,再删除以key=s的键值对,会导致前后hash值不同,从而导致index值不同,一定会删除失败。
    在这里插入图片描述
    在这里插入图片描述

很好的两个数组table的扩容机制案例,用IDEA调试去感受。

(1)达到阈值后扩容

public static void main(String[] args) {
    HashMap hashMap = new HashMap();

    /** 达到阈值后扩容
     * 1. 初始时table = null
     * 2. 数字1被添加进去后,table的容量变为16
     * 3. 数字13被添加进去后,table具有13个元素 > 阈值12 ,故table扩容为32
     * 4. 数字25被添加进去后,table具有25个元素 > 阈值24 ,故table扩容为64
     * 5. 数字49被添加进去后,table具有49个元素 > 阈值48 ,故table扩容为128
     */
    for (int i = 1; i <= 49; i++) {
        hashMap.put(i, i);
    }
}

(2)达到链表长度后扩容

import java.util.*;

public class Person{
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();

        /** 达到链表长度后扩容
         * 1. 初始时table = null
         * 2. 数字1被添加进去后,table的容量变为16
         * 3. 数字9被添加进去后,链表长度(包含table[index]处的结点)为9 > 8 ,故table扩容为32
         * 4. 数字10被添加进去后,链表长度(包含table[index]处的结点)为10 > 8 ,故table扩容为64
         * 5. 数字11被添加进去后,链表长度(包含table[index]处的结点) >= 8且table容量 >= 64。故将该链表转为红黑树。
         */
        for (int i = 1; i <= 11; i++) {
            hashMap.put(new ABC(i), i);
        }
    }
}
class ABC {
    public int age;
    ABC(int age) {
        this.age = age;
    }

    // 使得其有相同的hash值,故有相同的index
    @Override
    public int hashCode() {
        return 1;
    }
}
3.1.1.3 源码分析
3.1.1.3.1 put方法

在这里插入图片描述

3.1.1.3.2 remove方法

在这里插入图片描述

3.1.1.4 LinkedHashMap类
  1. LinkedHashMap继承了HashMap类
  2. 底层结构: 数组 + 链表 + 双向链表就是在HashMap上多加了一个双向链表。
    1. 数组 + 链表结构的维护和HashMap一样。故也会树化等。
    2. 添加元素的先后顺序依次采用双向链表相连
      在这里插入图片描述

3.1.2 HashTable类

3.1.2.1 HashTable介绍
  1. 类定义:继承了Dectionary类,实现了Map接口。
    在这里插入图片描述
  2. 构造器:
    1. Hashtable():table初始容量为11,加载因子为0.75.
    2. Hashtable(int initialCapacity):指定table容量。
    3. Hashtable(int initialCapacity, float loadFactor):指定table容量和加载因子。
    4. Hashtable(Map<? extends K, ? extends V> t):将t的键值对复制到新HashTable中,其table初始容量见下图,加载因子为默认的0.75
      在这里插入图片描述
  3. 重要字段:
    1. table:为一个 Entry[ ] 数组类型【而HashMap中是Node[ ]】,Entry代表了“拉链”的节点,每一个Entry存放了一个键值对。
    2. count:包含Entry键值对的数量。
    3. threshold:HashTable的阈值
    4. loadFactor:加载因子,默认为0.75。
    5. modCount:用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,某线程进行迭代操作时,若有其他线程对其进行结构性的修改(是结构上面的修改,而不是简单的修改集合元素的内容),这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你已经出错了。【https://blog.csdn.net/a745233700/article/details/83387688】
  4. 方法:
    1. Map接口的方法
    2. HashTable的方法
      在这里插入图片描述
  5. 细节:
    1. key和value都不能为空。
    2. HashTable是线程安全的。
3.1.2.2 HashTable底层结构
  1. HashTable底层结构与工作原理:拉链法(数组 + 链表) 的散列结构。
  2. 添加元素:
    1. hashCode经过某算法得到该元素的hash值—>hash值经过某算法得到其要存放在table的索引值index。
    2. 找到table,看table[index]处遍历此处的链表,逐个比较是否与之重复。如果遍历完链表都不重复,则添加table[index]处;若重复,则中断不添加该元素。

      注意:HashMap是将元素添加到链表末尾。

  3. 数组table的扩容机制:
    1. 初始为11;
    2. ① 后续每次添加元素后,达到阈值后扩容:hashTable中元素个数(包括table 和 链表中的元素) > 阈值(0.75 * n) 时会再次扩容。
      ② 扩容为2 * n + 1大小。
3.1.2.3 put()方法 底层源码分析

在这里插入图片描述
在这里插入图片描述

3.1.2.4 Properies类

继承HashTable类,常用于存储加载配置文件后的数据。

3.1.3 TreeMap

  1. 类定义:继承了AbstractMap类。
    在这里插入图片描述

  2. 构造器:

    1. TreeMap() :TreeSet中的元素自然排序。
    2. TreeMap(Map<? extends K, ? extends V> m) :将集合c中元素复制到TreeSet集合中。并自然排序。
    3. TreeMap(Comparator<? super K> comparator) :TreeSet中的元素根据指定的比较器进行排序。【通常使用匿名内部类做为形参】
      在这里插入图片描述
    4. TreeMap(SortedMap<K, ? extends V> m) :将集合s中元素复制到TreeSet集合中。并TreeSet元素顺序与集合s元素顺序一致。
  3. 方法:

    1. Map接口的方法
    2. TreeMap的方法
  4. 底层结构:
    在这里插入图片描述

  5. 细节:

    1. 增删改查时间复杂度都为 O(logn)
    2. TreeMap是有序的,则使用TreeMap一定要有排序规则,故要求:
      ① 要么 在添加元素时,要添加元素实现了Compareable接口 (比如String、Integer都是从小到大排序)
      ② 要么 在创建TreeMap对象时,传入比较器
    3. 不能插入 null 对象,否则会抛出 NullPointerException 异常
    4. 元素是否重复:
      ① 排序规则不同,调用的比较方法不同。
        (1)若采用自然排序:则以添加元素的key对象实现的Compareable接口的compareTo方法去比较。【若要添加键值对的key没有实现Compareable接口,则会报异常】
        (2)若采用比较器:则以比较器中的compare方法去比较。
      ② 根据返回结果判断是否重复: 将要添加元素的key与TreeMap中元素的key逐个传入 Compareable接口的compareTo方法比较器compare方法 中。若结果为0,则重复,不添加元素,但是将值替换;若所有比较结果都不为0,则不重复并添加改元素(其中返回值小于0,则说明该元素排在前面;若返回值大于0,则说明该元素排在后面)。
      在这里插入图片描述
      在这里插入图片描述

4. 以上类分别在何种情况下使用?

在这里插入图片描述

5. Collections工具类

全都是静态方法:
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ElegantCodingWH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值