java基础 之 List和Set集合(一篇就够)

在java中,List和Set是集合框架中两个核心接口,都位于java.util包下,用于存储和操作数据集合。

List

特点

List是一个有序集合,它按照元素添加的顺序来存储元素,也就是说,你可以通过索引来访问元素,就像数组一样。
List允许存储重复的元素,即可以将同一个对象添加到List中多次

实现类

ArrayList

  • 底层实现

    基于动态数组实现。它有一个数组来存储元素,当数组空间不足时,会创建一个更大的数组,并将原数组中的元素复制到新数组中

    自动扩容机制:默认容量为10,扩容因子为0.75,容量不足是按1.5倍增长

  • 性能特点

    • 随机访问速度快

      通过索引,时间复杂度为O(1)

    • 插入/删除元素效率低

      在数组的末尾添加元素速度很快,时间复杂度是O(1)
      如果在数组中间插入或删除,需要移动后续的元素来填补空位,时间复杂度为O(N)

  • 使用场景

    频繁查询,较少增删的场景

  • 原理

    跳转可以查看,自己实现一个ArrayList→java基础之数组,int[]和ArrayList

  • ArrayList常用方法

    // 创建一个空的 ArrayList,初始容量默认为 10
    ArrayList<String> list = new ArrayList<>();
    //创建一个具有指定初始容量的空 ArrayList
    ArrayList<String> list2 = new ArrayList<>(20);
    //创建一个包含指定集合元素的 ArrayList
    ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
    //在列表末尾添加一个元素
    list.add("D");
    //在指定索引位置插入一个元素
    list.add(1, "X");
    //将指定集合中的所有元素添加到列表末尾
    list.addAll(Arrays.asList("E", "F"));
    //将指定集合中的所有元素插入到指定索引位置
    list.addAll(2, Arrays.asList("Y", "Z"));
    //获取指定索引位置的元素
    String element = list.get(1);
    //返回指定元素在列表中的第一个匹配项的索引,如果不存在则返回 -1
    int index = list.indexOf("B");
    //返回指定元素在列表中的最后一个匹配项的索引,如果不存在则返回 -1
    int lastIndex = list.lastIndexOf("B");
    //删除指定索引位置的元素
    list.remove(1);
    //删除列表中第一个匹配的元素
    list.remove("A");
    //清空列表中的所有元素
    list.clear();
    //替换指定索引位置的元素
    list.set(1, "Z");
    //返回列表中的元素数量
    int size = list.size();
    //返回一个迭代器,用于遍历列表
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String element = iterator.next();
        System.out.println(element);
    }
    //返回从 fromIndex 到 toIndex(不包含)的子列表
    List<String> sublist = list.subList(1, 3);
    //检查列表中是否包含指定元素
    boolean contains = list.contains("A");
    //将列表转换为数组
    Object[] array = list.toArray();
    //将列表转换为指定类型的数组
    String[] array = list.toArray(new String[0]);
    //检查列表是否为空
    boolean isEmpty = list.isEmpty();
    

