Java集合之List集合(下)

简介: Java集合之List集合(上)

Java集合之List集合(上)https://developer.aliyun.com/article/1386301

代码证明:

public static void main(String[] args) throws Exception {
    List list = new ArrayList();
    for (int i = 0; i < 100; i++) {
      System.out.print(i + "==");
      list.add(i);
      elementDataLength(list); // 调用 elementDataLength 方法输出当前容量
    }
  }
  /**
   * 定义一个名为 "elementDataLength" 的方法,该方法用于使用反射获取 ArrayList 中存储元素的底层数组的长度并输出
   * 
   */
  public static void elementDataLength(List list) throws Exception {
    // 使用反射获取 ArrayList 对象的成员变量 elementData
    Field ed = list.getClass().getDeclaredField("elementData");
    ed.setAccessible(true); // 设置允许访问私有变量
    Object[] o = (Object[]) ed.get(list); // 获取 ArrayList 中存储元素的底层数组对象的引用
    // 输出 ArrayList 的容量信息,即底层数组的长度
    System.out.println("当前List集合容量为:" + o.length);
  }
//输出的内容:
0==当前List集合容量为:10
1==当前List集合容量为:10
2==当前List集合容量为:10
3==当前List集合容量为:10
4==当前List集合容量为:10
5==当前List集合容量为:10
6==当前List集合容量为:10
7==当前List集合容量为:10
8==当前List集合容量为:10
9==当前List集合容量为:10
10==当前List集合容量为:15
11==当前List集合容量为:15
12==当前List集合容量为:15
13==当前List集合容量为:15
14==当前List集合容量为:15
15==当前List集合容量为:22
16==当前List集合容量为:22
17==当前List集合容量为:22
18==当前List集合容量为:22
19==当前List集合容量为:22
20==当前List集合容量为:22
21==当前List集合容量为:22
22==当前List集合容量为:33
23==当前List集合容量为:33
24==当前List集合容量为:33
25==当前List集合容量为:33
26==当前List集合容量为:33
27==当前List集合容量为:33
28==当前List集合容量为:33
29==当前List集合容量为:33
30==当前List集合容量为:33
31==当前List集合容量为:33
32==当前List集合容量为:33
33==当前List集合容量为:49
34==当前List集合容量为:49
35==当前List集合容量为:49
36==当前List集合容量为:49
37==当前List集合容量为:49
38==当前List集合容量为:49
39==当前List集合容量为:49
40==当前List集合容量为:49
41==当前List集合容量为:49
42==当前List集合容量为:49
43==当前List集合容量为:49
44==当前List集合容量为:49
45==当前List集合容量为:49
46==当前List集合容量为:49
47==当前List集合容量为:49
48==当前List集合容量为:49
49==当前List集合容量为:73
50==当前List集合容量为:73
51==当前List集合容量为:73
52==当前List集合容量为:73
53==当前List集合容量为:73
54==当前List集合容量为:73
55==当前List集合容量为:73
56==当前List集合容量为:73
57==当前List集合容量为:73
58==当前List集合容量为:73
59==当前List集合容量为:73
60==当前List集合容量为:73
61==当前List集合容量为:73
62==当前List集合容量为:73
63==当前List集合容量为:73
64==当前List集合容量为:73
65==当前List集合容量为:73
66==当前List集合容量为:73
67==当前List集合容量为:73
68==当前List集合容量为:73
69==当前List集合容量为:73
70==当前List集合容量为:73
71==当前List集合容量为:73
72==当前List集合容量为:73
73==当前List集合容量为:109
74==当前List集合容量为:109
75==当前List集合容量为:109
76==当前List集合容量为:109
77==当前List集合容量为:109
78==当前List集合容量为:109
79==当前List集合容量为:109
80==当前List集合容量为:109
81==当前List集合容量为:109
82==当前List集合容量为:109
83==当前List集合容量为:109
84==当前List集合容量为:109
85==当前List集合容量为:109
86==当前List集合容量为:109
87==当前List集合容量为:109
88==当前List集合容量为:109
89==当前List集合容量为:109
90==当前List集合容量为:109
91==当前List集合容量为:109
92==当前List集合容量为:109
93==当前List集合容量为:109
94==当前List集合容量为:109
95==当前List集合容量为:109
96==当前List集合容量为:109
97==当前List集合容量为:109
98==当前List集合容量为:109
99==当前List集合容量为:109

