LinkedList源码解析

发布于:2025-05-11 ⋅ 阅读:(18) ⋅ 点赞:(0)

添加元素方法

add方法详解

/*
 E:创建LinkedList对象时泛型中设置好的数据类型
 e: 传递的参数
 boolean: 添加成功即为true,添加失败即为false
*/
 public boolean add(E e) {
        
        // 在链表最后一个结点后面插入新结点(根据待插入元素封装的结点)   
        linkLast(e);

        // 返回true,代表添加成功
        return true;
 }

    
 void linkLast(E e) {

        // 声明一个新结点,将尾节点赋给l
        final Node<E> l = last;

        // 将数据e封装为一个新结点,用于挂载到链表上,三个参数l代表新结点的前一个结点,e新结点的数据域,null即新结点的后继结点
        final Node<E> newNode = new Node<>(l, e, null);


        // 新插入的结点会变成新的尾结点
        last = newNode;
        
        // 如果原来的尾结点为null,代表此前链表中一个结点都没有,此刻心插入结点又是链表的首结点
        if (l == null)
            first = newNode;

        // 原来链表中存在结点时,新插入的结点会是原来尾结点l的后继结点
        else
            l.next = newNode;

        // 插入成功后,链表中结点/元素的数量+1
        size++;

        modCount++;
 }

结论: 通过插入过程,我们发现LinkedList是一个双向链表,因为数据会被封装成结点,进而挂载到双向链表

push方法详解

/**
 * 添加元素
 * 无返回值,参数为程序员调用push方法传递的值
 */
 public void push(E e) {
     addFirst(e);
 }

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

 private void linkFirst(E e) {
        // 声明一个结点f,将首结点赋给f结点
        final Node<E> f = first;

        // 将待添加数据封装成一个新结点,此时null为新结点的前驱结点,e为数据域,f为后继结点
        final Node<E> newNode = new Node<>(null, e, f);

        // 更新首结点
        first = newNode;

        // 如果f结点为null,代表之前首结点为null,即链表中没有结点,此刻新插入的结点也是尾结点
        if (f == null)
            last = newNode;

        // 否则代表之前链表中有结点,将之前的首结点f的前驱结点指向新结点
        else
            f.prev = newNode;

        // 添加后元素个数+1
        size++;
        modCount++;
  }

offer方法详解

// offer方法本质上就是add方法
public boolean offer(E e) {
    return add(e);
}

addAll方法详解


/**
 *  index:待添加元素的位置索引
 *	c:待添加的数据集
 */
public boolean addAll(int index, Collection<? extends E> c) {

		// 检测索引的合法性,当不合法时底层代码会抛出异常,此时程序结束
        checkPositionIndex(index);

        // 将数据集转换为Object类型的数组
        Object[] a = c.toArray();

        // 获取到数据集的长度
        int numNew = a.length;

        // 如果长度为0,证明你要添加的数据集是空集合,不需要添加到链表中去,返回false
        if (numNew == 0)
            return false;


        Node<E> pred, succ;

        // 如果索引为size,代表将集合c追加到链表中
        if (index == size) {
        	// 后继结点设置为null
            succ = null;
            // 当前链表的尾节点设置为前驱结点
            pred = last;
        } else {// 将集合c插入到链表中
        	
        	// 获取当前链表的index位置对应的结点 赋给succ
            succ = node(index);

            // 在获取succ的前驱结点赋给pred
            pred = succ.prev;
        }


        // 遍历整个a集合
        for (Object o : a) {

            @SuppressWarnings("unchecked") E e = (E) o;

            // 将数据o封装为新结点,新结点的前驱结点即为pred,
            // e即为数据域,null是当前结点的后继结点
            Node<E> newNode = new Node<>(pred, e, null);

            // 如果前驱结点为null,证明链表中此时没有结点
            if (pred == null)
            	// 新结点即为我们的首结点
                first = newNode;


            // 证明链表中此时有结点时
            else
            	// pred的后继结点指向新结点
                pred.next = newNode; 

            // pred更新为newNode
            pred = newNode;
        }


        // 如果succ为null,代表我们是以追加的形式存放集合c中
        if (succ == null) {

        	// 此时pred就是我们新链表的尾结点
            last = pred;

        } else {

        	// 代表随机插入的形式存放数据集c到链表,此时原来链表中index对应的结点成了pred的
            // 后继结点,prev本身是集合c中的最后一个元素封装成的结点
            pred.next = succ;
            //原来链表中index对应的结点succ的前驱结点指向了集合c中的最后一个元素封装成的结点
            succ.prev = pred;
        }


        // 元素个数+	集合c中元素的个数 代表此刻LinkedList中的元素个数
        size += numNew;
        modCount++;

        // 返回true,添加成功
        return true;
    }

    /**
     * 检测索引是否合法
     */
    private void checkPositionIndex(int index) {
    	// 如果isPositionIndex方法返回的是false,证明索引不合法,则抛出索引越界异常
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }


    private boolean isPositionIndex(int index) {
    	// 当索引>=0且索引<=size此时 我们是合法的索引 返回true,否则返回false
        return index >= 0 && index <= size;
    }




    /**
     * 获取index 位置对应的结点
     */
     Node<E> node(int index) {
       
        // 如果位置信息 小于 链表中元素个数的一半,此时从前往后遍历
        if (index < (size >> 1)) {

            // 获取首结点作为当前结点
            Node<E> x = first;

            // 从首结点开始向后遍历,index次
            for (int i = 0; i < index; i++)
                // 当前结点的后继结点赋给当前结点
                x = x.next;

            // 出来循环后即找到index位置的结点,将其返回即可
            return x;

        } else {
            //  此时代表从后向前遍历

            // 将尾节点作为当前结点
            Node<E> x = last;
             
            for (int i = size - 1; i > index; i--)
                当前结点往前移动一位    
                x = x.prev;

            // 返回index位置所对应的结点
            return x;
        }

        
        /*
            由此可以证明index参数本质不是索引的概念,因为索引不需要一个个的遍历
            知识点拓展:索引必须在物理内存上是连续的,而链表物理内存上并不连续,只是逻辑上连续而已
        */
    }

