JavaSE 学习 —— 浅谈 List

本文深入解析了ArrayList与LinkedList这两种Java集合框架中常用的列表实现方式。对比了两种数据结构的内部实现原理,包括数组与链表的不同特点,并通过具体示例演示了如何使用这两种列表结构及其各自的优势与不足。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

List 简介

  List 是 java.util 包下面的类,是一个有序集合(有时被称为序列),List 可以包含重复的元素,其继承了 Collection 的操作。除此之外,List 本身也是一个接口,其还包括以下操作:

  • 按位置访问:根据元素在序列中的位置索引访问元素。
  • 查找:在序列中查找指定的对象,并返回其位置索引。
  • 迭代:扩展了 Iterator 接口,以利用序列的顺序特性。
  • List 子集合:在序列上执行任意范围的操作。
public interface List<E> extends Collection<E>

ArrayList

ArrayList 简介

  ArrayList 基于数组实现,是一个动态的数组队列。但是它和 Java 中的数组又不一样,它的容量可以自动增长。

ArrayList 继承了 AbstractList,实现了 RandomAccess、Cloneable 和 Serializable 接口。

public class ArrayList<E> extends AbstractList<E>
			implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList 的容量为什么会自动增长?

  ArrayList 的列表对象实质上是存储在一个引用型数组里的,有人认为该数组有“自动增长机制”可以自动改变 size 大小。准确地说,该数组是无法改变大小的,实际上它只是改变了该引用型数组的指向而已。下面,让我们来看看 java 是怎样实现 ArrayList 类的。

ArrayList 源代码

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{	
	//创建 ArrayList 完成之后,默认的容量为 0。
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

	// ArrayList 的扩容问题其实就是这个 Object 类型的数组的扩容问题
	//创建一个容量为 x 的 ArrayList 对象,其实就是一个长度为 x 的 Object 数组
	transient Object[] elementData;	
	
	private void grow(int minCapacity) {
        // ArrayList的原始大小
        int oldCapacity = elementData.length;
		// 在原始大小的基础上计算扩充后的大小,扩充后的大小是元素大小的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
		//跟前面计算的扩充后长度 minCapacity 比较,取较大的那个为扩充后长度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
		// 如果扩充后长度大于最大长度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 扩充
        elementData = Arrays.copyOf(elementData, newCapacity);
	}
}

ArrayList 实质
  ArrayList 底层采用 Object 类型的数组实现,当使用不带参数的构造方法生成 ArrayList 对象时,实际上会在底层生成一个的 Object 类型数组。首先,ArrayList 定义了一个私有的未被序列化的数组 elementData ,用来存储 ArrayList 的对象列表。
  其次,以指定初始容量(Capacity)或把指定的 Collection 转换为引用型数组后实例化 elementData 数组;如果没有指定,则引用源码中的初始容量进行实例化。把私有数组预先实例化,然后通过 copyOf 方法覆盖原数组,是实现自动改变 ArrayList 的大小(size)的关键。

add() 与 addAll() 的区别

  add 是将传入的参数作为当前 List 中的一个 Item 存储,即使你传入一个 List 也只会另当前的List增加1个元素。
  addAll 是传入一个 List,将此 List 中的所有元素加入到当前List中,也就是当前 List 会增加的元素个数为传入的 List 的大小。

	Collection demo1 = new ArrayList();   
	Collection demo2 = new ArrayList(); 
	
	demo1.add(1);
	demo1.add(2);
	demo2.add("a");
	demo2.add("b");
	
	System.out.println("demo1:" + demo1);
	System.out.println("demo1Size:" + demo1.size());
	System.out.println("demo2:" + demo2);
	System.out.println("demo2Size:" + demo2.size());
	System.out.println();
	
	demo1.add(demo2);
	System.out.println("---------add--------");
	System.out.println("demo1:" + demo1);
	System.out.println("demo1Size:" + demo1.size());
	System.out.println();
	
	// 恢复原始的 demo1
	demo1.remove(demo2);
	
	demo1.addAll(demo2);
	System.out.println("-------addAll------");
	System.out.println("demo1:" + demo1);
	System.out.println("demo1Size:" + demo1.size());
结果

	demo1:[1, 2]
	demo1Size:2
	demo2:[a, b]
	demo2Size:2
	
	---------add--------
	demo1:[1, 2, [a, b]]
	demo1Size:3
	
	-------addAll------
	demo1:[1, 2, a, b]
	demo1Size:4
创建几个 ArrayList
Person p1 = new Person();
p1.setName("Springer");
p1.setAge(18);
p1.setSex("male");

