Java集合-框架详解

Java集合框架主要包括两种类型的容器,一种是集合,存储一个元素集合(Collection),另一种是图(Map),存储键/值对映射

容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。

Map接口和Collection是所有集合框架的父接口

List集合

1、List接口

List是一个有序的Collection,用户可以通过索引访问元素,并且允许重复元素。

特点:

有序(插入顺序)

允许重复元素

可以通过索引访问元素

主要方法:

E get(int index) - 获取指定位置的元素

E set(int index, E element) - 替换指定位置的元素

void add(int index, E element) - 在指定位置插入元素

E remove(int index) - 移除指定位置的元素

int indexOf(Object o) - 返回指定元素第一次出现的索引

int lastIndexOf(Object o) - 返回指定元素最后一次出现的索引

List<E> subList(int fromIndex, int toIndex) - 返回指定范围内的子列表

1、ArrayList

ArrayList 是 Java 中 List 接口的一个实现类,属于集合框架中的一个重要类。它是基于动态数组实现的,可以存储任意类型的元素(包括 null),并且可以自动调整数组大小。ArrayList 提供了对元素的顺序访问、添加、删除等操作。

主要特点:

1、动态数组:

ArrayList 是通过动态数组来实现的,因此它可以自动扩容。每当添加的元素超过当前数组的容量时,ArrayList 会自动扩展数组的大小。

2、顺序存储:

元素是按照插入顺序存储的,支持通过索引来访问和操作元素。可以使用 get(index) 方法获取指定索引位置的元素。

3、支持重复元素:

ArrayList 允许存储重复的元素,也就是说,列表中可以包含多个相同的元素。

4、允许 null 元素:

ArrayList 允许元素为 null,这与某些其他类型的集合(如 HashSet)不同。

5、有序集合:

ArrayList 是有序的,这意味着元素的插入顺序会被保留。通过索引可以访问任何元素。

6、动态扩容:

ArrayList 的容量不是固定的。当元素个数超过当前容量时,它会自动扩容,通常会扩展为原来容量的 1.5 倍。

你也可以在创建 ArrayList 时,通过 ensureCapacity 方法预设其容量,以减少扩容次数,从而提高性能。

7、线程不安全:

ArrayList 是 非线程安全 的,如果多个线程同时访问一个 ArrayList 并且至少有一个线程修改了它的结构(即增加或删除元素),那么就会导致数据不一致。

如果需要在多线程环境中使用 ArrayList,可以使用 Collections.synchronizedList 方法来将其包装为线程安全的集合。

8、查询性能:

在访问元素时,ArrayList 具有 常数时间 复杂度(O(1)),即通过索引快速访问元素。

9、插入和删除性能:

ArrayList 在末尾插入元素的性能较好(平均时间复杂度为 O(1))。

在中间或开头插入或删除元素的性能较差,因为需要移动后续元素,因此时间复杂度是 O(n)。

10、迭代器:

ArrayList 提供的迭代器是快速失败的,如果在迭代过程中结构发生修改(例如添加或删除元素),将会抛出 ConcurrentModificationException 异常。

底层数据结构

注意:transient:这个关键字表示该字段在序列化时会被忽略。序列化是将对象转换为字节流的过程,在这个过程中,elementData 字段不会被序列化。这是因为 elementData 可能在不同的环境下有不同的实际值,因此不需要被持久化到磁盘。

构造方法

ArrayList数组初始化长度
/**
* 通过反射验证ArrayList初始化长度为0
*/
public class ListDemoA {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ArrayList<Object> list = new ArrayList<>();
        // 获取 ArrayList 类的 elementData 字段
        Field field = ArrayList.class.getDeclaredField("elementData");
        // 使字段可访问
        field.setAccessible(true);
        // 获取 elementData 数组
        Object[] elementData = (Object[]) field.get(list);
        // 输出当前的容量,即数组的长度
        System.out.println("默认容量是: " + elementData.length);
    }
}

运行结果:

当存储一个值的时候

/**
 * @author liuwei
 * @version JDK 8
 * @className ListDemoB
 * @description 验证ArrayList存储一个值后的长度
 */
