【JavaLearn】#(15)集合提升训练:手写ArrayList、单链表、LinkedList、HashMap、HashSet、新一代并发集合类

1. 手写ArrayList

1.1 ArrayList 底层原理细节

  • 底层结构是一个长度可以动态增长数组(顺序表)

    // 指向数组的引用
    transient Object[] elementData; // transient修饰的变量,不参与序列化
    

    数组特点:在内存中分配连续的空间只存储数据,不存储地址信息

    • 节省存储空间(无需存储地址和数据间的关系)、索引查询效率高(其它地址可以通过首地址计算得到)
    • 插入和删除效率低,必须提前分配固定数量的空间,可能会导致空闲浪费,按照内容查询效率低
  • 通过无参构造方法,创建对象时,JDK1.7初始长度10,JDK1.8初始长度为0,在第一次添加元素时就进行扩容扩容到长度为10(后续每次扩容1.5倍)

  • 提供了一个内部类 Itr,实现了 Iterator 接口,用来对 ArrrayList 进行遍历

1.2 自定义:List、Iterator、异常处理

List

public interface List {
    /**
     * 返回线性表的大小,即数据元素的个数
     * @return
     */
    int size();

    /**
     * 返回线性表中序号为 i 的数据元素
     * @param i
     * @return
     */
    Object get(int i);

    /**
     * 如果线性表为空返回true,否则返回false
     * @return
     */
    boolean isEmpty();

    /**
     * 判断线性表中是否包含数据元素 e
     * @param e
     * @return
     */
    boolean contains(Object e);

    /**
     * 返回数据元素 e 在线性表中的序号
     * @param e
     * @return
     */
    int indexOf(Object e);

    /**
     * 将数据元素 e 插入到线性表中 i 号的位置
     * @param i
     * @param e
     */
    void add(int i, Object e);

    /**
     * 将数据元素 e 插入到线性表的末尾
     * @param e
     */
    void add(Object e);

    /**
     * 删除线性表中序号为 i 的元素,并返回值
     * @param i
     * @return
     */
    Object remove(int i);

    /**
     * 删除线性表中第一个与 e 相同的元素
     * @param e
     * @return
     */
    boolean remove(Object e);

    /**
     * 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
     * @param i
     * @param e
     * @return
     */
    Object replace(int i, Object e);

    /**
     * 迭代List
     * @return
     */
    Iterator iterator();
}

Iterator

public interface Iterator<T> {
    /**
     * 是否还有下一个元素
     * @return
     */
    boolean hasNext();

    /**
     * 下一个返回的元素值
     * @return
     */
    T next();
}

IndexOutOfBoundsException

public class IndexOutOfBoundsException extends RuntimeException{
    public IndexOutOfBoundsException() {
    }

    public IndexOutOfBoundsException(String message) {
        super(message);
    }
}

1.3 自定义:测试类

public class TestArrayList {
    public static void main(String[] args) {
        List list = new ArrayList();

        list.add("1111");
        list.add("aaaa");
        list.add("bbbb");
        list.add("3333");
        list.add("2222");
        list.add("1111");
        list.add("aaaa");
        list.add("bbbb");
        list.add("3333");
        list.add("2222");
        // 添加第 11 个元素时,注意扩容
        list.add("1111");
        // 添加到指定位置,注意移动元素
        list.add(3, "AAAA");

        System.out.println(list.size());
        System.out.println(list.isEmpty());
        System.out.println(list.get(2));
        System.out.println(list.contains("4444"));
        System.out.println(list.indexOf("2222"));
        // 注意判断查找 null 时
        System.out.println(list.indexOf(null));
		System.out.println(list.toString());

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        
        // 光标已经走到最后面,还往后走,进行报错
        System.out.println(iterator.next());
    }
}

1.4 自定义:ArrayList

最重要的扩容底层

image-20210928214129327

ArrayList类

package com.lwclick.list;

import java.util.Arrays;

/**
 * @author LIUWEI
 * @className ArrayList
 * @date 2021/9/27 22:42
 * @description 手写的ArrayList
 */
