List 接口常用子类及其特点

本文详细介绍了Java中List接口的几种实现:Vector、ArrayList及LinkedList的特点与应用场景,并提供了LinkedList的操作示例,展示了如何利用其特性模拟堆栈和队列。

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

List 常用子类:

- Vector: 内部是数组数据结构,是同步的. 增删, 查询都很慢
- ArrayList: 内部是数组数据结构,是不同步的,替代了 Vector,不同步的效率较高. 特点: 查询速度快
- LinkedList: 内部是链接列表实现,链表数据结构,是不同步的,有角标. 特点: 增删元素的速度很快

Vector (了解)

  • 取出元素的特有方法: Enumeration elements();
Vector v = new Vector();

v.addElement("abc1");
v.addElement("abc2");
v.addElement("abc3");

Enumeration en = v.elements();
while(en.hasMoreElements()){
    System.out.println(en.nextElement());
}

备注: Enumeration 接口和 Iterator 接口的功能是重复的, 新的实现优先考虑 Iterator 接口.

LinkedList

  • 操作列表的开头和结尾
addFirst(); : 将指定元素添加到列表的开头
addLast();  : 将指定元素添加到列表的结尾

getFirst(); : 获取列表的第一个元素, 但不移除. 如果链表为空,抛出 NoSuchElementException 异常
getLast();  : 获取列表的最后一个元素, 但不移除. 如果链表为空,抛出 NoSuchElementException 异常

removeFirst(); : 获取列表的第一个元素, 但移除. 如果链表为空,抛出 NoSuchElementException 异常
removeLast();  : 获取列表的最后一个元素, 但移除. 如果链表为空,抛出 NoSuchElementException 异常

// JDK 1.6 以后升级

offerFirst(); : 将指定元素添加到列表的开头
offerLast();  : 将指定元素添加到列表的结尾

peekFirst(); : 获取列表的第一个元素, 但不移除. 如果链表为空,返回 null
peekLast();  : 获取列表的最后一个元素, 但不移除. 如果链表为空,返回 null

pollFirst(); : 获取列表的第一个元素, 但移除. 如果链表为空,返回 null
pollLast();  : 获取列表的最后一个元素, 但移除. 如果链表为空,返回 null
  • 练习: 请使用 LinkedList 来模拟一个堆栈或者队列数据结构
// 分析:
// 堆栈: 先进后出, First in Last Out, 简写为 FILO
// 队列: 先进先出, First in First Out, 简写为: FIFO.  (例如排队)

// 我们应该描述这样一个容器, 给使用者提供一个容器对象完成这两种结构中的一种.

class DuiLie {
    private LinkedList link;

    // 构造函数
    public DuiLie(){
    link = new LinkedList();
}

    // 队列添加元素的功能
    public void myAdd(Object obj){
    link.addLast(obj);
}

    public void myGet(){
    return link.removeFrist();
}

    public boolean isNull(){
    return link.isEmpty();
}

}

ArrayList

  • 存储自定义对象
// 自定义 Person 对象
public class Person {
    private String name;
    private int age;

    public Person(){
        super();
    }

    public Person(String name, int age){
        super();
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }

    public void getAge(){
        return age;
    }

    public void setAge(int age){
        this.age = age;
    }
}