public class ListDemoB {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ArrayList<Object> list = new ArrayList<>();
        list.add("Hello");
        System.out.println("ArrayList元素个数为"+list.size());

        // 获取 ArrayList 类的 elementData 字段
        Field field = ArrayList.class.getDeclaredField("elementData");
        // 使字段可访问
        field.setAccessible(true);
        // 获取 elementData 数组
        Object[] elementData = (Object[]) field.get(list);
        // 输出当前的容量,即数组的长度
        System.out.println("默认容量是: " + elementData.length);

    }
}

运行结果:

自动扩容

每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

/**
 * ArrayList 扩容测试
 */
public class ArrayListResizeTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 创建一个空的 ArrayList
        ArrayList<Object> list = new ArrayList<>();
        // 初始容量检查
        System.out.println("初始容量: " + getCapacity(list));
        // 向 ArrayList 添加元素并检查扩容
        for (int i = 0; i < 30; i++) {
            list.add(i);
            System.out.println("添加第 " + (i + 1) + " 个元素后容量: " + getCapacity(list));
        }
    }

    // 通过反射获取 ArrayList 的容量
    private static int getCapacity(ArrayList<Object> list) throws NoSuchFieldException, IllegalAccessException {
        // 获取 elementData 字段
        Field field = ArrayList.class.getDeclaredField("elementData");
        field.setAccessible(true);
        // 获取 elementData 数组
        Object[] elementData = (Object[]) field.get(list);
        // 返回当前容量(即数组长度)
        return elementData.length;
    }
}

Arraylist常用方法:

方法名

说明

示例

add(E e)

添加元素到末尾

list.add("A");

add(int index, E element)

在指定位置插入元素

list.add(1, "B");

get(int index)

获取指定位置元素

list.get(0);

set(int index, E element)

修改指定位置的元素

list.set(0, "C");

remove(int index)

删除指定位置元素

list.remove(2);

remove(Object o)

删除首次出现的指定元素

list.remove("B");

size()

返回列表长度

int n = list.size();

isEmpty()

判断列表是否为空

list.isEmpty();

contains(Object o)

是否包含某元素

list.contains("A");

clear()

清空所有元素

list.clear();

indexOf(Object o)

返回第一次出现的位置

list.indexOf("A");

lastIndexOf(Object o)

返回最后一次出现的位置

list.lastIndexOf("A");

toArray()

转换成数组

Object[] arr = list.toArray();

iterator()

返回迭代器

Iterator<String> it = list.iterator();

forEach(Consumer<? super E>)

Lambda遍历

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

subList(int from, int to)

获取子列表(左闭右开)

list.subList(1, 3);

ensureCapacity(int minCapacity)

扩容容量(手动)

list.ensureCapacity(100);

Arraylist遍历(7种方式)
/**
 * @description: ArrayList 遍历方式全集(7种)
 * @author: liuwei
 */
public class ArrayListTraversalDemo {
    public static void main(String[] args) {
        // 初始化 ArrayList
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Python");
        list.add("C++");
        list.add("Go");

        System.out.println("=== 1. for 普通索引遍历 ===");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        System.out.println("\n=== 2. 增强 for-each 遍历 ===");
        for (String lang : list) {
            System.out.println(lang);
        }

        System.out.println("\n=== 3. Iterator 遍历 ===");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("\n=== 4. ListIterator 正向遍历 ===");
        ListIterator<String> listIterator = list.listIterator();
        while (listIterator.hasNext()) {
            System.out.println(listIterator.next());
        }

        System.out.println("\n=== 5. ListIterator 反向遍历 ===");
        while (listIterator.hasPrevious()) {
            System.out.println(listIterator.previous());
        }

        System.out.println("\n=== 6. forEach 方法 (Lambda表达式) ===");
        list.forEach(item -> System.out.println(item));

        System.out.println("\n=== 7. Java 8 Stream 遍历 ===");
        list.stream().forEach(System.out::println);
    }
}

2、LinkedList

LinkedList 是 Java 中的一个集合类,位于 java.util 包中,它实现了 List 接口,并且是基于链表实现的。与 ArrayList 基于数组存储元素不同,LinkedList 使用节点(Node)来存储元素,每个节点包含数据和指向前后节点的指针。

