IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java集合篇_01 实用集合详细介绍 -> 正文阅读

[Java知识库]Java集合篇_01 实用集合详细介绍


集合框架简介

集合可以看作是一种容器,用来存储对象信息。
集合类都位于java.util包下,支持多线程安全的集合类都位于java.util.concurrent包下

Collection集合框架图 (图示标注错了,是B继承或实现A)
在这里插入图片描述
Map集合框架图
在这里插入图片描述
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出 了三个子接口: List、 Set、Queue,因此Java集合大致也可分成List、Set、 Queue、Map四种接口体系。

List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储key-value对的集合,可根据元素的key来访问value。

集合和数组的区别?

  • 数组声明了它容纳的元素的类型,而集合不声明。
  • 数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
  • 数组的存放的类型只能是一种,集合存放的类型可以是任意对象类型(不加泛型时添加的类型是Object),要注意,集合中只能存放对象类型,若添加基本数据类型,会自动转成对应的包装类对象。

List接口

List,列表,有序集合,是Collection接口的子接口。可以通过下标访问集合中的元素。List允许重复元素,同时允许有多个null值。
List的实现类有LinkedList、ArrayList、Vector、Stack

List常用API:

方法说明
boolean add(E e)将指定的元素追加到此列表的末尾
void add(int index, E element)将指定的元素插入此列表中的指定位置,原有元素依次后移
E remove(int index)删除该列表中指定位置的元素,返回值为被删除的元素
boolean remove(Object o)从列表中删除指定元素的第一个出现(如果存在)
E set(int index, E element)用指定的元素(可选操作)替换此列表中指定位置的元素。
E get(int index)返回此列表中指定位置的元素。

对于集合的API,个人推荐看文档,它足够详细

ArrayList

ArrayList是List接口的常用实现类,底层采用数组实现,其容量能自动增长,并且支持索引访问,因此ArrayList特点是随机访问速度快,插入和移除性能较差(与LinkedList比较)。

ArrayList支持存放null元素,存放的元素是有序的可重复的

ArrayList线程不安全。

ArrayList容量是自动扩容的,其初始容量为10。
ArrayList添加一个元素,会自动扩容,过程如下:
在这里插入图片描述
因为数组是不可变的,所以ArrayList的底层数组在若想在原有基础上扩容,需要先创建一个新的数组,并且这个新的数组要比原数组大一个长度,然后将所有元素放到这个新数组中,最后将这个数组赋给原数组即可。代码实现如下:

        //1. 有一个固定容量为10的数组
        Object[] array = {1,2,3,4,5,6,7,8,9,10};

        //2. 创建一个新的数组,容量+1
        Object[] tempArray = new Object[array.length+1];

        //3. 将新元素"11"添加到新的集合
        tempArray[array.length] = 11;

        //4. 赋给原数组,原数组被回收
        array = tempArray;

ArrayList的使用

  • 创建 new ArrayList()
ArrayList arrayList = new ArrayList();
  • 添加元素 add(Object obj)
        arrayList.add("Hello");
        arrayList.add("World");
        arrayList.add("Forward");
        arrayList.add(666); //自动装箱 存放的是为Integer对象类型,而不是整型数值
  • 查询集合长度 size()
System.out.println("集合的长度为" + arrayList.size());
  • 删除元素 remove(Object obj),remove(int index)
//从列表中删除指定元素的第一个出现(如果存在)。 
arrayList.remove(new Integer(666));//这里存放的是对象类型,而不是数值
//通过索引删除,返回该索引对应的对象
arrayList.remove(2);//起始依旧为0
  • 清空集合 clear()
arrayList.clear();
  • 判断是否存在某元素 contains(Object obj)
boolean b = arrayList.contains("Forward");
System.out.println("集合是否包含“Forward”:" + b);
  • 判断是否为空 isEmpty()
System.out.println("集合是否为空:" + arrayList.isEmpty());
  • 取交集 retainAll(Collection)
arrayList.add("Hello");
arrayList.add("World");
ArrayList arrayList1 = new ArrayList();
arrayList1.add("Hello");
arrayList1.add("test");
boolean b1 = arrayList.retainAll(arrayList1);//arrayList只保留交集部分的元素
  • 删交集 removeAll(Collection)
arrayList.add("World");
arrayList.removeAll(arrayList1);//保留不是交集部分的元素

ArrayList执行遍历快,增删慢

import java.util.ArrayList;

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/10/15 17:57
 * @Description: ArrayList执行遍历快,增删慢
 */