Vector

  • 底层实现

    基于动态数组:类似于 ArrayList。它使用一个 Object 类型的数组 elementData 来存储元素
    自动扩容机制:当数组容量不足时,Vector 会自动扩容。默认情况下,扩容策略是将容量增加到当前容量的两倍

  • 线程安全

    所有公共方法(如 add、get 等)都使用了 synchronized 关键字,确保在多线程环境下对集合的操作是线程安全的

  • 性能特点
    • 线程安全带来的开销

      Vector 在单线程环境下的性能不如 ArrayList。每次操作都需要获取锁,这会导致性能下降

    • 随机访问高效

      底层是数组,Vector 支持通过索引快速访问元素,时间复杂度为 O(1)

    • 扩容性能问题

      扩容操作涉及数组的复制,当元素数量较多时,扩容会导致较大的性能开销

  • 适用场景
    • 多线程环境
    • 动态数组需求
    • 读多写少的场景
  • Vector 的使用逐渐减少(通常推荐使用 ArrayList 或 ConcurrentHashMap)
  • Vector常用方法
    //创建一个空的 Vector,初始容量为 10
    Vector<String> vector = new Vector<>();
    //创建一个具有指定初始容量的空 Vector
    Vector<String> vector1 = new Vector<>(20);
    //创建一个具有指定初始容量和扩容增量的空 Vector
    Vector<String> vector2 = new Vector<>(10, 5);
    //创建一个包含指定集合元素的 Vector
    Vector<String> vector3 = new Vector<>(Arrays.asList("A", "B", "C"));
    //在 Vector 的末尾添加一个元素
    vector.add("D");
    //在指定索引位置插入一个元素
    vector.add(1, "X");
    //将指定集合中的所有元素添加到 Vector 的末尾
    vector.addAll(Arrays.asList("E", "F"));
    //将指定集合中的所有元素插入到指定索引位置
    vector.addAll(2, Arrays.asList("Y", "Z"));
    //获取指定索引位置的元素
    String element = vector.get(1);
    //返回指定元素在 Vector 中的第一个匹配项的索引,如果不存在则返回 -1
    int index = vector.indexOf("B");
    //返回指定元素在 Vector 中的最后一个匹配项的索引,如果不存在则返回 -1
    int lastIndex = vector.lastIndexOf("B");
    //删除指定索引位置的元素
    vector.remove(1);
    //删除 Vector 中第一个匹配的元素
    vector.remove("A");
    //清空 Vector 中的所有元素
    vector.clear();
    //替换指定索引位置的元素
    vector.set(1, "Z");
    //返回 Vector 中的元素数量
    int size = vector.size();
    //返回 Vector 的当前容量
    int capacity = vector.capacity();
    //确保 Vector 的容量至少为指定值
    vector.ensureCapacity(30);
    //将 Vector 的容量调整为当前元素数量
    vector.trimToSize();
    //返回一个迭代器,用于遍历 Vector
    Iterator<String> iterator = vector.iterator();
    while (iterator.hasNext()) {
        String element = iterator.next();
        System.out.println(element);
    }
    //检查 Vector 中是否包含指定元素
    boolean contains = vector.contains("A");
    //将 Vector 转换为数组
    Object[] array = vector.toArray();
    //检查 Vector 是否为空
    boolean isEmpty = vector.isEmpty();
    //
    

LinkedList

  • 底层实现

    ** 基于双向链表实现**。每个元素都是一个节点,节点包括数据部分和指向前后节点的指针
    跳转可以查看,自己实现一个链表 →java基础 之 实现一个链表

  • 性能特点
    • 随机访问速度慢

      通过索引访问元素速度较慢,时间复杂度为O(N)。
      因为需要从头借点开始沿着列表逐个节点遍历

    • 插入/删除元素效率高

      如果已经知道要操作的节点位置,在链表的头部、尾部或中间插入和删除元素速度很快,时间复杂度是O(1)。
      因为只需要更改相关借点的指针即可

  • 适用场景

    适用频繁在中间插入和删除元素的场景

  • LinkedList常用方法
    LinkedList<String> list = new LinkedList<>();
    list.add("A");
    list.add("B");
    // 在指定索引位置插入一个元素
    list.add(1, "C"); // 在索引1的位置插入"C"
    //在链表的头部添加一个元素
    list.addFirst("X");
    //在链表的尾部添加一个元素(与 add(E e) 相同)
    list.addLast("Y");
    //清空链表中的所有元素
    list.clear();
    //检查链表中是否包含指定元素
    boolean contains = list.contains("A");
    //获取指定索引位置的元素
    String element = list.get(1);
    //返回指定元素在链表中的第一个匹配项的索引,如果不存在则返回 -1
    int index = list.indexOf("B");
    //返回指定元素在链表中的最后一个匹配项的索引,如果不存在则返回 -1
    int lastIndex = list.lastIndexOf("B");
    //删除指定索引位置的元素
    list.remove(1);
    //删除链表中第一个匹配的元素
    list.remove("A");
    //删除链表的第一个元素
    list.removeFirst()
    //删除链表的最后一个元素
    list.removeLast();
    //替换指定索引位置的元素
    list.set(1, "Z");
    //返回链表中的元素数量
    int size = list.size();
    //将元素添加到链表的末尾(作为队列使用)
    list.offer("A");
    //将元素添加到链表的头部(作为双端队列使用)
    list.offerFirst("X");
    //将元素添加到链表的尾部(与 offer(E e) 相同)
    list.offerLast("Y");
    //获取并移除链表的第一个元素(作为队列使用)
    String first = list.poll();
    //获取并移除链表的第一个元素(与 poll() 相同)
    String first = list.pollFirst();
    //获取并移除链表的最后一个元素
    String last = list.pollLast();
    //获取链表的第一个元素,但不移除它(作为队列使用)
    String first = list.peek();
    //获取链表的第一个元素,但不移除它(与 peek() 相同)
    String first = list.peekFirst();
    //获取链表的最后一个元素,但不移除它
    String last = list.peekLast();
    //返回一个迭代器,用于遍历链表
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String element = iterator.next();
        System.out.println(element);
    }
    //返回一个逆序迭代器,用于从尾到头遍历链表
    Iterator<String> descendingIterator = list.descendingIterator();
    while (descendingIterator.hasNext()) {
        String element = descendingIterator.next();
        System.out.println(element);
    }
    //返回从 fromIndex 到 toIndex(不包含)的子列表
    List<String> sublist = list.subList(1, 3);
    