Person p2 = new Person();
p2.setName("Simple");
p2.setAge(38);
p2.setSex("female");

List<Person> demo1 = new ArrayList<Person>();
demo1.add(p1);
demo1.add(p2);

ArrayList<Integer> argList = new ArrayList<Integer>();
argList.add(1);
argList.add(2);
argList.add(3);
argList.add(4);
argList.add(5);
argList.add(6);
四种遍历方法:

1)通过迭代器遍历

Iterator<Person> it = demo1.iterator();	
while(it.hasNext()){
	System.out.print(it.next());	
	System.out.println();
}

2)通过增强 for 循环遍历

for(Person d : demo1){
    System.out.println(d);
}

3)通过索引遍历

Iterator<Person> it = demo1.iterator();
for(int i = 0; i < demo1.size(); i++){
    System.out.print(demo1.get(i));
    System.out.println();
}
  1. Lambda 表达式

    demo1.forEach(System.out::println);
    System.out.println();

结果
name:Springer,age:18,sex:male 
name:Simple,age:38,sex:female
ArrayList 中几种常用的方法
1. 获取 List 中元素的个数
List<Person> demo1 = new ArrayList<Person>();
System.out.println("---------获取 List 中元素个数---------");
System.out.println(demo1.size());

---------获取 List 中元素个数---------
2
2.在指定位置插入元素
  • argList.add(index, element)

index:插入位置的下标
element:插入的元素

System.out.println("---------添加前遍历---------");
	for(int i = 0; i < argList.size(); i++){
		System.out.print(argList.get(i) + "  ");
	}
     
    argList.add(2, 7);
    
    System.out.println("---------添加后遍历---------");
    for(int i = 0; i < argList.size(); i++){
    System.out.print(argList.get(i) + "  ");
    }

---------添加前遍历---------  
1  2  3  4  5  6    
---------添加后遍历---------  
1  2  7  3  4  5  6  
3.删除指定位置的元素
  • argList.remove(index)

index:删除元素下标

    argList.remove(2); 
     System.out.println("---------删除指定位置元素后遍历---------");
     for(int i = 0; i < argList.size(); i++){
     System.out.print(argList.get(i) + "  ");
     }

---------删除指定位置元素后遍历---------  
1  2  3  4  5  6  
4.删除指定元素
  • argList.remove(Object)

Object:删除的元素

    argList.remove((Object)5);
    System.out.println("---------删除指定元素后遍历---------");
    for(int i = 0; i < argList.size(); i++){
    	System.out.print(argList.get(i) + "  ");
    }

---------删除指定元素后遍历---------  
1  2  3  4  6  
5.判断元素是否存在
  • argList.contains(Object)

Object:需要判断的元素

 boolean obj3 = argList.contains(3);
    boolean obj5 = argList.contains(5);

    System.out.println("---------判断是否存在元素---------");
    System.out.println("ArrayList contains 3 is: " + obj3);
    System.out.println("ArrayList contains 5 is: " + obj5);

---------判断是否存在元素---------  
ArrayList contains 3 is: true  
ArrayList contains 5 is: false 
6.清空集合

argList.clear()

System.out.println("---------清空前 argList 长度---------");
System.out.println("argList中元素个数:" + argList.size());
argList.clear();

System.out.println("---------清空后 argList 长度---------");
System.out.println("argList中元素个数:" + argList.size());

System.out.println("---------判断 argList 是否为空---------");
System.out.println("判断argList是否为空:" + argList.isEmpty());

---------清空前 argList 长度---------  
argList中元素个数:5  
---------清空后 argList 长度---------  
argList中元素个数:0  
---------判断 argList 是否为空---------  
判断argList是否为空:true

LinkedList

LinkedList 简介

LinkedList 是一个继承于 AbstractSequentialList 的双向链表。它也可以被当作堆栈、队列或双端队列进行操作,其相对于 ArrayList 来说,是可以快速添加,删除元素,ArrayList 添加删除元素的话需移动数组元素,可能还需要考虑到扩容数组长度。

LinkedList 继承了 AbstractSequentialList,实现了 List、Deque、Cloneable 和Serializable 接口

public class LinkedList<E> extends AbstractSequentialList<E>
	   implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList 属性

LinkedList 本身的的属性比较少,主要有三个,一个是 size,代表当前有多少个节点;一个是 first,代表第一个节点;一个是 last,代表最后一个节点。

创建一个 LinkedList
LinkedList<String> linkedList = new LinkedList<>();

linkedList.add("first");
linkedList.add("second");
linkedList.add("third");
System.out.println(linkedList);

[first, second, third]
LinkedList 中几种常用的方法
1. 在 LinkedList 中第一个节点添加元素
  • addFirst(element)