public class ArrayList implements List{
    /**
     * ArrayList底层是一个【长度可以动态增长】的数组,elementData是数组的引用
     */
    private transient Object[] elementData;

    /**
     * 集合中存储了元素的个数,不是数组空间的容量length
     * 增加、删除元素时,size的值都要发生变化
     */
    private int size;

    public ArrayList() {
        this(10);
    }

    public ArrayList(int initialCapacity) {
        elementData = new Object[initialCapacity];
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Object get(int i) {
        if (i >= size || i < 0) {
            throw new IndexOutOfBoundsException("数组索引越界" + i);
        }
        return elementData[i];
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean contains(Object e) {
        return indexOf(e) >= 0;
    }

    @Override
    public int indexOf(Object e) {
        int index = -1;
        if (e == null) { // 为空则比较引用
            for (int i = 0; i < size; i++) {
                if (e == elementData[i]) {
                    return i;
                }
            }
        } else { // 不为空则比较内容
            for (int i = 0; i < size; i++) {
                if (e.equals(elementData[i])) {
                    return i;
                }
            }
        }
        return index;
    }

    @Override
    public void add(int index, Object e) {
        if (elementData.length == size) {
            grow();
        }
        
        // 添加到指定位置时,一定要移动元素,不然会覆盖
        for (int i = size; i > index; i--) {
            elementData[i] = elementData[i - 1];
        }
        
        elementData[index] = e;
        size++;
    }

    @Override
    public void add(Object e) {
        // 如果数组已满,需要先扩容
        if (elementData.length == size) {
            grow();
        }
        
        // 添加元素到最后:初始elementData为空的,size的值也为0,所以就是 size的位置
        elementData[size] = e;
        // 存储的元素进行 ++
        size++;
    }

    private void grow() {
        // 1.新创建一个更大容量的数组
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 使用右移,是为了不出现小数(右移时注意优先级)
        Object[] newArr = new Object[newCapacity];

        // 2.将原来数组的元素,拷贝到新数组中,索引一一对应
        for (int i = 0; i < oldCapacity; i++) {
            newArr[i] = elementData[i];
        }

        // 3.成员变量elementData指向扩容后的新数组
        elementData = newArr;

        // 可以使用一句话实现
        // elementData = Arrays.copyOf(elementData, newCapacity);
    }

    @Override
    public Object remove(int index) {
        if (index >= size || index < 0) {
            throw new IndexOutOfBoundsException("数组索引越界" + index);
        }
        Object e = elementData[index];

        // 移动元素
        int numMoved = size - index - 1;
        if (numMoved > 0) {
            // System.arraycopy(源数组, 从源数组的起始位置开始, 拷贝到目标数组, 到目标数组的开始位置, 复制的个数)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }

        // 最后的元素指向 null,交给GC
        elementData[--size] = null;

        return e;
    }

    @Override
    public boolean remove(Object e) {
        if (e == null) { // 如果为空,比较地址
            for (int i = 0; i < size; i++) {
                if (e == elementData[i]) {
                    fastRemove(i);
                    return true;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (e.equals(elementData[i])) {
                    fastRemove(i);
                    return true;
                }
            }
        }
        return false;
    }

    private void fastRemove(int index) {
        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }

        elementData[--size] = null;
    }

    @Override
    public Object replace(int index, Object e) {
        if (index >= size || index < 0) {
            throw new IndexOutOfBoundsException("数组索引越界" + index);
        }
        if (e == null) {
            if (e != elementData[index]) {
                Object oldValue = elementData[index];
                elementData[index] = e;
                return oldValue;
            }
        } else {
            if (!e.equals(elementData[index])) {
                Object oldValue = elementData[index];
                elementData[index] = e;
                return oldValue;
            }
        }
        return this;
    }

    @Override
    public Iterator iterator() {
        return new Itr();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            builder.append(elementData[i] + ",");
        }
        if (size > 0) {
            builder.deleteCharAt(builder.length() - 1);
        }
        builder.append("]");
        return builder.toString();
    }

    private class Itr<T> implements Iterator<T> {
        // 定义一个光标依次指向数组
        int cursor = 0;
        @Override
        public boolean hasNext() {
            return cursor != size;
        }

        @Override
        public T next() {
            if (cursor >= size) {
                throw new RuntimeException("元素已经遍历结束");
            }
            return (T)elementData[cursor++];
        }
    }
}

2. 单链表

2.1 单链表技能点

  • 存储空间不连续
  • 每个结点对应一个数据元素,结点由指针域和数据域组成
  • 元素的逻辑关系通过结点间的链接关系体现,逻辑上相邻,物理上不一定相邻
  • 按索引查询慢,存储空间大
  • 插入、删除速度快
  • 有元素才会分配空间,不会有闲置的结点

注:为了对空表非空表的情况以及首元结点进行统一处理,常增加一个头结点

2.2 手写单链表

测试代码

public static void main(String[] args) {
    //创建线性顺序表
    List list = new SingleLinkedList();
    
    //向末尾添加元素
    list.add("11111");
    list.add("aaaaa");
    list.add("bbbbb");
    list.add("33333");
    list.add("22222");
    
    // 添加到指定位置
    list.add(3, "AAAAA");
    
    //进行各种操作验证添加
    System.out.println(list.size());
    System.out.println(list.isEmpty());
    System.out.println(list.get(2));
    System.out.println(list.contains("44444"));
    System.out.println(list.indexOf("22222"));
    System.out.println(list.toString());
}

add 操作及 get 操作的底层思想

List类

public interface List {
    // 返回线性表的大小,即数据元素的个数。
    int size();
    // 返回线性表中序号为 i 的数据元素
    Object get(int i);
    // 如果线性表为空返回 true,否则返回 false。
    boolean isEmpty();
    // 判断线性表是否包含数据元素 e
    boolean contains(Object e);
    // 返回数据元素 e 在线性表中的序号
    int indexOf(Object e);
    // 将数据元素 e 插入到线性表中 i 号位置
    void add(int i, Object e);
    // 将数据元素 e 插入到线性表末尾
    void add(Object e);
    // 将数据元素 e 插入到元素 obj 之前
    boolean addBefore(Object obj, Object e);
    // 将数据元素 e 插入到元素 obj 之后
    boolean addAfter(Object obj, Object e);
    // 删除线性表中序号为 i 的元素,并返回之
    Object remove(int i);
    // 删除线性表中第一个与 e 相同的元素
    boolean remove(Object e);
    // 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
    Object replace(int i, Object e);
}

Node类

public class Node {
    Object data;
    Node next; // 指向的元素也为结点

    public Node() {
    }

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Node(Object data) {
        this.data = data;
    }
    // getter / setter / toString方法
}

单链表 SingleLinkedList 类

public class SingleLinkedList implements List {
    private Node head = new Node(); // 事先存在一个头结点
    private int size; // 结点数量,默认是0(不包含头结点)

    @Override
    public int size() {
        return size;
    }

    @Override
    public Node get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("索引越界:" + index);
        }
        // 定义一个 p :指向头结点
        Node p = head;

        // 循环去找第 index 个结点
        for (int i = 0; i <= index; i++) {
            p = p.next; // !!!指向下一个结点
        }

        return p;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void add(int index, Object e) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("索引越界" + index);
        }
        // p 首先指向头结点
        Node p = head;

