首页 > 编程语言 >24-08-04 JavaSE java集合详解

24-08-04 JavaSE java集合详解

时间:2024-08-06 23:23:53浏览次数:16  
标签:24 java 04 list System add println public out

24-08-04 JavaSE 集合详解

文章目录

理解集合

传统的数组每次都要提前设置好容量,而且从某些方面来讲只能存储同一类型的元素,如果存储大小不够的话只能重新开辟一个新数组然后把元素移动到新数组中,非常麻烦

public class test01 {
    public static void main(String[] args) {
        Integer[] integers = new Integer[1];
        integers[0] = new Integer(1);
        Integer[] integers1 = new Integer[integers.length + 1];
        for (int i = 0; i < integers.length; i++) {
            integers1[i] = integers[i];
        }
    }
}

那么集合的作用是什么呢,集合有如下好处:

  1. 可以动态保存任意多个对象

  2. 提供一系列方便的操作对象的方法

  3. 使用集合添加,删除新元素的代码简洁明了

java集合的体系框架

java集合类分为两大类,一个是单列集合Collection,一个是双列集合Map,下面是它们的继承关系图。

image-20240804120635977

image-20240804120940775

Collection类

Collection接口的常用方法

由于collection接口不能被实例化,因此用它的子类ArrayList来演示。

public class CollectionMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        //添加元素
        list.add("hx");
        list.add(10);
        list.add(true);
        System.out.println("list=" + list);
        //删除元素
        list.remove("hx");
        list.remove(0);
        System.out.println("list=" + list);
        //查询元素
        System.out.println(list.contains(true));
        //返回集合大小
        System.out.println(list.size());
        //判断集合是否为空
        System.out.println(list.isEmpty());
        //清空集合
        list.clear();
        System.out.println("list=" + list);
        //在集合中添加另外一个集合
        List list2 = new ArrayList();
        list2.add("hxx");
        list2.add(2);
        list.addAll(list2);
        System.out.println("list=" + list);
        //在集合中查询是否存在某一集合
        System.out.println(list.containsAll(list2));
        //在集合中删除在某一集合中存在的元素
        list.removeAll(list2);
        System.out.println("list=" + list);
    }
}

list=[hx, 10, true]
list=[true]
true
1
false
list=[]
list=[hxx, 2]
true
list=[]

list方法在使用的过程中可以任意添加与删除不同类型的对象,同时可以支持一次插入多个,删除多个,查找多个元素的集合,这是数组没有的特点。

集合的遍历

iterator迭代器

我们可以通过集合的iterator()方法返回一个iterator类对象,使用这个对象的hasnext方法与next方法可以实现对集合的遍历,其中,hasnext方法是检查迭代器指向的位置还有没有元素,next方法负责下移并返回这个元素,搭配while循环即可实现对集合的遍历。

public class CollectionIterator {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        collection.add(new Person("小明",20));
        collection.add(new Person("小红",20));
        collection.add(new Person("小芳",20));
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }

    }
}
class Person {
    private String name;
    private int age;

    public Person(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 String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

image-20240804131858504

注意,通过快捷键itit即可快速生成while循环部分,此外,如果我们在while循环以后继续使用next方法,编译器会抛出一个异常,因为此时集合后面已经没有元素了。

image-20240804132052519

因此,next方法一定要在hasnext方法后面使用,也就是只有经过检查后面还有元素才能进行后移并返回元素。

那么如果我们要让迭代器现在重新指向开头该怎么办呢,其实很简单,只需要

 iterator = collection.iterator();

也就是为迭代器再返回一遍iterator对象,这样就可以重新遍历一次了。

增强for循环

遍历的第二种方法我们可以使用一种名叫增强for循环的方法。

public class CollectionFor {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        collection.add(new Person("小明",20));
        collection.add(new Person("小红",20));
        collection.add(new Person("小芳",20));
        for (Object object : collection) {
            System.out.println(object);
        }

    }
}

其实增强for循环的底层使用的还是iterator的那一套hasnext与next方法,这些通过调试我们都可以发现,还有这个增强for循环不止可以用在集合中,还可以用在普通数组中,它的快捷键是I。

List类

List类的常用方法

在理解list方法前,我们要先明白list集合的一些特性:

