java复习——List接口

119 阅读5分钟
  1. List集合中元素有序(添加顺序和取出顺序一致),可以重复
  2. List集合中的每个元素都有整数下标对应,即可以索引
  3. 其实现类常用的有三种:ArrayList 、LinkedList、Vector
public class trestt {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(10);
        list.add("String");
        list.add(true);
        list.add(true);
        list.add(true);
        list.add(true);
        //get获取对应下表的元素
        list.get(0);
        //indexOf从头查找对应元素第一次出现位置的下标
        list.indexOf(10);
        //lastIndexOf从尾查找对应元素第一次出现位置的下标
        list.lastIndexOf(10);
        //set修改对应下标的元素
        list.set(2,1);
        //subList截取,包左不包右边
        List list1 = list.subList(1,3);
    }
}

ArrayList 、LinkedList、Vector的三种遍历方式

List list = new ArrayList;
//迭代器
Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }
//增强for循环
        for (Object o :list) {
            System.out.println(o);
        }
//普通for循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

ArrayList

  1. 可以存放所有类型的元素,包括空
  2. 底层是由数组来实现的
  3. 线程不安全的,效率高

ArrayList底层分析

  1. ArrayList中维护的是一个Object类型的数组——elementData
  2. 使用无参构造器,一开始elementData的容量为0,添加一个元素后容量变为10,不够之后按1.5倍扩容
  3. 使用指定构造器,一开始elementData的容量就为指定容量,不够后不够之后按1.5倍扩容

1.使用无参构造器时,底层分析

//现有如下一段程序
public class trestt {
    public static void main(String[] args) {
        List list = new ArrayList();//创建一个list
        for (int i = 1; i <10 ; i++) {
            list.add(i);//进行10次添加
        }
    }
}

对程序进行DeBug,下列皆为源码