        if (index > 0) {
            // 让指针移动到第 index 个结点
            p = get(index - 1);
        }
        // 新建一个结点,并加入数据
        Node node = new Node(e);
        // 指定该新结点指向下一个结点的地址,也就是p指向的下一个结点的地址
        node.next = p.next;
        // 修改p指向的下一个结点的地址,为该新结点
        p.next = node;
        // 结点数量++
        size++;
    }

    @Override
    public void add(Object e) {
        add(size, e);
    }

    @Override
    public Object remove(int index) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("索引越界" + index);
        }
        Node p = head;
        if (index > 0) {
            p = get(index - 1);
        }
        // 将指向第 index 个结点先存起来
        Node currentNode = p.next;

        // 第 index 的结点指向下一个结点的地址,直接改为第 index + 1 个结点指向下一个结点的地址
        p.next = p.next.next;

        // 存起来的结点指向下一个结点的地址,直接指向 null
        currentNode.next = null;

        size--;
        return null;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("[");
        /** 直接调用get,相当于频繁的去查找 */
        // for (int i = 0; i < size; i++) {
        //     Node node = get(i);
        //     builder.append(node.data + ",");
        // }
        Node p = head;
        for (int i = 0; i < size; i++) {
            // 顺藤摸瓜
            p = p.next; // p 指向第 i个结点
            builder.append(p.data + ",");
        }
        if (size != 0) {
            builder.charAt(builder.length() - 1);
        }
        return builder.toString();
    }
}