public class ArrayListDemo2 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            arrayList.add(i);
        }
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("添加500000个数据用时:" + time);
        for (int i = 0; i < 500000; i++) {
            arrayList.get(i);
        }
        long end1 = System.currentTimeMillis();
        long time1 = end1 - end;
        System.out.println("访问500000个数据用时:" + time1);
        //根据结果显然添加数据耗时要长,而访问较快。
    }
}

ArrayList是线程不安全的

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/10/15 17:50
 * @Description: ArrayList是线程不安全的
 */
public class ArrayLIstDemo1 {
    public static void main(final String[] args) {
        final ArrayList arrayList = new ArrayList();

        for (int i = 0; i < 30; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    arrayList.add(UUID.randomUUID().toString().substring(1,8));
                    System.out.println(arrayList);
                }
            }).start();
        }
    }
}

LinkedList

LinkedList底层的数据结构是基于双向链表的。
在这里插入图片描述
LinkedList也是有序的可重复非线程安全的
LinkedList的常用方法要比ArrayLis多一些

//创建 new ArrayList()
LinkedList linkedList = new LinkedList();
//添加 add(Object obj)
linkedList.add("Hello");
linkedList.add("World");
linkedList.add(666); //自动装箱 存放的是为Integer对象类型,而不是整型数值
//查询集合长度 size()
System.out.println("集合的长度为" + linkedList.size());
//删除 remove(Object obj),remove(int index)
linkedList.remove(new Integer(666));//这里存放的是对象类型,而不是数值
linkedList.remove(2);//起始依旧为0
System.out.println("集合的长度为" + linkedList.size());
//通过get(int index)获取集合中的元素
for (int i = 0; i < linkedList.size(); i++) {
      System.out.println(linkedList.get(i));
}
//清空 clear()
linkedList.clear();
//判断存在 contains(Object obj)
boolean b = linkedList.contains("凤文");
System.out.println("集合是否包含“凤文”:" + b);
//判断是否为空 isEmpty()
System.out.println("集合是否为空:" + linkedList.isEmpty());
//取交集 retainAll(Collection)
linkedList.add("Hello");
linkedList.add("World");
ArrayList arrayList1 = new ArrayList();
arrayList1.add("Hello");
arrayList1.add("test");
boolean b1 = linkedList.retainAll(arrayList1);//只保留交集部分的元素
System.out.println("arrayList中是否包含arrList1中的元素:" + b1);
    for (int i = 0; i < linkedList.size(); i++) {
        System.out.println(linkedList.get(i));
    }
//删交集 removeAll(Collection)
linkedList.add("World");
linkedList.removeAll(arrayList1);//保留不是交集部分的元素

除了这些,LinkedList还有操作链表的方法:

        LinkedList<Object> list = new LinkedList<>();
        list.add("World"); //添加元素
        list.addFirst("Hello"); //在首部添加元素
        list.addLast("!"); //在尾部添加元素,貌似这与add效果一样,不过它的返回类型是void
        list.add(1,","); //在指定索引处添加元素
        for (Object e : list) {
            System.out.print(e);
        }

上面是新增元素的方法,这些是访问和删除元素的方法

        System.out.println(list.getFirst()); //返回首个元素
        System.out.println(list.getLast()); //返回尾部元素
        System.out.println(list.removeFirst()); //删除并返回首个元素
        System.out.println(list.removeLast()); //删除并返回尾部元素

LinkedList执行遍历慢,增加和删除元素快,正好与ArrayList相对。

import java.util.LinkedList;
/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/10/15 17:59
 * @Description: LinkedList遍历慢,增加删除快
 */
public class LinkedListDemo2 {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            linkedList.add(i);
        }
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("添加500000个数据用时:" + time);
        for (int i = 0; i < 500000; i++) {
            linkedList.get(i);
        }
        long end1 = System.currentTimeMillis();
        long time1 = end1 - end;
        System.out.println("访问500000个数据用时:" + time1);
        //根据结果显然添加数据耗时要长,而访问较快。                            
    }
}
比较随机访问插入删除空间复杂度
ArrayList通过索引快速定位,快需要创建新数组并重新装入元素,慢只存储数据,内存占用少
LinkedList逐个查找,慢只需要添加一项Entry对象加上指向,快除了存储数据还有节点,内存占用多

LinkedList除了链表实现的List结构,还有队列的数据结构。

栈特性

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/04/16 20:31
 * @Description: 栈的特性,后进先出或先进后出
 */
