数组链表合体 - 链式数组结构!集合链表和数组的优点于一身,一百万数据的随机添加以及随机删除最快只需 2.2 秒,是 ArrayList 的 70 倍 !

数组链表合体 - 链式数组结构!集合链表和数组的优点于一身,一百万数据的随机添加以及随机删除最快只需 2.2 秒,是 ArrayList 的 70 倍 !

简介及测试结果

我们都知道,java 中最常用的集合是 ArrayList、LinkedList,前者是数组实现的,后者是双向链表实现的。

对于 ArrayList 来说适合读多写少的场景,因为它写数据的时候需要扩容或者移动数据;

对于 LinkedList 来说则适合写多读少的场景,因为它读数据的时候需要对节点进行遍历。

那么问题来了,有没有一种数据结构能够综合这两种数据结构的优点呢?
于是我设想了这样一种数据结构,我称它为链式数组结构:
链表数组
那么基于这种结构,它的操作如下:

插入元素:首先确定 index 应该在哪个数组的范围。然后判断当前数组是否已经达到最大容量。
如果没有,直接按照 ArrayList 的方式插入即可;
如果达到了,那么判断 next 指针是否为空。
如果 next 为空,则创建一个新的数组,并让 next 指向这个新数组;
如果 next 不为空,那么判断 next 的数组是否达到最大容量。
如果没有达到最大容量,那么将所有的数据向后移动一位;
如果达到了最大容量,那么在当前数组和 next 数组的中间插入一个新的数组。
最后新数组的第一位数据赋值为当前数组的最后一位数据。
接着当前数组的数据向后移动一位,将元素插入当前位置。

删除操作:首先确定 index 应该在哪个数组的范围,然后直接根据 index 删除数据,最后将其后面的数据向前移动一位。

由此可以看到,不管插入还是删除,其移动或者复制的数据都是很小的一部分,因此性能很可观。

其实现代码如下:

package com.kfyty.core.lang.util;

import com.kfyty.core.support.Pair;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.RandomAccess;

/**
 * 描述: 链式数组,适用于大量随机插入随机删除
 *
 * @author kfyty725
 * @date 2022/11/19 9:52
 * @email kfyty725@hotmail.com
 */
public class LinkedArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Serializable {
    /**
     * 每个数组最小容量
     */
    private static final int MIN_CAPACITY = 16;

    /**
     * 每个数组的容量
     * 该参数对性能影响非常大,建议预计总元素数 / 200
     */
    private final int capacity;

    /**
     * 集合元素数量
     */
    private transient int size;

    /**
     * 头节点
     */
    private transient LinkedArrayNode head;

    /**
     * 尾节点
     */
    private transient LinkedArrayNode tail;

    public LinkedArrayList() {
        this(MIN_CAPACITY);
    }

    public LinkedArrayList(int capacity) {
        super();
        this.size = 0;
        this.capacity = capacity;
        this.head = this.tail = new LinkedArrayNode(capacity);
    }

    public LinkedArrayList(Collection<E> collection) {
        this(collection.size() / 200, collection);
    }

    public LinkedArrayList(int capacity, Collection<E> collection) {
        this(capacity);
        this.addAll(collection);
    }

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

    @Override
    public Iterator<E> iterator() {
        return new Iter();
    }

    @Override
    public boolean add(E e) {
        this.add(size(), e);
        return true;
    }

    @Override
    public E get(int index) {
        if (index < (this.size >> 2)) {
            return Optional.ofNullable(this.findNode(index)).map(v -> v.getValue().get(v.getKey())).orElse(null);
        }
        return Optional.ofNullable(this.findNodeReverse(index)).map(v -> v.getValue().get(v.getKey())).orElse(null);
    }

    @Override
    public E set(int index, E element) {
        if (index < (this.size >> 2)) {
            return Optional.ofNullable(this.findNode(index)).map(v -> v.getValue().set(v.getKey(), element)).orElse(null);
        }
        return Optional.ofNullable(this.findNodeReverse(index)).map(v -> v.getValue().set(v.getKey(), element)).orElse(null);
    }