ArrayList与Vector的区别

  • 线程安全性
    • Vector是线程安全的

      其方法(如add、get、remove等)是同步的(synchronized),这意味着在多线程环境中,多个线程可以安全地并发访问和修改 Vector,而不会导致数据不一致。

    • ArrayList不是线程安全的

      ArrayList 的方法没有同步机制,因此在多线程环境中,如果多个线程同时对 ArrayList 进行写操作,可能会导致数据不一致

  • 性能
    • Vector在多线程环境中可以安全使用,但同步机制会带来额外的性能开销。所以单线程环境中,Vector的性能通常比ArrayList差
    • ArrayList没有同步机制,在单线程环境中性能更高,更适合在单线程或线程安全不是问题的场景中使用。
  • 扩容机制
    • Vector默认情况下每次扩容为原来的两倍。可以通过构造函数指定扩容增量。
    • ArrayList的扩容机制是每次扩容为原来的1.5倍。ArrayList不支持在构造函数中指定扩容增量,扩容机制是固定的。
  • 同步机制
    • Vector内部使用synchronized方法来实现线程安全
    • ArrayList如果需要在多线程环境中使用,可以通过Collections.synchronizedList

将ArrayList设置为线程安全

List<String> syncArrayList = Collections.synchronizedList(new ArrayList<>());

小结

优点

  • List是有序的,可以方便的通过索引进行元素的访问和操作,对于需要按照特定顺序处理数据的场景非常有用,比如处理一个有序的任务列表
  • 提供了多种实现方式,可以根据实际需求选择合适的实现。

    如果需要频繁随机访问元素,可以选择 ArrayList;
    如果需要频繁在中间插入和删除元素,可以选择 LinkedList。

缺点

  • 可能存储大量重复元素

    因为允许重复元素,会导致集合中存储了大量重复的数据,在某些需要唯一性数据的场景下是不合适的。

  • ArrayList 的动态扩容机制可能导致性能问题

    当 ArrayList 的容量不足时,需要进行扩容操作,这个过程涉及到创建新数组和复制旧数组元素,可能会消耗较多时间。虽然这种情况不经常发生,但在处理大量数据时仍需要注意。

ArrayListLinkedListVector
底层原理基于动态数组实现基于双向链表实现基于动态数据实现
线程安全不安全不安全线程安全
频繁的插入和删除-适合-
适用场景适合随机访问适合频繁插入和删除基本不使用了

Set

特点

  • 无序集合(部分实现是有序的)

    Set 是一个无序集合,它不保证元素的存储顺序(HashSet)。不过,也有一些 Set 的实现(如 LinkedHashSet 和 TreeSet)会保持一定的顺序。

  • 不允许重复元素

    Set 中不允许存储重复的元素。当你尝试向 Set 中添加一个已经存在的元素时,添加操作会失败,Set 会自动去重

实现类

HashSet

  • 底层实现

    基于哈希表实现。它通过哈希函数将元素映射到一个桶(bucket)中。当添加元素时,会先计算元素的哈希值,然后根据哈希值确定元素存储的位置。

  • 性能特点
    • 添加、删除和查找效率高

      在理想情况下(哈希函数分布均匀,没有大量冲突),添加、删除和查找元素的时间复杂度都是 O(1)。但如果出现大量哈希冲突,性能会下降。

  • 适用场景

    适用非线程安全场景

  • 常用方法
    //创建一个空的 HashSet
    HashSet<String> set = new HashSet<>();
    //创建一个包含指定集合元素的 HashSet
    HashSet<String> set2 = new HashSet<>(Arrays.asList("A", "B", "C"));
    //向集合中添加一个元素
    set.add("D");
    //从集合中删除一个元素
    set.remove("A");
    //检查集合中是否包含指定元素
    boolean contains = set.contains("B");
    //清空集合中的所有元素
    set.clear();
    //返回集合中的元素数量
    int size = set.size();
    //返回一个迭代器,用于遍历集合
    Iterator<String> iterator = set.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
    