public class LinkedListDemoForStack {
    public static void main(String[] args) {
        LinkedList<Object> stack = new LinkedList<>();
        //入栈
        stack.push(10);
        stack.push(20);
        stack.push(30);
        stack.push(40);
        //出栈
        while (!stack.isEmpty()) {
            System.out.println(stack.pop());
        }
    }
}

队列特性

import java.util.LinkedList;
import java.util.Queue;
/**
 * @Author: Forward Seen
 * @CreateTime: 2022/04/16 20:36
 * @Description: LinkedList的队列特性,先进先出
 */
public class LinkedListDemoForQueue {
    public static void main(String[] args) {
        Queue<Object> queue = new LinkedList<>();
        //入队
        queue.offer(10);
        queue.offer(20);
        queue.offer(30);
        //访问队首元素
        System.out.println(queue.peek());
        //出队,删除并返回队首元素
        while (!queue.isEmpty()) {
            System.out.println(queue.poll());
        }
    }
}

Vector

Vector类称作向量类,它实现了动态数组,用于元素变化的对象数组。
Vector也是以0开始的下标表示元素的位置。
当Vector对象创建后,数组的个数会随着Vector集合中元素个数的增加和缩小而自动变化。
Vector类中的大多数方法是同步的,使用synchronized修饰的,Vector是线程安全的。

在使用者的角度,Vector与ArrayList的主要区别就是Vector是线程安全的,但效率会低很多。因此,他们所选择的应用场景应为,单线程下用ArrayList,多线程下用Vector。

Stack

Stack是栈,它的特性是先进后出

Java工具包中的Stack是继承于Vector的, 意味着Vector拥有的属性和功能,Stack都拥有。

Stack底层实际上也是通过数组去实现的,具体操作如下:

  1. 执行push时(将元素推入栈中),是通过将元素追加的数组的末尾中。
  2. 执行peek时(取出栈顶元素,不执行删除),是返回数组末尾的元素。
  3. 执行pop时(取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。

Stack的使用

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/10/15 18:09
 * @Description: Stack(栈)是继承Vector的 ,先进后出
 */
public class StackDemo {
    public static void main(String[] args) {
        Stack stack = new Stack();
        stack.push(1);//自动装箱
        stack.push(2);
        stack.push(3);

        stack.pop();//取出栈顶元素

        System.out.println("查看栈顶元素" + stack.peek());
        System.out.println("Stack是否为空:" + stack.isEmpty());
        System.out.println("Stack栈顶元素:" + stack.peek());
        System.out.println("Stack中所有元素:" + stack);
    }
}

Set接口

Set接口继承自Collection接口,但是并没有对方法进行扩充。
Set接口中元素是有序的不重复的且最多有一个null
Set主要有两个实现类,分别是HashSet和TreeSet

Set接口存储元素的过程
在这里插入图片描述

HashSet

HashSet是Set接口的一个实现类,它不保证Set的迭代顺序,无序存放。
Set接口的唯一性:底层依赖于hashCode()和equals()方法。

    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();

        set.add("张三");
        set.add("李四");
        set.add("王五");
        //再添加一个重复的元素
        set.add("张三");

//        set集合的长度
        System.out.println(set.size());

        //输出发现结果是无序的,不重复的
        for (String name : set) {
            System.out.println(name);
        }
    }

String类已经重写过hashCode()和equals方法了。如果要去重属性相同的对象,那需要手动重写这两个方法。当然,这两个方法只需要重写一个就可以,因为Set比较真正依赖的是equals(),而默认情况下(Object),equals()依赖的是hashCode()方法,所以重写一个也可。

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/10/27 18:42
 * @Description: Set实现过滤重复
 * 重写equals或hashcode方法
 */
public class HashSetDemo2 {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<Student>();
        set.add(new Student("张三", "男", 18));
        set.add(new Student("张三", "男", 14));
        set.add(new Student("张三", "男", 18));
        set.add(new Student("李四", "男", 18));
        //这里的两个张三信息一样,但不是同一个对象,equals方法比较不出来,所以
        //在重写方法前,并不能去重,会有两个一样的张三
        //这时,我们就需要重写方法,equals和hashcode重写一个即可
        //需要知道的是,重写hashcode的效率更高一些
        for (Student stu : set) {
            System.out.println(stu.toString());
        }
    }
}

class Student {
    private String name;
    private String gender;
    private int age;

    public Student() {
    }