    @Override
    public void add(int index, E element) {
        if (index < (this.size >> 2)) {
            Optional.ofNullable(this.findNode(index)).ifPresent(v -> v.getValue().add(v.getKey(), element));
        } else {
            Optional.ofNullable(this.findNodeReverse(index)).ifPresent(v -> v.getValue().add(v.getKey(), element));
        }
        this.size++;
    }

    @Override
    public E remove(int index) {
        E remove = index < (this.size >> 2)
                ? Optional.ofNullable(this.findNode(index)).map(v -> v.getValue().remove(v.getKey())).orElse(null)
                : Optional.ofNullable(this.findNodeReverse(index)).map(v -> v.getValue().remove(v.getKey())).orElse(null);
        this.size--;
        return remove;
    }

    @Override
    public int indexOf(Object o) {
        int index = -1;
        for (E e : this) {
            index++;
            if (o == null && e == null || o != null && o.equals(e)) {
                return index;
            }
        }
        return -1;
    }

    @Override
    @SuppressWarnings("unchecked")
    public int lastIndexOf(Object o) {
        int index = this.size();
        LinkedArrayNode node = this.tail;
        while (node != null) {
            for (int i = node.nodeSize - 1; i > -1; i--) {
                index--;
                E e = (E) node.element[i];
                if (o == null && e == null || o != null && o.equals(e)) {
                    return index;
                }
            }
            node = node.prior;
        }
        return -1;
    }