3. LinkedList

3.1 底层原理

底层是一个双向链表(添加、删除元素效率高,按索引查询效率低)

image-20211020214205757

同时实现了 List、Deque、Queue接口,所以可以当作线性表、队列、双端队列、栈来用

不存在容量不足的问题

3.2 手写LinkedList

LinkedList类

public class LinkedList implements List {

    transient int size = 0; // 有几个结点
    transient Node first; // 指向链表的第一个结点(类似于C的指针,并不是结点对象)
    transient Node last; // 指向链表的最后一个结点

    // 内部类,Node 结点类
    private static class Node<E> {
        E item; // 数据
        Node<E> next; // 指向下一个结点
        Node<E> prev; // 指向前一个结点

        public Node(Node<E> prev, E item, Node<E> next) {
            this.item = item;
            this.next = next;
            this.prev = prev;
        }

        @Override
        public String toString() {
            return "Node{item=" + item + ", next=" + next + '}';
        }
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Object get(int i) {
        Node node = node(i);
        return node.item;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void add(int i, Object e) {
        if (i == size) {
            linkLast(e);
        } else {
            linkBefore(e, node(i));
        }
    }

    /**
     * 查询指定索引的结点
     * @param index
     * @return
     */
    private Node node(int index) {
        if (index < (size >> 1)) { // 前一半结点,从第一个结点开始向后查询
            Node p = first;
            for (int i = 0; i < index; i++)
                p = p.next;
            return p;
        } else { // 后一半结点,从后边向前
            Node l = last;
            for (int i = size - 1; i > index; i--)
                l = l.prev;
            return l;
        }
    }

    @Override
    public void add(Object e) {
        linkLast(e);
    }

    // 在最后面插入数据!!!!
    private void linkLast(Object e) {
        final Node l = last;
        final Node newNode = new Node(l, e, null);
        last = newNode;
        if (l == null) {
            first = newNode;
        } else {
            l.next = newNode;
        }
        size++;
    }

    // 在最前面插入数据
    private void linkBefore(Object e, Node succ) {
        final Node pred = succ.prev;
        final Node newNode = new Node(pred, e, succ);
        succ.prev = newNode;
        if (pred == null) {
            first = newNode;
        } else {
            pred.next = newNode;
        }
        size++;
    }
}

分析linkLast添加结点时的内存分配图

在这里插入图片描述

4. 手写HashMap

4.1 底层原理

  • 底层结构是哈希表,采用了 顺序表+链表 结合结构,同一个链表上的所有元素的存储地址都是相同的,是发生冲突的元素

    image-20211212115640545

  • 链表上每个节点就是一个 Entry,字段包括四部分

    image-20211212115748438

    同一个链表上节点的哈希码可能不同,存储地址(主数组的索引)相同

  • 哈希表的优点:

    • 添加快、查询快(通过计算得到存储位置,而不是比较)
    • 无序,key 唯一
  • HashMap 关键参数

    • 默认主数组长度为 16

    • 默认装填因子 0.75 (当存储元素达到 数组长度 * 装填因子 时,就需要扩容)

    • 每次主数组扩容为原来的 2 倍

    • JDK 8,当链表长度大于 8 时,链表变为红黑树