    public Student(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof Student) {
            Student stu = (Student) o;
            return this.name.equals(stu.getName()) && this.gender.equals(stu.getGender()) && this.age == stu.getAge();
        }
        return false;
    }

    @Override
    public int hashCode() {
        //为了提高效率,建议重写hashcode,这样在hashcode判断出就不需要到equals了
        return this.name.hashCode()+this.gender.hashCode()+age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

TreeSet

TreeSet是Set接口的一个实现类,主要作用是对象的排序(应对Set的无序性)及确定存入对象的唯一性

    public static void main(String[] args) {
        TreeSet<String> tree = new TreeSet<>();

        tree.add("张D");
        tree.add("张B");
        tree.add("张C");
        tree.add("张A");
        //再添加一个重复的
        tree.add("张A");
        tree.add("李A");

        //发现结果会给排序,并去重
        for (String name : tree) {
            System.out.println(name);
        }
    }

在这里插入图片描述
很明显,TreeSet会将元素自动排序。

TreeSet中的元素支持2钟排序方式

  • 第一种:自然排序,在元素定义排序规则。元素自身具有比较性,实现Comparable接口,重写compareTo方法
  • 第二种:比较器排序,创建TreeSet时提供Comparator进行排序。实现Comparator接口,实现compare方法。
    /**
     * 自然排序:在添加元素的过程中就进行排序 需要实现Comparable接口
     */
    public static void main(String[] args) {
        TreeSet<StudentA> set = new TreeSet<>();
        set.add(new StudentA("张三", "男", 20));
        set.add(new StudentA("张三", "男", 14));
        set.add(new StudentA("张三", "男", 16));
        set.add(new StudentA("李四", "男", 18));
        set.add(new StudentA("王五", "男", 18));
        set.add(new StudentA("刘六", "男", 18));
        set.add(new StudentA("赵七", "女", 18));

        for (StudentA stu : set) {
            System.out.println(stu.toString());
        }
    }
/**
 * 实现Comparable接口
 * 重写compareTo方法
 */
class StudentA implements Comparable {
    private String name;
    private String gender;
    private int age;

    public StudentA() {
    }

    public StudentA(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof StudentA) {
            StudentA stu = (StudentA) o;
            return this.name.equals(stu.getName()) && this.gender.equals(stu.getGender()) && this.age == stu.getAge();
        }
        return false;
    }

    @Override
    public int hashCode() {
        //为了提高效率,建议重写hashcode,这样在hashcode判断出就不需要到equals了
        return this.name.hashCode() + this.gender.hashCode() + age;
    }

    @Override
    public String toString() {
        return "StudentA{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Object o) {
        if (!(o instanceof StudentA)) {
            throw new RuntimeException("比较的对象类型不对!");
        }
        StudentA stu = (StudentA) o;
        //小于返回一个负数;大于返回一个正数;等于返回0,最后按 从小到大 排放
        if (this.age > stu.age) {
            return 1;
        }
        if (this.age < stu.age) {
            return -1;
        }
        //年龄相等,比较名字 String类型的compareTo方法已经重写
        if (this.age == stu.age) {
            return this.name.compareTo(stu.name);
        }
        return 0;
    }
}