我们可以看到内部容量不足时,ArrayList会将原数组长度乘以1.5进行扩容。

注意,由于 ArrayList 底层使用的是数组,因此一旦创建了 ArrayList,它的大小就是固定的。而当元素添加到 ArrayList 中时,如果底层数组已满,则需要创建一个更大的数组,并将原有元素复制到新数组中,这会带来一定的性能损耗。

因此,在实际开发中,建议在创建 ArrayList 时设置一个合适的初始化容量,避免在运行时频繁进行扩容操作,同时也可避免浪费过多空间资源。

拿以上例子稍作修改,将ArrayList容量改为50:

public static void main(String[] args) throws Exception {
    List list = new ArrayList(50);
    for (int i = 0; i < 100; i++) {
      System.out.print(i + "==");
      list.add(i);
      elementDataLength(list); // 调用 elementDataLength 方法输出当前容量
    }
  }
  /**
   * 定义一个名为 "elementDataLength" 的方法,该方法用于使用反射获取 ArrayList 中存储元素的底层数组的长度并输出
   * 
   */
  public static void elementDataLength(List list) throws Exception {
    // 使用反射获取 ArrayList 对象的成员变量 elementData
    Field ed = list.getClass().getDeclaredField("elementData");
    ed.setAccessible(true); // 设置允许访问私有变量
    Object[] o = (Object[]) ed.get(list); // 获取 ArrayList 中存储元素的底层数组对象的引用
    // 输出 ArrayList 的容量信息,即底层数组的长度
    System.out.println("当前List集合容量为:" + o.length);
  }
//输出的内容:
0==当前List集合容量为:50
1==当前List集合容量为:50
2==当前List集合容量为:50
3==当前List集合容量为:50
4==当前List集合容量为:50
5==当前List集合容量为:50
6==当前List集合容量为:50
7==当前List集合容量为:50
8==当前List集合容量为:50
9==当前List集合容量为:50
10==当前List集合容量为:50
11==当前List集合容量为:50
12==当前List集合容量为:50
13==当前List集合容量为:50
14==当前List集合容量为:50
15==当前List集合容量为:50
16==当前List集合容量为:50
17==当前List集合容量为:50
18==当前List集合容量为:50
19==当前List集合容量为:50
20==当前List集合容量为:50
21==当前List集合容量为:50
22==当前List集合容量为:50
23==当前List集合容量为:50
24==当前List集合容量为:50
25==当前List集合容量为:50
26==当前List集合容量为:50
27==当前List集合容量为:50
28==当前List集合容量为:50
29==当前List集合容量为:50
30==当前List集合容量为:50
31==当前List集合容量为:50
32==当前List集合容量为:50
33==当前List集合容量为:50
34==当前List集合容量为:50
35==当前List集合容量为:50
36==当前List集合容量为:50
37==当前List集合容量为:50
38==当前List集合容量为:50
39==当前List集合容量为:50
40==当前List集合容量为:50
41==当前List集合容量为:50
42==当前List集合容量为:50
43==当前List集合容量为:50
44==当前List集合容量为:50
45==当前List集合容量为:50
46==当前List集合容量为:50
47==当前List集合容量为:50
48==当前List集合容量为:50
49==当前List集合容量为:50
50==当前List集合容量为:75
51==当前List集合容量为:75
52==当前List集合容量为:75
53==当前List集合容量为:75
54==当前List集合容量为:75
55==当前List集合容量为:75
56==当前List集合容量为:75
57==当前List集合容量为:75
58==当前List集合容量为:75
59==当前List集合容量为:75
60==当前List集合容量为:75
61==当前List集合容量为:75
62==当前List集合容量为:75
63==当前List集合容量为:75
64==当前List集合容量为:75
65==当前List集合容量为:75
66==当前List集合容量为:75
67==当前List集合容量为:75
68==当前List集合容量为:75
69==当前List集合容量为:75
70==当前List集合容量为:75
71==当前List集合容量为:75
72==当前List集合容量为:75
73==当前List集合容量为:75
74==当前List集合容量为:75
75==当前List集合容量为:112
76==当前List集合容量为:112
77==当前List集合容量为:112
78==当前List集合容量为:112
79==当前List集合容量为:112
80==当前List集合容量为:112
81==当前List集合容量为:112
82==当前List集合容量为:112
83==当前List集合容量为:112
84==当前List集合容量为:112
85==当前List集合容量为:112
86==当前List集合容量为:112
87==当前List集合容量为:112
88==当前List集合容量为:112
89==当前List集合容量为:112
90==当前List集合容量为:112
91==当前List集合容量为:112
92==当前List集合容量为:112
93==当前List集合容量为:112
94==当前List集合容量为:112
95==当前List集合容量为:112
96==当前List集合容量为:112
97==当前List集合容量为:112
98==当前List集合容量为:112
99==当前List集合容量为:112