      image-20211212120542570

4.2 手写 HashMap

Map接口

public interface Map {
    /**
     * 放置元素
     * @param key
     * @param value
     */
    void put(Object key, Object value);

    /**
     * 获取元素
     * @param key
     * @return
     */
    Object get(Object key);

    /**
     * 储存元素个数
     * @return
     */
    int size();

    /**
     * 是否为空
     * @return
     */
    boolean isEmpty();

    interface Entry {
        /**
         * 获取 Entry的Key值
         * @return
         */
        Object getKey();

        /**
         * 获取 Entry的 value
         * @return
         */
        Object getValue();
    }
}

TestMap测试类

public static void main(String[] args) {
    Map map = new HashMap();
    map.put(23,"China");
    map.put(36,"Japan");
    map.put(48,"America");
    map.put(86,"the United States");
    map.put(67,"France");
    map.put(23,"Italian");
    map.put(47,"England");
    System.out.println(map.size());
    System.out.println(Arrays.toString(map.table));
    System.out.println(map);
}

HashMap 类

public class HashMap implements Map{

    /** 指向哈希表【主数组】的引用变量(数组的名字)*/
    transient Entry[] table;

    /** 哈希表中节点的数量(链表节点的个数) */
    transient int size = 0;

    public HashMap() {
        this(16);
    }

    public HashMap(int capacity) {
        table = new Entry[capacity];  // !!! JDK7中,初始化时,不管指定多大的容量都会处理为 2^n
    }

    /** 链表中节点类 */
    class Entry implements Map.Entry {
        // key值,final修饰,不可变
        final Object key;
        // value值
        Object value;
        // 指向下一个节点
        Entry next;
        // key的哈希码
        int hash;

        public Entry(int hash, Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
            this.hash = hash;
        }

        @Override
        public Object getKey() {
            return key;
        }

        @Override
        public Object getValue() {
            return value;
        }

        @Override
        public String toString() {
            if (next != null) {
                return "Entry{" +
                        "key=" + key +
                        ", value=" + value +
                        ", next=" + next +
                        ", hash=" + hash +
                        '}';
            } else {
                return "Entry{" +
                        "key=" + key +
                        ", value=" + value +
                        ", hash=" + hash +
                        '}';
            }
        }
    }

    @Override
    public void put(Object key, Object value) {
        // 计算哈希码
        int hash = key.hashCode();

        // 根据哈希码,计算存储位置(主数组的索引)
        int index = hash % table.length;

        // 存入指定位置
        if (table[index] == null) { // 情况1: 存储位置为空,直接放入即可
            table[index] = new Entry(hash, key, value, null);
            size++;
        } else {
            // 如果指定的位置不为空,发生了冲突,需要逐个比较    // 情况2:使用新的 value 覆盖旧的value
            Entry entry = table[index]; // 指向链表的第一个节点
            while (entry != null) {
                // 进行 key 的比较,如果找到了相同的key
                if (entry.hash == hash && (entry.key == key || key.equals(entry.key))) {
                    // 使用新的value,替换旧的value
                    entry.value = value;
                    return;
                }

                // 指向 链表的下一个节点
                entry = entry.next;
            }
            // 情况3:如果发生了冲突,并且没有相同的 key,添加一个新的节点【到链表的首节点】
            Entry firstEntry = table[index];
            table[index] = new Entry(hash, key, value, firstEntry);
            size++;
        }
    }

    @Override
    public Object get(Object key) {
        // 计算哈希码
        int hash = key.hashCode();

        // 根据哈希码,计算索引位置(主数组的索引)
        int index = hash % table.length;

        // 查找对应的 Entry
        Entry entry = null;
        if (table[index] != null) { // 情况2: table[index] 不为空,找到了key
            Entry currentEntry = table[index];
            while (currentEntry != null) {
                if (currentEntry.hash == hash && (currentEntry.key == key || key.equals(currentEntry.key))) {
                    entry = currentEntry;
                    break;
                }

                // 指向链表的下一个节点
                currentEntry = currentEntry.next;
            }

            // 情况3:没找到对应的key,entry仍然为 null
        }

        // 情况1: table[index] 为 null
        return entry == null ? null : entry.getValue();
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("{");
        for (int i = 0; i < table.length; i++) {
            if (table[i] != null) {
                Entry entry = table[i]; // 指向索引为 i 的链表的第一个节点
                do {
                    builder.append(entry.key + "=" + entry.value + ",");
                    entry = entry.next;
                } while (entry != null);
            }
        }
        if (size > 0) {
            builder.deleteCharAt(builder.length() - 1);
        }
        builder.append("}");
        return builder.toString();
    }
}

5. 手写 HashSet