特点:

1、基于链表实现:

LinkedList 使用链表结构(双向链表)来存储元素,每个元素(节点)包含两个部分:数据部分和两个指针部分(指向前一个节点和后一个节点)。

由于采用链表结构,LinkedList 没有固定的大小,因此在添加或删除元素时,它不会发生数组大小的变化,而是通过修改指针来进行操作。

2、动态扩展:

LinkedList 不像 ArrayList 那样需要重新分配数组空间。当元素被插入或删除时,链表的结构通过修改指针来完成扩展或收缩。

3、访问速度:

由于 LinkedList 是基于链表结构的,它在随机访问元素时性能较差。访问某个元素需要从头或尾部逐一遍历节点,时间复杂度为 O(n)。

对比之下,ArrayList 在随机访问时具有 O(1) 的时间复杂度,因为它是基于数组实现的。

4、插入和删除:

在链表中,插入和删除元素的时间复杂度通常是 O(1),尤其是在头部或尾部插入时,因为不需要移动其他元素,只需要改变指针。

对比 ArrayList,当在中间插入或删除元素时,ArrayList 需要移动后续元素,因此时间复杂度为 O(n)。

5、内存使用:

由于每个节点不仅保存数据,还需要额外存储两个指针(前驱和后继指针),因此 LinkedList 在内存上的开销较大。

ArrayList 仅需要存储元素本身,因此内存占用较小。

6、双向链表:

LinkedList 是一个双向链表,即每个节点都有指向前后节点的指针。因此,LinkedList 可以从头到尾遍历,也可以从尾到头遍历。

7、线程安全:

LinkedList 是非线程安全的。如果多个线程同时操作同一个 LinkedList,需要手动同步。

8、适用场景:

频繁插入和删除元素的场景:如果需要在中间或两端频繁插入或删除元素,LinkedList 的性能优于 ArrayList。

不需要频繁随机访问的场景:对于需要顺序遍历或在两端操作的场景,LinkedList 也是合适的。

9、主要实现:

LinkedList 实现了 List、Queue 和 Deque 接口,因此它既可以作为列表使用,也可以作为队列或双端队列来使用。

构造方法

LinkedList常用方法:

方法名

说明

示例

add(E e)

添加元素到末尾

list.add("A");

addFirst(E e)

添加元素到开头

list.addFirst("B");

addLast(E e)

添加元素到末尾(等同于 add)

list.addLast("C");

add(int index, E e)

在指定位置插入元素

list.add(1, "D");

get(int index)

获取指定位置的元素

list.get(2);

getFirst()

获取第一个元素

list.getFirst();

getLast()

获取最后一个元素

list.getLast();

set(int index, E e)

替换指定位置元素

list.set(0, "Z");

remove()

移除第一个元素(等同于 removeFirst()

list.remove();

removeFirst()

移除第一个元素

list.removeFirst();

removeLast()

移除最后一个元素

list.removeLast();

remove(int index)

移除指定位置的元素

list.remove(1);

remove(Object o)

移除指定元素(第一个匹配项)

list.remove("A");

peek()

查看第一个元素但不移除

list.peek();

peekFirst()

查看第一个元素但不移除

list.peekFirst();

peekLast()

查看最后一个元素但不移除

list.peekLast();

poll()

获取并移除第一个元素

list.poll();

pollFirst()

获取并移除第一个元素

list.pollFirst();

pollLast()

获取并移除最后一个元素

list.pollLast();

offer(E e)

添加元素到队尾

list.offer("X");

offerFirst(E e)

添加元素到队头

list.offerFirst("Y");

offerLast(E e)

添加元素到队尾

list.offerLast("Z");

contains(Object o)

判断是否包含某元素

list.contains("B");

indexOf(Object o)

返回元素第一次出现位置

list.indexOf("A");

lastIndexOf(Object o)

返回元素最后一次出现位置

list.lastIndexOf("A");

clear()

清空所有元素

list.clear();

size()

返回集合大小

list.si

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘子编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值