LinkedHashSet

  • 底层实现

    基于哈希表+双向链表实现。它在 HashSet 的基础上,通过双向链表维护元素的插入顺序。

  • 性能特点
    • 添加、删除和查找效率高

      基本操作的时间复杂度和 HashSet 类似,也是 O(1)。同时,它还能保持元素的插入顺序。

  • 适用场景

    非线程安全场景

  • 常用方法:

    LinkedHashSet 继承了 HashSet 的所有方法,因此它与 HashSet 的方法完全相同

    LinkedHashSet<String> linkedSet = new LinkedHashSet<>();
    

TreeSet

  • 底层实现

    基于红黑树实现。它是一个有序的 Set 实现,元素会按照自然顺序(或者指定的比较器顺序)进行排序。

  • 性能特点

    • 添加、删除和查找

      时间复杂度都是 O(log n)。因为红黑树是一种平衡二叉树,每次操作都需要在树中进行搜索或调整,所以时间复杂度是 O(log n)。

  • 常用方法:

    //创建一个空的 TreeSet,元素按自然顺序排序
    TreeSet<String> treeSet = new TreeSet<>();
    
    //创建一个空的 TreeSet,元素按指定比较器排序
    // 创建一个自定义比较器,按降序排列
    Comparator<Integer> descendingComparator = (a, b) -> b.compareTo(a);
    TreeSet<Integer> treeSet2 = new TreeSet<>(descendingComparator);
    
    //创建一个包含指定集合元素的 TreeSet,元素按自然顺序排序
    TreeSet<String> treeSet3 = new TreeSet<>(Arrays.asList("C", "A", "B"));
    
    //向集合中添加一个元素
    treeSet.add("D");
    //从集合中删除一个元素
    treeSet.remove("A");
    //检查集合中是否包含指定元素
    boolean contains = treeSet.contains("B");
    //清空集合中的所有元素
    treeSet.clear();
    //返回集合中的元素数量
    int size = treeSet.size();
    //返回一个迭代器,用于遍历集合
    Iterator<String> iterator = treeSet.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
    //返回集合中的第一个元素(最小值)
    String first = treeSet.first();
    //返回集合中的最后一个元素(最大值)
    String last = treeSet.last();
    //headSet(E toElement):返回一个包含集合中小于 toElement 的所有元素的子集合
    TreeSet<String> head = treeSet.headSet("C");
    //tailSet(E fromElement):返回一个包含集合中大于等于 fromElement 的所有元素的子集合
    TreeSet<String> tail = treeSet.tailSet("B");
    //subSet(E fromElement, E toElement):返回一个包含集合中大于等于 fromElement 且小于 toElement 的所有元素的子集合
    TreeSet<String> sub = treeSet.subSet("A", "C");
    

小结

优点

  • 自动去重

    Set 的最大优点是自动去重,这使得它在处理需要唯一性数据的场景时非常方便。例如,当你需要从一个数据源中提取不重复的记录时,使用 Set 可以轻松实现。

  • 高效的查找性能

    对于 HashSet 和 LinkedHashSet,由于基于哈希表实现,在理想情况下查找元素的速度非常快,时间复杂度是 O(1)。这使得它们在需要快速判断元素是否存在的场景下表现良好。

缺点

  • 无序性

    HashSet 是无序的,如果你需要按照一定的顺序处理数据,那么使用 HashSet 可能不合适。不过你可以选择 LinkedHashSet 或 TreeSet 来解决这个问题。

  • TreeSet的性能开销

    TreeSet 由于需要维护元素的排序,所以在添加、删除和查找操作时,时间复杂度是 O(log n),相比 HashSet 的 O(1)(理想情况)性能开销要大一些。

HashSetLinkedHashSetTreeSet
定义
底层原理基于哈希表基于哈希表+链表基于红黑书
是否有序无序保留插入顺序按照自然顺序或自定义顺序排序
线程安全不安全不安全不安全
时间复杂的O(1)略低于HashSetO(log n)
使用场景存储一组不重复的用户 ID存储一组不重复的用户操作记录,保持操作顺序存储一组不重复的分数,并按分数排序

总结

  • 一、定义
    • List是有序的集合,允许重复元素
    • Set是无序的集合,不允许重复元素
  • 二、元素顺序
    • List是有序的,适合需要保留插入顺序的场景
    • Set是无序的或者按照特定的规则排序的,
  • 三、元素唯一
    • List允许重复的元素
    • Set不允许重复元素
  • 四、快速查找元素
    • List不适合,因为需要遍历
    • Set适合(因为基于哈希值或树的查找)
  • 五、适用场景
    • List适合需要保留插入顺序或允许重复元素的场景
    • Set适合需要去重或者快速查找的场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值