在这里插入图片描述

    /**
     * 比较器排序
     */
    public static void main(String[] args) { {
        TreeSet<StudentB> set = new TreeSet<>(new Comparator<StudentB>() {
            @Override
            public int compare(StudentB o1, StudentB o2) {
                if (o1.getAge() == o2.getAge()) {
                    return o1.getName().compareTo(o2.getName());
                }
                return o1.getAge() - o2.getAge();
            }
        });
        set.add(new StudentB("张三", "男", 20));
        set.add(new StudentB("张三", "男", 14));
        set.add(new StudentB("张三", "男", 16));
        set.add(new StudentB("李四", "男", 18));
        set.add(new StudentB("王五", "男", 18));
        set.add(new StudentB("刘六", "男", 18));
        set.add(new StudentB("赵七", "女", 18));

        for (StudentB stu : set) {
            System.out.println(stu.toString());
        }
    }

class StudentB {
    private String name;
    private String gender;
    private int age;

    public StudentB() {
    }

    public StudentB(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof StudentB) {
            StudentB stu = (StudentB) o;
            return this.name.equals(stu.getName()) && this.gender.equals(stu.getGender()) && this.age == stu.getAge();
        }
        return false;
    }

    @Override
    public int hashCode() {
        //为了提高效率,建议重写hashcode,这样在hashcode判断出就不需要到equals了
        return this.name.hashCode() + this.gender.hashCode() + age;
    }

    @Override
    public String toString() {
        return "StudentB{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

代码中TreeSet的构造器中有个Comparator参数,我这里用的匿名内部类,你也可以用lambda表示。
在这里插入图片描述

Set与List的异同

集合ListSet
存储元素有序、可重复无序、不可重复
常用类ArrayList、LinkedListHashSet、TreeSet
访问元素提供索引,效率高无序只能一个个比较,效率低

Collection的遍历

增强for循环

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("java");
        list.add("world");
        list.add("China");

        //使用增强for循环
        //:右边的是要遍历的集合,左边是类型名称,这个类型一定与集合中元素或者说泛型中的类型一致
        //这相当于把集合中的某个元素取出到s,直接使用s即可。
        for (String s : list) {
            System.out.println(s);
        }
        //数组同时适用增强for
        int[] arr = {1,2,4,5,6};
        for (int i : arr) {
            System.out.println(i);
        }
    }

迭代输出

对于顺序存储方式的ArrayList的迭代输出

    /**
     * 对于顺序存储方式的ArrayList,还是传统的效率较高
     */
    public void iteratorArray() {
        //添加50,000个元素
        ArrayList list = new ArrayList();
        for (int i = 0; i < 500000; i++) {
            list.add(i);
        }

        //传统for循环遍历
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("for循环遍历用时:" + (end1 - start1));

        //使用Iterator迭代器遍历
        long start2 = System.currentTimeMillis();
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
        long end2 = System.currentTimeMillis();
        System.out.println("使用Iterator迭代器遍历用时:" + (end2 - start2));
    }

在这里插入图片描述
根据结果,对于ArrayList,还是使用传统的遍历方式比较效率。

虽然Iterator迭代器对ArrayList帮助不大,但是对于LinkedList的遍历就有飞跃了。

    /**
     * 但是对于链式存储的LinkedList,迭代器就有速度优势了 ,更快!
     */
    public void iteratorLinked() {
        //添加50,000个元素
        LinkedList list = new LinkedList();
        for (int i = 0; i < 50000; i++) {
            list.add(i);
        }

        //传统for循环遍历
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("for循环遍历用时:" + (end1 - start1));

        //使用Iterator迭代器遍历
        long start2 = System.currentTimeMillis();
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
        long end2 = System.currentTimeMillis();
        System.out.println("使用Iterator迭代器遍历用时:" + (end2 - start2));
    }

在这里插入图片描述
这次的差距明显展现出Iterator的优势了,它非常适合LinkedList的遍历。

遍历Set集合

    public static void main(String[] args) {
        //添加50,000个元素
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < 1000000; i++) {
            set.add(i);
        }
        //增强for循环遍历
        long start1 = System.currentTimeMillis();
        for (Integer i : set) {
            int n = i;
        }

        long end1 = System.currentTimeMillis();
        System.out.println("增强for循环遍历用时:" + (end1 - start1));

        //使用Iterator迭代器遍历
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()) {
            int n = iterator.next();
        }
        long end2 = System.currentTimeMillis();
        System.out.println("使用Iterator迭代器遍历用时:" + (end2 - end1));
    }

在这里插入图片描述
我测试了很多次,Set增强for循环和迭代器的效率都差不多,增强for循环简单点,所以更推荐它。

结论:Iterator迭代器适合用于LinkedList集合

双向迭代输出

双向迭代输出 ListIterator

/**
 * ListIterator可以双向迭代,除了hasNext()和next()方法,
 * 还有hasPrevious()和previous()方法从后往前迭代
 */
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("World");
        list.add("Java");

        ListIterator<String> listIterator = list.listIterator();
        //顺序输出
        while (listIterator.hasNext()) {
            System.out.println(listIterator.next());
        }
        System.out.println("-----------------------------------------");
        //逆序输出
        while (listIterator.hasPrevious()) {
            System.out.println(listIterator.previous());
        }
    }

在这里插入图片描述
双向迭代输出用于List,给List集合添加了逆序遍历效果。
这个功能给我的想法是双指针算法,它可以在很多场景应用。

枚举输出

使用Enumeration接口实例化对象,只能够依靠Vector子类,因为Enumeration的设计就是为Vector服务的。使用hasMoreElements()方法来判断是否有下一个元素,通过nextElement()方法取得元素。

    public static void main(String[] args) {
        Vector<String> list = new Vector<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");

        Enumeration<String> em = list.elements();
        while (em.hasMoreElements()) {
            System.out.println(em.nextElement());
        }
    }

