• Java中级编程大师班<第一篇:初识数据结构与算法-链表(4)>


    在上一期,我们深入探讨了数组数据结构以及其在编程中的广泛应用,包括了 HashMap、HashTable、ConcurrentHashMap、HashSet、LinkedHashMap 等数据结构。这期,我们将开始介绍链表数据结构以及与之相关的扩展内容。链表是一种基础而强大的数据结构,掌握链表的原理和应用将为您的编程技能增添新的维度。接下来的内容将帮助您更深入地理解链表,以及如何在实际项目中灵活应用它们。

    链表的基本特性

    链表(Linked List)是一种常见的线性数据结构,具有以下基本特性:

    1. 链式结构:链表是由节点(Node)组成的,每个节点包含两部分:数据域和指针域。数据域用于存储数据,指针域用于指向下一个节点,从而将节点串联在一起,形成链式结构。

    2. 动态大小:链表的大小可以动态增加或减少,不像数组一样需要预先分配固定大小的空间。这使得链表更加灵活,能够有效地处理不定数量的元素。

    3. 插入和删除效率高:由于链表的动态性,插入和删除元素的效率通常较高。只需改变节点之间的指针,而不需要像数组一样涉及元素的搬迁。

    4. 不连续的内存分配:链表中的节点可以分布在内存的不同位置,它们通过指针连接在一起,这与数组内存中连续的存储方式不同。

    5. 无需预分配内存空间:链表不需要预先分配连续的内存空间,因此不会出现数组中的“溢出”问题,但会引入一些额外的空间开销用于存储指针。

    6. 有单向链表和双向链表:链表可以是单向的,其中每个节点只有一个指向下一个节点的指针,也可以是双向的,其中每个节点同时具有指向下一个节点和上一个节点的指针,使得双向链表在某些操作中更加高效。

    7. 不支持随机访问:链表不支持像数组那样的随机访问,要访问链表中的某个元素,需要从头节点开始按顺序遍历。

    8. 常见操作:链表的常见操作包括在头部插入节点、在尾部插入节点、在指定位置插入节点、删除节点、查找节点等。

    链表是计算机科学中重要且常用的数据结构,特别适用于需要频繁的插入和删除操作的场景,例如实现栈、队列、高级语言中的垃圾回收器等。在学习链表时,通常会深入研究单向链表、双向链表、循环链表等不同类型的链表以及它们的应用。

    示例代码

    下面是一个示例代码,演示如何创建一个简单的单向链表以及基本的链表操作,包括插入、删除和遍历节点:

    class Node {
        int data;
        Node next;
    
        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    }
    
    class LinkedList {
        Node head;
    
        public LinkedList() {
            head = null;
        }
    
        // 插入节点到链表尾部
        public void append(int data) {
            Node newNode = new Node(data);
            if (head == null) {
                head = newNode;
                return;
            }
            Node current = head;
            while (current.next != null) {
                current = current.next;
            }
            current.next = newNode;
        }
    
        // 删除节点
        public void delete(int data) {
            if (head == null) {
                return;
            }
            if (head.data == data) {
                head = head.next;
                return;
            }
            Node current = head;
            while (current.next != null && current.next.data != data) {
                current = current.next;
            }
            if (current.next != null) {
                current.next = current.next.next;
            }
        }
    
        // 遍历链表并打印节点值
        public void printList() {
            Node current = head;
            while (current != null) {
                System.out.print(current.data + " ");
                current = current.next;
            }
            System.out.println();
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            LinkedList list = new LinkedList();
            list.append(1);
            list.append(2);
            list.append(3);
    
            System.out.println("原始链表:");
            list.printList();
    
            list.delete(2);
    
            System.out.println("删除节点后的链表:");
            list.printList();
        }
    }
    
    • 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

    上述代码创建了一个简单的单向链表,包括插入、删除和遍历操作。在这个示例中,我们首先创建了一个链表对象 list,然后向链表中追加了三个节点。接着,我们删除了节点值为 2 的节点,并打印了链表的最终状态。

    链表的使用场景

    链表是一种灵活的数据结构,适用于多种实际开发场景。以下是一些链表的常见使用场景:

    1. 实现栈和队列:链表可用于实现栈(先进后出)和队列(先进先出)这两种重要的数据结构。在栈中,节点从链表的头部添加和删除;而在队列中,节点从链表的尾部添加,从头部删除。

    2. 缓存实现:链表可用于实现简单的缓存结构,例如 LRU(最近最少使用)缓存策略。当缓存达到容量限制时,最久未使用的数据可以从链表头部删除。

    3. 编辑器的撤销功能:许多文本编辑器和图形设计工具使用链表来实现撤销(undo)和重做(redo)功能。每个编辑操作被保存为一个节点,用户可以通过链表的前进和后退来查看和还原编辑历史。

    4. 实现高级数据结构:链表是许多高级数据结构的基础,如跳表、图、树(例如二叉搜索树的底层实现 AVL 树)等。链表的灵活性使其成为构建这些复杂数据结构的有力工具。

    5. 表达无限数据流:在处理无限数据流时,链表可以很好地表示数据的有限窗口。只需保留链表中的有限数量的节点,以适应可用内存。

    6. 实现符号表:链表可用于实现符号表,其中每个节点包含键值对。这在编译器、解析器和数据库中有广泛的应用。

    7. 任务调度器:链表可用于管理和调度任务队列,例如操作系统中的任务调度器或异步编程中的任务队列。

    8. 多项式运算:链表可以用来表示多项式,其中每个节点包含一个项的系数和指数。这在数学和科学计算中很常见。

    9. 游戏开发:链表可以用于游戏中的对象管理,例如管理敌人、子弹、动画等游戏元素。

    需要注意的是,虽然链表在某些场景中非常有用,但在其他场景中,数组或其他数据结构可能更适合。选择合适的数据结构取决于具体的问题和性能需求。链表的主要优势在于插入和删除操作的效率,但它在随机访问和空间占用方面可能不如数组。因此,在选择数据结构时,需要综合考虑问题的特性和需求。

    主要知识点讲解

    链表是计算机科学中重要的数据结构,有许多与之相关的主要知识点。以下是关于链表的主要知识点的讲解:

    1. 节点(Node)
      • 节点(Node)通常用于构建链表等数据结构。
      • 链表的基本构建块是节点。每个节点包含两部分:数据域和指针域。
      • 数据域用于存储数据,可以是整数、字符串、对象等。
      • 指针域用于指向下一个节点(单向链表)或同时指向下一个节点和前一个节点(双向链表)。

    以下是一个示例代码,展示了一个简单的节点类的定义和用法:

    public class Node<T> {
        private T data;
        private Node<T> next;
    
        public Node(T data) {
            this.data = data;
            this.next = null;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public Node<T> getNext() {
            return next;
        }
    
        public void setNext(Node<T> next) {
            this.next = next;
        }
    }
    
    • 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

    在上面的示例中,我们定义了一个泛型的 Node 类,包含以下成员变量和方法:

    • data:用于存储节点的数据。
    • next:指向下一个节点的引用。

    使用示例:

    public class Main {
        public static void main(String[] args) {
            // 创建节点
            Node<Integer> node1 = new Node<>(10);
            Node<Integer> node2 = new Node<>(20);
            Node<Integer> node3 = new Node<>(30);
    
            // 构建链表:node1 -> node2 -> node3
            node1.setNext(node2);
            node2.setNext(node3);
    
            // 遍历链表并打印数据
            Node<Integer> current = node1;
            while (current != null) {
                System.out.println(current.getData());
                current = current.getNext();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在上述示例中,我们创建了三个整数类型的节点,并使用 setNext 方法将它们链接在一起,形成一个简单的链表。然后,我们遍历链表并打印每个节点的数据。这是一个基本的节点示例,用于构建链表等数据结构。

    1. 单向链表
      • 单向链表中,每个节点只有一个指针域,指向下一个节点。
      • 链表的头节点用于标识链表的起始位置。

    以下是一个简单的单向链表(Singly Linked List)的示例代码:

    public class SinglyLinkedList<T> {
        private Node<T> head;
        
        public SinglyLinkedList() {
            this.head = null;
        }
        
        // 添加元素到链表的末尾
        public void append(T data) {
            Node<T> newNode = new Node<>(data);
            if (head == null) {
                head = newNode;
            } else {
                Node<T> current = head;
                while (current.getNext() != null) {
                    current = current.getNext();
                }
                current.setNext(newNode);
            }
        }
        
        // 删除指定元素
        public void delete(T data) {
            if (head == null) {
                return;
            }
            if (head.getData().equals(data)) {
                head = head.getNext();
                return;
            }
            Node<T> current = head;
            while (current.getNext() != null && !current.getNext().getData().equals(data)) {
                current = current.getNext();
            }
            if (current.getNext() != null) {
                current.setNext(current.getNext().getNext());
            }
        }
        
        // 打印链表元素
        public void print() {
            Node<T> current = head;
            while (current != null) {
                System.out.print(current.getData() + " -> ");
                current = current.getNext();
            }
            System.out.println("null");
        }
    }
    
    • 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

    在上述代码中,我们定义了一个 SinglyLinkedList 类,表示单向链表。该链表包括以下方法:

    • append(data):将元素添加到链表的末尾。
    • delete(data):删除指定元素。
    • print():打印链表元素。

    节点(Node)的定义与之前的示例相同。

    使用示例:

    public class Main {
        public static void main(String[] args) {
            SinglyLinkedList<Integer> list = new SinglyLinkedList<>();
            
            list.append(1);
            list.append(2);
            list.append(3);
            
            list.print(); // 输出: 1 -> 2 -> 3 -> null
            
            list.delete(2);
            
            list.print(); // 输出: 1 -> 3 -> null
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在示例中,我们创建了一个整数类型的单向链表,并演示了如何向链表中添加元素、删除元素以及打印链表的操作。这是一个简单的单向链表示例,用于基本的链表操作。

    1. 双向链表
      • 双向链表中,每个节点同时包含两个指针域,一个指向下一个节点,另一个指向前一个节点。
      • 双向链表可以更容易地实现某些操作,如反向遍历和在节点之间的插入和删除。

    以下是一个简单的双向链表(Doubly Linked List)的示例代码:

    public class DoublyLinkedList<T> {
        private Node<T> head;
        private Node<T> tail;
        
        public DoublyLinkedList() {
            this.head = null;
            this.tail = null;
        }
        
        // 在链表末尾添加元素
        public void append(T data) {
            Node<T> newNode = new Node<>(data);
            if (head == null) {
                head = newNode;
                tail = newNode;
            } else {
                newNode.setPrev(tail);
                tail.setNext(newNode);
                tail = newNode;
            }
        }
        
        // 在链表开头添加元素
        public void prepend(T data) {
            Node<T> newNode = new Node<>(data);
            if (head == null) {
                head = newNode;
                tail = newNode;
            } else {
                newNode.setNext(head);
                head.setPrev(newNode);
                head = newNode;
            }
        }
        
        // 删除指定元素
        public void delete(T data) {
            Node<T> current = head;
            while (current != null) {
                if (current.getData().equals(data)) {
                    if (current.getPrev() != null) {
                        current.getPrev().setNext(current.getNext());
                    } else {
                        head = current.getNext();
                    }
                    if (current.getNext() != null) {
                        current.getNext().setPrev(current.getPrev());
                    } else {
                        tail = current.getPrev();
                    }
                    return;
                }
                current = current.getNext();
            }
        }
        
        // 打印链表元素
        public void print() {
            Node<T> current = head;
            while (current != null) {
                System.out.print(current.getData() + " <-> ");
                current = current.getNext();
            }
            System.out.println("null");
        }
    }
    
    • 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

    在上述代码中,我们定义了一个 DoublyLinkedList 类,表示双向链表。该链表包括以下方法:

    • append(data):在链表末尾添加元素。
    • prepend(data):在链表开头添加元素。
    • delete(data):删除指定元素。
    • print():打印链表元素。

    节点(Node)的定义包括 prevnext 引用,用于指向前一个节点和后一个节点。

    使用示例:

    public class Main {
        public static void main(String[] args) {
            DoublyLinkedList<Integer> list = new DoublyLinkedList<>();
            
            list.append(1);
            list.append(2);
            list.append(3);
            
            list.print(); // 输出: 1 <-> 2 <-> 3 <-> null
            
            list.prepend(0);
            
            list.print(); // 输出: 0 <-> 1 <-> 2 <-> 3 <-> null
            
            list.delete(2);
            
            list.print(); // 输出: 0 <-> 1 <-> 3 <-> null
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在示例中,我们创建了一个整数类型的双向链表,并演示了如何向链表中添加元素、删除元素以及打印链表的操作。这是一个简单的双向链表示例,用于基本的链表操作。

    1. 循环链表
      • 循环链表是一种特殊的链表,其中最后一个节点的指针指向链表的头节点,形成一个循环结构。
      • 循环链表可以用于模拟循环队列或其他需要循环访问的场景。

    以下是一个简单的循环链表(Circular Linked List)的示例代码:

    public class CircularLinkedList<T> {
        private Node<T> head;
        private Node<T> tail;
        
        public CircularLinkedList() {
            this.head = null;
            this.tail = null;
        }
        
        // 在链表末尾添加元素
        public void append(T data) {
            Node<T> newNode = new Node<>(data);
            if (head == null) {
                head = newNode;
                tail = newNode;
                newNode.setNext(newNode); // 将尾节点的 next 指向自身,形成循环
            } else {
                newNode.setNext(head); // 新节点的 next 指向头节点
                tail.setNext(newNode); // 尾节点的 next 指向新节点
                tail = newNode; // 更新尾节点
            }
        }
        
        // 删除指定元素
        public void delete(T data) {
            if (head == null) {
                return;
            }
            if (head.getData().equals(data)) {
                if (head.getNext() == head) { // 如果链表中只有一个节点
                    head = null;
                    tail = null;
                } else {
                    tail.setNext(head.getNext()); // 更新尾节点的 next
                    head = head.getNext(); // 更新头节点
                }
                return;
            }
            Node<T> current = head;
            while (current.getNext() != head) {
                if (current.getNext().getData().equals(data)) {
                    current.setNext(current.getNext().getNext());
                    return;
                }
                current = current.getNext();
            }
        }
        
        // 打印链表元素
        public void print() {
            if (head == null) {
                System.out.println("null");
                return;
            }
            Node<T> current = head;
            do {
                System.out.print(current.getData() + " -> ");
                current = current.getNext();
            } while (current != head);
            System.out.println(" (head)");
        }
    }
    
    • 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

    在上述代码中,我们定义了一个 CircularLinkedList 类,表示循环链表。该链表包括以下方法:

    • append(data):在链表末尾添加元素,同时确保尾节点的 next 指向头节点,形成循环。
    • delete(data):删除指定元素,需要特别处理头节点的删除和链表中只有一个节点的情况。
    • print():打印链表元素,从头节点开始遍历,并在头节点处标记 “(head)”。

    节点(Node)的定义与之前的示例相同。

    使用示例:

    public class Main {
        public static void main(String[] args) {
            CircularLinkedList<Integer> list = new CircularLinkedList<>();
            
            list.append(1);
            list.append(2);
            list.append(3);
            
            list.print(); // 输出: 1 -> 2 -> 3 ->  (head)
            
            list.delete(2);
            
            list.print(); // 输出: 1 -> 3 ->  (head)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在示例中,我们创建了一个整数类型的循环链表,并演示了如何向链表中添加元素、删除元素以及打印链表的操作。这是一个简单的循环链表示例,用于基本的链表操作。

    1. 链表的插入和删除

      • 链表的插入操作可以在指定位置或链表头部插入新节点。
      • 链表的删除操作可以删除指定位置或值匹配的节点。
    2. 链表的遍历

      • 遍历链表是通过迭代依次访问链表中的每个节点,通常使用循环实现。
      • 遍历过程中可以对节点进行查找、修改或其他操作。
    3. 时间复杂度分析

      • 链表的插入和删除操作通常具有 O(1) 的时间复杂度,因为只需要修改节点的指针。
      • 链表的查找操作通常具有 O(n) 的时间复杂度,因为需要遍历整个链表。
    4. 链表的优点和缺点

      • 优点包括高效的插入和删除操作、动态大小和灵活性。
      • 缺点包括随机访问效率低、占用额外的内存空间用于存储指针。
    5. 应用场景

      • 链表广泛用于栈、队列、缓存、编辑器的撤销功能、任务调度器、符号表、多项式运算等应用中。
    6. 复杂链表结构

      • 除了基本的单向、双向和循环链表,还存在复杂的链表结构,如跳表(Skip List)和各种树结构的底层实现。

    了解这些主要知识点将帮助您深入理解链表的原理、应用和性能特征,从而更好地应用它们解决实际问题。链表是数据结构领域的基础,也是许多高级数据结构的构建模块。

    链表的应用扩展

    Java中有一些数据类型和类可以被看作是链表的扩展,它们提供了更多的功能和特性,例如双向遍历、随机访问、自动扩容等。以下是一些常见的链表扩展类型:

    1. ArrayList
      • ArrayList 是 Java 中的一个动态数组实现。它允许在数组末尾高效地添加和删除元素,并且支持随机访问,因此在某些场景下可以被视为链表的扩展。
      • 与链表不同,ArrayList 需要预先分配一定的内存空间,并会在需要时自动扩容。

    以下是 ArrayList 的核心代码讲解,涵盖了初始化、添加元素、删除元素、扩容等关键操作。

    首先,让我们看一下 ArrayList 的基本属性和构造方法:

    // ArrayList 类的定义
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        
        // 默认初始容量
        private static final int DEFAULT_CAPACITY = 10;
    
        // 存储元素的数组
        transient Object[] elementData;
    
        // ArrayList 的大小,即元素的个数
        private int size;
    
        // 构造方法
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
            }
        }
        
        // ...
    }
    
    • 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

    在上述代码中,ArrayList 使用一个 Object 类型的数组 elementData 来存储元素。默认情况下,elementData 初始化为空数组,容量为 DEFAULT_CAPACITY(默认为 10)。size 属性表示当前 ArrayList 中的元素个数。

    接下来,让我们看一下 ArrayList 的添加元素操作,包括 add 方法和扩容:

    // 添加元素到 ArrayList
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 确保容量足够
        elementData[size++] = e;
        return true;
    }
    
    // 确保容量足够,如果不够则进行扩容
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        // 如果需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    // 扩容操作
    private void grow(int minCapacity) {
        // 原容量
        int oldCapacity = elementData.length;
        // 新容量,扩容为原来的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新容量仍小于所需容量,新容量则为所需容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 复制元素到新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    • 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

    在上述代码中,add 方法用于将元素添加到 ArrayList 的末尾。如果添加元素后,元素个数超过了当前数组的容量,就会触发扩容操作。扩容操作会创建一个新的数组,并将元素从旧数组复制到新数组中,然后将新数组赋给 elementData

    最后,让我们看一下 ArrayList 的删除元素操作,包括 remove 方法:

    // 删除指定索引位置的元素
    public E remove(int index) {
        rangeCheck(index);
    
        modCount++;
        E oldValue = elementData(index);
    
        // 计算需要移动的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        }
        elementData[--size] = null; // 清空最后一个元素,协助垃圾回收
        return oldValue;
    }
    
    // 辅助方法,检查索引是否越界
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    // 辅助方法,获取指定索引位置的元素
    E elementData(int index) {
        return (E) elementData[index];
    }
    
    • 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

    remove 方法用于删除指定索引位置的元素。首先,它会检查索引是否越界,然后使用 System.arraycopy 将后面的元素向前移动,覆盖被删除的元素。最后,将末尾的元素置为 null,以协助垃圾回收。

    这些是 ArrayList 的核心操作,它们确保了 ArrayList 的高效添加

    、删除和扩容能力。ArrayList 是 Java 集合框架中常用的数据结构之一,适用于大多数情况下的动态数组需求。

    1. LinkedList
      • LinkedList 是 Java 中的双向链表实现。它支持在链表头部和尾部高效地添加和删除元素,同时也支持随机访问,但访问效率相对较低。
      • LinkedList 的双向特性允许双向遍历。

    以下是 LinkedList 的核心代码讲解,涵盖了初始化、添加元素、删除元素等关键操作。

    首先,让我们看一下 LinkedList 的基本属性和构造方法:

    // LinkedList 类的定义
    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    {
        // 链表的首节点
        transient Node<E> first;
    
        // 链表的尾节点
        transient Node<E> last;
    
        // 链表的大小,即元素的个数
        transient int size = 0;
    
        // 构造方法
        public LinkedList() {
        }
    
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在上述代码中,LinkedList 使用 Node 类来表示链表的节点。firstlast 分别表示链表的首节点和尾节点。size 属性表示当前链表中的元素个数。

    接下来,让我们看一下 LinkedList 的添加元素操作,包括 addFirstaddLastadd 方法:

    // 在链表头部添加元素
    public void addFirst(E e) {
        linkFirst(e);
    }
    
    // 在链表尾部添加元素
    public void addLast(E e) {
        linkLast(e);
    }
    
    // 在链表尾部添加元素,等效于 addLast
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    // 在链表的指定位置添加元素
    public void add(int index, E element) {
        checkPositionIndex(index);
    
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    
    • 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

    上述代码中,addFirst 方法用于在链表头部添加元素,addLast 方法用于在链表尾部添加元素,而 add 方法用于在链表尾部添加元素,并返回 trueadd 方法还支持在链表的指定位置添加元素。

    接下来,让我们看一下 LinkedList 的删除元素操作,包括 removeFirstremoveLastremove 方法:

    // 删除链表头部的元素
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    // 删除链表尾部的元素
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    
    // 删除链表中指定的元素
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    
    // ...
    
    • 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

    上述代码中,removeFirst 方法用于删除链表头部的元素,removeLast 方法用于删除链表尾部的元素。remove 方法用于删除链表中指定的元素。删除操作会调用 unlink 方法,将节点从链表中断开。

    最后,让我们看一下 LinkedList 的遍历操作,包括 get 方法和迭代器:

    // 获取链表中指定索引位置的元素
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    // 返回一个迭代器,支持从前往后遍历
    public Iterator<E> iterator() {
        return new ListItr(0);
    }
    
    // 返回一个迭代器,支持从后往前遍历
    public Iterator<E> descendingIterator() {
        return new DescendingIterator(size());
    }
    
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    get 方法用于获取链表中指定索引位置的元素,迭代器支持从前往后和从后往前遍历链表。

    这些是 LinkedList 的核心操作,它们确保了链表的高效添加和删除能力。LinkedList 适用于需要频繁在链表头部或尾部进行添加和删除操作的场景,但在需要随机访问元素的情况下效率相对较低。

    1. Vector
      • VectorArrayList 类似,也是一个动态数组实现,但它是线程安全的。它提供了与 ArrayList 相似的操作,并且支持自动扩容。
      • 由于线程安全的特性,Vector 在多线程环境中使用,但通常会因为性能问题而被 ArrayListLinkedList 替代。

    以下是 Vector 的核心代码讲解,涵盖了初始化、添加元素、删除元素、扩容等关键操作。

    首先,让我们看一下 Vector 的基本属性和构造方法:

    // Vector 类的定义
    public class Vector<E>
        extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        // 默认初始容量
        private static final int DEFAULT_CAPACITY = 10;
    
        // 存储元素的数组
        protected Object[] elementData;
    
        // Vector 的大小,即元素的个数
        protected int elementCount;
    
        // 构造方法
        public Vector() {
            this(DEFAULT_CAPACITY);
        }
    
        public Vector(int initialCapacity) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
            elementData = new Object[initialCapacity];
        }
        
        // ...
    }
    
    • 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

    在上述代码中,Vector 使用一个 Object 类型的数组 elementData 来存储元素。默认情况下,elementData 初始化为指定的容量,容量不足时会触发扩容操作。elementCount 属性表示当前 Vector 中的元素个数。

    接下来,让我们看一下 Vector 的添加元素操作,包括 addElementadd 方法:

    // 向 Vector 添加元素
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
    
    // 向 Vector 尾部添加元素
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    
    // 辅助方法,确保容量足够
    private void ensureCapacityHelper(int minCapacity) {
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    // 扩容操作
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0)
                      ? capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    • 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

    上述代码中,addElement 方法用于向 Vector 添加元素,add 方法用于向 Vector 尾部添加元素。如果添加元素后,元素个数超过了当前数组的容量,就会触发扩容操作。扩容操作会创建一个新的数组,并将元素从旧数组复制到新数组中,然后将新数组赋给 elementData

    接下来,让我们看一下 Vector 的删除元素操作,包括 removeElement 方法:

    // 删除指定元素
    public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }
    
    // 删除指定索引位置的元素
    public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null;
    }
    
    • 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

    removeElement 方法用于删除指定元素,而 removeElementAt 方法用于删除指定索引位置的元素。删除操作会调用 System.arraycopy 将后面的元素向前移动,覆盖被删除的元素。

    最后,让我们看一下 Vector 的遍历操作,包括 get 方法和迭代器:

    // 获取指定索引位置的元素
    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
    
        return elementData(index);
    }
    
    // 返回一个迭代器,支持从前往后遍历
    public Iterator<E> iterator() {
        return listIterator(0);
    }
    
    // 返回一个列表迭代器,支持从前往后遍历
    public synchronized ListIterator<E> listIterator(int index) {
        if (index < 0 || index > elementCount)
            throw new IndexOutOfBoundsException("Index: "+index);
    
        return new ListItr(index);
    }
    
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    get 方法用于获取指定索引位置的元素,迭代器支持从前往后遍历 Vector

    这些是 Vector 的核心操作,它们确保了 Vector 的线程安全和高效添加、删除、扩容能力。Vector 在多线程环境中使用,但通常会因为性能问题而被 ArrayListLinkedList 替代。

    1. Deque
      • Deque 是 Java 中的双端队列(Double-ended Queue)接口,它提供了在队列的两端进行添加和删除操作的能力。LinkedList 实现了 Deque 接口,因此可以用作双向队列。
      • Deque 可以用于模拟栈(先进后出)和队列(先进先出)以及其他需要双向操作的数据结构。

    Deque(双端队列)是 Java 中的一种队列数据结构,它可以在队列的两端添加和删除元素,支持先进先出(FIFO)和后进先出(LIFO)等多种操作。Deque 接口有多个实现类,其中最常用的是 ArrayDequeLinkedList。以下是 Deque 的核心代码讲解,涵盖了初始化、添加元素、删除元素等关键操作。

    首先,让我们看一下 Deque 的基本属性和构造方法,以及 ArrayDeque 作为一个实现的示例:

    // Deque 接口的定义
    public interface Deque<E> extends Queue<E> {
        // 添加元素到队列的头部
        void addFirst(E e);
    
        // 添加元素到队列的尾部
        void addLast(E e);
    
        // 移除并返回队列头部的元素
        E removeFirst();
    
        // 移除并返回队列尾部的元素
        E removeLast();
    
        // ...
    }
    
    // ArrayDeque 类的定义
    public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, java.io.Serializable {
        // 存储元素的数组
        transient Object[] elements;
    
        // 队列的头部索引
        transient int head;
    
        // 队列的尾部索引
        transient int tail;
    
        // ...
        
        // 构造方法
        public ArrayDeque() {
            elements = new Object[16];
        }
    
        public ArrayDeque(int numElements) {
            allocateElements(numElements);
        }
    
        // ...
    }
    
    • 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

    在上述代码中,Deque 接口定义了双端队列的基本操作,包括在头部和尾部添加元素、在头部和尾部删除元素等。ArrayDequeDeque 接口的一个实现,使用一个数组 elements 来存储元素。headtail 分别表示队列的头部和尾部索引。

    接下来,让我们看一下 Deque 的添加元素操作,包括 addFirstaddLastoffer 方法:

    // 添加元素到队列的头部
    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
    }
    
    // 添加元素到队列的尾部
    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }
    
    // 在队列的尾部添加元素,返回是否成功
    public boolean offer(E e) {
        return addLast(e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    addFirst 方法用于添加元素到队列的头部,addLast 方法用于添加元素到队列的尾部,而 offer 方法则是在尾部添加元素并返回是否成功。在添加元素时,如果队列已满,就会触发扩容操作。

    接下来,让我们看一下 Deque 的删除元素操作,包括 removeFirstremoveLastpoll 方法:

    // 移除并返回队列头部的元素
    public E removeFirst() {
        E x = pollFirst();
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }
    
    // 移除并返回队列尾部的元素
    public E removeLast() {
        E x = pollLast();
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }
    
    // 移除并返回队列头部的元素,返回 null 如果队列为空
    public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];
        // 如果头部元素为空,返回 null
        if (result == null)
            return null;
        // 释放头部元素
        elements[h] = null;
        head = (h + 1) & (elements.length - 1);
        return result;
    }
    
    // ...
    
    • 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

    removeFirst 方法用于移除并返回队列头部的元素,removeLast 方法用于移除并返回队列尾部的元素,而 pollFirst 方法则是移除并返回队列头部的元素,如果队列为空则返回 null

    这些是 Deque 的核心操作,它们确保了双端队列的高效添加和删除能力。不同的 Deque 实现类可能有不同的性能特性和用途,开发人员可以根据实际需求选择合适的实现类。

    1. CopyOnWriteArrayList
      • CopyOnWriteArrayList 是 Java 中的线程安全的列表实现,它在写操作时进行复制,以保证线程安全。这意味着写操作会创建一个新的底层数组,因此写操作的效率较低。
      • 适用于读多写少的场景,例如读取频繁但很少修改的数据。

    CopyOnWriteArrayList 是 一个线程安全的动态数组实现,它通过在修改操作时创建一个副本(copy)来保证线程安全,因此适用于多线程环境下的读多写少的场景。以下是 CopyOnWriteArrayList 的核心代码讲解,涵盖了初始化、添加元素、删除元素等关键操作。

    首先,让我们看一下 CopyOnWriteArrayList 的基本属性和构造方法:

    // CopyOnWriteArrayList 类的定义
    public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        // 存储元素的数组
        private transient volatile Object[] array;
    
        // 构造方法
        public CopyOnWriteArrayList() {
            setArray(new Object[0]);
        }
    
        public CopyOnWriteArrayList(Collection<? extends E> c) {
            setArray(c.toArray());
        }
    
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在上述代码中,CopyOnWriteArrayList 使用一个 Object 类型的数组 array 来存储元素。在默认构造方法中,array 初始化为空数组。在带有 Collection 参数的构造方法中,array 初始化为 Collection 中的元素数组。

    接下来,让我们看一下 CopyOnWriteArrayList 的添加元素操作,包括 addaddAllset 方法:

    // 向列表尾部添加元素
    public boolean add(E e) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }
    
    // 向列表尾部添加元素集合
    public boolean addAll(Collection<? extends E> c) {
        if (c.isEmpty())
            return false;
    
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + c.size());
            int added = 0;
            for (E e : c) {
                newElements[len + added] = e;
                added++;
            }
            setArray(newElements);
            return added > 0;
        }
    }
    
    // 将指定位置的元素设置为新元素
    public E set(int index, E element) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            if (index < 0 || index >= len)
                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + len);
            Object oldValue = elements[index];
            if (oldValue != element) {
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            }
            return oldValue;
        }
    }
    
    • 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

    上述代码中,add 方法用于向列表尾部添加元素,addAll 方法用于向列表尾部添加元素集合,而 set 方法则是将指定位置的元素设置为新元素。这些添加和修改操作都在同步块内完成,确保了线程安全。

    接下来,让我们看一下 CopyOnWriteArrayList 的删除元素操作,包括 removeremoveAll 方法:

    // 删除指定元素
    public boolean remove(Object o) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            int index = indexOf(o, elements, 0, len);
            if (index < 0)
                return false;
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index, numMoved);
                setArray(newElements);
            }
            return true;
        }
    }
    
    // 删除列表中包含在指定集合中的元素
    public boolean removeAll(Collection<?> c) {
        if (c.isEmpty())
            return false;
    
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            int newlen = 0;
            Object[] tmp = new Object[len];
            for (int i = 0; i < len; i++) {
                if (!c.contains(elements[i]))
                    tmp[newlen++] = elements[i];
            }
            if (newlen == len)
                return false;
            setArray(Arrays.copyOf(tmp, newlen));
            return true;
        }
    }
    
    • 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

    remove 方法用于删除指定元素,removeAll 方法用于删除列表中包含在指定集合中的元素。删除操作同样在同步块内完成。

    这些是 CopyOnWriteArrayList 的核心操作,它们确保了在多线程环境下的线程安全性。需要注意的是,CopyOnWriteArrayList 的读操作不需要加锁,因此适用于读多写少的场景,但在写入操作较频繁的情况下,性能可能会受到影响。

    注意事项

    以下是针对不同的列表实现(ArrayListLinkedListVectorDequeCopyOnWriteArrayList)的使用注意事项:

    ArrayList:
    1. 动态数组ArrayList 是基于动态数组实现的,因此在添加元素时要注意动态扩容带来的性能开销。如果知道列表的大致大小,可以在初始化时指定初始容量,减少扩容次数。

    2. 随机访问快ArrayList 支持常数时间内的随机访问,但在中间插入或删除元素时会导致后续元素的位移,性能较低。适用于频繁读取但较少修改的场景。

    3. 线程安全ArrayList 不是线程安全的,如果在多线程环境中使用,需要采取额外的同步措施,或者考虑使用线程安全的替代实现。

    LinkedList:
    1. 双向链表LinkedList 是基于双向链表实现的,支持高效的插入和删除操作,但随机访问性能较差。适用于频繁插入和删除元素的场景。

    2. 迭代器:在遍历 LinkedList 时,建议使用迭代器而不是索引。索引访问可能导致遍历性能变得很差。

    3. 内存占用LinkedList 每个节点都需要额外的内存空间存储前后节点的引用,因此在大量数据情况下可能占用较多内存。

    Vector:
    1. 线程安全Vector 是线程安全的,但在多线程环境中,如果需要高性能,可能需要考虑更轻量级的同步机制。

    2. 扩容开销VectorArrayList 类似,需要进行动态扩容,因此在大量元素插入时,要注意频繁的扩容开销。

    3. 性能:由于同步机制,Vector 的性能可能低于 ArrayList,除非需要线程安全性,否则不建议使用。

    Deque:
    1. 双端队列Deque 支持在队列头部和尾部进行高效的添加和删除操作,是一种灵活的数据结构。

    2. 多种实现:Java 提供了多种 Deque 的实现,如 ArrayDequeLinkedList,可以根据具体需求选择合适的实现。

    3. 线程安全Deque 不是线程安全的,如果需要在多线程环境中使用,需要采取适当的同步措施。

    CopyOnWriteArrayList:
    1. 线程安全CopyOnWriteArrayList 是线程安全的,适用于读多写少的场景。在写入操作较频繁的情况下,性能可能不如其他非线程安全的列表。

    2. 内存占用:由于写操作会复制整个数组,因此可能会消耗较多的内存。不适合存储大量数据。

    3. 不支持修改操作:在 CopyOnWriteArrayList 上执行修改操作(如 addremove)会抛出 UnsupportedOperationException 异常。只能通过复制一个新的列表来进行修改。

    根据实际需求和性能要求,选择合适的列表实现非常重要。不同的列表实现有不同的适用场景和性能特点,需要根据具体情况进行选择。

    应用场景

    例如电商场景下,可以根据不同的业务需求选择合适的数据结构,以下是一些示例:

    1. ArrayList

      • 应用场景:商品列表展示。
      • 示例:在电商网站的商品列表页面中,通常会使用 ArrayList 存储商品信息,以支持快速的随机访问和排序操作。
    2. LinkedList

      • 应用场景:购物车管理。
      • 示例:购物车通常需要频繁地添加、删除商品,因此使用 LinkedList 可以更高效地执行这些操作,同时支持迭代浏览购物车中的商品。
    3. Vector

      • 应用场景:遗留系统的订单管理。
      • 示例:在维护遗留的订单管理系统时,可能需要继续使用 Vector,以保持与原有代码的兼容性。
    4. Deque

      • 应用场景:订单处理系统。
      • 示例:订单处理系统可能需要实现一个订单队列,支持高效的订单添加和处理。使用 Deque 可以在队列的头部和尾部进行快速插入和删除。
    5. CopyOnWriteArrayList

      • 应用场景:商品库存管理。
      • 示例:在管理商品库存时,可能需要使用 CopyOnWriteArrayList 来保证多线程环境下的读取一致性,以防止商品库存数据不一致的问题。

    需要注意的是,以上示例仅为参考,实际的业务场景可能更加复杂,需要根据具体情况权衡性能、并发性和代码复杂性,选择合适的数据结构。不同的业务模块可能会使用不同的数据结构来满足其需求。

    这些数据类型和类提供了不同的性能和线程安全特性,我们可以根据具体的需求选择合适的数据结构。链表适用于需要高效插入和删除操作的情况,而动态数组(如 ArrayList)适用于需要快速随机访问的情况。同时,双端队列和线程安全的实现可以满足特定的多线程需求。

    结语

    好的,本期我们的链表数据类型的讲解就到此结束。链表是一种非常重要的数据结构,它为我们提供了灵活性和效率,特别是在插入和删除元素时。希望您能够充分理解链表的概念和操作,以便在编程中更好地应用它们。

    在下期,我们将继续深入探讨栈(Stack)数据结构。栈是一种基本的数据结构,它遵循后进先出(Last-In-First-Out,LIFO)的原则,常用于解决许多计算机科学和编程问题。我们将详细介绍栈的工作原理、常见操作以及如何在实际编程中使用它。

    谢谢您的关注,下期敬请期待!如果在本期的内容中发现了错误或异常,或者您有任何疑问或建议,请随时留言或私信与我联系。您的反馈对于改进和完善内容非常重要,我将非常感激您的指正。期待与您继续分享有关编程和计算机科学的知识,祝您一切顺利!

  • 相关阅读:
    JSP基于Javaweb学籍管理系
    Codeforces-1696 D: Permutation Graph【构造、分治、数据结构】
    3个月测试员自述:4个影响我职业生涯的重要技能
    Android 播放mp3文件
    通讯录的实现(详解)
    A Mathematical Framework for Transformer Circuits—(三)
    springcloudalibaba架构(4):Sentinel流控规则
    gdb常用调试命令 + 多进程调试命令
    稳定性实践:容量规划之压测系统建设
    彩涂钢板密封板申请BS 476-3如何备样?
  • 原文地址:https://blog.csdn.net/qq_34445142/article/details/132814153