浅谈LinkedList

Posted mango-tree

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈LinkedList相关的知识,希望对你有一定的参考价值。

浅谈LinkedList

LinkedList是一个链表结构类型的列表,底层通过链表结构来存储数据的链式存储,可以无限链接新元素(受限于硬盘存储容量),不存在ArrayList(底层使用数组实现)中的数组扩容问题,具有插入,删除元素快捷、方便的特点,但因为每个节点需要有上一个节点和下一个节点的引用,从而导致了每个结点需要存储空间的增加,而且不能做到像ArrayList那种快速随机访问指定元素(即可以直接根据索引访问元素值),在LinkedList中获取元素都需要从头节点开始逐个访问链表节点,源码中的节点查找方法(node(int index)方法)使用了头节点和尾节点同时往中间节点查找的方式,一定程度上提高了查找效率,以下为对应的源码:

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

下面我们开始进入LinkedList的介绍,依旧是从构造方法开始讲起,在LinkedList中为我们提供了两个构造方法,我们来逐个查看:

LinkedList();

这是一个无形参的空构造,方法内除了隐藏的super();再没有其他初始化代码,实例对象中的未赋值属性将全部按照成员变量的默认值进行赋值,分别有first和last,还有一个size属性赋值为0

LinkedList(Collection<? extends E> c);

该构造方法传入了一个集合对象c,并在构造方法的首行使用this()调用了空构造方法,并在之后调用addAll(Collection<? extends E> c)方法将传入集合的所有元素添加到列表中

public LinkedList(Collection<? extends E> c) {
    //调用空构造
    this();
    //将集合中的所有元素添加到列表的尾部
    addAll(c);
}

到这里构造方法部分也就介绍完了,在介绍LinkedList中提供的公开方法之前,要注意的是LinkedList和ArrayList一样是一个非线程安全的类对象,所以如果涉及并发操作,建议在初始化的时候就调用Collections.synchronizedList(list)来将线程不安全的列表对象转化为线程安全的对象,内部的实现原理:使用代理模式在原来的方法执行之前嵌套了一个同步机制,这里摘取其中的size()方法,源码如下:

public int size() {
   synchronized (mutex) {  //同步代码块
       return c.size();    //c为被代理对象
   }
}

因为给所有的方法加上锁会降低代码的执行效率,而且有些方法是不需要加锁的,如果不想对所有的方法都加锁,可以在需要加锁的特定方法调用之前手动的做同步处理

下面进入LinkedList中公开方法的详细解析,对于功能完全一样的方法会做为同类处理,对于索引这个词语的使用,仅表示从首节点开始算起,迭代的个数值减一,非真正的数组索引:

offer(E element); offerLast(E element); add(E element);

这三个方法功能相同,都是往列表的尾部链接上新元素,并返回一个布尔值,在offer(E element)方法中仅仅只是调用了add(E element)方法,没有做其他任何操作,而offerLast(E element)调用的是addLast(E element)方法,在add(E element)和addLast()方法中调用了内部的linkLast(E element)方法

offerFirst(E element);

该方法用于往列表的头部链接上新地元素,并返回一个布尔值,在offerFirst(E element)方法中调用的是addFirst(E element)方法,在addFirst(E element)方法调用内部的linkFirst(E element)方法

add(int index, E element);

该方法用于在指定索引的位置插入指定元素,无返回值

push(E element); addFirst(E element);

这两个方法功能相同,都是往列表头部链接上新元素,无返回值,在push(E element)方法中仅仅只是调用了addFirst(E element)方法,没有做其他任何操作,在addFirst(E element)中调用了内部的linkFirst(E element)方法

addLast(E element);

该方法用于在列表尾部链接上新元素,无返回值,方法内调用内部的linkLast(E element)方法

pop(); remove(); removeFirst();

这三个方法功能相同,都是移除列表的首元素,如果首元素不存在会抛出异常,存在则返回首元素的内容值,在pop()方法和remove()方法中仅仅只是调用了removeFirst()方法,没有做其他任何操作,在removeFirst()方法调用了内部的unlinkFirst(first)方法