element:需要添加的元素

    linkedList.addFirst("addFirst");
    System.out.println(linkedList);

[addFirst, first, second, third]
2. 在 LinkedList 中最后一个节点添加元素
  • addLast(element)

element:需要添加的元素

    linkedList.addLast("addLast");
    System.out.println(linkedList);

[addFirst, first, second, third, addLast]
3. 在 LinkedList 中指定位置插入元素
  • add(index,element)

index:插入位置的下标
element:需要添加的元素

    linkedList.add(2, "addByIndex");
    System.out.println(linkedList);

[addFirst, first, addByIndex, second, third, addLast]

LinkedList 和 ArrayList 的区别

   ArrayList 与 LinkedList 都是 List 接口的实现类,因此都实现了 List 的所有未实现的方法,只是实现的方式有所不同,而 List 接口继承了 Collection 接口, Collection 接口又继承了 Iterable 接口,因此可以看出 List 同时拥有了 Collection 与 Iterable 接口的特性。
   ArrayList 实现了 List 接口,它是以数组的方式来实现的,数组的特性是可以使用索引的方式来快速定位对象的位置, 因此对于快速的随机取得对象的需求,使用 ArrayList 实现执行效率上会比较好。
  LinkedList 是采用链表的方式来实现 List 接口的,它本身有自己特定的方法,如: addFirst() , addLast() , getFirst(), removeFirst()等。 由于是采用链表实现的,因此在进行 insert 和 remove 动作时在效率上要比ArrayList要好得多!适合用来实现 Stack (堆栈)与 Queue (队列),前者先进后出,后者是先进先出。

关于 ArrayList 和 LinkedList 的效率问题

ArrayList 底层是用数组来保存对象的,这种方式将对象放在连续的位置中。

优点:可以通过数组下标快速的拿到值,大量修改时高效。
缺点:每一次添加和删除都需要将操作的元素后面的元素们全部移动,非常麻烦。

LinkedList 则是将对象放在独立的空间中,而且在每一个空间中存放下一个链接的索引。

优点:增加和删除非常快速,其底层是链表,插入元素只要打断这个节点插入元素即可。
缺点:要定位元素的位置时,要从头一个一个寻找,耗费资源。

package test;

import java.util.ArrayList;
import java.util.LinkedList;

public class TimeForList {

public static long getArrayListFindTime(){
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        for (int i = 0; i < 71000; i++) {
            list.add(0,1);
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.get(i);
            
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
    public static long getLinkedListFindTime(){
        LinkedList<Integer> list = new LinkedList<Integer>(); 
        for (int i = 0; i < 71000; i++) {
            list.add(0,1);
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            
            list.get(i);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }

    public static long getLinkedListAddTime(){
        LinkedList<Integer> list = new LinkedList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(0,1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
    
    public static long getArrayListAddTime(){
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(0, 1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
	public static long getLinkedListAddLastTime(){
        LinkedList<Integer> list = new LinkedList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(i,1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }
    
    
    public static long getArrayListAddLastTime(){
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 71000; i++) {
            list.add(i, 1);
        }
        long end = System.currentTimeMillis();
        return end-start;
    }

    public static void main(String[] args) {
        System.out.println("用LinkedList完成添加操作(开头添加)所需时间为:" + getLinkedListAddTime() + "ms");
        
        System.out.println( "用Linked完成添加操作(末尾添加)所需时间为:" + getLinkedListAddLastTime() + "ms");
        
        System.out.println("用ArrayList完成添加操作(开头添加)所需时间为:" + getArrayListAddTime() + "ms");
        
        System.out.println( "用ArrayList完成添加操作(末尾添加)所需时间为:" + getArrayListAddLastTime() + "ms");
        
        System.out.println("用LinkedList完成查询操作所需时间为:" + getLinkedListFindTime() + "ms");
        
        System.out.println("用ArrayList完成查询操作所需时间为:" + getArrayListFindTime() + "ms");
    }
}

结果
用 LinkedList 完成添加操作(开头添加)所需时间为:15ms
用 Linked 完成添加操作(末尾添加)所需时间为:17ms
用 ArrayList 完成添加操作(开头添加)所需时间为:941ms
用 ArrayList 完成添加操作(末尾添加)所需时间为:11ms
用 LinkedList 完成查询操作所需时间为:4457ms
用 ArrayList 完成查询操作所需时间为:2ms

由此可以直观的看出 LinkedList 和 ArrayList 的效率


点击下载文章内容原代码
提取密码:jnfj
本文内容部分取自百度内容,如有雷同部分请见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值