Java集合框架之List ArrayList LinkedList使用详解刨析
作者:谢谢你,泰罗! 时间:2022-04-24 13:43:43
1. List
1.1 List 的常见方法
方法 | 描述 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置的元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置的元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List<E> subList(int fromIndex, int toIndex) | 截取部分 list |
1.2 代码示例
注意: 下面的示例都是一份代码分开拿出来的,上下其实是有逻辑关系的
示例一: 用 List 构造一个元素为整形的顺序表
List<Integer> list = new ArrayList<>();
示例二: 尾插 e
list.add(1);
list.add(2);
System.out.println(list);
// 结果为:[1, 2]
示例三: 将 e 插入到 index 位置
list.add(0,10);
System.out.println(list);
// 结果为:[10, 1, 2]
示例四: 尾插 c 中的元素
List<Integer> list1=new LinkedList<>();
list1.add(99);
list1.add(100);
list.addAll(list1);
System.out.println(list);
// 结果为:[10, 1, 2, 99, 100]
只要是继承于 Collection 的集合类的元素都可以 * 入进去,但要注意传过来的具体的类型要么是和 list 的具体类型是一样的,要么是 list 具体类型的子类
示例五: 删除 index 位置的元素
System.out.println(list.remove(0));
System.out.println(list);
// 结果为:10 和 [1, 2, 99, 100]
示例六: 删除遇到的第一个 o
System.out.println(list.remove((Integer) 100));
System.out.println(list);
// 结果为:true 和 [1, 2, 99]
示例七: 获取下标 index 位置的元素
System.out.println(list.get(0));
// 结果为:1
示例八: 将下标 index 位置元素设置为 element
System.out.println(list.set(2,3));
System.out.println(list);
// 结果为:99 和 [1, 2, 3]
示例九: 判断 o 是否在线性表中
System.out.println(list.contains(1));
// 结果为:true
示例十: 返回第一个 o 所在下标
System.out.println(list.indexOf(1));
// 结果为:0
示例十一: 返回最后一个 o 的下标
list.add(1);
System.out.println(list.lastIndexOf(1));
// 结果为:3
示例十二: 截取部分 list
List<Integer> list2=list.subList(1,3);
System.out.println(list2);
// 结果为:[2, 3]
注意,当我们将 list2 通过 set 更改元素,其实对 list 也会有影响
list2.set(0,5);
System.out.println(list2);
System.out.println(list);
// 结果为:[5, 3] 和 [1, 5, 3, 1]
通过 subList 方法进行的截取,得到的集合的数值指向的地址和原集合中数值的地址是一样的
2. ArrayList
2.1 介绍
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。其继承了 AbstractList,并实现了 List 接口。LinkedList 不仅实现了 List 接口,还实现了 Queue 和 Deque 接口,可以作为队列去使用。
ArrayList 类位于 java.util
包中,使用前需要引入它。
2.2 ArrayList 的构造方法
方法 | 描述 |
---|---|
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
示例一:
ArrayList<Integer> list1 = new ArrayList<>();
示例二:
ArrayList<Integer> list2 = new ArrayList<>(10);
// 该构造方法就是在构建时就将底层数组大小设置为了10
示例三:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
ArrayList<Integer> list3 = new ArrayList<>(list);
Collection<? extends E> c
只要是具体类型都和 list3 是一样的集合都可以放入转化成 ArrayList
2.3 ArrayList 底层数组的大小
当我们使用 add 方法给 ArrayList 的对象进行尾插时,突然想到了一个问题:既然 ArrayList 的底层是一个数组,那么这个数组有多大呢?
为了解决这个问题,我进行了如下探索
跳转到 ArrayList 的定义,我们看到了 elementData
和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
跳转到 elementData
的定义,我们可以了解 ArrayList 底层是数组的原因
跳转到 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的定义,初步分析得到这个数组其实是空的
为什么这个数组是空的但存储元素的时候没有报异常呢?我们再去了解下 add 是怎样存储的
通过转到 ArrayList 的 add 方法的定义
通过定义,不难发现,数组容量和 ensureCapacityInternal
这个东西有关,那我们就看看它的定义
我们看里面的 calculateCapacity
,他有两个参数,此时数组为空,那么 minCapacity
就为 1。我们再转到 calculateCapacity
看看它的定义
此时我们就好像可以与之前串起来了,当数组为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
时,就返回 DeFauLt_CAPACITY
和 minCapacity
(此时为1) 的最大值。DeFauLt_CAPACITY
其实是默认容量的意思,我们可以转到它的定义看看有多大
DeFauLt_CAPACITY
的值是10,故 calculateCapacity
函数此时的返回值为10,最后我们再确定一下 ensureExplicitCapacity
是干啥的
此时 minCapacity 的值是10,而数组为空时数组长度为0,所以进入 if 语句,执行 grow 方法,我们继续转到 grow 的定义
此时我们就可以了解,当我们创建一个 ArrayList 时,其底层数组大小其实是0。当我们第一次 add 的时候,经过 grow ,数组的大小就被扩容为了10。并且这大小为10的容量放满以后,就会按1.5倍的大小继续扩容。至于这个数组最大能存放多少,大家可以再转到 MAX_ARRAY_SIZE
的定义去查看。
3. LinkedList
3.1 介绍
LinkedList 类是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
Java 的 LinkedList 底层是一个双向链表,位于 java.util
包中,使用前需要引入它
3.2 LinkedList 的构造方法
方法 | 描述 |
---|---|
LinkedList() | 无参构造 |
LinkedList(Collection<? extends E> c) | 利用其他 Collection 构建 LinkedList |
示例一:
LinkedList<Integer> list1 = new LinkedList<>();
示例二:
List<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
LinkedList<Integer> list2 = new LinkedList<>(list);
Collection<? extends E> c
只要是具体类型都和 list2 是一样的集合都可以放入转化成 LinkedList
4. 练习题
习题一
题目描述:
霍格沃茨学院有若干学生(学生对象放在一个 List 中),每个学生有一个姓名(String)、班级(String)和考试成绩(double)。某次考试结束后,每个学生都获得了一个考试成绩。遍历 list 集合,并把每个学生对象的属性都打印出来
本题代码:
class Student{
private String name;
private String classes;
private double score;
// 重写构造方法
public Student(String name, String classes, double score) {
this.name = name;
this.classes = classes;
this.score = score;
}
// 构造 get 和 set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClasses() {
return classes;
}
public void setClasses(String classes) {
this.classes = classes;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
// 重写 toString 方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", classes='" + classes + '\'' +
", score=" + score +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("哈利波特","大二班",95.5));
students.add(new Student("赫敏格兰杰","小三班",93));
students.add(new Student("罗恩韦斯莱","小二班",91));
for(Student s: students){
System.out.println(s);
}
}
}
// 结果为:
// Student{name='哈利波特', classes='大二班', score=95.5}
// Student{name='赫敏格兰杰', classes='小三班', score=93.0}
// Student{name='罗恩韦斯莱', classes='小二班', score=91.0}
习题二
题目描述:
有一个 List 当中存放的是整形的数据,要求使用 Collections.sort
对 List 进行排序
该题代码:
public class TestDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(3);
list.add(7);
list.add(1);
list.add(6);
list.add(2);
Collections.sort(list);
System.out.println(list);
}
}
// 结果为:[1, 2, 3, 6, 7]
补充:
Collections 是一个工具类,sort 是其中的静态方法,它是用来对 List 类型进行排序的
注意:
如果具体的类是类似于习题一那样的 Student 类,该类中含有多个属性,那就不能直接使用这个方法。要对 comparator
或者 comparable
接口进行重写,确定比较的是哪个属性才行
习题三
题目描述:
输出删除了第一个字符串当中出现的第二个字符串中的字符的字符串,例如
String str1 = "welcome to harrypotter";
String str2 = "come";
// 结果为:wl t harrypttr
希望本题可以使用集合来解决
该题代码:
public static void removeS(String str1, String str2){
if(str1==null || str2==null){
return;
}
List<Character> list = new ArrayList<>();
int lenStr1=str1.length();
for(int i=0; i<lenStr1; i++){
char c = str1.charAt(i);
if(!str2.contains(c+"")){
list.add(c);
}
}
for(char ch: list){
System.out.print(ch);
}
}
5. 扑克牌小游戏
我们可以通过上述所学,运用 List 的知识,去写一个关于扑克牌的逻辑代码(如:获取一副牌、洗牌、发牌等等)
class Card{
private String suit; // 花色
private int rank; // 牌面值
public Card(String suit, int rank){
this.suit=suit;
this.rank=rank;
}
@Override
public String toString() {
return "[ "+suit+" "+rank+" ] ";
}
}
public class TestDemo {
public static String[] suits = {"♣", "♦", "♥", "♠"};
// 获取一副牌
public static List<Card> getNewCards(){
// 存放 52 张牌
List<Card> card = new ArrayList<>();
for(int i=0; i<4; i++){
for(int j=1; j<=13; j++) {
card.add(new Card(suits[i], j));
}
}
return card;
}
public static void swap(List<Card> card, int i, int j){
Card tmp = card.get(i);
card.set(i, card.get(j));
card.set(j, tmp);
}
// 洗牌
public static void shuffle(List<Card> card){
int size = card.size();
for(int i=size-1; i>0; i--){
Random random = new Random();
int randNum = random.nextInt(i);
swap(card, i, randNum);
}
}
public static void main(String[] args) {
// 得到一副新的牌
List<Card> cardList = getNewCards();
System.out.println("已获取新的扑克牌");
System.out.println("洗牌:");
shuffle(cardList);
System.out.println(cardList);
System.out.println("抓牌:(3个人,每人轮流抓牌总共抓5张)");
List<Card> hand1 = new ArrayList<>();
List<Card> hand2 = new ArrayList<>();
List<Card> hand3 = new ArrayList<>();
List<List<Card>> hands = new ArrayList<>();
hands.add(hand1);
hands.add(hand2);
hands.add(hand3);
for(int i=0; i<5; i++){
for(int j=0; j<3; j++){
Card card = cardList.remove(0);
hands.get(j).add(card);
}
}
System.out.println("第一个人的牌:"+hand1);
System.out.println("第二个人的牌:"+hand2);
System.out.println("第三个人的牌:"+hand3);
}
}
/**
结果为:
已获取新的扑克牌
洗牌:
[[ ♥ 9 ] , [ ♦ 6 ] , [ ♣ 8 ] , [ ♦ 2 ] , [ ♣ 6 ] , [ ♦ 4 ] , [ ♣ 11 ] , [ ♣ 9 ] , [ ♠ 8 ] , [ ♣ 5 ] , [ ♦ 8 ] , [ ♦ 10 ] , [ ♦ 1 ] , [ ♦ 12 ] , [ ♥ 10 ] , [ ♥ 7 ] , [ ♠ 12 ] , [ ♥ 12 ] , [ ♦ 7 ] , [ ♣ 13 ] , [ ♠ 6 ] , [ ♠ 5 ] , [ ♥ 3 ] , [ ♦ 5 ] , [ ♦ 11 ] , [ ♣ 12 ] , [ ♠ 7 ] , [ ♦ 3 ] , [ ♥ 5 ] , [ ♦ 13 ] , [ ♣ 1 ] , [ ♥ 8 ] , [ ♠ 10 ] , [ ♠ 4 ] , [ ♣ 4 ] , [ ♣ 7 ] , [ ♥ 1 ] , [ ♠ 1 ] , [ ♣ 3 ] , [ ♥ 11 ] , [ ♥ 13 ] , [ ♦ 9 ] , [ ♠ 13 ] , [ ♣ 10 ] , [ ♥ 6 ] , [ ♠ 11 ] , [ ♠ 3 ] , [ ♣ 2 ] , [ ♠ 2 ] , [ ♥ 2 ] , [ ♥ 4 ] , [ ♠ 9 ] ]
抓牌:(3个人,每人轮流抓牌总共抓5张)
第一个人的牌:[[ ♥ 9 ] , [ ♦ 2 ] , [ ♣ 11 ] , [ ♣ 5 ] , [ ♦ 1 ] ]
第二个人的牌:[[ ♦ 6 ] , [ ♣ 6 ] , [ ♣ 9 ] , [ ♦ 8 ] , [ ♦ 12 ] ]
第三个人的牌:[[ ♣ 8 ] , [ ♦ 4 ] , [ ♠ 8 ] , [ ♦ 10 ] , [ ♥ 10 ] ]
*/
上述代码中有一处代码是这样写的 List<List<Card>>
,其实不难理解,这个类型其实就是 List 中存放的每个元素都是一个 List 类型的,并且每一个 List 元素中的元素都是 Card 类型,类似于二维数组。
来源:https://blog.csdn.net/weixin_51367845/article/details/120838319