• 数据结构 | (二) List


    什么是 List
    在集合框架中, List 是一个接口,继承自 Collection
    Collection 也是一个接口 ,该接口中规范了后序容器中常用的一些方法,具体如下所示:
    Iterable 也是一个接口,表示实现该接口的类是可以逐个元素进行遍历的,具体如下:
    站在数据结构的角度来看, List 就是一个线性表,即 n 个具有相同类型元素的有限序列,在该序列上可以执行增删 改查以及变量等操作
    常见接口介绍
    List 中提供了好的方法,具体如下:
    虽然方法比较多,但是常用方法如下:
    方法解释
    boolean add(E e)尾插 e
    void add(int index, E element)将 e 插入到 index 位置
    boolean addAll(Collection c)尾插 c 中的元素
    E remove(int index)删除 index 位置元素
    boolean remove(Object o)删除遇到的第一个 o
    E get(int index)获取下标 index 位置元素
    E set(int index, E element)将下标 index 位置元素设置为 element
    void clear()清空
    boolean contains(Object o)判断 o 是否在线性表中
    int indexOf(Object o)返回第一个 o 所在下标
    int lastIndexOf(Object o)返回最后一个 o 的下标
    List subList(int fromIndex, int toIndex)截取部分 list
    List的使用
    注意: List 是个接口,并不能直接用来实例化
    如果要使用,必须去实例化 List 的实现类。在集合框架中, ArrayList LinkedList 都实现了 List 接口
    线性表
    线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列...
    线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。
    顺序表
    顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成 数据的增删查改。
    ArrayList 简介
    在集合框架中, ArrayList 是一个普通的类,实现了 List 接口,具体框架图如下:
    说明
    1. ArrayList是以泛型方式实现的,使用时必须要先实例化
    2. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
    3.  ArrayList实现了Cloneable接口,表明ArrayList是可以clone
    4. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
    5. Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者 CopyOnWriteArrayList
    6. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
    ArrayList 使用
    ArrayList 的构造
    方法解释
    ArrayList()无参构造
    ArrayList(Collection c)利用其他 Collection 构建 ArrayList
    ArrayList(int initialCapacity)指定顺序表初始容量

    1. public static void main(String[] args) {
    2. // ArrayList创建,推荐写法
    3. // 构造一个空的列表
    4. List list1 = new ArrayList<>();
    5. // 构造一个具有10个容量的列表
    6. List list2 = new ArrayList<>(10);
    7. list2.add(1);
    8. list2.add(2);
    9. list2.add(3);
    10. // list2.add("hello"); // 编译失败,List已经限定了,list2中只能存储整形元素
    11. // list3构造好之后,与list中的元素一致
    12. ArrayList list3 = new ArrayList<>(list2);
    13. // 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
    14. List list4 = new ArrayList();
    15. list4.add("111");
    16. list4.add(100);
    17. }
    ArrayList 常见操作
    ArrayList 虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,可以自行查看 ArrayList 的帮助文档
    方法解释
    boolean add(E e)尾插 e
    void add(int index, E element)将 e 插入到 index 位置
    boolean addAll(Collection c)尾插 c 中的元素
    E remove(int index)删除 index 位置元素
    boolean remove(Object o)删除遇到的第一个 o
    E get(int index)获取下标 index 位置元素
    E set(int index, E element)将下标 index 位置元素设置为 element
    void clear()清空
    boolean contains(Object o)判断 o 是否在线性表中
    int indexOf(Object o)返回第一个 o 所在下标
    int lastIndexOf(Object o)返回最后一个 o 的下标
    List subList(int fromIndex, int toIndex)截取部分 list
    1. public static void main(String[] args) {
    2. List list = new ArrayList<>();
    3. list.add("JavaSE");
    4. list.add("JavaWeb");
    5. list.add("JavaEE");
    6. list.add("JVM");
    7. list.add("测试课程");
    8. System.out.println(list);
    9. // 获取list中有效元素个数
    10. System.out.println(list.size());
    11. // 获取和设置index位置上的元素,注意index必须介于[0, size)间
    12. System.out.println(list.get(1));
    13. list.set(1, "JavaWEB");
    14. System.out.println(list.get(1));
    15. // 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
    16. list.add(1, "Java数据结构");
    17. System.out.println(list);
    18. // 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
    19. list.remove("JVM");
    20. System.out.println(list);
    21. // 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
    22. list.remove(list.size()-1);
    23. System.out.println(list);
    24. // 检测list中是否包含指定元素,包含返回true,否则返回false
    25. if(list.contains("测试课程")){
    26. list.add("测试课程");
    27. }
    28. // 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
    29. list.add("JavaSE");
    30. System.out.println(list.indexOf("JavaSE"));
    31. System.out.println(list.lastIndexOf("JavaSE"));
    32. // 使用list中[0, 4)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
    33. List ret = list.subList(0, 4);
    34. System.out.println(ret);
    35. list.clear();
    36. System.out.println(list.size());
    37. }
    ArrayList的遍历
    ArrayList 可以使用三方方式遍历: for 循环 + 下标、 foreach 、使用迭代器
    1. public static void main(String[] args) {
    2. List list = new ArrayList<>();
    3. list.add(1);
    4. list.add(2);
    5. list.add(3);
    6. list.add(4);
    7. list.add(5);
    8. // 使用下标+for遍历
    9. for (int i = 0; i < list.size(); i++) {
    10. System.out.print(list.get(i) + " ");
    11. }
    12. System.out.println();
    13. // 借助foreach遍历
    14. for (Integer integer : list) {
    15. System.out.print(integer + " ");
    16. }
    17. System.out.println();
    18. Iterator it = list.listIterator();
    19. while(it.hasNext()){
    20. System.out.print(it.next() + " ");
    21. }
    22. System.out.println();
    23. }
    ArrayList 的扩容机制
    ArrayList 是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。以下是 ArrayList 中扩容方式:
    1. 检测是否真正需要扩容,如果是调用grow准备扩容
    2. 预估需要库容的大小
              初步预估按照1.5倍大小扩容
              如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
              
      真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
    3. 使用copyOf进行扩容
    ArrayList 的问题及思考
    1. ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
    2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
    3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间

    ArrayList的缺陷

    由于ArrayList是通过数组来实现的其底层是一段连续空间,当 ArrayList 任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后 搬移,时间复杂度为 O(n) ,效率比较低,因此 ArrayList 不适合做任意位置插入和删除比较多的场景 。因此: java 集合中又引入了LinkedList ,即链表结构。
    链表的概念及结构
    链表是一种 物理存储结构上非连续 存储结构,数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。
    LinkedList
    LinkedList 的底层是双向链表结构 ,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
    在集合框架中, LinkedList 也实现了 List 接口,具体如下:
    【说明】
    • LinkedList实现了List接口
    • LinkedList的底层使用了双向链表
    • LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
    • LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
    • LinkedList比较适合任意位置插入的场景
    LinkedList 的使用
    方法解释
    LinkedList()无参构造
    public LinkedList(Collection c)使用其他集合容器中元素构造List
    1. public static void main(String[] args) {
    2. // 构造一个空的LinkedList
    3. List list1 = new LinkedList<>();
    4. List list2 = new java.util.ArrayList<>();
    5. list2.add("JavaSE");
    6. list2.add("JavaWeb");
    7. list2.add("JavaEE");
    8. // 使用ArrayList构造LinkedList
    9. List list3 = new LinkedList<>(list2);
    10. }
    LinkedList 的其他常用方法介绍
    方法解释
    boolean add(E e)尾插 e
    void add(int index, E element)将 e 插入到 index 位置
    boolean addAll(Collection c)尾插 c 中的元素
    E remove(int index)删除 index 位置元素
    boolean remove(Object o)删除遇到的第一个 o
    E get(int index)获取下标 index 位置元素
    E set(int index, E element)将下标 index 位置元素设置为 element
    void clear()清空
    boolean contains(Object o)判断 o 是否在线性表中
    int indexOf(Object o)返回第一个 o 所在下标
    int lastIndexOf(Object o)返回最后一个 o 的下标
    List subList(int fromIndex, int toIndex)截取部分 list

    1. public static void main(String[] args) {
    2. LinkedList list = new LinkedList<>();
    3. list.add(1); // add(elem): 表示尾插
    4. list.add(2);
    5. list.add(3);
    6. list.add(4);
    7. list.add(5);
    8. list.add(6);
    9. list.add(7);
    10. System.out.println(list.size());
    11. System.out.println(list);
    12. // 在起始位置插入0
    13. list.add(0, 0); // add(index, elem): 在index位置插入元素elem
    14. System.out.println(list);
    15. list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()
    16. list.removeFirst(); // removeFirst(): 删除第一个元素
    17. list.removeLast(); // removeLast(): 删除最后元素
    18. list.remove(1); // remove(index): 删除index位置的元素
    19. System.out.println(list);
    20. // contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false
    21. if(!list.contains(1)){
    22. list.add(0, 1);
    23. }
    24. list.add(1);
    25. System.out.println(list);
    26. System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置
    27. System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置
    28. int elem = list.get(0); // get(index): 获取指定位置元素
    29. list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
    30. System.out.println(list);
    31. // subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回
    32. List copy = list.subList(0, 3);
    33. System.out.println(list);
    34. System.out.println(copy);
    35. list.clear(); // 将list中元素清空
    36. System.out.println(list.size());
    37. }
    LinkedList 的遍历
    1. public static void main(String[] args) {
    2. LinkedList list = new LinkedList<>();
    3. list.add(1); // add(elem): 表示尾插
    4. list.add(2);
    5. list.add(3);
    6. list.add(4);
    7. list.add(5);
    8. list.add(6);
    9. list.add(7);
    10. System.out.println(list.size());
    11. // foreach遍历
    12. for (int e:list) {
    13. System.out.print(e + " ");
    14. }
    15. System.out.println();
    16. // 使用迭代器遍历---正向遍历
    17. ListIterator it = list.listIterator();
    18. while(it.hasNext()){
    19. System.out.print(it.next()+ " ");
    20. }
    21. System.out.println();
    22. // 使用反向迭代器---反向遍历
    23. ListIterator rit = list.listIterator(list.size());
    24. while (rit.hasPrevious()){
    25. System.out.print(rit.previous() +" ");
    26. }
    27. System.out.println();
    28. }
    ArrayList LinkedList 的区别
    不同点ArrayListLinkedList
    存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
    随机访问支持O(1)不支持:O(N)
    头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
    插入空间不够时需要扩容没有容量的概念
    应用场景元素高效存储+频繁访问任意位置插入和删除频繁

  • 相关阅读:
    Java基础知识总结(2023版)
    基于springboot+vue的在线音乐播放系统
    C++ 测试框架 Gtest学习——qt版本
    Java基础—反射
    mysql数据库之字段类型
    自己动手写java虚拟机(第一话)
    12 mysql char/varchar 的数据存储
    面试资料快速复习 Git常用命令(简单实用)
    TensorFlow 2.10.0 已发布
    java-php-python-绿色生活基于PS、DW的绿色环保宣传网站计算机毕业设计
  • 原文地址:https://blog.csdn.net/khh1014173041/article/details/133679217