//第一步:初始化
public ArrayList() {
    //进入无参构造器,这里将elementData数组初始化为空,并且初始化modCount:被修改次数
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
//第二步:装箱
//因为elementData数组是一个Object类型,所以添加时需要对元素进行装箱
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
//第三步:扩容
public boolean add(E e) {
    //1.进入该方法,传入一个最小容量也就是1,因为size是0
    ensureCapacityInternal(size + 1);
    //11.将元素放入扩容后的数组当中
    elementData[size++] = e;
    return true;
}
​
private void ensureCapacityInternal(int minCapacity) {
    //2.将最小容量和elementData传入calculateCapacity方法,
    //  并将返回值传入ensureExplicitCapacity方法
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//5.这里将10传入
}
​
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //3.在这里判断数组是不是为空,不会空就返回最小容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //4.当前数组为空,进入方法,这里DEFAULT_CAPACITY=10
        //  max对这里DEFAULT_CAPACITY和最小容量进行比较,选出最多并返回
        return Math.max(DEFAULT_CAPACITY, minCapacity);//这里返回10
    }
    return minCapacity;
}
​
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;//修改次数记录
​
    if (minCapacity - elementData.length > 0)
        //6.如果最小容量减去数组的长度大于0,就说明数组容量不够,就进入grow进行扩容
        //  例如现在,最小容量为10,数组长度为0,所以10-0大于1,需要扩容
        grow(minCapacity);
}
​
*重点*
private void grow(int minCapacity) {//接收到最小容量10
    //7.将数组长度进行记录,oldCapacity = 0
    int oldCapacity = elementData.length;
    
    //8.这里将会进行1.5倍扩容,0右移1位是1,所以这里newCapacity = 0+1 = 1
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    //9.用新容量减去最小容量,如果小于零说明扩容后的容量没有最小容量大,
    //  所以将最小容量赋值给新容量,
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;//这里newCapacity = 10;
    
    //将新容量-MAX_ARRAY_SIZE。如果大于0,将会进行溢出处理,此处小于0,就直接过了
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //溢出处理:首先判断最小容量是否小于0,
        //如果小于0,就抛出内存不足异常;
        //否则判断最小容量是否大于MAX_ARRAY_SIZE,是则返回一个更大的容量,否则返回MAX_ARRAY_SIZE
        newCapacity = hugeCapacity(minCapacity);
    //10.将旧数组的元素进行拷贝和扩容,赋给新数组   
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2.使用条件构造器时,底层分析

//现有如下一段程序
public class trestt {
    public static void main(String[] args) {
        List list = new ArrayList(5);//创建一个list,初始化容量为5
        for (int i = 1; i <10 ; i++) {
            list.add(i);//进行10次添加
        }
    }
}

对程序进行DeBug,下列皆为源码

//第一步:初始化
public ArrayList(int initialCapacity) {
    //首先对初始容量进行判断
    //如果大于0,就创建一个Object类型容量为initialCapacity的数组elementData
    //如果等于0,就讲elementData初始化为空数组
    //如果小于0,就抛出异常
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
//第二步:装箱
//因为elementData数组是一个Object类型,所以添加时需要对元素进行装箱
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
//第三步:扩容大部分与无参类似
//不同点在于一开始添加元素的时候
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
     //在这里,elementData并不为空,所以不会将容量扩充为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
     //而是返回定义好的容量
        return minCapacity;
    }
​
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    //到该方法时,最小容量-数组长度小于零,所以在一开始的时候并不会调用grow去进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//当容量不够时就会和无参一样进行1.5倍扩容

Vector

  1. Vector的底层也是一个对象数组,elementData
  2. Vector是线程同步的,即线程安全的,有synchronized属性
  3. 线程安全,但是效率不高

Vector底层分析

  1. Vector中维护的是一个Object类型的数组——elementData
  2. 使用无参构造器,一开始调用有参构造器并赋值10,不够之后按2倍扩容
  3. 使用指定构造器,一开始elementData的容量就为指定容量,不够后不够之后按2倍扩容
//现有如下一段程序
public class trestt {
    public static void main(String[] args) {
        //List list = new Vector();
        List list = new Vector(1);
        for (int i = 1; i <10 ; i++) {
            list.add(i);
        }
    }
}
//第一步:创建对象
//1.在创建对象时,使用有参构造器时
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
//2.使用无参构造器时底层调用与有参基本相同
//就是在一开始时调用有参构造器,并初始化10的容量
public Vector() {
        this(10);//初始化容量为10
    }
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

之后使用无参讲解

//第二步:初始化
public Vector(int initialCapacity, int capacityIncrement) {
    //初始化modCount:被修改次数
        super();
    //如果初始容量小于0,就抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    //否则,就创建一个Object类型容量为initialCapacity的数组elementData
    //容量增量赋值为0
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
//第三步:装箱
//因为elementData数组是一个Object类型,所以添加时需要对元素进行装箱
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
//第四步:扩容
public synchronized boolean add(E e) {
        modCount++;//修改增量加加
    //1.判断是否扩容,这里elementCount=10
        ensureCapacityHelper(elementCount + 1);
    //7.将元素放入扩容后的数组当中
        elementData[elementCount++] = e;
        return true;
    }
​
private void ensureCapacityHelper(int minCapacity) {
    //2.如果最小容量-数组长度大于0
    //  就说明数组容量不够,需要扩容
    //  此时最小容量等于11,数组长度等于10,11-10大于0所以需要扩容
        if (minCapacity - elementData.length > 0)
            //进入方法
            grow(minCapacity);
    }
​
private void grow(int minCapacity) {
    //3.将数组长度赋值给oldCapacity = 10
        int oldCapacity = elementData.length;
    //4.如果扩容增量大于0,capacityIncrement未指定的情况下为0,所以newCapacity = 2倍oldCapacity = 20
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
    //5.判断新容量-最小容量是否小于0,此时20-11大于0,所以跳过
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
    //此处时溢出处理,内存不足问题
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
    //6.将旧数组的元素进行拷贝和扩容,赋给新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

需要注意:capacityIncrement(容量增量),当他不为0时,扩容不一定是以2倍扩容

capacityIncrement是在有参构造器中是可以指定的

LinkedList

1.LinkedList的底层实现了双向链表双端队列特点

2.可以添加任意元素,可以重复,可以为null

3.线程不安全

LinkedList底层分析

  1. LinkedList底层维护的是一个双向链表
  2. LinkedList中维护了两个属性first和last,分别指向链表的首和尾
  3. 每个节点,又维护了prev、next、item三个属性,next指向下一个节点,prev指向上一个节点;
  4. 所以LinkedList的添加和删除,不是通过数组实现的,效率较高
//模拟双向链表
public class trestt {
    public static void main(String[] args) {
        //创建三个节点
        Node jacke = new Node("jacke");
        Node tom = new Node("tom");
        Node kave = new Node("kave");
        //创建头结点和尾结点
        Node first = jacke;
        Node last = kave;
        //将节点之间首尾连接
        jacke.next = tom;
        tom.next = kave;
        kave.prve = tom;
        tom.prve = jacke;
        //循环遍历
        while (true){
            if(first==null){
                break;
            }
            System.out.println(first);
            first = first.next;
        }
    }
}
//创建node类
class Node{
    public Object item;//元素
    public Node next;//指向下一个节点
    public Node prve;//指向上一个节点
​
    public Node(Object item) {
        this.item = item;
    }
    @Override
    public String toString() {
        return  "item=" + item ;
    }
}

对LinkedList的增的结构分析

添加元素

//现有如下一段代码
public class trestt {
    public static void main(String[] args) {
        List list = new LinkedList();
        list.add(1);//断点DeBug
        list.add(2);
    }
}

对程序进行DeBug,下列皆为源码

//第一步:装箱
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
//第二步:添加
public boolean add(E e) {
        linkLast(e);//1.进入linkLast方法
        return true;
    }
void linkLast(E e) {
    //2.创建一个节点存放last,此时last为null
        final Node<E> l = last;
    //3.创建一个新节点,存放元素e,此时next和prev都为null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
    //4.如果l等于空,那么这个节点即是头结点也是尾节点
        if (l == null)
            first = newNode;
        else//当第二次添加的时候,l里存放的是上一个节点的位置,
            //所以l不等于空,说明前面有节点,这里只需要把尾节点指向newNode
            l.next = newNode;
        size++;
        modCount++;
    }

List的选择

底层结构增删效率改查效率线程问题
ArrayListelementData数组低,需要扩容高,可以根据下标查询线程不安全
VectorelementData数组低,需要扩容高,可以根据下标查询线程安全
LinkedListNode节点,双向链表高,可以直接插入低,需要从头查询线程不安全

根据不同的业务场景进行选择使用。