JavaSE List接口(ArrayList、LinkedList、Vector)

一、List接口概述

  • List接口继承自Collection(is a)
  • 存储元素特点:有序可重复
    有序是指元素存与取的顺序相同(不是指排序),元素有下标,下标从0开始,以1递增
    可重复是指集合中的元素可以相同

二、List接口特有的常用的方法

链接: Collection中的常用方法

1. add()

  • void add(int index, Object element)
  • 在列表的指定位置插入指定元素(第一个参数是下标)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		/**
		 * 在列表的指定位置插入指定元素(第一个参数是下标)
		 */
		// 创建List类型的集合
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("D");

        // 这个方法使用不多,因为对于ArrayList集合来说效率比较低
        myList.add(1, "a");

        Iterator it = myList.iterator();
        while(it.hasNext()){
            Object o = it.next();
            System.out.println(o);
        }
	}
}

2. set()

  • Object set(int index, Object element)
  • 修改指定位置的元素
import java.util.ArrayList;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		/**
		 * 修改指定位置的元素
		 */
		// 创建List类型的集合
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");
        
        // 修改指定位置的元素
        myList.set(2, "M");

        // 遍历集合
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }
	}
}

3. get()

  • Object get(int index)
  • 根据下标获取元素
public class Test {
	public static void main(String[] args) {
		/**
		 * 根据下标获取元素
		 */
		// 创建List类型的集合
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("D");

        // 根据下标获取元素
        Object firstObj = myList.get(0);
        System.out.println(firstObj); // A

        // 因为有下标,所以List集合有自己比较特殊的遍历方式
        // 通过下标遍历,List集合特有的方式,Set没有
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }
	}
}

4. indexOf()

  • int indexOf(Object o)
  • 获取指定对象第一次出现处的索引
import java.util.ArrayList;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		/**
		 * 获取指定对象第一次出现处的索引
		 */
		// 创建List类型的集合
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        // 获取指定对象第一次出现处的索引
        System.out.println(myList.indexOf("C")); // 2
	}
}

5. lastIndexOf()

  • int lastIndexOf(Object o)
  • 获取指定对象最后一次出现处的索引
import java.util.ArrayList;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		/**
		 * 获取指定对象最后一次出现处的索引
		 */
		// 创建List类型的集合
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        // 获取指定对象最后一次出现处的索引
        System.out.println(myList.lastIndexOf("C")); // 3
	}
}

6. remove()

  • Object remove(int index)
  • 删除指定下标位置的元素
import java.util.ArrayList;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		/**
		 * 删除指定下标位置的元素
		 */
		// 创建List类型的集合
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        // 删除指定下标位置的元素
        myList.remove(0);
        System.out.println(myList.size()); // 4

	}
}

三、ArrayList

  • ArrayList 继承了 AbstractList ,并实现了 List 接口

  • 底层是可变长数组,查询快、增删改慢,因为底层数组操作连续的内存空间,只适合查询,不适合频繁的增删

  • 以下情况使用 ArrayList :

  1. 频繁访问列表中的某一个元素
  2. 只需要在列表末尾进行添加和删除元素操作

1. ArrayList的初始化容量

  • ArrayList底层是Object类型的数组,默认初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)
// 底层是Object类型的数组
transient Object[] elementData;
private static final int DEFAULT_CAPACITY = 10; // //默认容量
// 数组,没有向数组中添加元素
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空数组,底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 无参构造方法
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2. ArrayList的扩容

  • ArrayList底层默认长度是10,每次元素进来判断是否溢出,有溢出则扩大为原来的1.5倍
  1. 先判断列表的capacity容量是否足够,是否需要扩容;
  2. 再将元素放在列表的元素数组里
    源码分析:
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 检查是否需要扩容
    elementData[size++] = e; // 把元素插到最后一位
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

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

    // 扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 容量增长为原来的1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity; // 如果新容量发现比最小容量还小,则以最小容量为准
    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); //以新容量扩容出来一个新数组
}

2.1 ArrayList扩容优化

  • 尽可能少的扩容,因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量
  • 回顾一下数组的优缺点
  • 优点:
    查询/检索某个下标对应的元素效率极高
  • 原因:
    每一个元素的内存地址在空间存储上是连续的
    每一个元素数据类型相同,所以占用空间大小一样
    已知元素内存地址、元素占用空间的大小、下标,可以方便的通过一个数学表达式计算出某个下标上元素的内存地址(偏移量),即直接通过内存地址定位元素
  • 缺点:
    随机增删效率较低(最后一个元素除外);无法存储大数据量
  • 原因:
    为了保证数组中每个数组元素的内存地址连续,增删时会涉及到某些元素向前或向后位移的操作
    很难在内存空间上找到一块特别大的连续的内存空间
  • ArrayList集合向数组末尾添加元素,效率很高,不受影响
  • 面试题:哪个集合使用最多?
    ArrayList集合,因为往数组末尾添加元素,效率不受影响,并且通常检索/查找某个元素的操作比较多