set方法详解

/**
 * index:我要修改的结点位置(不是索引,链表中本质是没有索引的概念的)
 * element:要修改成的新元素
 */
 public E set(int index, E element) {

        // 检测位置信息是否合法
        checkElementIndex(index);
        
        // 获取到index对应的结点
        Node<E> x = node(index);

        // 获取要被修改结点的数据域
        E oldVal = x.item;

        // 更新当前结点的数据域
        x.item = element;

        // 返回旧值
        return oldVal;
}

get方法详解

/**
 * index:我查询的位置信息对应的数据,即链表中第index+1结点对应的数据
 */

public E get(int index) {
    // 检测位置信息的合法性
    checkElementIndex(index);

    // 位置信息合法,获取该位置对应的node的数据域,并返回
    return node(index).item;
}

contains代码详解:

/**
 * 查询集合中是否包含元素o,包含则返回true,不包含则返回false
 */
public boolean contains(Object o) {

      // 如果元素o在链表中的位置信息为-1,说明o并不存在,否则o包含在链表之中
    return indexOf(o) != -1;
}


/**
 * 获取元素o对应的位置信息,o在集合中存在则返回对应的位置信息,不存在返回-1
 */
public int indexOf(Object o) {
        // 初始化 位置信息的值为0
        int index = 0;

        // 当o为null
        if (o == null) {
            // 从链表的首结点开始遍历,一直到尾节点
            for (Node<E> x = first; x != null; x = x.next) {

                // 如果当前结点的数据域为null,则说明找到我想要的结点了
                if (x.item == null)
                    // 返回当前结点对应的位置信息
                    return index;
                
                // 没找到时index+1
                index++;
            }
        } else {
              // 从链表的首结点开始遍历,一直到尾节点
            for (Node<E> x = first; x != null; x = x.next) {

                 // 如果当前结点的数据域为null,则说明找到我想要的结点了
                if (o.equals(x.item))
                    return index;

                 // 没找到时index+1
                index++;
            }
        }

        // 整个链表都没找到,那么返回-1
        return -1;
}

clear方法详解

/**
 * 清理整个链表中的数据
 */
 public void clear() {
        // 遍历整个链表,x作为当前结点,初始化为首结点
        for (Node<E> x = first; x != null; ) {

            // 获取到当前结点的下一个结点
            Node<E> next = x.next;
            
            // 将当前结点的数据域、后继结点、前驱结点全设置为空,为了方便GC
            x.item = null;
            x.next = null;
            x.prev = null;
            
            // 将next结点设置为当前要处理的结点
            x = next;
        }
        // 将first last结点设置null
        first = last = null;
        // 链表中元素个数回复为0
        size = 0;

        modCount++;
    }


网站公告

今日签到

点亮在社区的每一天
去签到