  1. list集合类中的元素是有序的,即添加顺序与取出顺序一致,并且可以重复
  2. list集合中每个元素都有自己的索引
  3. list接口是Collection的子接口,collection有的方法它也有,但是set的方法它不一定有。
  4. list接口有许多具体实现的类,但是我们常用的list实现类有ArrayList,LinkList,Vector这三类

下面是list的一些常用方法

public class ListMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("红色");
        list.add(true);
        list.add(6);
        list.add('a');
        System.out.println(list);
        //在index位置插入元素
        list.add(0,"在0位置新插入的元素");
        System.out.println(list);
        List list1 = new ArrayList();
        list1.add("1号");
        list1.add("2号");
        //在index位置插入集合
        list.addAll(0,list1);
        System.out.println(list);
        //获取指定位置元素
        Object object = list.get(2);
        System.out.println(object);
        //返回对象首次出现的位置
        int i = list.indexOf(true);
        System.out.println(i);
        //返回对象最后一次出现的位置
        list.add(true);
        int i1 = list.lastIndexOf(true);
        System.out.println(i1);
        //移除指定位置元素并返回此元素
        Object remove = list.remove(2);
        System.out.println(remove);
        System.out.println(list);
        //替换index位置元素
        Object set = list.set(2, false);
        System.out.println(set);
        System.out.println(list);
        //返回从fromindex-toindex位置的子集合
        List list2 = list.subList(2, 4);
        System.out.println(list2);
    }
}

image-20240804140416169

List的三种遍历方法

除了我们上面说到的iterator迭代器已经增强for循环以外,list集合还支持使用普通的for循环遍历。

public class ListFor {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("红色");
        list.add(true);
        list.add(6);
        list.add('a');
        //1. Iterator迭代器遍历
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }

        //2. 增强for循环遍历
        for (Object object : list) {
            System.out.println(object);
        }

        //3. 普通for循环遍历
        for (int i = 0; i < list.size(); i++) {
            Object object  = list.get(i);
            System.out.println(object);
        }

    }
}

而且这三种方法适用于list的所有子类实现,包括像ArrayList,LinkList,Vector什么的。

List的排序

下面是list的两种排序方法,分别是使用接口编程与使用冒泡排序的方法

public class ListExercise02_2 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("海底两万里",80,"MIKE"));
        list.add(new Book("海底三万里",60,"MIKE01"));
        list.add(new Book("海底四万里",100,"MIKE02"));
        sort(list);
        for (Object object :list) {
            System.out.println(object);
        }

    }
    public static void sort(List list) {
        for (int i = 0; i < list.size()-1; i++) {
            for (int j = 0; j < list.size()-1-i; j++) {
                Book book1 = (Book) list.get(j);
                Book book2 = (Book) list.get(j+1);
                if(book1.getPrice()>book2.getPrice()){
                    list.set(j,book2);
                    list.set(j+1,book1);
                }
            }
        }
    }
}

在冒泡排序的代码中我们没有使用temp变量,而是直接使用set方法将集合中的两个对象交换位置,然后我们使用增强for循环输出集合。

public class ListExercise02 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("海底两万里",80,"MIKE"));
        list.add(new Book("海底三万里",60,"MIKE01"));
        list.add(new Book("海底四万里",100,"MIKE02"));
        list.sort(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Book book1 = (Book)o1;
                Book book2 = (Book)o2;
                if(book1.getPrice()-book2.getPrice()<0)
                {
                    return -1;
                }
                if(book1.getPrice()-book2.getPrice()>0)
                {
                    return 1;
                }
                return 0;
            }
        });
        System.out.println(list);
    }


}

在接口排序中,我们使用匿名内部类实例化接口,考虑到价格是一个double,但是系统只有返回int类型的compare方法,使用我们使用两个if语句,通过计算得出的小数返回具体的整数。