3. ArrayList的构造方法

3.1 无参构造方法底层源码

即 new ArrayList();

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

3.2 有参构造方法1底层源码

即 new ArrayList(传入一个int类型的容量值);

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

3.3 有参构造方法2底层源码

即 new ArrayList(传入一个集合);

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3.4 测试

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		// 默认初始化容量10
		List myList1 = new ArrayList();

		// 指定初始化容量100
		List myList2 = new ArrayList(100);

		// 创建一个HashSet集合
		Collection c = new HashSet();
		// 添加元素到Set集合
		c.add(1);
		c.add(2);
		c.add(3);
		c.add(4);

		// 通过这个构造方法就可以将HashSet集合转换成List集合
		List myList3 = new ArrayList(c);
		for (int i = 0; i < myList3.size(); i++) {
			System.out.println(myList3.get(i));
		}
	}
}

四、LinkedList

  • LinkedList 继承了 AbstractSequentialList ,并实现了 List 接口

  • 底层采用双向循环链表,查询慢、增删改快,因为底层操作链表,链表的元素在空间存储上内存地址不连续,在查询的时候只能从头结点开始遍历查找,所以查询效率低,但是增删的时候可以进行断链操作,所以增删效率高

1. 链表数据结构

  • 链表是一种非线性、非顺序的物理结构,是由若干个节点组成,即基本的单元是节点(Node)
  • 每一个链表都包含多个节点,节点又包含两个部分,第一个是数据域(往节点里面储存的信息),第二个是引用域(相当于指针,单向链表有一个指针,指向下一个节点;双向链表有两个指针,分别指向下一个和上一个节点)
  • 链表的物理存储方式为随机存储,访问方式为顺序访问
  • 查找节点的时间复杂度为O(n),插入、删除节点的时间复杂度为O(1)
  • 链表适用于写操作多,读操作少的场景
  • 以下情况使用 LinkedList :
  1. 需要通过循环迭代来访问列表中的某些元素
  2. 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作

1.1 单向链表数据结构

  • 对于单向链表来说,任何一个节点Node中都有两个属性:
    存储的数据、下一节点的内存地址(即当前节点的值和一个指向下一个节点的链接)
  • 所以单向链表无法从后一节点找到前一节点

1.2 双向链表数据结构

  • 对于双向链表来说,有两个指针,分别指向下一个和上一个节点,即一个节点既有向前连接的引用, 也有一个向后连接的引用

2. 向LinkedList中添加元素

  • LinkedList没有初始化容量这一概念,最初的链表中没有任何元素,且头尾节点都为null,可以通过add()方法向LinkedList中添加元素
  • 底层源码:
transient int size = 0; // 最初链表中没有任何元素(LinkedList没有初始化容量这一概念)
(first == null && last == null) || (first.prev == null && first.item != null)
transient Node<E> first; // 头节点为null
(first == null && last == null) || (last.next == null && last.item != null)
transient Node<E> last; // 尾结点为null

2.1 测试

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

public class Test {
	public static void main(String[] args) {
		List list = new LinkedList();
		list.add("a");
		list.add("b");
		list.add("c");

		for (int i = 0; i < list.size(); i++) {
			Object obj = list.get(i);
			System.out.println(obj);
		}
	}
}

2.2 源码分析

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last; // 默认为null
    final Node<E> newNode = new Node<>(l, e, null); // 新的节点,Node构造方法(这是一个内部类)
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
// 静态内部类Node
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;
    }
}

五、Vector

  • Vector 继承了 AbstractList ,并实现了 List 接口

1. Vector的初始化容量

  • Vector底层是Object类型的数组,初始化容量为10

2. Vector的扩容

  • 源码分析:
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
  • Vector扩容为原来的2倍

3. Vector线程安全

  • Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,但效率比较低,使用较少

3.1 ArrayList转换为线程安全

  • 使用集合工具类:java.util.Collections
  • java.util.Collection 是集合接口,java.util.Collections 是集合工具类
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Test {
	public static void main(String[] args) {
		// 非线程安全
		List myList = new ArrayList();

		// 变成线程安全的
		Collections.synchronizedList(myList);

		// myList线程安全
		myList.add("1");
		myList.add("2");
		myList.add("3");
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jayco江柯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值