Map接口

  • Map集合中存储的元素是一组键值对象,是key与value的一个映射。
  • Map提供了一个更通用的元素存储方法,每个键映射有一个值。
  • Map集合中一个key对应一个value,key是唯一的,但是value可重复
  • key和value之间存在单向一对一关系,即通过指定的key总能找到确定的value,但反过来不行,就类似于数学函数y=f(x)
  • Map接口的常用实现类有HashMap、Hashtable、LinkedHashMap、TreeMap、ConcurrentHashMap。

Map与Collection的区别

  1. Map集合存储元素是成对出现的,也就是以键值对为单个元素,且键是不可以重复的。
  2. Collection集合存储元素是一个个存储的,也就是单一存储的。

Map接口常用方法介绍

方法说明
V get(Object key)根据键获取值
V put(K key, V value)添加元素。如果是相同的键再次添加,则后添加的Value覆盖之前的
void putAll(Map<? extends K,? extends V> m)将指定m的所有映射复制到此映射(可选操作)。
void clear()移除所有的键值对
V remove(Object key)根据key删除该键值对,并将value返回
boolean containsKey(Object key)判断Map是否包含指定的键
boolean containsValue(Object value)判断Map是否包含指定的值
boolean isEmpty()判断集合是否为空
Set<Map.Entry<K,V>> entrySet()返回集合的键值对对象
Set keySet()返回集合中所有键的集合
Collection values()获取值的集合
int size()返回键值对的数量

HashMap

HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序确是不确定的。

HashMap底层是由数组+链表组成,jdk1.8版本改为数组+链表+红黑树。

HashMap最多只允许一条记录的键为null,允许多条记录的值为null。

HashMap线程不安全,即同一时刻多个线程同时操作HashMap,可以会导致数据的不一致。
如果需要满足线程安全,可以用Collections的synchronizedMap方法是HashMap具有线程安全的能力,或者使用ConcurrentHashMap以及HashTable,后面有具体介绍。

HashMap的基本使用

  • 构造一个HashMap集合
        HashMap map = new HashMap();

HashMap(int initialCapacity, float loadFactor) 构造方法,构造一个空的 HashMap具有指定的初始容量和负载因子。 默认初始容量(16)和默认负载系数(0.75)。
HashMap(Map<? extends K,? extends V> m) ,构造方法,构造一个新的 HashMap与指定的相同的映射 Map 。

  • 添加元素(键值对)
        map.put("key1", "value1");
        map.put("name", "张三");
        map.put(1, 666);
        map.put(2, 667);
  • 根据键获取元素
        System.out.println(map.get("name"));
        System.out.println(map.get(1));
  • 根据键删除键值对
       map.remove(1);
  • 移除所有的键值对
       map.clear();
  • 判断集合是否包含指定的键
        boolean b1 = map.containsKey("name");
        System.out.println("是否包含键name:" + b1);
  • 判断集合是否包含指定的值
        boolean b2 = map.containsValue("张三");
        System.out.println("是否包含值张三:" + b2);
  • 判断集合是否为空
        boolean b3 = map.isEmpty();
        System.out.println("集合是否为空:" + b3);
  • 获取集合中所有键的集合
        Set keys = map.keySet();
        for (Object key : keys) {
            System.out.println(key + ":" + map.get(key));
        }
  • 获取map集合中所有的values值的集合
        Collection values = map.values();
        Iterator i = values.iterator();
        while (i.hasNext()) {
            System.out.println(i.next());
        }

LinkedHashMap

LinkedHashMap是链表和哈希表组合的一个数据结构;
LinkedHashMap存储数据是有序的;
LinkedHashMap是线程不安全的。

        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("key1", "111");
        map.put("key2", "222");
        map.put("key3", "333");
        map.put("key4", "444");

        for (String key : map.keySet()) {
            System.out.println(key + ":" + map.get(key));
        }

ConcurrentHashMap

ConcurrentHashMap底层采用数组+链表+红黑树的存储结构。
ConcurrentHashMap的key和value都不能为null。
ConcurrentHashMap是线程安全的,支持高并发操作。
ConcurrentHashMap采用分段锁技术有效提升并发访问效率。

public class ConcurrentHashMapDemo1 {
    public static void main(String[] args) {
        ConcurrentHashMapDemo1 demo1 = new ConcurrentHashMapDemo1();
        for (int i = 0; i < 10; i++) {
            demo1.testConcurrentHashMap();
        }
    }

    public void testConcurrentHashMap() {
        ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
        CountDownLatch cd = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            new MyThread2(map, cd).start();
        }
        try {
            cd.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("多线程操作后HashMap的大小:" + map.size());
    }
}

