Collection接口

集合

image-20230108172811088

集合中可以分为单列集合和多列集合

  • 单列集合(collection)

    每次只能往集合里面添加一个元素

  • 多列集合(map)

    添加数据的时候,一次添加一对数据

Collection(接口)

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()返回集合中元素的个数/集合的长度

注意:在contains方法中,底层是依赖equals方法进行判断是否存在,所以,如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javaBean类中一定要从写equals方法。

image-20230128133730363

Collection的遍历方式

迭代器遍历

迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式,是不依赖索引就可以遍历的,如果在遍历过程中需要删除元素,请使用迭代器,如果仅仅想遍历,使用增强for或者Lambda表达式

Collection集合获取迭代器
方法名称说明
Iteratoriterator()返回迭代器对象,默认指向当前集合的0索引
Iterator中常用的方法
方法名称说明
boolean hasNext()判断当前位置是否有元素,有元素返回true,没有元素返回false
E next()获取当前位置的元素,并将迭代器对象移向下一个位置
1
2
3
4
5
6
7
8
9
10
/**
1.创建指针
2.判断是否有元素
3.获取元素、移动指针
*/
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
细节注意点
  1. 报错NoSuchElementException(没有这个元素异常)
  2. 迭代器遍历完毕,指针不会复位。如果需要再次遍历,可以再次获取迭代器对象(Iterator)
  3. 循环中只能使用一次next方法
  4. 迭代器遍历时,不能用集合的方法进行增加或者删除
迭代器源码分析

image-20230108203900386

增强for遍历

增强for的底层就是迭代器,为了简化迭代器的代码书写的

所有的单列集合和数组才能用增强for进行遍历

1
2
3
4
5
6
/**
for(元素的数据类型 变量名:数组或者集合){}
*/
for(String s:list){
System.out.println(s);
}
增强for的细节
  • 修改增强for中的变量,不会改变集合中原本的数据

Lambda表达式遍历

方法名称说明
default void forEach(Consumer<? super T> action)结合Lambda遍历集合
forEach底层原理

​ 其实也会自己遍历集合,依次得到每一个元素,把得到的每一个元素,传递给下面的accept方法

1
2
3
4
5
6
7
8
9
list.forEach(new Consumer<String>(){
@Override
public void accept(String s){
System.out.println(s);
}
})

//Lambda表达式
list.forEach(s->System.out.println(s));

List(接口)

List系列集合:添加的元素是有序(指的是存取顺序)、可重复(集合中元素是可以重复的)、有索引(可以通过索引获取集合的每一个元素)

  • Collection的方法List都继承了
  • List集合因为有索引,所以多了很多索引的操作方法
方法名称说明
void add(int index,E element)在此集合中指定位置插入指定元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素
  • 细节

    • add方法,在指定位置插入元素后,原来索引上的元素会依次往后移

    • remove方法,在Collection中remove方法传递的是一个Object对象,如果List的参数是Integer类型,在remove(1);的时候,删除的是1索引上的元素还是1这个元素?

      删除的是1索引上的这个元素,因为在调用方法的时候,如果方法出现了重载现象,优先调用,实参和形参类型一致的那个方法。

    • remove方法在删除指定索引处的元素后,原来索引后面的元素会依次往前移

list遍历

  1. 迭代器(在遍历的过程中需要删除元素,请使用迭代器)
  2. 列表迭代器(在遍历的过程中需要添加元素,请使用列表迭代器)
  3. 增强for(仅仅想遍历)
  4. Lambda表达式(仅仅想遍历)
  5. 普通for循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.zhuixun.demo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Consumer;

