实现List接口的ArrayList和LinkedList
package study;
import java.util.*;
public class day01_list {
public static void main(String[] args) {
// <Integer> 这个尖括号表示的是 Java 的泛型(Generics)
// 泛型是 Java 5 引入的一项特性,它允许你在 类、接口和方法 中使用类型参数,从而使代码更具通用性和类型安全性
// List 是一个接口,代表一个有序的集合,其中可以包含重复的元素
// <Integer> 是泛型参数,指定这个列表只能存储Integer类型的对象
/*
ArrayList是List接口的一个具体实现类,使用动态数组来存储元素
不能使用new List<>(),因为List是一个接口,不能直接实例化,接口只是定义了一组方法,没有具体实现
必须实例化一个 List 接口的实现类,比如 ArrayList、LinkedList
钻石操作符 <> 是 Java 7 引入的一个简化语法,用于推断泛型参数类型。
在右边使用 new ArrayList<>() 时,编译器会自动推断出这个 ArrayList 的类型参数是 Integer,因为左边明确指定了 List<Integer>
*/
/*
List 是一个接口,而 ArrayList 是这个接口的一个具体实现类。
在编写代码时,使用接口类型来声明变量,而用具体的实现类来实例化对象,这是面向对象编程中的一种最佳实践,
称为“接口和实现分离”。
多态性:
通过使用接口类型,你可以利用多态性来处理对象。
例如,你可以编写一个方法,它接收 List 类型的参数,这样这个方法就可以处理任何 List 的实现类(如 ArrayList、LinkedList、Vector 等)
public void processList(List<Integer> list) {
for (Integer number : list) {
System.out.println(number);
}
}
虽然 List 是一个接口,但在 Java 中,接口类型的变量可以引用任何实现这个接口的对象。这是因为接口定义了一组方法,而实现类提供了这些方法的具体实现。
*/
// 多态性
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
processList(arrayList);
processList(linkedList);
/*
使用接口类型声明变量的限制
当你使用接口类型(例如 List)声明一个变量时,你只能调用接口中定义的方法
然而,如果你需要调用实现类特有的方法,例如 ArrayList 的 ensureCapacity 方法,你就不能直接通过 List 类型的引用来调用。以下代码是非法的:
*/
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
System.out.println(list.size()); // 这行代码是合法的,因为 add 和 size 是 List 接口中的方法
// list.ensureCapacity(100); // 编译错误,因为 List 接口中没有 ensureCapacity 方法
}
public static void processList(List<Integer> list) {
for (Integer number : list) {
System.out.println(number);
}
}
}
interface Animal {
void eat();
}
interface jiekou {
void t();
}
// 一个类只能继承一个父类(单继承),但可以实现多个接口(多实现)
// 一个接口可以继承多个其他接口(多继承)
interface Dog extends Animal, jiekou {}
class ArrayList_use {
/*
Iterable<E> (接口)
↑
Collection<E> (接口) - 继承自 Iterable<E>
↑
List<E> (接口) - 继承自 Collection<E>
↑
AbstractCollection<E> (抽象类) - 部分实现 Collection<E>
↑
AbstractList<E> (抽象类) - 继承自 AbstractCollection 部分实现 List<E>
↑
ArrayList<E> (类) - 继承自 AbstractList<E> 并实现 List<E> 和 Collection<E>
ArrayList 类除了实现 List 接口的方法外,还提供了一些独有的方法。这些方法在 List<> list 中无法直接使用:
clone(): ...
trimToSize(): 调整 ArrayList 的容量大小,使其等于当前列表的大小,减少内存使用。
ensureCapacity(int minCapacity): 增加 ArrayList 的容量,确保它至少可以容纳指定数量的元素。
*/
// 数组可以包含基本数据类型和引用类型,ArrayList只能包含引用类型
// 数组的大小在定义后不可以调整大小。ArrayList是基于动态数组实现的,可以通过内部扩容自动调整容量
public static void main(String[] args) {
// 三种构造方法
List<Integer> list0 = new ArrayList<>(); // 构造一个初始容量为 10 的空列表
list0.add(1);
System.out.println(list0.size());
List<Integer> list1 = new ArrayList<>(0); // 直接指定初始大小
list1.add(1);
System.out.println(list1.size());
List<Integer> existingList = Arrays.asList(1, 2, 3); // 返回一个ArrayList对象
List<Integer> list2 = new ArrayList<>(existingList); // 使用现成的list来构建
System.out.println(list2.size());
Set<Integer> existingSet = new HashSet<>(Arrays.asList(1, 2, 3, 4));
List<Integer> list3 = new ArrayList<>(existingSet); // 使用现成的set来构建
System.out.println(list3.size());
// 尾插数据时速度是o(1)
// 非尾插数据,会涉及元素的移动,在添加元素时会涉及到扩容的问题。
// 超出初始长度时,会创建新的数组,将原数组数据赋值到新数组中。扩容:原来数组的大小+原来数组的一半(1.5倍)
// ArrayList 是线程不安全的:
// 线程不安全表示当多个线程同时访问一个对象时,如果没有适当的同步机制,可能会导致数据不一致或其他错误。
// 在 ArrayList 中,多个线程同时修改列表可能会导致未定义的行为,包括但不限于数据丢失、重复或异常
// 1. add
List<String> list4 = new ArrayList<>(Arrays.asList("A", "B", "C"));
list4.add("D");
System.out.println(list4);
list4.add(1, "X");
System.out.println(list4);
// 2. addAll
List<String> temp = Arrays.asList("A", "B", "C");
list4.addAll(temp);
System.out.println(list4);
list4.addAll(1, temp);
System.out.println(list4);
// 3. forEach
list4.forEach(System.out::println);
// 4. clear
list4.clear();
System.out.println(list4);
// 5. clone
// clone() 方法是 ArrayList 类特有的方法,而不是 List 接口的方法。
// 因此,如果你需要克隆一个 ArrayList,你需要确保你的变量类型是 ArrayList 而不是 List。
ArrayList<String> existingListTempClone = new ArrayList<>(Arrays.asList("A", "C"));
ArrayList<String> clonedArrayList = (ArrayList<String>) existingListTempClone.clone();
List<String> cloneList = (List<String>) existingListTempClone.clone();
// 修改克隆list的对象看看会不会对原来造成影响
// 6. set
cloneList.set(0, "xiugai");
// 7. get
System.out.println(cloneList.get(0));
System.out.println(existingListTempClone.get(0));
// 8.contains
System.out.println(cloneList.contains("xiugai"));
// 9. containsAll
List<String> containsList0 = new ArrayList<>(Arrays.asList("xiugai", "C"));
List<String> containsList1 = new ArrayList<>(Arrays.asList("xiugai", "X"));
System.out.println(cloneList.containsAll(containsList0));
System.out.println(cloneList.containsAll(containsList1));
// 10. indexOf
List<Integer> indexOfList = new ArrayList<>(Arrays.asList(1, 2, 3));
System.out.println(indexOfList.indexOf(2));
// 11. removeAll
List<Integer> removeList = new ArrayList<>(Arrays.asList(2, 3, 2));
indexOfList.removeAll(removeList);
// 12. remove
List<Integer> removeList1 = new ArrayList<>(Arrays.asList(1, 2, 3));
removeList1.remove(0);
removeList1.remove(Integer.valueOf(2)); // Integer.valueOf(2) 将整数 2 包装为 Integer 对象
System.out.println(removeList1);
// 13. size
// 14. isEmpty
// 15. subList
// 16. toArray
// 17. toString
// 18. lastIndexOf
List<Double> doubleList = new ArrayList<>(Arrays.asList(1.0, 5.0, 2.0, 2.0));
System.out.println(doubleList.size());
System.out.println(doubleList.isEmpty());
System.out.println(doubleList.subList(1, 2));
System.out.println(doubleList.toArray()[2]);
System.out.println(doubleList.toString());
System.out.println(doubleList.lastIndexOf(Double.valueOf(2.0)));
}
}
class LinkedList_use {
/*
Iterable<E> (接口)
↑
Collection<E> (接口) - 继承自 Iterable<E>
↑
List<E> (接口) - 继承自 Collection<E>
↑
AbstractCollection<E> (抽象类) - 部分实现 Collection<E>
↑
AbstractList<E> (抽象类) - 继承自 AbstractCollection 部分实现 List<E>
↑
AbstractSequentialList<E> (抽象类) - 继承自 AbstractList<E>
↑
LinkedList<E> (类) - 继承自 AbstractSequentialList<E> 并实现 List<E>, Deque<E>, Queue<E>, Collection<E>
Iterable<E> (接口)
↑
Collection<E> (接口) - 继承自 Iterable<E>
↑
Queue<E> (接口) - 继承自 Collection<E>
↑
Deque<E> (接口) - 继承自 Queue<E> Double Ended Queue
LinkedList 类除了实现 List 接口的方法外,
还实现了 Deque 接口,因此提供了一些双端队列(Deque)和队列(Queue)的特有方法。这些方法在 List<> list 中无法直接使用:
void addFirst(E e)
void addLast(E e)
boolean offerFirst(E e) // 头部插入元素,返回是否成功,成功为 true,失败为 false
boolean offerLast(E e)
E removeFirst()
E removeLast()
E pollFirst() // 检索并删除此列表的第一个元素,如果此列表为空则返回null
E pollLast()
E getFirst()
E getLast()
E peekFirst() // 返回头部元素,不删除
E peekLast()
LinkedList也是多线程环境下不安全
*/
LinkedList<Integer> list = new LinkedList<>();
}
/**
* ArrayList和LinkedList
*
* 数据结构
* ArrayList底层是动态数组,在空间中是一段连续的内存
* LinkedList底层是链表,内存不连续,对内存要求低。
*
* 空间利用:
* ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间
* LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
*
* 尾插数据:对ArrayList和LinkedList而言,在列表末尾增加一个元素时间复杂度都是 o(1)。
* 对ArrayList来说,尾插速度非常快,但是会涉及到一个扩容的问题,会进行大量的复制操作。
* 而对LinkedList而言,使用了链表结构,不需要维护大小,但是他每次添加需要新建entry对象,进行赋值操作。
* 总得来说如果不涉及到扩容的问题,ArrayList的尾插会更快一些。
*
* 非尾插:
* 在ArrayList的中间插入或删除一个元素会导致列表中剩余的元素都会被移动,因此效率较低,插入位置越靠前,需要复制调整的元素也越多,效率也就越慢。
* LinkedList的非尾插,首先要通过循环找到需要插入的位置。如果此位置处于前半段,则从前往后找;若其位置处于后半段,则从后往前找。所以在靠前和靠后的位置插入非常高效,但是在拥有大量元素的情况下,在链表的中间插入元素效率很低。(首尾复杂度为o(1),其余为o(n),整体来说还是o(n))
*
* 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会有更好的性能。
* 当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
*/
class TestArrayListAndLinkedList {
public static void main(String[] args) {
List<String> arraylist = new ArrayList<>();
int listSize = 100;
for(int i=0; i<listSize; i++){
arraylist.add("初始数据");
}
System.out.println("ArrayList插数据用时:" + insert(arraylist));
List<String> linkedList = new LinkedList<>();
for(int i=0; i<listSize; i++){
linkedList.add("初始数据");
}
System.out.println("LinkedList插数据用时:" + insert(linkedList));
}
// index=10插入:
// ArrayList数据用时:3
// LinkedList数据用时:0
// list.size()/2插入
// ArrayList插数据用时:4
// LinkedList插数据用时:1
// list.size()-10
// ArrayList插数据用时:4
// LinkedList插数据用时:2
// 尾插:
// ArrayList插数据用时:3
//LinkedList插数据用时:0
//插入数据,分别在头部、中间、尾部(四种插入位置:10、list.size()/2、list.size()-10、尾插)
public static Long insert(List<String> list){
long startTime = System.currentTimeMillis();
int insertNum = 500;
for (int i=0; i<insertNum; i++){
list.add(10,"数据"+i); // 中间靠前部分插入
// list.add(list.size()/2,"数据"+i); //中间插入
// list.add(list.size()/2+10,"数据"+i); //中间靠后插入
// list.add("数据"+i); // 尾插
}
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
}