20集合(List接口补充)

Posted shawnyue-08

tags:

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

List接口

List接口:元素有序,可重复,集合中每个元素都有其对应的索引。

常用实现类有:ArrayList、LinkedList、Vector。

ArrayList源码

JDK 1.7情况下:

ArrayList<String> list = new ArrayList<>();

使用空参构造器创建集合,底层创建了长度为10的Object类型数组elementData。

扩容机制:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
//扩容为原来的1.5倍
elementData = Arrays.copyOf(elementData, newCapacity);

Arrays数组工具类的copyOf()拷贝原数组,底层会根据newCapacity创建一个新的数组,然后

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

这样,返回的copy数组是一个大数组,其余元素均为0。

JDK 1.8情况下:

通过空参构造器创建集合

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

通过有参构造器创建集合

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
transient Object[] elementData;
private static final Object[] EMPTY_ELEMENTDATA = {};

当使用add()方法向空参创建的集合中添加元素时:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

首先确保内部容量,ensureCapacityInternal(1),先看内部的容量是否大于1,

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

首先计算容量,

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

if的条件表达式返回true,return Math.max(10, 1);所以返回10。

private static final int DEFAULT_CAPACITY = 10;

计算完容量后,执行ensureExplicitCapacity(10)方法;

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

if的条件表达式返回true(minCapacity = 10),执行扩容。

扩容机制和JDK 1.7一致。

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //0
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //0
    if (newCapacity - minCapacity < 0)
        //0 - 10 < 0
        newCapacity = minCapacity;
    	//newCapacity = 10
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
    //elementData = Arrays.copyOf(elementData, 10);
}

方法弹栈,继续执行add(E e)方法。

elementData[size++] = e;
return true;

elementData[0] = e,size = 1;

结论:JDK 1.7是饿汉式创建elementData,而JDK 1.8是懒汉式创建elementData,在第一次add添加时再扩容。

并发修改异常

Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果你在迭代遍历某个容器的过程中,另一个进程介入其中,并且插入,删除或修改此容器的某个对象,就会立刻抛出ConcurrentModificationException

使用迭代器删除集合元素

package org.westos.demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

/**
 * @author lwj
 * @date 2020/5/12 14:23
 */
public class MyTest {
    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<>();
        strings.add("aaa");
        strings.add("bbb");
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            iterator.remove();
        }
        //删除集合中的元素
        //增强for不能在遍历途中修改集合,要想在遍历过程中修改集合,只能使用迭代器或者普通for循环
        System.out.println(strings);
        //[]
    }
}

LinkedList源码

LinkedList,泛型类,继承泛型抽象类,泛型接口,暂不声明类型,等到创建对象时声明类型。

LinkedList<String> strings = new LinkedList<>();

将参数"String"传递给E类型,即Node节点的值域存储字符串类型。当传递自定义类型时,

LinkedList<Student> students = new LinkedList<>();

Node节点的值域就存储Student类型数据。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;
    transient Node<E> first;
    //第一个节点node的指针
    transient Node<E> last;
    //最后一个节点的指针
}

Node是LinkedList的静态内部泛型类,和LinkedList共用一个类型,定义了一个节点类,包含item域,next后驱节点,prev前驱结点。

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

当使用add(E e)方法向链表中增加元素时,

public boolean add(E e) {
    linkLast(e);
    return true;
}
//linkLast(e) 链接e作为最后一个元素
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    //prev = l, item = e, next = null
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

方法addLast(E e)和add(E e)逻辑相同,但是返回值为void;

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

方法addFirst(E e)底层调用linkFirst(E e);

public void addFirst(E e) {
    linkFirst(e);
}
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++;
}

方法add(int index, E e)底层:

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
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++;
}

插入节点,首先调用node(index)找到待插入位置的节点。

删除节点

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;
}
package org.westos.demo;

import java.util.LinkedList;

/**
 * @author lwj
 * @date 2020/5/12 15:44
 */
public class MyTest2 {
    public static void main(String[] args) {
        LinkedList<String> strings = new LinkedList<>();
        strings.add("aaa");
        strings.addLast("bbb");
        strings.addFirst("ccc");

        System.out.println(strings);
        //[ccc, aaa, bbb]

        strings.add(1, "ddd");

        System.out.println(strings);
        //[ccc, ddd, aaa, bbb]

        String remove = strings.remove(3);
        System.out.println(remove);
        //bbb
        System.out.println(strings);
        //[ccc, ddd, aaa]
    }
}

List接口的常用方法

LinkedList,双向链表,内部没有声明数组(不是数组实现),而是定义了Node<E>类型的first和last,用于记录首尾元素。同时,定义内部类Node<E>,作为LinkedList中保存数据的基本结构。

List接口除了从Collection接口继承的方法外,List接口添加了一些根据索引来操作集合元素的方法。

