day06_Collection、List、Set

41 阅读6分钟

day06_Collection、List、Set

1. 集合进阶

image-20230814215250452.png

  • List系列集合:添加的元素是有序、可重复、有索引
  • Set系列集合:添加的元素是无序、不重复、无索引

2. Collection

2.1 方法汇总

  • Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
方法名说明
public boolean add(E e)把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(Object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数
public Object[] toArray()把集合中的元素,存储到数组中

2.2 遍历方式

  • 迭代器

    • 迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator
    • 通过Iterator<E> iterator()方法获取集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素
    • 通过boolean hasNext()方法询问当前位置是否有元素存在,存在返回true,不存在返回false
    • 通过E next()方法获取当前位置的元素,并同时将迭代器对象指向下一个元素处
    • 代码演示
    Iterator<String> it = lists.iterator();
    while (it.hasNext()) {
        String ele = it.next();
        System.out.println(ele);
    }
    
  • 增强for循环

    • 代码演示
    for (元素的数据类型 变量名:数组或集合) {
        ...
    }
    
    // 举例
    Collection<String> c = new ArrayList<>();
    ...
    for(String s : c) {
        System.out.println(s);
    }
    
    
    • 增强for可以用来遍历集合或者数组,本质就是迭代器遍历集合的简化写法
    • 修改增强for中的变量值不会影响到集合中的元素
  • Lambda表达式

    • 使用default void forEach(Consumer<? super T> action)结合lambda完成遍历
    • 代码演示
    Collection<String> lists = new ArrayList<>();
    ...
    /*
    lists.forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });
    */
    lists.forEach(s -> {
        System.out.println(s);
    });
    

3. List集合

  • List系列集合特点: 有序,可重复,有索引

  • ArrayList:有序,可重复,有索引

  • LinkedList:有序,可重复,有索引

3.1 常用方法

方法名称说明
void add(int index,E element)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素

3.2 遍历方式

  • for循环

    List<String> col = new ArrayList<String>();
    col.add("张三丰");
    col.add("张无忌");
    col.add("张翠山");
    col.add("张学友");
    for (int i = 0; i < col.size(); i++) {
        System.out.println(col.get(i));
    }
    
  • 迭代器

    Iterator<String> it = col.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
    
  • 增强for循环

    for (String s : col) {
        System.out.println(s);
    }
    
  • Lambda表达式

    System.out.println("----------lambda----------");
    col.forEach((s) -> System.out.println(s));
    
    System.out.println("----------方法引用----------");
    col.forEach(System.out::println);
    

3.3 ArrayList集合的底层原理

实现:

  • 基于数组实现

特点:

  • 查询快,增删慢
  • 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
  • 删除效率低:可能需要把后面很多的数据进行前移
  • 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容

底层原理:

  • 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
  • 添加第一个元素时,底层会创建一个新的长度为10的数组
  • 存满时,会扩容1.5倍
  • 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

适用场景:

  • 适合根据索引查询数据的场景
  • 不适合数据量大的同时,又要频繁的进行增删操作的场景

3.4 LinkedList集合的底层原理

实现:

  • 基于双链表实现

底层原理:

image-20230814223745226.png

链表:

  • 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址
  • 查询慢,无论查询哪个数据都要从头开始找
  • 链表增删相对快

双链表:

  • 查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的

特有方法:

方法名称说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

适用场景:

  • 队列

4. set集合

  • Set系列集合特点: 无序;添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引
  • HashSet : 无序、不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:可排序、不重复、无索引

案例演示:

public static void main(String[] args) {
    Set<String> hashSet = new HashSet<>();
    hashSet.add("q");
    hashSet.add("w");
    hashSet.add("e");
    hashSet.add("r");
    hashSet.add("t");
    hashSet.add("y");
    hashSet.add("u");
    hashSet.add("i");
    hashSet.add("o");
    hashSet.add("p");
    System.out.println(hashSet); // [p, q, r, t, e, u, w, y, i, o]
    Iterator<String> it = hashSet.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }

    Set<String> linkedHashSet = new LinkedHashSet<>();
    linkedHashSet.add("a");
    linkedHashSet.add("s");
    linkedHashSet.add("d");
    linkedHashSet.add("f");
    linkedHashSet.add("g");
    linkedHashSet.add("h");
    linkedHashSet.add("j");
    linkedHashSet.add("k");
    linkedHashSet.add("l");
    System.out.println(linkedHashSet); // [a, s, d, f, g, h, j, k, l]
    for (String s : linkedHashSet) {
        System.out.println(s);
    }

    Set<String> treeSet = new TreeSet<>();
    treeSet.add("z");
    treeSet.add("x");
    treeSet.add("c");
    treeSet.add("v");
    treeSet.add("b");
    treeSet.add("n");
    treeSet.add("m");
    System.out.println(treeSet); // [b, c, m, n, v, x, z]
    treeSet.forEach(System.out::println);
}