// ArrayList 集合中存储自定义对象 Person
public class ArrayListTest {
    public static void main(String[] args){

        ArrayList al = new ArrayList();

        al.add(new Person("lisi1",21));
        al.add(new Person("lisi2",22));
        al.add(new Person("lisi3",23));
        al.add(new Person("lisi4",24));

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

// 输出结果为:
// cn.itcast.p.bean.Person@7852e922
// cn.itcast.p.bean.Person@4e25154f
// cn.itcast.p.bean.Person@70dea4e
// cn.itcast.p.bean.Person@5c647e05

// 原因分析: add(Object obj); Person 对象存入 ArrayList 集合中,向上转型(多态)
// 直接 it.next().getName(); 错误, 因为 Object 对象没有 getName() 方法

// 升级:
Iterator it = al.iterator();
while(it.hasNext()){
    Person p = (Person)it.next(); // 向下转型
    System.out.println(p.getName()+" : " + p.getAge());
}

ArrayList 集合中存储的是 Person 对象的引用

其他: ArrayList 集合不能存储基本数据类型

al.add(5); // 代表着: al.add(new Integer(5)); 自动装箱
           // add() 方法的元素为 Object, Object obj = new Integer(5); 多态





参考资料

  • JavaSE 基础视频(毕向东)
  • JDK 1.6 中文文档
<think>我们正在讨论Java中List接口常用实现及其底层数据结构和工作原理。主要关注ArrayList、LinkedList和Vector,但也可以提到CopyOnWriteArrayList等。 根据用户要求,我们需要详细说明每个实现类的数据结构和工作原理,并对比它们的区别。 注意:用户特别要求理解数据结构和实现原理,因此这部分需要详细展开。 参考之前的回答,我们已有ArrayList、LinkedList和Vector的对比,现在需要更深入解析它们的实现原理。 步骤: 1. 分别介绍每个实现类的数据结构。 2. 详细说明它们的工作原理,包括关键操作(增删改查)的实现和扩容机制(如果适用)。 3. 对比总结。 注意:用户要求使用LaTeX格式的数学公式,比如时间复杂度用$O(n)$表示。 另外,生成相关问题。 由于用户要求参考上一次的回答,我们保留之前的对比结构,但更侧重原理的深入解析。 我们将按照以下结构组织答案: 一、ArrayList 1. 数据结构 2. 工作原理(包括初始化、增删改查、扩容) 二、LinkedList 1. 数据结构 2. 工作原理(增删改查,注意链表操作) 三、Vector 1. 数据结构 2. 工作原理(与ArrayList对比,突出线程安全和扩容) 四、其他实现类(如CopyOnWriteArrayList)简要提及 五、对比总结 六、相关问题 注意:在描述原理时,可以引用关键代码片段(如扩容机制)来辅助说明。 开始回答:</think>### Java中List接口常用实现类的数据结构与实现原理详解 #### 一、ArrayList:动态数组实现 **数据结构**: 基于**动态数组**(`Object[] elementData`),内存连续分配,支持快速随机访问。 **工作原理**: 1. **初始化** ```java // 默认创建空数组(首次添加时扩容至10) List<String> list = new ArrayList<>(); // 指定初始容量 List<String> list = new ArrayList<>(100); ``` 2. **动态扩容** - 当元素数量超过数组容量时触发 - **扩容公式**:$$ \text{newCapacity} = \text{oldCapacity} + \left\lfloor \frac{\text{oldCapacity}}{2} \right\rfloor $$ - 示例:容量10 → 添加第11个元素 → 扩容至15(10 + 5)[^1] 3. **关键操作** | 操作 | 时间复杂度 | 实现原理 | |------------|------------|----------------------------------| | `get(int)` | $O(1)$ | 直接通过数组索引访问:`elementData[index]` | | `add(E)` | 均摊$O(1)$ | 尾部插入;扩容时复制原数组 | | `add(int, E)` | $O(n)$ | 需移动插入点后的所有元素 | | `remove(int)` | $O(n)$ | 需移动被删除元素后的所有元素 | #### 二、LinkedList:双向链表实现 **数据结构**: 基于**双向链表**,节点结构: ```java private static class Node<E> { E item; // 当前元素 Node<E> next; // 后继节点指针 Node<E> prev; // 前驱节点指针 } ``` **工作原理**: 1. **插入操作** ```java // 在succ节点前插入新节点 void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) head = newNode; // 插入头部 else pred.next = newNode; // 插入中间 } ``` 2. **关键操作** | 操作 | 时间复杂度 | 实现原理 | |--------------------|------------|----------------------------------| | `get(int)` | $O(n)$ | 需遍历链表:$index < \frac{size}{2}$ 时从头遍历,否则从尾遍历 | | `add(E)` | $O(1)$ | 修改尾节点指针即可 | | `add(int, E)` | $O(1)$ | 仅需修改相邻节点的指针(需先$O(n)$定位) | | `remove(int)` | $O(1)$ | 修改相邻节点指针(需先$O(n)$定位) | #### 三、Vector:线程安全的动态数组 **数据结构**: 与ArrayList相同(`Object[] elementData`),但所有方法用**`synchronized`修饰**实现线程安全。 **核心区别**: 1. **扩容机制** - 扩容公式:$$ \text{newCapacity} = \text{oldCapacity} \times 2 $$ - 示例:容量10 → 添加第11个元素 → 扩容至20[^2] - 支持自定义增长因子:`new Vector<>(100, 50)` // 容量100,每次增50 2. **线程安全代价** ```java public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); // 检查扩容 elementData[elementCount++] = e; return true; } ``` - 同步锁导致并发性能低于ArrayList[^3] #### 四、其他实现类 1. **CopyOnWriteArrayList** - **数据结构**:动态数组 + 写时复制 - **原理**:写操作时复制整个数组(`ReentrantLock`保证线程安全),读操作无锁 - **适用场景**:读多写少的并发环境 #### 五、对比总结 | 特性 | ArrayList | LinkedList | Vector | |--------------------|-----------------|-----------------|-----------------| | **底层结构** | 动态数组 | 双向链表 | 动态数组 | | **扩容因子** | 1.5倍 | 无需扩容 | 2倍 | | **线程安全** | ❌ | ❌ | ✅ (`synchronized`) | | **随机访问** | $O(1)$ | $O(n)$ | $O(1)$ | | **头部插入** | $O(n)$ | $O(1)$ | $O(n)$ | | **内存占用** | 连续空间,碎片少 | 每个元素额外2指针 | 同ArrayList | #### 六、适用场景建议 1. **选择ArrayList当**: - 需要频繁随机访问(如`list.get(i)`) - 内存敏感场景(无指针开销) - 多线程环境需配合`Collections.synchronizedList` 2. **选择LinkedList当**: - 频繁在**中间位置**增删元素(如实现队列) - 不需要随机访问(或访问较少) 3. **避免直接使用Vector**: - 同步锁导致性能瓶颈,推荐用`CopyOnWriteArrayList`替代 ```java // 典型使用场景示例 // 场景1:高频查询(选ArrayListList<Product> catalog = new ArrayList<>(10_000); // 场景2:频繁增删(选LinkedList) Deque<LogEntry> logBuffer = new LinkedList<>(); // 场景3:多线程写(选CopyOnWriteArrayListList<Session> activeSessions = new CopyOnWriteArrayList<>(); ``` --- ### 相关问题 1. ArrayList扩容时为什么选择1.5倍而不是2倍?[^4] 2. LinkedList在Java中如何实现快速访问头尾元素? 3. Vector的同步锁机制会导致哪些性能问题? 4. CopyOnWriteArrayList的写时复制策略如何保证数据一致性? 5. 为什么LinkedList的迭代器比ArrayList更适合在遍历时删除元素? [^1]: ArrayList扩容机制通过Arrays.copyOf实现 [^2]: Vector的2倍扩容策略可能导致内存浪费 [^3]: Vector的synchronized方法造成线程阻塞 [^4]: 1.5倍扩容是空间与时间效率的平衡选择
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值