    @Override
    public void clear() {
        LinkedArrayNode node = this.head;
        while (node != null) {
            node.nodeSize = 0;
            Arrays.fill(node.element, null);
            LinkedArrayNode next = node.next;
            node.next = node.prior = null;
            node = next;
        }
        this.size = 0;
        this.tail = this.head;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof List)) {
            return false;
        }
        Iterator<?> self = this.iterator();
        Iterator<?> other = ((List<?>) o).iterator();
        while (self.hasNext() && other.hasNext()) {
            if (!Objects.equals(self.next(), other.next())) {
                return false;
            }
        }
        return !(self.hasNext() || other.hasNext());
    }

    private Pair<Integer, LinkedArrayNode> findNode(int index) {
        LinkedArrayNode node = this.head;
        while (node != null && index >= node.nodeSize) {
            index -= node.nodeSize;
            node = node.next;
        }
        return node == null ? null : new Pair<>(index, node);
    }

    private Pair<Integer, LinkedArrayNode> findNodeReverse(int index) {
        index = this.size - 1 - index;
        LinkedArrayNode node = this.tail;
        while (node != null && index >= node.nodeSize) {
            index -= node.nodeSize;
            node = node.prior;
        }
        return node == null ? null : new Pair<>(node.nodeSize - 1 - index, node);
    }

    @SuppressWarnings("unchecked")
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        this.size = 0;
        this.head = this.tail = new LinkedArrayNode(this.capacity);
        for (int i = 0, count = s.readInt(); i < count; i++) {
            this.add((E) s.readObject());
        }
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeInt(this.size);
        LinkedArrayNode node = this.head;
        while (node != null) {
            for (int i = 0; i < node.nodeSize; i++) {
                s.writeObject(node.element[i]);
            }
            node = node.next;
        }
    }

    /**
     * 迭代器
     */
    private class Iter implements Iterator<E> {
        /**
         * 当前游标
         */
        int cursor;

        /**
         * 当前的 size
         */
        int curSize;

        /**
         * 当前的节点游标
         */
        int curNodeCursor;

        /**
         * 当前的节点
         */
        LinkedArrayNode curNode;

        Iter() {
            this.curSize = LinkedArrayList.this.size;
            this.curNode = LinkedArrayList.this.head;
        }

        @Override
        public boolean hasNext() {
            return cursor < LinkedArrayList.this.size;
        }

        @Override
        @SuppressWarnings("unchecked")
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            if (curSize != LinkedArrayList.this.size) {
                throw new ConcurrentModificationException();
            }

            // 由于 remove() 操作,可能有的节点容量为 0,因此需要找到下一个容量大于 0 的节点
            while (curNode != null && curNodeCursor >= curNode.nodeSize) {
                curNodeCursor = 0;
                curNode = curNode.next;
            }
            cursor++;
            return curNode == null ? null : (E) curNode.element[curNodeCursor++];
        }

        @Override
        public void remove() {
            this.cursor--;
            this.curNode.remove(--curNodeCursor);
            this.curSize--;
            LinkedArrayList.this.size--;
        }
    }

    /**
     * 链式数组节点
     */
    private class LinkedArrayNode {
        /**
         * 元素
         */
        Object[] element;

        /**
         * 节点大小
         */
        int nodeSize;

        /**
         * 前驱节点
         */
        LinkedArrayNode prior;

        /**
         * 后继节点
         */
        LinkedArrayNode next;

        LinkedArrayNode(int capacity) {
            this.nodeSize = 0;
            this.element = new Object[capacity];
        }

        LinkedArrayNode(LinkedArrayNode prior) {
            this(capacity);
            this.prior = prior;
        }

        @SuppressWarnings("unchecked")
        protected E get(int index) {
            this.checkRange(index, size() - 1);
            return (E) this.element[index];
        }

        @SuppressWarnings("unchecked")
        protected E set(int index, E e) {
            this.checkRange(index, size() - 1);
            E result = (E) element[index];
            element[index] = e;
            return result;
        }

        @SuppressWarnings("unchecked")
        protected E remove(int index) {
            this.checkRange(index, size() - 1);
            E result = (E) element[index];
            if (index < nodeSize - 1) {
                System.arraycopy(element, index + 1, element, index, nodeSize - 1 - index);
            }
            element[--nodeSize] = null;
            if (this.nodeSize == 0) {
                this.removeNode(this);
            } else {
                this.shiftNextIfNecessary();
            }
            return result;
        }

        /**
         * 添加元素到指定索引
         * 1、索引范围检查
         * 2、如果插入索引位置大于等于节点大小,则尝试添加到下一个节点
         * 3、如果节点大小大于等于最大容量,则创建新的节点
         * 4、如果索引位置已有数据,则将该索引及其后面的元素后移
         * 5、将元素插入该索引位置
         *
         * @param index 索引
         * @param e     元素
         */
        protected void add(int index, E e) {
            this.checkRange(index);
            if (index >= nodeSize) {
                this.addToNextIfNecessary(index, e);
                return;
            }
            if (this.nodeSize >= capacity) {
                this.newNextIfNecessary();
            }
            if (this.nodeSize > 0 && index < this.nodeSize && index < capacity - 1) {
                int tempSize = this.nodeSize == capacity ? this.nodeSize - 1 : this.nodeSize;
                System.arraycopy(element, index, element, index + 1, tempSize - index);
            }
            this.element[index] = e;
            if (this.nodeSize < capacity) {
                this.nodeSize++;
            }
        }

        /**
         * 索引范围检查
         *
         * @param index 索引
         */
        private void checkRange(int index) {
            this.checkRange(index, size());
        }

        /**
         * 索引范围检查
         *
         * @param index 索引
         * @param limit 最大限制
         */
        private void checkRange(int index, int limit) {
            if (index < 0 || index > limit) {
                throw new ArrayIndexOutOfBoundsException("Size: " + size() + ", index: " + index);
            }
        }

        /**
         * 移除节点
         *
         * @param node 节点
         */
        private void removeNode(LinkedArrayNode node) {
            if (node.prior == null) {
                if (node.next == null) {
                    return;
                }
                node.next.prior = null;
                LinkedArrayList.this.head = node.next;
                return;
            }
            if (node.next == null) {
                LinkedArrayList.this.tail = node.prior;
                node.prior.next = null;
                return;
            }
            node.prior.next = node.next;
            node.next.prior = node.prior;
        }

        /**
         * 当前节点大小大于等于最大容量,尝试创建新的节点
         * 1、如果 next 为空,则直接创建新的数组节点
         * 2、如果 next 不为空并且容量未满,则将 next 的全部元素后移一位
         * 3、如果 next 不为空并且容量已满,则创建新的数组节点插入其后
         * 4、将当前元素的最后一个元素移动到 next 的第一位元素
         */
        private void newNextIfNecessary() {
            if (this.next == null) {
                LinkedArrayList.this.tail = this.next = new LinkedArrayNode(this);
            } else if (next.nodeSize < capacity) {
                System.arraycopy(next.element, 0, next.element, 1, next.nodeSize);
            } else {
                LinkedArrayNode temp = new LinkedArrayNode(this);
                temp.next = this.next;
                this.next.prior = temp;
                this.next = temp;
            }
            this.next.element[0] = this.element[nodeSize - 1];
            this.next.nodeSize++;
        }

        /**
         * 如果插入索引位置大于等于节点大小,则尝试添加到下一个节点
         * 1、如果当前节点未满并且索引在最大容量范围内,则尝试左移元素,并直接返回
         * 2、否则如果 next 为空,则创建新的数组节点
         * 3、调用 next.add() 插入元素
         *
         * @param index 索引
         * @param e     元素
         */
        private void addToNextIfNecessary(int index, E e) {
            if (this.nodeSize < capacity && index < capacity) {
                this.shiftLeft(index, e);
                return;
            }
            if (this.next == null) {
                LinkedArrayList.this.tail = this.next = new LinkedArrayNode(this);
            }
            this.next.add(index - nodeSize, e);
        }

        /**
         * 尝试左移元素
         * 1、如果 next 为空,则无需左移,直接添加即可
         * 2、计算需要左移的元素数量,如果 next 节点大小小于左移的元素数量,则无需左移,直接调用 next.add() 添加即可
         * 3、需要左移,执行左移,并将该元素插入当前节点最后的位置
         *
         * @param index 索引
         * @param e     元素
         */
        private void shiftLeft(int index, E e) {
            if (this.next == null) {
                this.element[nodeSize++] = e;
                return;
            }
            int move = index - nodeSize;
            if (next.nodeSize < move) {
                this.next.add(index - nodeSize, e);
                return;
            }
            this.doShiftLeft(move);
            this.element[nodeSize++] = e;
        }

        /**
         * 执行左移,即将 next 的前 move 个元素移动到当前节点
         * 1、如果移动后的 next 节点大小小于等于 move,则说明 next 节点已空,直接移除 next 节点
         * 2、否则将 next 节点剩余的元素移动到数组头部
         *
         * @param move 需要移动的元素数量
         */
        private void doShiftLeft(int move) {
            System.arraycopy(next.element, 0, this.element, this.nodeSize, move);
            this.nodeSize += move;
            if (next.nodeSize <= move) {
                Arrays.fill(next.element, 0, next.nodeSize, null);
                this.next = this.next.next;
                this.next.prior = this;
                return;
            }
            System.arraycopy(next.element, move, next.element, 0, next.nodeSize - move);
            Arrays.fill(next.element, next.nodeSize - move, next.nodeSize, null);
            next.nodeSize -= move;
        }

        /**
         * 删除时,尝试移动下一个节点的元素到当前节点,避免碎片化过多
         * 仅当当前节点的空余元素大于等于下一个节点的元素时,执行移动,并删除下一个节点,避免移动元素过多
         */
        private void shiftNextIfNecessary() {
            if (this.next == null || LinkedArrayList.this.capacity - this.nodeSize < this.next.nodeSize) {
                return;
            }
            System.arraycopy(next.element, 0, this.element, this.nodeSize, next.nodeSize);
            Arrays.fill(next.element, 0, next.nodeSize, null);
            this.nodeSize += this.next.nodeSize;
            this.removeNode(this.next);
        }
    }
}

下面放上测试代码:

    @Test
    public void test5() {
        int count = 1000000;
        Random random = new Random();
        List<Integer> list = new LinkedArrayList<>(5000);
//        List<Integer> list = new ArrayList<>(5000);
//        List<Integer> list = new LinkedList<>();
        long start = System.currentTimeMillis();
        list.add(1);
        for (int i = 0; i < count; i++) {
            list.add(random.nextInt(list.size()), random.nextInt(count));
        }
        for (int i = 0; i < count - 10; i++) {
            list.remove(random.nextInt(list.size()));
        }
        System.out.println(list);
        System.out.println(System.currentTimeMillis() - start + " ms");
    }

测试的时候,需要传入一个数组初始化容量,这个参数对 LinkedArrayList 的性能影响非常大,一千万级别的顺序插入,不同的参数的性能差距在两个数量级以上!

最后,在上述测试代码下,一百万的数据随机插入以及随机删除:
LinkedArrayList 的性能在 2.2 秒左右;
ArrayList 的性能在 153 秒左右;
LinkedList 的性能在 (还在等待中。。。算了吧) 秒左右。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值