4.1 HashSet集合的底层原理

哈希值:

  • 一个int类型的数值,Java中每个对象都有一个哈希值
  • Java中的所有对象,都可以调用public int hashCode();方法,返回该对象自己的哈希值
  • 同一个对象多次获取的哈希值是相同的
  • 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)

树结构

  • 二叉树

    如图所示就是一棵典型的二叉树,即任意节点的度不大于2

image-20230814230522291.png

*   度:每一个节点的子节点数量
*   树高:树的总层数
*   根节点:最顶层的节点
*   左子节点、右子节点、左子树、右子树
  • 二叉查找树

image-20230814230741163.png

规则:

*   小的存左边
*   大的存右边
*   一样的不存
  • 平衡二叉树

    在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能

image-20230814230900753.png

  • 红黑树

    红黑树就是可以自平衡的二叉树,是一种增删改查数据性能相对都较好的结构

image-20230814230935708.png

实现:

  • 基于哈希表实现
  • 哈希表是一种增删改查数据,性能都较好的数据结构
  • JDK8之前,哈希表 = 数组+链表
  • JDK8开始,哈希表 = 数组+链表+红黑树

JDK8前HashSet的底层原理:

JDK8前HashSet的底层原理.gif

  • 根据hashCode值计算存放的位置
  • 如果这个位置没有元素,直接存储
  • 如果这个位置有元素,调用equals比较
  • equals()为false,存储
  • equals()为true,不存储(认为是相同的元素)

JDK8开始HashSet的底层原理:

  1. 开始

image-20230814224959961.png

  1. 当链表元素过多

image-20230814225246348.png

  1. 转换成红黑树

image-20230814225312437.png

  • 创建一个默认长度16的数组,默认加载因子为0.75
  • 当数组快占满了(16 * 0.75 = 12),则会扩容
  • 扩容后的数组是原数据长度的2倍 (16 * 2 = 32)
  • 当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树

自定义去重:

  • 重写对象的hashCode()和equals()方法

4.2 LinkedHashSet集合的底层原理

实现:

  • 基于哈希表(数组、链表、红黑树)实现的
  • 每个元素都额外的多了一个双链表的机制记录它前后元素的位置

底层实现:

LinkedHashSet底层原理.gif

4.3 TreeSet集合

实现:

  • 基于红黑树实现的排序

注意:

  • 对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序
  • 对于字符串类型:默认按照首字符的编号升序排序
  • 对于自定义类型如Student对象,TreeSet默认是无法直接排序的

去重原理:

  • 排序时当遇到相等情况,则认为两个相同,后添加的不会被记录到TreeSet集合中

自定义排序规则:

  • 让自定义的类实现Comparable接口,重写里面的compareTo方法来指定比较规则

  • 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)

    public TreeSet(Comparator<? super E> comparator)

注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序

  • 案例演示

    // 方法一:类实现Comparable接口并重写compareTo方法
    @Override
    public int compareTo(Student o) {
        return Double.compare(this.age, o.age);
    }
    
    // 方法二:使用构造器,传入比较规则
    Set<Student> treeSet = new TreeSet<>((o1, o2) -> Double.compare(o1.age, o2.age));