我们可以看到这次只扩容了两次,从50-75、从75-112,这大大减少了性能损耗,所以在创建 ArrayList 时设置一个合适的初始化容量是非常重要的。

4.5 ArrayList去重

4.5.1字符串去重

使用contains方法

public static void main(String[] args) {
          List list = new ArrayList();
          list.add("a");
          list.add("b");
          list.add("c");
          System.out.println("目前集合容器中的元素:"+list);//目前集合容器中的元素:[a, b, c]
          if (!list.contains("b")){//如果不包含“b”
              list.add("b");//才增加
          }
          System.out.println("目前集合容器中的元素:"+list);//目前集合容器中的元素:[a, b, c]
      }
}

4.5.2对象去重

public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Student("zs",16));
        list.add(new Student("ls",17));
        list.add(new Student("ww",18));
        System.out.println("目前集合容器中的元素:"+list);
        //目前集合容器中的元素:[com.xqx.demo.Student@33909752, com.xqx.demo.Student@55f96302, com.xqx.demo.Student@3d4eac69]
        if (!list.contains(new Student("ls",17))){//不包含才新增
            list.add(new Student("ls",17));
        }
        System.out.println("目前集合容器中的元素:"+list);
        //目前集合容器中的元素:[com.xqx.demo.Student@33909752, com.xqx.demo.Student@55f96302, com.xqx.demo.Student@3d4eac69, com.xqx.demo.Student@42a57993]
        if (list.contains(new Student("ls",17))){//包含才移除
            list.remove(new Student("ls",17));
        }
        System.out.println("目前集合容器中的元素:"+list);
        //目前集合容器中的元素:[com.xqx.demo.Student@33909752, com.xqx.demo.Student@55f96302, com.xqx.demo.Student@3d4eac69, com.xqx.demo.Student@42a57993]
    }
}
class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
为什么 student(‘ls’,17) 并没有新增与移除?

由于在 Java 中,比较对象是否相等是基于对象的地址进行的,而不是基于对象的属性值进行的。而实例化后,会新开一个地址。所以即使两个student的属性值相等,但实例化后地址不同,那么这两个对象就也不是相等的。

解决方案

重写equals方法即可

public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Student("zs",16));
        list.add(new Student("ls",17));
        list.add(new Student("ww",18));
        System.out.println("目前集合容器中的元素:"+list);
  //目前集合容器中的元素:[com.xqx.demo.Student@33909752, com.xqx.demo.Student@55f96302, com.xqx.demo.Student@3d4eac69]
        if (!list.contains(new Student("ls",17))){//不包含才新增
            list.add(new Student("ls",17));
        }
        System.out.println("目前集合容器中的元素:"+list);
        //目前集合容器中的元素:[com.xqx.demo.Student@33909752, com.xqx.demo.Student@55f96302, com.xqx.demo.Student@3d4eac69]
        if (list.contains(new Student("ls",17))){//包含才移除
            list.remove(new Student("ls",17));
        }
        System.out.println("目前集合容器中的元素:"+list);
        //目前集合容器中的元素:[com.xqx.demo.Student@33909752, com.xqx.demo.Student@3d4eac69]
    }
}
class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
        @Override
    public boolean equals(Object o) {
        System.out.println("调用了equals方法。。。");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && name.equals(student.name);
    }

在重写的equals方法中,我们定义了只有姓名和年龄都相同时,才认为两个学生相等。因此,当通过新建一个Student对象来判断是否存在于ArrayList中时,即使该对象的内存地址不同,只要该对象的姓名和年龄与列表中某个元素的姓名和年龄相等,就会被视为存在于ArrayList中,从而避免了重复添加元素。而在删除元素时,由于通过该对象的equals方法判断该对象是否在ArrayList中存在,因此也可以正常进行删除。

总结