removeLast();

该方法用于移除列表的尾元素,如果尾元素不存在会抛出异常,存在则返回尾元素的内容值,在removeLast()方法调用了内部的unlinkLast(last)方法

poll(); pollFirst();

这两个方法功能相同,都是移除列表的首元素并返回该元素,与removeFirst()方法不同的是,如果首元素不存在则返回null,poll()方法和pollFist()方法内都是调用的内部unlinkFirst(first)方法

pollLast();

该方法用于移除列表的尾元素并返回该元素,与removeLast()方法不同的是,如果尾元素不存在则返回null,方法内调用的是内部unlinkLast(last)方法

peek(); peekFirst();

这两个方法功能相同,都是获取列表的首元素的内容值,而且不从列表中删除该元素,如果首节点不存在则返回null

peekLast();

该方法用于获取列表的尾元素的内容值,而且不从列表中删除该元素,如果尾元素不存在则返回null

element(); getFirst();

这两个方法功能相同,都是检索列表的首元素、返回该元素而且不删除该元素,如果首元素不存在则抛出异常,在element()方法中仅仅只是调用了getFirst()方法,没有做其他任何操作

getLast();

该方法用于检索列表尾元素、返回该元素而且不删除该元素,如果尾元素不存在,则抛出异常

removeFirstOccurance(Object o); remove(Object o);

这两个方法功能相同,都是用于删除列表中出现的第一个指定元素,返回值为布尔值,在removeFirstOccurance(Object o)方法中仅仅只是调用了remove(Object o)方法,没有做其他任何操作,remove(Object o)方法内调用内部方法unlink(x)方法,其中x为迭代查找到的列表元素

removeLastOccurance(Object o);

该方法用于删除列表中最后一个指定元素,返回值为布尔类型,方法内调用内部方法unlink(x)方法,其中x为迭代查找到的列表元素

remove(int index);

该方法用于删除列表中的指定索引位置的元素,并返回被删除的元素内容值,方法内调用内部方法unlink(node(index))方法

addAll(Collection<? extends E> c);

该方法用于向列表中添加传入集合的元素值,内部调用addAll(size,c);即从列表尾部链接所有新元素

addAll(int index, Collection<? extends E> c);

该方法用于向列表中的指定索引位置链接上所有的新元素

get(int index);

该方法用于获取指定索引位置的列表元素内容值

indexOf(Object o);

该方法用于获取指定元素在列表中第一次出现的索引值,未找到则返回-1

lastIndexOf(Object o);

该方法用于获取指定元素在列表中最后一次出现的索引值,未找到则返回-1

contains(Object o);

该方法用于判断列表中是否含有特定的元素值,内部调用indexOf(Object o)方法

size();

该方法用于获取列表中元素的个数值,即列表的长度

set(int index, E element);

该方法用于将指定索引的元素替换为传入值,并返回被替换下元素的内容值

clear();

该方法用于清空列表中的所有值,用遍历方式清空所有列表元素,最后把last=first=0,并把size=0

toArray(T[] a);

该方法用于将列表转化为数组,可以在形参中指定返回数组的类型,例如:list.toArray(new String[0])即可将列表转化为String[]类型的数组并返回,无参方法相当于传入new Object[0],返回值类型为Object[]

clone();

该方法用于返回一个当前列表的浅克隆对象

listIterator(int index);

该方法用于返回一个当前列表的迭代器对象,可以指定迭代起始位置,内部提供了hasNext(),next(),hasPrevious(),previous(),nextIndex(),previousIndex(),remove()-删除当前位置的元素值,set(E element)-替换当前位置的元素值,add(E element)-在当前位置插入新的元素,forEachRemaining(Consumer<? super E> action)-用于对所有未遍历对象执行传入的指定操作逻辑
注意:remove()方法不可连续调用多次,因为在一次调用之后,内部的lastReturned属性将变为null,当下一次调用previous()或next()方法时会重新被赋值,其次,在使用迭代器遍历列表元素时,不可以直接使用外部列表的方法对列表的结构进行修改(新增元素或删除元素),否则会报错,不过可以使用迭代器内部的add和remove方法来实现元素的新增和删除操作