ArrayList类

ArrayList类的注意事项

  1. ArrayList不止可以存不同类型的对象,也可以存空值null
  2. ArrayList是由数组实现数据存储的
  3. Arraylist基本等同于Vector,除了它线程不安全外,但是它的效率高,在多线程的情况下,不建议使用ArrayList。

Arraylist的底层结构与源码分析

  1. ArrayList在底层维护一个Object类型的数组elementData,它的修饰符transient是短暂的,瞬间的意思,表示该属性不会被序列化。

    image-20240804152212424

  2. 当创建一个ArrayList对象时候,如果调用的是无参构造器,那么初始的elementdata容量为0,第一次添加元素,那么elementData的容量会扩大到10,如果需要再次扩容,每次为前一次的1.5倍。

  3. 如果使用的是指定大小的int类型的构造器,每次再次扩容的大小也是前一次的1.5倍。

下面我们来用一个例子讲解ArrayList的底层机制

SuppressWarnings({"all"})
public class ArrayListSource {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        for (int i = 0; i <= 10; i++) {
            list.add(i);//在这里设置断点
        }

        for (int i = 11; i < 15 ; i++) {
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);
    }
}

当程序执行到list.add时候,首先是进行自动装箱操作

image-20240804161125846

紧接着进入add方法屏幕截图 2024-08-04 161153

ensureCapacityInternal(size + 1); 意思是验证一下容量够不够,此时容量为0,肯定装不进去,那么我们进入ensureCapacityInternal(size + 1)里面看看会执行什么操作。

image-20240804161410418

在ensureCapacityInternal方法中调用calculateCapacity方法,参数分别为element数组与minCapacity,minCapacity的值为size+1,也就是1,我们接下来看calculateCapacity

image-20240804161539093

如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就返回DEFAULT_CAPACITY与10的较大的值,其中DEFAULT_CAPACITY为提前设置好的10,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空数组,所以会进入if语句然后返回一个10。紧着着回到上一步调用ensureExplicitCapacity(10));

image-20240804161410418

我们进入函数

image-20240804162234920

modCount指的是数组被修改的次数,这用于多线程的监控,同时minCapacity变为10,如果10-现在数组的长度大于0就说明数组需要扩容,进入grow方法

image-20240804164928487

在grow方法中我们可以清楚地看到为什么数组后面每次会扩大1.5倍,原来是因为本身加上半身的一半(右移一位),然后使用copyof方法将值赋过去,其余的用null补全。

Vector类

Vector定义与说明

  1. Vector底层也是一个数组对象。
  2. Vector是线程同步的,即线程安全的,Vector操作方法带有synchronized.
  3. 在开发中,需要线程安全时候,考虑使用Vector。

Vector扩容的源码解析

我们将通过下面一段代码来理解vector的扩容机制

image-20240805130747851

如下,我们为其设定了三个断点,现在进行调试,首先调用的是vector的构造函数,由于我们没有赋初值,这里编译器自动为我们赋值10大小。

image-20240805130838126

然后是我们熟悉的自动装箱过程

image-20240805130946340

add函数首先判断一下大小够不够装,如果不够就要扩容,,需要调用ensureCapacityHelper函数

屏幕截图 2024-08-05 131001

在这个函数中,判断最小需要的大小1和目前数组长度10之差小不小于0,如果小于,就启动grow进行扩容,显然我们现在10大小的容量足够我们经历for循环的装箱。

但是,当vector容器满了,我现在还要add一个100进去,那情况可能就不一样了。

前面的所有步骤都一样,但是不同的是这次进入了if语句调用了grow方法,因为需要大小是11,而数组长度是10,小于0.

image-20240805131550642

进入grow语句,我们直接看新的大小是怎么计算出来的,它是由old(10)+一个三元运算符得出的结果而来,那个三元运算符为