Java中的引用类型在判断相等性时,是根据对象在内存中的地址进行比较的。如果不重写equals方法,那么,由于ArrayList中存储的是对象的引用,所以相当于比较的是每个对象在内存中的地址是否相同,只有两个对象的地址相同时才会被视为相等。而我们通常认为,只有两个对象的属性值相同时才能被视为相等。因此,我们需要根据对象的实际比较规则重写equals方法。

五、LinkedList

5.1 LinkedList概述

5.1.1概念

LinkedList也是Java中的一个常用的集合类,实现了List接口,底层使用的是双向链表数据结构

与ArrayList不同,LinkedList在内部存储元素时,不是使用连续的内存空间,而是使用一个链表来存储元素。

5.1.2数据结构

LinkedList底层采用的是双向链表(doubly linked list) 数据结构。链表中的每个节点(结点)都由两个部分组成,一部分是存储数据元素的值域,另一部分是指向前一个节点和后一个节点的指针(引用)。对于双向链表来说,除了一个指向前一个节点的指针外,还有一个指向后一个节点的指针

5.2LinkedList的特点

5.2.1随机访问性能较差:

LinkedList的随机访问性能较差,因为在链表中要从头开始遍历链表,直到找到目标元素。所以如果在代码中需要频繁进行随机访问元素的操作,LinkedList可能不是一个最佳的选择。

5.2.2添加/删除操作快:

由于LinkedList底层使用双向链表,因此它的添加和删除操作非常快,因为只需要更改指针的指向即可,不需要像ArrayList一样重新分配数组空间,而且LinkedList还支持在指定位置插入和删除元素。

5.2.3需要额外空间:

链表中每个节点都需要额外存储到前一个和后一个节点的指针,因此比数组等其他数据结构需要更多的内存空间。

5.2.4适用于队列和双端队列:

LinkedList还可以支持队列和双端队列的功能,如在链表头部或尾部添加或删除元素,实现队列和双端队列的常见操作。

  • 双端队列(Deque,即Double Ended Queue的缩写)是一种允许在队列的两端进行插入和删除操作的数据结构。双端队列可以从队列的头部和尾部添加和移除元素。

5.3常用方法

方法名 说明
addFirst(E element) 将元素添加到列表的开头
getFirst(): 返回列表的第一个元素。
getLast(): 返回列表的最后一个元素。
removeFirst(): 删除并返回列表的第一个元素。
removeLast(): 删除并返回列表的最后一个元素。

5.4堆栈和队列

5.4.1堆栈

堆栈(Stack)是一种具有后进先出特性的数据结构。在堆栈结构中,新的元素总是被添加到栈顶(也就是最新元素),而只有在栈顶的元素才可以被移除。

堆栈最典型的例子是计算器的运算操作,先输入的操作数最后被计算,而最后输入的操作数最先被计算。对于堆栈结构,我们可以在程序中使用push(添加)和pop(移除)方法来实现后进先出的存储和读取数据。

5.4.2队列

队列(Queue)是一种具有先进先出特性的数据结构。在队列结构中,新的元素总是从队列的末尾添加到队列中,然后从队列的开头进行访问或删除。

队列最典型的例子是排队。排队的人总是按先来后到的顺序排队,当前面的人离开队列之后,后面的人才能够进入。对于队列结构,我们可以使用offer()方法将新的元素添加到队尾,使用poll()方法从队列开头移除元素。

5.5常见面试题:用LinkedList完成一个堆栈容器

package com.xqx.demo;
import java.util.LinkedList;
public class demo3 {
    public static void main(String[] args) {
      //创建集合
        LinkedList ll = new LinkedList();
        //添加元素
        ll.add("lisi");
        ll.add("zs");
        ll.add("ww");
        //实例化堆栈
        DuiZhan DuiZhan = new DuiZhan(ll);
        System.out.println(DuiZhan.pop());
        System.out.println(DuiZhan.pop());
        System.out.println(DuiZhan.pop());
    }
}
class DuiZhan{
    LinkedList ll = null;
    public DuiZhan(LinkedList ll){// 堆栈的构造函数,接受一个 LinkedList 对象作为参数,用于存储堆栈元素
        this.ll = ll;
    }
    //压栈
    public void add(String a){
        ll.add(a);
    }
    //弹栈
    public String pop(){
        return (String) ll.removeLast(); 从链表末尾移除一个元素并返回其值
    }
}