descendingIterator();

该方法用于返回一个逆序的迭代器对象,实现原理:内部包含一个listIterator对象,并将索引初始化为size,然后每当调用descendingIterator的next方法时,调用内部listIterator的previous方法,previous方法同理

spliterator();

该方法用于返回一个可分割的列表对象(该对象底层为数组结构),可以用于多线程并发操作同一个列表的多个分割对象,调用该方法返回的对象可以调用trySplit()方法进行元素分割(五五分成),不过要注意的是第一次调用的trySplit()方法和后面调用的trySplit()方法不是同一个,具体查看源码:

//首次调用的是列表对象调用spliterator方法生成的LLSpliterator内部类的trySplit方法,返回值为ArraySpliterator实例对象
public Spliterator<E> trySplit() {
    Node<E> p;
    //获取剩余元素个数
    int s = getEst();
    //若元素个数大于1,并且首节点存在
    if (s > 1 && (p = current) != null) {
        //设置单次切割上限值,最大一次切分1024个元素
        int n = batch + BATCH_UNIT;
        //调整切割上限值
        if (n > s)
            n = s;
        //调整切割上限值,其中MAX_BATCH=2^25=33554432
        if (n > MAX_BATCH)
            n = MAX_BATCH;
        //新建一个对象数组,用于存储分割出的元素,因为ArraySpliterator是不支持链表结构的
        Object[] a = new Object[n];
        int j = 0;
        //其中的j<n用于控制切割数量
        do { a[j++] = p.item; } while ((p = p.next) != null && j < n);
        //将当前元素置为切割出去元素的后一个元素
        current = p;
        //将batch值置为切分出去的最后一个元素的索引的下一个索引位置
        batch = j;
        //当前列表元素个数减取切割出去的个数
        est = s - j;
        //调用Spliterators.spliterator方法,返回一个ArraySpliterator
        //下一个调用的trySplit将不再是这里的trySplit,而是ArraySpliterator中的trySplit方法,对列表元素进行对半切分
        return Spliterators.spliterator(a, 0, j, Spliterator.ORDERED);
    }
    return null;
}
public static <T> Spliterator<T> spliterator(Object[] array, int fromIndex, int toIndex, int additionalCharacteristics) {
    checkFromToBounds(Objects.requireNonNull(array).length, fromIndex, toIndex);
    return new ArraySpliterator<>(array, fromIndex, toIndex, additionalCharacteristics);
}
//第一次之后调用的trySplit方法,来自ArraySpliterator类中
public Spliterator<T> trySplit() {
    int lo = index, mid = (lo + fence) >>> 1;
    return (lo >= mid) ? null : new ArraySpliterator<>(array, lo, index = mid, characteristics);
}

使用spliterator的示例代码

LinkedList<Object> list = new LinkedList<>();
list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");
list.add("6");list.add("7");list.add("8");list.add("9");
Spliterator<Object> spliterator = list.spliterator();
Spliterator<Object> a = spliterator.trySplit();
Spliterator<Object> b = a.trySplit();
Spliterator<Object> c = a.trySplit();
Spliterator<Object> d = b.trySplit();
a.forEachRemaining(System.out::println);
System.out.println();
b.forEachRemaining(System.out::println);
System.out.println();
c.forEachRemaining(System.out::println);
System.out.println();
d.forEachRemaini

对应的处理结果为:

7
8
9

3
4

5
6

1
2

如果对你有帮助,点个赞,或者打个赏吧,嘿嘿
整理不易,请尊重博主的劳动成果

以上是关于浅谈LinkedList的主要内容,如果未能解决你的问题,请参考以下文章

浅谈java容器Connection和Map

浅谈Mybatis

浅谈AngularJS中的$parse和$eval

浅谈Vector

浅谈java集合

浅谈List容器