class MyThread2 extends Thread {
    ConcurrentHashMap<Integer, Integer> map;
    CountDownLatch cd;

    public MyThread2(ConcurrentHashMap<Integer, Integer> map, CountDownLatch cd) {
        this.map = map;
        this.cd = cd;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            this.map.put(i, i);
        }
        cd.countDown();
    }
}

Hashtable

Hashtable是线程同步安全的。
Hashtable的键和值不能为null。

Map的遍历

        HashMap<String, String> map = new HashMap<String, String>();
        map.put("key1", "Hello");
        map.put("key2", "World");
        map.put("key3", "java");

通过keySet的方式遍历

        //通过keySet的方式遍历
        for (String key : map.keySet()) {
            System.out.println(key + ":" + map.get(key));
        }

通过entry的方式遍历集合

        //通过entry的方式遍历集合
        Set<Map.Entry<String, String>> set = map.entrySet();
        for (Map.Entry<String, String> entry : set) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

通过迭代器Iterator的方式遍历集合

        //通过迭代器Iterator的方式遍历集合
        //entry返回的是Set类型,可以使用迭代器
        Iterator<Map.Entry<String, String>> i = map.entrySet().iterator();
        while (i.hasNext()) {
            Map.Entry<String, String> entry = i.next();
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

直接遍历values集合

        //直接遍历values集合
        for (String value : map.values()) {
            System.out.println(value);
        }

通过迭代器遍历values集合

        //通过迭代器遍历values集合
        Iterator<String> iterator = map.values().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

集合队列

队列Queue接口

队列是一种先进先出的数据结构,只能从队头删除元素,从队尾插入元素。
在这里插入图片描述

import java.util.LinkedList;
import java.util.Queue;

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/11/01 20:35
 * @Description: 队列是一个具有先进先出特点的线性结构
 */
public class QueueDemo1 {
    public static void main(String[] args) {
        //创建队列对象(LinkedList有队列的结构)
        Queue<String> queue = new LinkedList<>();
        //将指定元素添加到列表的尾部
        queue.offer("A1");
        queue.offer("A2");
        queue.offer("A3");
        queue.offer("A4");
        queue.offer("A5");
        queue.offer("A6");
        //检索并删除此列表的头(第一个元素),返回取出的元素
        String context = queue.poll();
        System.out.println("被提取的元素:" + context);
        for (String s : queue) {
            System.out.println("剩余元素:" + s);
        }
        System.out.println(queue.size());
    }
}

Queue接口的常用方法

方法说明
boolean offer(E e)如果在不违反容量限制的情况下立即执行,则将指定的元素插入到此队列中,若没有添加成功返回false
E poll()检索并删除此队列的头,如果此队列为空,则返回 null 。
E peek()检索但不删除此队列的头,如果此队列为空,则返回 null 。

Java中的Queue的实现主要有三种方式:

  • 阻塞队列:阻塞队列是一个可以阻塞的先进先出集合,比如某个线程在空队列获取元素时、或已存满队列存储元素时,都会被阻塞。
  • 非阻塞队列:非阻塞队列是使用CAS(compare and set)机制实现,并发性能好。
  • 双端队列(Deque):Deque是一个既可以在头部操作元素,又可以在尾部操作元素,俗称双向队列。

阻塞队列

ArrayBlockingQueue基于数组实现的一个阻塞队列,在创建ArrayBlockQueue对象时必须制定容量大小。
ArrayBlockingQueue可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能访问队列。

ArrayBlockingQueue常用方法

  • put方法用来向队尾存入元素,如果队列满,则等待。
  • take方法用来从队首取元素,如果队列为空,则等待。
  • offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true。
  • poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取不到,则返回null;否则返回取得的元素。

ArrayBlockingQueue阻塞队列实现的生产者-消费者模式

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/11/03 20:36
 * @Description:
 */
public class Test {
    //当前队列的长度
    private int queueSize = 10;
    //创建阻塞队列对象
   private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(queueSize);

    public static void main(String[] args) {
        Test test = new Test();
        //创建生产者线程对象
        Provider provider = new Provider(test.queue, test.queueSize);
        //创建消费者线程对象
        Consumer consumer = new Consumer(test.queue);
        //启动线程
        provider.start();
        consumer.start();
    }
}
import java.util.concurrent.ArrayBlockingQueue;

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/11/03 20:09
 * @Description: 创建生产者线程
 */
public class Provider extends Thread {
    //共享队列对象
    ArrayBlockingQueue<Integer> queue;
    //队列的长度
    int queueSize;

    public Provider(ArrayBlockingQueue<Integer> queue, int queueSize) {
        this.queue = queue;
        this.queueSize = queueSize;
    }

    @Override
    public void run() {
        provide();
    }

    private void provide() {
        //使用put方法向队尾存入对象
        while (true) {
            try {
                synchronized (this) {
                    queue.put(1);
                    System.out.println("向队列插入了一个元素,队列剩余空间:" + (queueSize - queue.size()));
                }
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.util.concurrent.ArrayBlockingQueue;

/**
 * @Author: 凤文  沈阳医学院2019级医学信息工程 0213
 * @CreateTime: 2021/11/03 20:24
 * @Description: 消费者线程
 */
public class Consumer extends Thread {
    ArrayBlockingQueue<Integer> queue;

    public Consumer(ArrayBlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        consume();
    }

    private void consume() {
        while (true) {
            //用来从队首取元素,如果队列为空则等待
            try {
                //等待3秒再取
                Thread.sleep(3000);
                queue.take();
                System.out.println("从队列中取出一个元素,队列剩余:"+queue.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产速率是大于消费速率的,所以队列的空间逐渐变小
在这里插入图片描述
当没有空间的时候,生产者被阻塞,需要等待,此时生产消费速率一致。
在这里插入图片描述

优先级队列

PriorityQueue优先级队列既可以根据元素的自然顺序来排序,也可以根据Comparator来设置排序规则。
PriorityQueue优先级队列对于自定义的类来说,需要自定义比较器。
PriorityQueue优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。
PriorityQueue优先队列不允许null。
PriorityQueue是非线程安全的,所以提供了PriorityBlockingQueue在多线程环境使用。

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Random;

public class PriorityQueueTest {
    public static void main(String[] args) {
        //创建PriorityQueue队列对象
        PriorityQueue<People> queue = new PriorityQueue<>(11, new Comparator<People>() {
            @Override
            public int compare(People o1, People o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        //循环遍历,向队列中添加People数据
        for (int i = 0; i < 10; i++) {
            queue.add(new People("张_"+i,new Random().nextInt(100)));
        }
        //循环遍历,打印队列中的元素
        while(!queue.isEmpty()){
            System.out.println(queue.poll().toString());
        }
    }
}
public class People {
    private String name;
    private int 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 "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

在这里插入图片描述

延时队列

DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即对头对象的延迟到期时间最长。
不能将null元素放置到DelayQueue队列中。
DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象。

public class DelayQueueTest {
    //创建延迟队列对象
    private static DelayQueue queue = new DelayQueue();

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                queue.offer(new MyDelayedTask("task1", 0000));
                queue.offer(new MyDelayedTask("task2", 1800));
                queue.offer(new MyDelayedTask("task3", 4900));
                queue.offer(new MyDelayedTask("task4", 3800));
                queue.offer(new MyDelayedTask("task5", 3300));
                queue.offer(new MyDelayedTask("task6", 5500));
                queue.offer(new MyDelayedTask("task7", 9900));
                queue.offer(new MyDelayedTask("task8", 1500));
            }
        }).start();

        //遍历延迟队列中的元素
        while (true) {
            MyDelayedTask task = (MyDelayedTask) queue.take();
            System.out.println("任务名称:" + task.getName() + "时间是:" + task.getTime());
        }
    }
}
public class MyDelayedTask implements Delayed {

    //获取当前时间的毫秒值
    private long start = System.currentTimeMillis();
    //名称
    private String name;
    //过期时间
    private long time;

    public MyDelayedTask(String name, long time) {
        this.name = name;
        this.time = time;
    }

    /**
     * 获得延迟时间,用过期时间减去当前时间
     * 该方法返回小于或等于零的值时,就会发生过期
     *
     * @param unit 时间单位
     * @return 延迟时间
     */
    @Override
    public long getDelay(TimeUnit unit) {
        //convert()方法是把整数转换成毫秒值
        long convert = unit.convert((start + time) - System.currentTimeMillis(),
                TimeUnit.MILLISECONDS);
        return convert;
    }


    /**
     * 用于延迟队列内部比较排序 - 当前时间的延迟时间 - 比较对象的延迟时间
     *
     * @param o 比较对象
     * @return 比较结果
     */
    @Override
    public int compareTo(Delayed o) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }
}

在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-04 07:25:22  更:2022-05-04 07:26:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:39:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码