/**
* @Author: zhuixun
* @Date: 2023/1/8 16:32
* @Version 1.0
*/
public class ListDemo {
/**
* list系列集合的五种遍历方式
* 1.迭代器
* 2.列表迭代器
* 3.增强for
* 4.Lambda表达式
* 5.普通for循环
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");

//迭代器遍历
System.out.println("============迭代器遍历============");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}

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

//Lambda表达式
System.out.println("=============Lambda表达式===========");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});

//简化Lambda
System.out.println("=============简化Lambda===========");
list.forEach(s -> {
System.out.println(s);
});

//普通for
System.out.println("=============普通for===========");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

//列表迭代器
System.out.println("=========列表迭代器==========");
//获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
//额外添加了一个方法,在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while (it.hasNext()){
String next = it.next();
if ("bbb".equals(next)){
//在bbb之后添加qqq
it.add("qqq");
}
System.out.println(next);
}
System.out.println(list.toString());
}
}

ArrayList

​ ArrayList可以直接使用List和Collection中的方法

底层原理
  1. 利用空参创建的集合,在底层创建一个默认长度为0的数组,数组的名字叫elementData,还会存在一个变量size,默认值为0,size有两层含义,分别表示元素个数和下次存入的位置。

  2. 添加第一个元素时,底层会创建一个新的长度为10的数组,数组的默认值为null,在长度为10的数组中添加元素后,size++。

  3. 存满时,会自动扩容原来数组长度的1.5倍

  4. 如果一次添加多个元素,数组扩容1.5倍还放不下(15个),则新创建数组的长度以实际为准(如果添加100个数据,则长度为110=100+10)

image-20230108175200638

添加多个元素,或者是数组中元素满了之后在添加一个元素

image-20230108175925196

LinkedList

  • 底层数据结构式双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的
  • LinkedList本身多了很多直接操作首尾元素的特有API
特有方法说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

image-20230108200904868

Vector(jdk1.2就被遗弃)

Set(接口)

Set接口中的方法基本上与Collection的API一致。

Set系列集合:添加的元素是无序(存取无序)、不重复(集合中不能存储重复的元素)、无索引(不能通过索引去获取元素,所以不能使用普通for去循环遍历)

HashSet(无序、不重复、无索引)

  • HashSet集合底层采取哈希表存储数据
  • 哈希表是一种对于增删改查数据性能都比较好的结构
哈希表的组成
  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树
哈希值(对象的整数表现形式)

添加数据的时候,添加数据的数组下标 int index = (数组长度-1)&哈希值;

为了方便计算,则需要把对象转换为一个整数才能计算。

  • 根据hashCode方法算出来的int类型的整数
  • 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
  • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值。
对象的哈希值特点
  • 如果没有重写hashCode方法,就是通过地址值去计算,所以不同对象计算出的哈希值是不同的
  • 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
  • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.zhuixun.demo;

/**
* @Author: zhuixun
* @Date: 2023/1/28 10:19
* @Version 1.0
*/
public class Test14 {
public static void main(String[] args) {
/**
* 哈希值:
* 对象的整数表现形式
* 1.如果没有重写hashCode方法,就是通过地址值去计算,所以不同对象计算出的哈希值是不同的
* 2.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
* 3.在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
*/

//1.创建对象
Student s1 = new Student(1, "zhangsan", 23);
Student s2 = new Student(1, "zhangsan", 23);

//如果没有重写hashCode方法,就是通过地址值去计算,所以不同对象计算出的哈希值是不同的
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());

//在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
System.out.println("abc".hashCode());//96354
System.out.println("acD".hashCode());//96354
}
}

HashSet底层原理

HashSethm = new HashSet<>();

  1. 创建一个默认长度16,默认加载因子为0.75(用于数组的扩容,当数组存入个数到16*0.75就开始扩容)的数组,数组名table,数组中数据都为null

  2. 添加数据时,根据元素的哈希值跟数组的长度计算出应存入的位置(计算公式:int index = (数组长度-1)& 哈希值)

  3. 计算出存入的位置后,判断当前位置是否为null,如果是null直接存入

  4. 如果位置不为null,表示有元素,则调用equals方法比较属性值

    1. 如果equals属性值一样,不存入
    2. 如果equals属性值不一样,存入数组,形成链表
  5. jdk8以前:新元素存入数组,老元素挂在新元素的下面

  6. jdk8以后:新元素直接挂在老元素下面,

    1. 这样组成的链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
  7. 如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

HashSet的三个问题
  • HashSet为什么存和取的顺序不一样

    hashSet在遍历取的时候,是从数组的0索引开始遍历的,索引下有链表的话就依次遍历,而存数据是根据数据的hashCode值去存的,不是从0索引开始,链表中的数据,也不是按照顺序去存的。

  • HashSet为什么没有索引

    数据结构不够纯粹,是由数组、链表、红黑树组成,不太好定义那个是0索引、那个是1索引,所以就取消了索引。

    有人会说,可以使用数组下有索引,但是数组下会存在链表或者红黑树,这么多数据的话公用一个索引不合适。

  • HashSet是利用什么机制保证数据去重的。

    利用HashCode方法(得到哈希值)和equals方法(比较对象内部属性的值)