((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

由于capacityIncrement已知等于0,所以三元运算符部分会再返回一个old,最后得出新的new=2*old。所以每次扩容的大小是前一次的二倍。

Vector与ArrayList的比骄

image-20240805131945685

感觉有点像StringBuilder与StringBuffer哈哈。。。

LinkedList类

LinkedList说明

  1. LinkList底层实现了双向链表与双端队列的特点
  2. 可以添加任意元素,也可以重复,包括null。
  3. 线程不安全,没有实现同步与互斥。
  4. LinkList底层维护两个属性first与last,分别指向首结点与尾结点。
  5. 每个结点里面又维护了pre,next,item三个属性,pre指向前一个结点,next指向后一个结点,最终实现双向链表。
  6. LinkList的插入与删除不是通过数组来完成的,所以效率比较高。

下面是我们模拟的一个简单的双向链表,演示其遍历与插入元素的功能。

public class LinkList_ {
    public static void main(String[] args) {
        Node hong = new Node("小红");
        Node ming = new Node("小明");
        Node fang = new Node("小芳");
        //建立联系
        hong.next = ming;
        ming.next = fang;

        fang.pre = ming;
        ming.pre = hong;

        Node first = hong;
        Node Last = fang;
        //从头遍历
        System.out.println("从头遍历");
        while(true)
        {
            if(first == null) break;
            System.out.println(first);
            first = first.next;
        }
        //从尾遍历
        System.out.println("从尾遍历");
        while(true)
        {
            if(Last == null) break;
            System.out.println(Last);
            Last = Last.pre;
        }
        //插入一个元素
        System.out.println("插入一个元素");
        Node qiang = new Node("小强");
        hong.next = qiang;
        ming.pre = qiang;
        qiang.next = ming;
        qiang.pre = hong;
        first = hong;
        while(true)
        {
            if(first == null) break;
            System.out.println(first);
            first = first.next;
        }
    }
}
class Node {
    public Node pre;
    public Node next;
    public Object item;

    public Node(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Node{" +
                "item=" + item +
                '}';
    }
}

从头遍历
Node{item=小红}
Node{item=小明}
Node{item=小芳}
从尾遍历
Node{item=小芳}
Node{item=小明}
Node{item=小红}
插入一个元素
Node{item=小红}
Node{item=小强}
Node{item=小明}
Node{item=小芳}

LinkedList的add方法底层源码解析

image-20240805142359741

首先进入的是一个无参的构造函数,什么都没有,为空。

image-20240805142532456

然后还是装箱,将int类型变为Integer类型对象才可以

image-20240805142743798

下一步就是add函数了

image-20240805142809303

add函数中调用了linkLast方法,继续深入

image-20240805142852355

我们来到了真正插入对象的部分—linklist方法,首先让结点l指向last指向的位置,为空,然后创建一个新节点,把数放进去,next与pre为空,再让last指向这个结点,由于l为空,所以进入if语句,first也指向这个新节点。

image-20240805143553854

我们回到最初,现在来插入第二个看看

image-20240805143626368

其他过程一样,我们直接看最后。

image-20240805142852355

与上次不一样的是,这次last没有指向空,所以结点l指向第一个结点,新建的结点的pre也指向第一个结点(根据Node的构造器,如下)

image-20240805143854236

由于l已经不为空了,所以执行else语句,将第一个结点的Last指向这个新节点。最后就像下面这样。

image-20240805144225164

题外话,由于同属于List实现子集,所以Linkedlist也可以使用for,增强for以及iterator迭代器实现输出。

List集合的选择

Arrayist与LinkedList的比较

底层结构增删的效率改查的效率
Arraylist可变数组较低,数组扩容较高
LinkedList双向链表较高,通过链表追加较低
  1. 改查多,选ArrayList
  2. 增删多,选LinkedList
  3. 一般项目80%-90%都是查询,因此大部分情况下用ArrayList

标签:24,java,04,list,System,add,println,public,out
From: https://blog.csdn.net/2301_79291071/article/details/140927188

相关文章

  • Java泛型中的PECS 原则
    在Java泛型中,使用extends和super关键字来定义通配符的上界和下界,主要是为了保证类型安全,并且能够灵活地处理不同类型的集合。具体来说,使用extends和super的原因可以通过理解PECS(ProducerExtends,ConsumerSuper)原则来解释。PECS原则ProducerExtends:如果......
  • Day19--Java多线程编程入门学习
    1.什么是多线程?多线程是一种并发编程技术,它允许程序同时执行多个线程。线程是程序执行的基本单位,一个程序至少有一个线程,即主线程。通过使用多线程,可以在一个程序中同时处理多个任务,提高程序的效率和响应能力。2.为什么要使用多线程?提升性能:在多核处理器上,多线程可以将......
  • 2024中山纪念暑假集训日记
    2024/8/6摆烂的一天上午专心打了一场模拟赛1h想出来T1,1h打,1h调,终于在本次集训中第一次切了一道题1h想T2,本来想到正解力,但急着打暴力,没管,最后暴力挂了,遗憾的中午快乐的看gyy和隔壁象棋大佬下棋,精彩!遗憾的就是gyy下的太慢了,马上就要输了然而却睡觉了,属于是一个急下午在写教主......
  • JavaEE 第3节 线程安全知识铺垫2
    Java中线程终止的方式下面代码我们创建了一个thread线程,如何在main线程(main方法中)终止thread线程?1、手动标记publicclassThreads{publicstaticvoidmain(String[]args){Threadthread=newThread(()->{while(true){......
  • Java基础6
    类与对象类:具有相同特征的事物的抽象描述,是抽象的、概念上的定义。对象:实际存在的该类事物的每个个体,是具体的,因而也称为实例。 面向过程编程(POP) vs   面向对象编程(OOP)面向过程:以“函数”为组织单位,是一种“执行者思维”,适合解决简单问题。拓展能力差、后期维护难度......
  • 文化课 2024.8.6 日记
    退役很久了,高考加油。T1:(1).注意到\(a_1,a_2,a_3,a_4,a_5\)一定互斥,那么\(I\ge5\),一方面\(\{a_i,a_{5+i}\},i\in[1,5]\)是一组可行解,于是\(I_{\min}=5\)。(2).将数列从前往后划分,第\(i\)段的段长为\(2^{i-1}\),\(a_m\)划归到第二段。则每一段均有\(\suma_j<2^......
  • java学习一周小知识
    java初学习appletJavaApplet可以大大提高Web页面的交互能力和动态执行能力。包含Applet的网页被称为Java-powered页,可以称其为Java支持的网页。当Applet用户访问这样的网页时,Applet被下载到用户的计算机上执行,但前提是用户使用的是支持Java的网络浏览器。由于Applet是在用户的......
  • JavaScript (二十六)——JavaScript 代码规范
    目录JavaScript代码规范变量名空格与运算符代码缩进语句规则对象规则每行代码字符小于80命名规则HTML载入外部JavaScript文件文件扩展名所有的JavaScript项目适用同一种规范。JavaScript代码规范代码规范通常包括以下几个方面:变量和函数的命名规则......
  • JavaWeb中的Tomcat,Servlet详解
    JavaWebJavaWeb技术主要包括服务器技术(后端),如Tomcat,Servlet,JSP等待,以及客户端技术(前端)如HTML,CSS,JavaScript等等Web服务器Web服务器主要负责处理客户端发出的HTTP请求,并做出相应回应Web服务器:安装了服务器软件的计算机,只用于复杂处理请求,发出相应Web服务器......
  • TMDOG的微服务之路_04——Nest.js 的异常筛选器
    TMDOG的微服务之路_04——Nest.js的异常筛选器博客地址:TMDOG的博客在上一篇博客中,我们实现了一个简易的用户管理API并添加了中间件功能。本篇博客,我们将探讨如何在Nest.js中使用异常筛选器。可以帮助我们更好地处理异常。异常筛选器1.创建异常筛选器异常筛选器用......