方法名 描述
void add(int index, E element) 将指定元素插入此列表的指定位置
void addAll(int index, Collection<? extends E> c) 将指定集合插入此列表的指定位置
E get(int index) 返回此列表中指定位置的元素
int indexOf(Object o) 返回此列表中指定元素第一次出现的索引
int lastIndexOf(Object o) 返回此列表中指定元素最后一个出现的索引
E remove(int index) 删除此列表中指定位置的元素
E set(int index , E element) 用指定的元素替换此列表中指定位置的元素
List<E> subList(int fromIndex, int toIndex) 返回此列表中指定的fromIndex到toIndex的元素集合
package org.westos.demo;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * @author lwj
 * @date 2020/5/12 18:56
 */
public class MyTest3 {
    public static void main(String[] args) {
        LinkedList<String> strings = new LinkedList<>();
        strings.add(0, "aaa");
        strings.add(1, "bbb");
        strings.add(2, "ccc");
        strings.addFirst("AAA");
        strings.addLast("CCC");
        System.out.println(strings);
        //[AAA, aaa, bbb, ccc, CCC]

        strings.addAll(5, Arrays.asList("ddd", "DDD"));
        System.out.println(strings);
        //[AAA, aaa, bbb, ccc, CCC, ddd, DDD]

        String remove = strings.remove(0);
        //删除指定索引的元素
        System.out.println(remove);
        //AAA
        System.out.println(strings);
        //[aaa, bbb, ccc, CCC, ddd, DDD]
        //strings.removeFirst();
        //strings.removeLast();

        String s = strings.get(0);
        //根据索引返回元素
        System.out.println(s);
        //aaa
        String first = strings.getFirst();
        System.out.println(first);
        //aaa
        String last = strings.getLast();
        System.out.println(last);
        //DDD


        int bbb = strings.indexOf("bbb");
        System.out.println(bbb);
        //1
        int ccc = strings.lastIndexOf("ccc");
        System.out.println(ccc);
        //2

        String aaa = strings.set(0, "AAA");
        System.out.println(aaa);
        //aaa
        System.out.println(strings);
        //[AAA, bbb, ccc, CCC, ddd, DDD]

        List<String> strings1 = strings.subList(0, 3);
        System.out.println(strings1);
        //[AAA, bbb, ccc]
        System.out.println(strings);
        //[AAA, bbb, ccc, CCC, ddd, DDD]
    }
}

总结:

增:add(Object obj)/add(int index, E e)/addFirst(E e)/addLast(E e)/addAll(Collection c)/addAll(int index, Collection c)

删:remove(Object obj)/remove(int index)/removeFirst()/removeLast()

改:set(int index, E element)

查:get(int index)/getFirst()/getLast()

List的遍历方法

除了普通for循环+size()方法、增强for循环、Iterator接口外,新增了一个ListIterator接口。

package org.westos.demo;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

/**
 * @author lwj
 * @date 2020/5/12 19:30
 */
public class MyTest4 {
    public static void main(String[] args) {
        LinkedList<String> strings = new LinkedList<>(Arrays.asList("aaa", "bbb", "ccc"));
        //1、普通for循环 + size()
        for (int i = 0; i < strings.size(); i++) {
            System.out.println(strings.get(i));
        }
        //2、增强for循环
        for (String string : strings) {
            System.out.println(string);
        }
        //3、Iterator
        Iterator<String> iterator = strings.iterator();
        /*
        public Iterator<E> iterator() {
            return listIterator();
        }
         */
        System.out.println(iterator);
        //java.util.LinkedList$ListItr@1b6d3586
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        //4、ListIterator
        ListIterator<String> stringListIterator = strings.listIterator();
        System.out.println(stringListIterator);
        //java.util.LinkedList$ListItr@4554617c
        //private class ListItr implements ListIterator<E> {}
        while (stringListIterator.hasNext()) {
            System.out.println(stringListIterator.next());
        }
        //5、ListIterator从后向前遍历
        ListIterator<String> stringListIterator1 = strings.listIterator(strings.size());
        while (stringListIterator1.hasPrevious()) {
            System.out.println(stringListIterator1.previous());
        }
    }
}

练习题

remove(int index)和remove(Object o)

package org.westos.demo;

import java.util.Arrays;
import java.util.LinkedList;

/**
 * @author lwj
 * @date 2020/5/12 19:42
 */
public class MyTest5 {
    public static void main(String[] args) {
        LinkedList<Integer> integers = new LinkedList<>(Arrays.asList(1, 2, 3, 4, 5, 6));
        Integer remove = integers.remove(2);
        //删除集合中索引为2的元素
        System.out.println(remove);
        //3
        //根据索引删除,返回原索引位置的值

        //如果想要删除值为2的元素呢
        boolean remove1 = integers.remove(Integer.valueOf(2));
        System.out.println(remove1);
        //true
        //删除值,返回boolean类型

        System.out.println(integers);
        //[1, 4, 5, 6]
    }
}

以上是关于20集合(List接口补充)的主要内容,如果未能解决你的问题,请参考以下文章

20集合(Set接口补充)

20集合(Set接口补充)

Java的Iterator迭代器补充,增强for循环,泛型,List接口,set接口

JavaSE 集合补充点(JDK1.9对集合添加的优化)

集合总结(Collection,List,Set,Map)(补充集合结构图的关系)

201621123057 《Java程序设计》第9周学习总结