精华内容
下载资源
问答
  • 2022-02-14 14:00:46

    更多相关内容
  • LinkedList 存储原理详解

    千次阅读 2020-05-31 11:34:21
    LinkedList 1. LinkedList的数据结构是什么 双向链表: 每个节点上有两个指针(item/prve/next) 链表中的每一个元素称之为节点,节点在运行时动态生成 存储数据的数据域 存储下一个节点地址的指针域 2. ...

    LinkedList

    1. LinkedList的数据结构是什么

    双向链表: 每个节点上有两个指针(item/prve/next)
    	链表中的每一个元素称之为节点,节点在运行时动态生成
    		存储数据的数据域
    		存储下一个节点地址的指针域
    

    2. LinkedList的存取原理

    存:
    	尾部插入:
            1. 创建一个新节点,前驱结点指向原尾结点,后继节点指向null
            2. 更新新节点为尾结点(last)
            3. 判断头结点是否为null?
                把新节点作为头结点 : 把原尾结点的后继指节点执行新节点
       	指定位置插入:
       		1. 获取指定索引为的节点,并获取这个节点的前一个节点
       		2. 创建一个新的节点,前驱结点指向前一个节点,后继节点指向后一个节点
       		3. 把前一个节点的后继节点指向新节点,那个指定索引位的节点的前驱结点指向新节点
    取:
    	取指定索引元素:
    		1. 根据索引判断从头部还是尾部来查找
    		2. 遍历查查找
    

    3. LinkedList的删除原理

    1. 获取指定要删除的节点,这个节点的前一个节点和后一个节点
    2. 判断上一个节点是否null:
    	说明这个节点头结点,把下一个节点赋值为first节点 : 把上一个节点的后继节点指向下一个节点
    	删除当前节点的前驱结点
    3. 判断下一个节点是否null:
    	说明这个节点尾结点,把上一个节点赋值为last节点 : 把下一个节点的前驱节点指向上一个节点
    	删除当前节点的后继结点
    4. 删除当前节点(方便GC回收)
    
    展开全文
  • 数组顺序存储,直接通过索引值访问每个元素,实现了数组元素的随机访问 链式存储:每次从头结点或者尾结点开始依次查找 如果线性表主要是查询操作,有限选择顺序存储的线性表 ...
    • 时间上的比较

      • 线性表的查询:

        • 数组顺序存储,直接通过索引值访问每个元素,实现了数组元素的随机访问

        • 链式存储:每次从头结点或者尾结点开始依次查找

        • 如果线性表主要是查询操作,有限选择顺序存储的线性表

      • 线性表的插入与删除:

        • 数组顺序实现的线性表,在插入\删除时,需要移动大量的元素

        • 链式存储,只需要修改结点的前驱后继结点即可,不需要移动元素

        • 如果线性表经常用于插入\删除操作,有限选择链式存储实现的线性表

    • 空间比较

      • 顺序存储:预先分配一块连续的存储空间,在使用过程中会出现空置的空间

      • 链式存储的空间是动态分配的,不会浪费空间

      • 如果线性表的长度经常变化,优先选择链式存储

      • 如果线性表的长度变化不大时,优先选择顺序存储,因为链式存储需要额外的空间存储它的前驱后继

    展开全文
  • 相信大家都明白 LinkedList 是基于双向链表而实现的,本篇文章主要讲解一下双向链表的实现,并且我们参考 LinkedList 自己实现...由于不必须按顺序存储,链表的插入和删除操作可以达到 O(1) 的复杂度。 而双向链表比单
  • Java LinkedList

    2020-12-16 17:37:22
    链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。 链表可分为单向链表和双向链表。 一个单向链表包含两个值: 当前节点的值...
  • LinkedList详解

    2022-01-21 15:00:39
    java集合总结: ArrayList详解_Allence的博客-CSDN博客一、介绍ArrayList是以什么数据结构实现的ArrayList底层的数据结构是顺序...物理结构:又称存储结构,是数据结构在计算机中的表示(又称映像)。例如,数组,指针

    java集合总结:

    ArrayList详解_Allence的博客-CSDN博客一、介绍ArrayList是以什么数据结构实现的ArrayList底层的数据结构是顺序表。顺序表:物理内存上连续、逻辑上连续、大小可以动态扩展顺序表是由数组实现的,说道这里就理一下数组、链表、顺序表之间的关系。逻辑结构:结构定义中是对操作对像的数学描述,描述的是数据元素之间的逻辑关系。例如,线性结构,树形结构,图状结构或网状结构。它们都属于逻辑结构。物理结构:又称存储结构,是数据结构在计算机中的表示(又称映像)。例如,数组,指针。线性表:属于逻辑结构中的线性结构,它包括顺序表和链表。https://blog.csdn.net/m0_37707561/article/details/122527303

    一、LinkedList的数据结构

    LinkedList数据结构是线性表,底层由双向链表实现

    二、LinkedList源码分析

    1.LinkedList继承的类和实现的接口分别什么作用

    (1).LinkedList继承的AbstractSequentialList和实现的List接口作用

    AbstractSequentialList继承的AbstractList,所以和ArrayList一样本质就是实现增删改查还有Iterator的功能

    (2).LinkedList实现的Deque接口作用

    Deque是双端队列,它继承自Queue队列,Queue继承集合

    说到队列我们复习一下队列这个数据结构:

    队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。我认为LinkedList虽然实现了Deque接口,但是它不是一个队列,它只是具有一些队列的特性,严格的队列是不允许在中间直接插入元素的,只能在队尾插入,所以想把一个元素插入到中间只能从队头全出队然后重新入队在入队的时候插入。

    关于队列数据结构好的文章:

    数据结构与算法—队列图文详解 - bigsai - 博客园前言 前言 栈和队列是一对好兄弟,前面我们介绍过数据结构与算法—栈详解,那么栈的机制相对简单,后入先出,就像进入一个狭小的山洞,山洞只有一个出口,只能后进先出(在外面的先出去)。而队列就好比是一个隧道https://www.cnblogs.com/bigsai/p/11363071.html(3).Cloneable

    实现了这个接口才能用Object的clone方法,详细的看Java集合总结的ArrayList

    (4).Serializable

    可以序列化了,详细的看Java集合总结的ArrayList

    2.源码中重要的字段和方法

    (1).长度

    transient int size = 0;

    (2).双向链表的首节点、尾节点

    transient Node<E> first;
    transient Node<E> last;

    为什么不用序列化,因为序列化的时候会把整个链表都存起来,读取的时候取第一个就是first节点,最后一个就是last节点,不用单独再去存储

    (3).链表的节点数据结构

        private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;
    
            Node(Node<E> prev, E element, Node<E> next) {
                this.item = element;
                this.next = next;
                this.prev = prev;
            }
        }

    上一个节点的指针 prev,下一个节点的指针next,和当前节点的数据item

    (4).无参构造

        public LinkedList() {
        }

    (5).参数为集合的构造

        public LinkedList(Collection<? extends E> c) {
            this();
            addAll(c);
        }

    (6).在头部插入一个节点,private给内部使用

        private void linkFirst(E e) {
            final Node<E> f = first;
            final Node<E> newNode = new Node<>(null, e, f);
            first = newNode;
            if (f == null)
                last = newNode;
            else
                f.prev = newNode;
            size++;
            modCount++;
        }

    主要是指针操作和维护size、modCount

    指针操作:final Node<E> f = first;   创建一个局部变量f保存旧链表头节点first指针

    final Node<E> newNode = new Node<>(null, e, f); 然后创建一个以参数为值的Node节点,把f作为新newNode节点的next后继,然后first = newNode; 然后把头节点的指针first指向newNode节点,

    判断如果f是null说明是一个空集合。那first和last都是刚创建的newNode节点

    如果f不为null,把f的前驱prev指向newNode,这样就达到了f.prev是newNode和newNode.next是f

    成功的在头部插入了一个节点newNode

    维护size++集合长度加一,modCount++对集合结构修改的行为加一。

    modCount的作用请看:ArrayList详解

    (7).在链表的尾部插入一个节点,默认权限修饰符虽然同一个包能用但对我们来说还是内部使用

        void linkLast(E e) {
            final Node<E> l = last;
            final Node<E> newNode = new Node<>(l, e, null);
            last = newNode;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }

    和在头部插入一个节点相反,这里就不再重复说,注意一点这是双向链表

    (8).在某一个节点之前插入一个节点,默认权限修饰符修饰对我们来说就是内部使用

        /**
         * Inserts element e before non-null Node succ.
         */
        void linkBefore(E e, Node<E> succ) {
            // assert succ != null;
            final Node<E> pred = succ.prev;
            final Node<E> newNode = new Node<>(pred, e, succ);
            succ.prev = newNode;
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            size++;
            modCount++;
        }

    把succ的前驱节点用pred保存下来,创建一个前驱节点是pred后继节点是succ的节点newNode,然后把succ的前驱节点设置为newNode,如果pred是null说明succ是首节点就直接把first指针指向newNode,如果pred就把pred的后继节点指向newNode,然后维护size和modCount

    (9).删除头节点,内部使用方法

        private E unlinkFirst(Node<E> f) {
            // assert f == first && f != null;
            final E element = f.item;
            final Node<E> next = f.next;
            f.item = null;
            f.next = null; // help GC
            first = next;
            if (next == null)
                last = null;
            else
                next.prev = null;
            size--;
            modCount++;
            return element;
        }

    (10).删除尾节点,内部使用方法

        private E unlinkLast(Node<E> l) {
            // assert l == last && l != null;
            final E element = l.item;
            final Node<E> prev = l.prev;
            l.item = null;
            l.prev = null; // help GC
            last = prev;
            if (prev == null)
                first = null;
            else
                prev.next = null;
            size--;
            modCount++;
            return element;
        }

    (11).删除某一个节点,内部使用

        E unlink(Node<E> x) {
            // assert x != null;
            final E element = x.item;
            final Node<E> next = x.next;
            final Node<E> prev = x.prev;
    
            if (prev == null) {
                first = next;
            } else {
                prev.next = next;
                x.prev = null;
            }
    
            if (next == null) {
                last = prev;
            } else {
                next.prev = prev;
                x.next = null;
            }
    
            x.item = null;
            size--;
            modCount++;
            return element;
        }

    主要就是断链,然后把前驱、后继联系起来

    (12).获取集合的第一个元素,可在外部调用

        public E getFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return f.item;
        }

    实际就是返回链表的第一个元素

    (13).获取集合的最后一个元素,可在外部调用

        public E getLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return l.item;
        }

     (14).移除集合的第一个元素,外部调用

        public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return unlinkFirst(f);
        }

    实质是调用了内部的unlinkFirst方法,上面有介绍

    (15).移除集合的最后一个元素,外部调用

        public E removeLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return unlinkLast(l);
        }

    实质是调用内部的unlinkLast方法,上面有介绍

    (16).在头部插入一个元素,外部调用

        public void addFirst(E e) {
            linkFirst(e);
        }

    实质是调用了linkFirst,为啥不直接暴露linkFirst方法感觉是为了扩展用的

    (17).在尾部插入一个元素,内部调用

        public void addLast(E e) {
            linkLast(e);
        }

     本质是调用linkLast方法

    (18).集合是否包含某个对象

        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }

    包含就返回索引,不包含就返回 -1

    (19).indexOf方法

        public int indexOf(Object o) {
            int index = 0;
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null)
                        return index;
                    index++;
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item))
                        return index;
                    index++;
                }
            }
            return -1;
        }

    代码很简单就是循环查找,区分了一下null和非null,注意:LinkedList的indexOf的实现逻辑和ArrayList的一样都是从头开始查找,返回第一个匹配元素的下标如果没有就返回-1

    (20).添加一个元素

        public boolean add(E e) {
            linkLast(e);
            return true;
        }

    和addLast方法一样添加一个元素到末尾,到这里可能会产生一个疑问为啥前面有了addLast还要搞个add明明效果是一样的,除了返回值问题,还有一个就是add是collection的方法,addLast是队列独有的方法

    (21).remove移除一个元素

        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;
        }

    和indexOf逻辑类似,只不过这个查找到之后是调用unlink把元素从双向链表中移除,如果查找到成功移除了返回true,如果失败了返回false

    (22).添加一个集合

        public boolean addAll(Collection<? extends E> c) {
            return addAll(size, c);
        }

    这个方法默认是把参数集合添加到链表的末尾

    (23).添加一个集合到指定位置

      public boolean addAll(int index, Collection<? extends E> c) {
            checkPositionIndex(index);
    
            Object[] a = c.toArray();
            int numNew = a.length;
            if (numNew == 0)
                return false;
    
            Node<E> pred, succ;
            if (index == size) {
                succ = null;
                pred = last;
            } else {
                succ = node(index);
                pred = succ.prev;
            }
    
            for (Object o : a) {
                @SuppressWarnings("unchecked") E e = (E) o;
                Node<E> newNode = new Node<>(pred, e, null);
                if (pred == null)
                    first = newNode;
                else
                    pred.next = newNode;
                pred = newNode;
            }
    
            if (succ == null) {
                last = pred;
            } else {
                pred.next = succ;
                succ.prev = pred;
            }
    
            size += numNew;
            modCount++;
            return true;
        }

    先看一下checkPositionIndex(index);

        private void checkPositionIndex(int index) {
            if (!isPositionIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
        private boolean isPositionIndex(int index) {
            return index >= 0 && index <= size;
        }
    
        private String outOfBoundsMsg(int index) {
            return "Index: "+index+", Size: "+size;
        }

    很简单就是防止指针越界,LinkedList源码中还有一个类似的方法checkElementIndex(int index)

        private void checkElementIndex(int index) {
            if (!isElementIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
        private boolean isElementIndex(int index) {
            return index >= 0 && index < size;
        }
    
        private String outOfBoundsMsg(int index) {
            return "Index: "+index+", Size: "+size;
        }

    也是用来防止指针越界的,这两个方法唯一不同的地方就是

    index <= size;和index < size;,为啥呢?checkPositionIndex这个方法可以看到是给add方法指定索引用的,是可能会添加到队尾,队尾的index就是size,而像查找、移除、修改只会在< size范围内

    我们接着看addAll的方法

            Node<E> pred, succ;
            if (index == size) {
                succ = null;
                pred = last;
            } else {
                succ = node(index);
                pred = succ.prev;
            }

    index如果== size说明要直接放队尾,else其实代表着index < size,先找到index代表的元素,用succ指针指向index下标对应的元素,用pred指针指向succ的prev前驱节点,我们要在index位置插入元素所以succ需要和pred断开后,把插入的元素和pred作为前驱、succ作为后继连接起来,因为是双向链表所以插入的元素分别作为:pred的后继和succ的前驱

    node方法通过下标获取链表中某个元素的

        Node<E> node(int index) {
            // assert isElementIndex(index);
    
            if (index < (size >> 1)) {
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }

    为了查找的速度,这里先判断这个index是在链表的前半段还是后半段:

    index < (size >> 1) 等价于 index < (size / 2),如果在前半段就正向循环前半段,如果在后半段就反向循环后半段,注意循环时候的细节

    然后继续往下看addAll方法

            for (Object o : a) {
                @SuppressWarnings("unchecked") E e = (E) o;
                Node<E> newNode = new Node<>(pred, e, null);
                if (pred == null)
                    first = newNode;
                else
                    pred.next = newNode;
                pred = newNode;
            }

    需要把整个集合插入到pred和succ之间,所以集合的第一个元素要以pred为前驱,因为是双向链表第一个元素也得是pred的后继,因为有可能插入到index为0的位置,如果index为0那succ是没有前驱的pred就等于null这样就相当于从头部插入整个集合,这段代码的实现就是把集合中的节点按顺序以双向链表的形式连接起来,第一个元素和pred连接

    for循环执行完之后pred会指向参数集合最后一个元素

    下面分析addAll的最后一段代码:

            if (succ == null) {
                last = pred;
            } else {
                pred.next = succ;
                succ.prev = pred;
            }
    
            size += numNew;
            modCount++;
            return true;

    这段代码就是要把参数集合的最后一个元素和succ连接起来,上面分析过了可能出现直接在队尾插入这个集合所以succ可能为null,pred保存的是参数集合最后一个元素,所以出现succ==null的情况参数集合的最后一个元素就是整个链表的最后一个元素,如果succ不为null则需要把succ和pred连接起来,这时参数集合的最后一个元素和succ连接完毕,最后维护一下size和modCount

    (24).clear()

        public void clear() {
            // Clearing all of the links between nodes is "unnecessary", but:
            // - helps a generational GC if the discarded nodes inhabit
            //   more than one generation
            // - is sure to free memory even if there is a reachable Iterator
            for (Node<E> x = first; x != null; ) {
                Node<E> next = x.next;
                x.item = null;
                x.next = null;
                x.prev = null;
                x = next;
            }
            first = last = null;
            size = 0;
            modCount++;
        }

    这就是一个清空集合,实质上是断链,这里可以体会一下for循环的本质:

    for(①;②;④){
       ③
    }

    实质就是先执行1语句,1语句没啥特殊要求,然后进行2语句,2语句是条件判断,符合条件进循环执行3语句,然后执行4语句,不符合就跳出循环,注意不要被平时的习惯掩盖了本质

    (25).public E get(int index) 通过索引获取一个节点的值

        public E get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }

    可以看到get方法检查指针越界使用的是checkElementIndex(index);它的判断是

    index >= 0 && index < size上面已经分析比较过两种越界检查了,然后通过node(index)方法从链表中获取index下标的节点,然后返回这个节点的值

    (26).public E set(int index, E element) 修改index下标节点的值,并返回原来的值

        public E set(int index, E element) {
            checkElementIndex(index);
            Node<E> x = node(index);
            E oldVal = x.item;
            x.item = element;
            return oldVal;
        }

    也是通过checkElementIndex(index);方法检测是否指针越界,通过node(index)方法获取index为下标的节点,记录这个节点的值,修改这个节点的值,返回记录的旧值

    (27).public void add(int index, E element) 在index的位置插入一个元素

        public void add(int index, E element) {
            checkPositionIndex(index);
    
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }

    add方法的指针越界检查是使用的checkPositionIndex(index);它的判断是

    index >= 0 && index <= size,前面比较过两种判断这里就不再说了。

    通过这个指针越界方法我们知道它是允许index == size的,所以要做一个特殊处理,如果直接加在末尾的话就调用linkLast(element);,其他情况就先获取index的元素然后插入,这正好是linkBefore(element, node(index));方法的实现

    (28).public E remove(int index)

        public E remove(int index) {
            checkElementIndex(index);
            return unlink(node(index));
        }

    先做指针越界检查,然后调用unlink(node(index))方法移除一个元素

    (29).public int lastIndexOf(Object o)

        public int lastIndexOf(Object o) {
            int index = size;
            if (o == null) {
                for (Node<E> x = last; x != null; x = x.prev) {
                    index--;
                    if (x.item == null)
                        return index;
                }
            } else {
                for (Node<E> x = last; x != null; x = x.prev) {
                    index--;
                    if (o.equals(x.item))
                        return index;
                }
            }
            return -1;
        }

    和indexOf本质一样,只不过是倒着循环查找,返回第一个匹配的下标

    (30).public E peek()

        public E peek() {
            final Node<E> f = first;
            return (f == null) ? null : f.item;
        }

    返回第一个元素的值,如果集合是空的第一个元素为null就返回null,和getFirst的区别是getFirst如果第一个元素是null就抛NoSuchElementException异常

    (31).public E element()

        public E element() {
            return getFirst();
        }

    调用了getFirst获取集合第一个元素,如果第一个元素是null就抛NoSuchElementException异常

    (32).public E poll()

        public E poll() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }

    返回集合第一个元素的值,并且移除第一个元素,如果是一个空集合第一个元素就是null则返回null

    (33).public E remove()

        public E remove() {
            return removeFirst();
        }

    实际调用的是removeFirest(),返回集合的第一个元素,并且移除第一个元素,如果是一个空集合第一个元素是null则抛出NoSuchElementException异常

    (34).public boolean offer(E e)

        public boolean offer(E e) {
            return add(e);
        }

    调用的add(e)在链表的末尾添加一个元素

    (35).public boolean offerFirst(E e)

        public boolean offerFirst(E e) {
            addFirst(e);
            return true;
        }

    在链表的头部插入一个元素,调用的是addFirst(e)

    (36).public boolean offerLast(E e)

        public boolean offerLast(E e) {
            addLast(e);
            return true;
        }

    在链表的末尾增加一个元素,调用的是addLast(e)

    (37).public E peekFirst()

        public E peekFirst() {
            final Node<E> f = first;
            return (f == null) ? null : f.item;
         }

    和peek的代码一摸一样

    (39).public E peekLast()

        public E peekLast() {
            final Node<E> l = last;
            return (l == null) ? null : l.item;
        }

    代码逻辑和peekFirst类似,只不过是获取的最后一个元素

    (40).public E pollFirst()

        public E pollFirst() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }

    和poll代码一摸一样

    (41).public E pollLast()

        public E pollLast() {
            final Node<E> l = last;
            return (l == null) ? null : unlinkLast(l);
        }

    和pollFirst代码一样,只不过是返回并移除最后一个元素

    (42).public void push(E e)

        public void push(E e) {
            addFirst(e);
        }

    (43).public E pop()

        public E pop() {
            return removeFirst();
        }

    (44).public boolean removeFirstOccurrence(Object o)

        public boolean removeFirstOccurrence(Object o) {
            return remove(o);
        }

    (45).removeLastOccurrence(Object o)

        public boolean removeLastOccurrence(Object o) {
            if (o == null) {
                for (Node<E> x = last; x != null; x = x.prev) {
                    if (x.item == null) {
                        unlink(x);
                        return true;
                    }
                }
            } else {
                for (Node<E> x = last; x != null; x = x.prev) {
                    if (o.equals(x.item)) {
                        unlink(x);
                        return true;
                    }
                }
            }
            return false;
        }

    和remove(o)的代码逻辑类似,只不过是倒着从后往前循环查找,如果有多个相同的元素只移除倒数第一个

    (46).LinkedList的迭代器,public ListIterator<E> listIterator(int index)

        public ListIterator<E> listIterator(int index) {
            checkPositionIndex(index);
            return new ListItr(index);
        }
    
    
         private class ListItr implements ListIterator<E> {
            private Node<E> lastReturned;
            private Node<E> next;
            private int nextIndex;
            private int expectedModCount = modCount;
    
            ListItr(int index) {
                // assert isPositionIndex(index);
                next = (index == size) ? null : node(index);
                nextIndex = index;
            }
    
            public boolean hasNext() {
                return nextIndex < size;
            }
    
            public E next() {
                checkForComodification();
                if (!hasNext())
                    throw new NoSuchElementException();
    
                lastReturned = next;
                next = next.next;
                nextIndex++;
                return lastReturned.item;
            }
    
            public boolean hasPrevious() {
                return nextIndex > 0;
            }
    
            public E previous() {
                checkForComodification();
                if (!hasPrevious())
                    throw new NoSuchElementException();
    
                lastReturned = next = (next == null) ? last : next.prev;
                nextIndex--;
                return lastReturned.item;
            }
    
            public int nextIndex() {
                return nextIndex;
            }
    
            public int previousIndex() {
                return nextIndex - 1;
            }
    
            public void remove() {
                checkForComodification();
                if (lastReturned == null)
                    throw new IllegalStateException();
    
                Node<E> lastNext = lastReturned.next;
                unlink(lastReturned);
                if (next == lastReturned)
                    next = lastNext;
                else
                    nextIndex--;
                lastReturned = null;
                expectedModCount++;
            }
    
            public void set(E e) {
                if (lastReturned == null)
                    throw new IllegalStateException();
                checkForComodification();
                lastReturned.item = e;
            }
    
            public void add(E e) {
                checkForComodification();
                lastReturned = null;
                if (next == null)
                    linkLast(e);
                else
                    linkBefore(e, next);
                nextIndex++;
                expectedModCount++;
            }
    
            public void forEachRemaining(Consumer<? super E> action) {
                Objects.requireNonNull(action);
                while (modCount == expectedModCount && nextIndex < size) {
                    action.accept(next.item);
                    lastReturned = next;
                    next = next.next;
                    nextIndex++;
                }
                checkForComodification();
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

    先看构造方法:

            ListItr(int index) {
                // assert isPositionIndex(index);
                next = (index == size) ? null : node(index);
                nextIndex = index;
            }

    找到以index为下标的元素,作为next,index作为nextIndex,做了一个特殊处理如果index == size那next就是null,为啥index不做current而做next呢?这一点没搞明白。

    remove方法和next还有previous方法得连在一起看,先看next和previous方法

            public E next() {
                checkForComodification();
                if (!hasNext())
                    throw new NoSuchElementException();
    
                lastReturned = next;
                next = next.next;
                nextIndex++;
                return lastReturned.item;
            }
    
            public E previous() {
                checkForComodification();
                if (!hasPrevious())
                    throw new NoSuchElementException();
    
                lastReturned = next = (next == null) ? last : next.prev;
                nextIndex--;
                return lastReturned.item;
            }

    next方法执行完后lastReturned指向当前指针,next指向当前节点的next,然后返回当前的值。

    previous方法需要注意,lastReturned = next = (next == null) ? last : next.prev;

    previous方法执行完后lastReturned和next同时指向next节点的prev就是当前节点,返回当前值

    不知道大家发现没有,这个设计思维就是通过next作为桥梁来实现iterator的功能,尤其是构造方法传入的index直接就当作nextIndex,index所对应的节点直接就当作nextNode节点。

     上面知道了next和previous方法现在看remove方法:

            public void remove() {
                checkForComodification();
                if (lastReturned == null)
                    throw new IllegalStateException();
    
                Node<E> lastNext = lastReturned.next;
                unlink(lastReturned);
                if (next == lastReturned)
                    next = lastNext;
                else
                    nextIndex--;
                lastReturned = null;
                expectedModCount++;
            }

    在执行remove方法前至少执行一次next或previous,因为有这个判断

    if (lastReturned == null)
        throw new IllegalStateException();

    有这个判断导致了一些操作不能进行例如:

    连续进行两次remove操作、先add再remove

    Node<E> lastNext = lastReturned.next;
    unlink(lastReturned);

     上面这两句就是用lastNext保存当前节点的next节点,移除掉当前节点后还能通过lastNext找到next,unlink(lastReturned)方法移除当前节点

    if (next == lastReturned)
        next = lastNext;
    else
        nextIndex--;

    我们上面分析了previous方法,知道执行完previous方法后next和lastReturned会指向同一个节点我们称为当前节点,所以next == lastReturned说明是执行完previous方法后执行的remove,移除当前节点后next和lastReturned不能丢失,那我们就需要把next和lastReturned指向当前节点的next节点(后面的节点会补上),其实就是lastNext节点,这里不用维护nextIndex因为next和nextIndex表示的是当前节点和位置,移除当前节点后,后面节点补上,正序来看位置没有改变。

    如果next != lastReturned说明是执行了next方法后执行的remove,因为next方法执行完后next保存的是当前节点的next节点,所以移除当前节点不会发生指针丢失的情况,所以不用操作指针,因为next和nextIndex都是当前节点的下一个节点,当前节点被移除了下一个节点就会往前移一位所以需要 nextIndex--。

    add方法

            public void add(E e) {
                checkForComodification();
                lastReturned = null;
                if (next == null)
                    linkLast(e);
                else
                    linkBefore(e, next);
                nextIndex++;
                expectedModCount++;
            }

    作用是在next节点前插入一个节点

    (47).public Object clone()

        @SuppressWarnings("unchecked")
        private LinkedList<E> superClone() {
            try {
                return (LinkedList<E>) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new InternalError(e);
            }
        }
    
        /**
         * Returns a shallow copy of this {@code LinkedList}. (The elements
         * themselves are not cloned.)
         *
         * @return a shallow copy of this {@code LinkedList} instance
         */
        public Object clone() {
            LinkedList<E> clone = superClone();
    
            // Put clone into "virgin" state
            clone.first = clone.last = null;
            clone.size = 0;
            clone.modCount = 0;
    
            // Initialize clone with our elements
            for (Node<E> x = first; x != null; x = x.next)
                clone.add(x.item);
    
            return clone;
        }

    ArrayList详解中我们分析过clone方法,知道object的clone是浅克隆,所以需要把指针初始化之后重新生成一个链表。

    (48).public Object[] toArray()

        public Object[] toArray() {
            Object[] result = new Object[size];
            int i = 0;
            for (Node<E> x = first; x != null; x = x.next)
                result[i++] = x.item;
            return result;
        }

    就是遍历链表放入数组,注意 i++

    (49).序列化

        private static final long serialVersionUID = 876323262645176354L;
    
        /**
         * Saves the state of this {@code LinkedList} instance to a stream
         * (that is, serializes it).
         *
         * @serialData The size of the list (the number of elements it
         *             contains) is emitted (int), followed by all of its
         *             elements (each an Object) in the proper order.
         */
        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
            // Write out any hidden serialization magic
            s.defaultWriteObject();
    
            // Write out size
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (Node<E> x = first; x != null; x = x.next)
                s.writeObject(x.item);
        }
    
        /**
         * Reconstitutes this {@code LinkedList} instance from a stream
         * (that is, deserializes it).
         */
        @SuppressWarnings("unchecked")
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            // Read in any hidden serialization magic
            s.defaultReadObject();
    
            // Read in size
            int size = s.readInt();
    
            // Read in all elements in the proper order.
            for (int i = 0; i < size; i++)
                linkLast((E)s.readObject());
        }

    没啥好说的,通篇都是对链表的操作,完结

    展开全文
  • 前言 因之前没有系统学习过数据结构相关知识,所以从现在起将此部分知识过一...物理结构:指数据的逻辑结构在计算机中的存储形式,包含顺序存储结构和链式存储结构 二、线性表之顺序表 1、 为什么会有线性表 数组的特征
  • 按照层次遍历;但是话要说回来,按层遍历,其实就是使用先序遍历,分层遍历二叉树的。...import java.util.LinkedList;class TreeNode{public int val;public TreeNode left;public TreeNode right;public Tr...
  • LinkedList

    千次阅读 2016-04-15 10:04:50
    链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些...
  • ArrayList 和 LinkedList 是 Java 集合框架中用来存储对象引用列表的两个类。ArrayList 和 LinkedList 都实现 List 接口。首先,让我们了解一下它们最重要的父接口——List。 1、List 接口 列表(list)是元素的有序...
  • public static void main(String[] args) { LinkedList list = new LinkedList(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5);//add尾插 //直接遍历 System.out.println(list);//...
  • 如何实现HashMap顺序存储

    千次阅读 2019-01-24 15:14:38
    方法一:维护一张表,存储数据插入的顺序,可以使用vector。但是如果删除数据呢 首先得在vector里面找到那个数据,再删除,而删除又要移动大量数据,性能效率很低 使用list,移动问题可以解决,但是查找数据O的...
  • 在内存中,数据有两种存储方式:顺序存储和链式存储,在Java中有也对应了两种封装的实现—&gt;ArrayList和LinkedList ArrayList 数组的特点:长度固定,可以用索引直接找到元素 插入数据:由于ArrayList的底层...
  • Java对LinkedList进行排序

    千次阅读 2021-03-08 20:42:34
    链表是一个线性数据结构,其中的元素是不能存储在连续的存储单元。 按升序对单链接列表的节点进行排序: 原始清单 排序清单 我们可以通过许多排序技术对LinkedList进行排序: 气泡排序 插入排序 快速...
  • java基础之LinkedList

    2021-10-27 10:59:51
    本文主要是java集合中LinkedList类的相关知识
  • Set和存储顺序

    万次阅读 2019-05-17 12:58:21
    Set和存储顺序 在java中使用set容器存储时,除非是使用了诸如Integer和String 的java预定义的类型,这些类型是被设计可以在容器内部使用的。当我们自己创建类型时,我们需要怎么样的形式来维护存储顺序呢?其实在...
  • LinkedList相关知识总结

    2022-02-10 20:47:10
    LinkedList 是链表实现的线性表(双链表)。是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,遍历时很快,但是插入、删除时都需要移动后面的元素,...
  • Java LinkedList源码分析

    2021-01-20 03:39:05
     LinkedList 是一个常用的集合类,用于顺序存储元素。 LinkedList 经常和 ArrayList 一起被提及。大部分人应该都知道 ArrayList 内部采用数组保存元素,适合用于随机访问比较多的场景,而随机插入、删除等操作因为...
  • 图解LinkedList

    2021-03-04 17:09:07
    上一篇说了ArrayList,这篇文章主要谈谈...先看一下链表的定义:链表是一种物理存储单元上非连续、非顺序存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素
  • Linkedlist:底层是双向链表,因为不是连续存储,只是能找到下一个元素的地址,所以进行添加删除操作效率较高。但查询效率较低,因为只能从第一个挨个找。 1.2基本使用 public static void main(String[] args){ ...
  • 杂谈基本数据结构–线性表: ...  ArrayList和LinkedList顺序存储结构和链式存储结构的表在java语言中的实现.  ArrayList提供了一种可增长数组的实现,使用ArrayList,因为内部使用数组实现,所以,它
  • ArrayList与LinkedList

    2022-04-13 18:47:23
    链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 ArrayList的缺陷 ArrayList底层是使用数组来存储元素的,由于其底层是一段连续空间,当在ArrayList任意位置...
  • 2.CopyOnWriteArrayList内部用于存储元素的Object数组使用volatile //CopyOnWriteArrayList private transient volatile Object[] array; //ArrayList private transient Object[] elementData;//transient 可以...
  • 线性表可以理解为区别于非线性的数据结构(如树[二叉树]、图)来说的 常见的链表(单链表、循环链表、双链表)就是线性表。另外,栈或队列就是一...1.顺序存储ArrayList 2.链式存储LinkedList 两种存储的特点比较:
  • 在上一篇文章中,我们详细介绍了线性表数据结构的原理以及顺序存储结构,并结合ArrayList源码进行了分析,相关文章大家可以点击这里回看我的博客:线性表数据结构解读(一)顺序存储结构ArrayList  本篇文章,我将...
  • 因为不太了解fastjson...所以写了个自定义的顺序存储 JSONObject jsonObject = new JSONObject(); int orignalCount = getTodayCurrentMlfCount(); int waibuCount = 0; int jizhezhanCount = 0; int xhsCoun...
  • 线性表的顺序存储(顺序表)和链式存储(链表) ** 一、顺序表(SeqList)使用一维数组一次存放书元素。一维数组占用一块内存空间,每个存储单元的地址是连续的,通过下标识别元素,它的下标就代表了他的存储单元...
  • LinkedList-双向链表

    2020-11-19 11:19:04
    LinkedList-双向链表 《Hash Map源码基于jdk_8》 1. 类的继承关系 双向链表实现了List和Deque(双向队列)接口 2. 变量及其含义 变量名 默认值 含义 其他补充 size 0 地球人都知道 first 头节点 ...
  • Java集合:LinkedList

    2021-03-08 16:39:17
    链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些存储...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 72,512
精华内容 29,004
关键字:

linkedlist顺序存储