考察核心

  • 考察LinkedList的api方法
  • 考察堆栈/队列的数据结构特点

六、ArrayList和LinkedList的比较

  • 由于ArrayList的数据结构为数组,所以查询修改快,新增删除慢;而LinkedList的数据结构为链表结构,所以查询修改慢,新增删除快
  • ArrayList是基于数组实现的动态数组,在内存中有连续的空间,可以通过下标访问元素,由于数组需要提前分配一定大小的空间,因此当元素数量增多之后,可能会导致数组空间不足需要重新分配数组,这种情况下可能会出现内存空间浪费;相比之下,LinkedList是基于链表实现的,每个元素都有一个引用指向下一个元素,不需要提前分配空间,因此能够更加灵活地插入和删除元素。然而,链表在内存中是不连续的,每个元素的引用占用额外的内存空间。由于链表中每个元素都需要有一个指向下一个元素的引用,因此在存储同样数量的元素时,LinkedList通常会占用比ArrayList更大的内存空间

好啦,今天的分享就到此为止!希望你看完本篇文章有所收获,祝你变得更强!!!

目录
相关文章
|
2月前
|
安全
List集合特有功能
List集合特有功能
31 2
|
19天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
5天前
|
NoSQL Java Redis
List集合按照由小到大排序或者由大到小排序
List集合按照由小到大排序或者由大到小排序
10 3
|
8天前
|
存储 安全 Java
Java 常用集合分类
Java 常用集合分类
13 2
|
2月前
|
Java
用JAVA架建List集合为树形结构的代码方法
这段代码定义了一个表示树形结构的 `Node` 类和一个用于构建树形结构的 `TreeController`。`Node` 类包含基本属性如 `id`、`pid`、`name` 和 `type`,以及子节点列表 `children`。`TreeController` 包含初始化节点列表并将其转换为树形结构的方法。通过过滤和分组操作实现树形结构的构建。详情可见:[代码示例链接1](http://www.zidongmutanji.com/zsjx/43551.html),[代码效果参考链接2](https://www.257342.com/sitemap/post.html)。
31 5
|
1月前
|
Java API 开发者
代码小妙招:用Java轻松获取List交集数据
在Java中获取两个 `List`的交集可以通过 `retainAll`方法和Java 8引入的流操作来实现。使用 `retainAll`方法更为直接,但会修改原始 `List`的内容。而使用流则提供了不修改原始 `List`、更为灵活的处理方式。开发者可以根据具体的需求和场景,选择最适合的方法来实现。了解和掌握这些方法,能够帮助开发者在实际开发中更高效地处理集合相关的问题。
28 1
|
2月前
|
存储 Java 程序员
Java中的集合框架:从入门到精通
【8月更文挑战第30天】在Java的世界里,集合框架是一块基石,它不仅承载着数据的存储和操作,还体现了面向对象编程的精髓。本篇文章将带你遨游Java集合框架的海洋,从基础概念到高级应用,一步步揭示它的奥秘。你将学会如何选择合适的集合类型,掌握集合的遍历技巧,以及理解集合框架背后的设计哲学。让我们一起探索这个强大工具,解锁数据结构的新视角。
|
2月前
|
存储 算法 Java
Java中的集合框架深度解析云上守护:云计算与网络安全的协同进化
【8月更文挑战第29天】在Java的世界中,集合框架是数据结构的代言人。它不仅让数据存储变得优雅而高效,还为程序员提供了一套丰富的工具箱。本文将带你深入理解集合框架的设计哲学,探索其背后的原理,并分享一些实用的使用技巧。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
2月前
|
存储 Java API
【Java高手必备】揭秘!如何优雅地对List进行排序?掌握这几种技巧,让你的代码瞬间高大上!
【8月更文挑战第23天】本文深入探讨了Java中对List集合进行排序的各种方法,包括使用Collections.sort()、自定义Comparator以及Java 8的Stream API。通过示例代码展示了不同情况下如何选择合适的方法:从简单的整数排序到自定义类对象的排序,再到利用Comparator指定特殊排序规则,最后介绍了Stream API在排序操作中的简洁应用。理解这些技术的区别与应用场景有助于提高编程效率。
22 4
|
2月前
|
存储 算法 Java
Java 中的同步集合和并发集合
【8月更文挑战第22天】
25 5
下一篇
无影云桌面