LinkedHashSet(有序、不重复、无索引)

这里的有序是指保证存储和取出的元素顺序一致。

原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序。

  • 如果以后要数据去重,我们使用哪个?

    默认使用HashSet,如果要求去重且存取有序,才使用LikedHashSet

TreeSet(可排序、不重复、无索引)

  • 可排序:按照元素的默认规则(由小到大)排序
    1. 对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
    2. 对于字符、字符串类型,按照字符在ASCII码表中的数字升序进行排序,字符串类型,就是从第一个字母开始比较,相同则继续往下比较,如果比较的字母不同,按照ASCII码表中的数字升序进行排序,不在比较后续的字母,所以字符串排序与字符串长度无关。
  • TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都比较好
TreeSet的两种比较方式

使用原则:默认使用第一种,如果第一种不能满足当前需求,就是用第二种

  • 方式一

    默认排序/自然排序:JavaBean类实现Comparable接口指定比较规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    package com.zhuixun.pojo;

    import java.util.Objects;

    /**
    * @Author: zhuixun
    * @Date: 2023/1/28 12:55
    * @Version 1.0
    */
    public class Student implements Comparable<Student>{
    private String name;
    private Integer age;

    public Student() {
    }

    public Student(String name, Integer age) {
    this.name = name;
    this.age = age;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "Student{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }


    @Override
    public int compareTo(Student o) {
    //指定排序规则 this:当前要添加的元素 o:已经存在红黑树中的元素
    //返回值:负数:表示当前添加的元素是小的,要存左边
    //正数:表示当前添加的元素是大的,要存右边
    // 0 : 表示当前添加的元素已存在,则舍弃不存
    int i = this.getAge()- o.getAge();
    return i;
    }
    }



    package com.zhuixun.demo;
    import com.zhuixun.pojo.Student;

    import java.util.TreeSet;

    /**
    * @Author: zhuixun
    * @Date: 2023/1/28 12:50
    * @Version 1.0
    */
    public class TreeSetDemo {

    /**
    * 需求:创建TreeSet集合,并添加三个学生对象
    * 学生对象属性:
    * 姓名、年龄
    * 要求按照学生的年龄进行排序
    * 同年龄按照姓名字母排序(暂不考虑中文)
    * 同姓名、同年龄认为是同一个人
    *
    * 方式1: 默认排序/自然排序:JavaBean类实现Comparable接口指定比较规则
    *
    *
    */
    public static void main(String[] args) {
    //1.创建三个学生对象
    Student s1 = new Student("zhangsan",23);
    Student s2 = new Student("lisi",24);
    Student s3 = new Student("wangwu",25);

    //2.创建集合对象
    TreeSet<Student> ts = new TreeSet<>();

    //3.添加元素
    ts.add(s3);
    ts.add(s2);
    ts.add(s1);

    //4.打印集合
    System.out.println(ts);

    }
    }

    存入红黑树过程

    image-20230128132136308

  • 方式二

    比较器排序:创建TreeSet对象的时候,传递比较器Comparrator指定规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public void treeSetDemo2(){
    /**
    * 需求:请自行选择比较器排序和自然排序两种方式
    * 要求:存入四个字符串:"c","ab","df","qwer"
    * 按照长度排序,如果一样长则按照首字母排序
    * 采用第二种排序,比较器排序
    */
    //1.创建集合 o1:表示当前添加的元素,o2表示已经在红黑树存在的元素
    //返回值:负数:表示当前添加的元素是小的,要存左边
    //正数:表示当前添加的元素是大的,要存右边
    // 0 : 表示当前添加的元素已存在,则舍弃不存
    TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
    //按照长度排序
    int i = o1.length()-o2.length();
    //如果长度一样按照首字母排序
    i=i==0?o1.compareTo(o2):i;
    return i;
    }
    });
    ts.add("c");
    ts.add("ab");
    ts.add("df");
    ts.add("qwer");
    //打印集合
    System.out.println(ts);
    }

image-20230128133614645


Collection接口
http://example.com/2023/01/29/Java基础/集合/Collection接口/
作者
zhuixun
发布于
2023年1月29日
许可协议