  • HashSet 底层就是 HashMap,哈希表的每个 Entry 的key是 HashSet 中的每个元素,而 Entry 的value统一为 new Object()

Set接口

public interface Set {
    int size();
    void add(Object obj);
    boolean isEmpty();
    boolean contains(Object obj);
}

TestHashSet测试类

public static void main(String[] args) {
    HashSet set = new HashSet();
    set.add("JavaSE");
    set.add("MySQL");
    set.add("JavaScript");
    set.add("JavaSE");

    System.out.println(set.size());
    System.out.println(set.contains("JavaSE"));
    System.out.println(set.contains("JavaSe"));
    System.out.println(set);
}

HashSet类

public class HashSet implements Set {
    private HashMap map;
    private static final Object PRESENT = new Object();

    public HashSet() {
        map = new HashMap<>();
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public void add(Object obj) {
        map.put(obj, PRESENT);
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean contains(Object obj) {
        return map.get(obj) != null;
    }
}

6. 新一代并发集合类

6.1 三代集合类发展过程

  • 第一代线程安全集合类

    Vector、HashTable(使用 synchronized 修饰方法)

  • 第二代线程非安全集合类(主流)

    ArrayList、HashMap(线程不安全,但是性能好)

    如果要保证线程安全 ==》 使用 Collections.synchronizedList(list)Collections.synchronizedMap(m)(底层使用 synchronized 代码块锁,但是锁在方法里面

  • 第三代线程安全集合类

    java.util.concurrent.*;

    ConcurrentHashMap

    CopyOnWriteArrayList

    CopyOnWriteArraySet (注意:底层为 list)

    底层大都采用 Lock锁(JDK1.8的 ConcurrentHashMap 不使用 Lock锁)

代码说明

public static void main(String[] args) {
    // 第一代  同步方法锁 锁住的是this 效率底下
    Vector v = new Vector();

    // 第二代  同步代码块  锁住的也是 this
    List list = new ArrayList();
    list = Collections.synchronizedList(list);

    // 第三代 大都是 Lock 锁
    ConcurrentHashMap chm; // 1.8 不是
    CopyOnWriteArrayList cwal; // 可以当作 ArrayList 使用
}

6.2 新一代并发集合类

  • ConcurrentHashMap:分段(segment)锁定 + Lock锁

    使用多个锁来控制对 hash 表的不同部分(段 segment)进行的修改,采用ReentrantLock 锁来实现,如果多个操作发生在不同的段上,它们就可以并发的进行

    image-20211212223508216

    JDK1.8中:摒弃了Segment的概念,采用了 volatile + CAS 实现无锁化操作(底层由 数组+链表+红黑树 的方式,锁哈希表的链表中的首元素

  • CopyOnWriteArrayList: CopyOnWrite + Lock 锁

    对于写操作(set()、add()、remove()等 每个操作都复制一份数组,修改完成后改变数组的引用)使用 ReentrantLock 的 lock 和 unlock 来加锁和解锁,读操作不需要加锁

    只能保证数据的最终一致性,不能保证数据的实时一致性(读的是旧数组中的元素)

    image-20211212224913327

  • CopyOnWriteArraySet

    与 HashMap 及 HashSet 的方式类似,只是底层结构变为了数组,所以与 ArrayList 性质类似

    但是ArrayList 要求元素有序、不唯一,而 CopyOnWriteArraySet 要求元素顺序无所谓,唯一,所以在添加元素时,需要先判断是否已经有了元素

    image-20211212225945575

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LRcoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值