实现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; } }
上一篇:基于上一篇博客,用阻塞队列实现异步下单-引言