zoukankan      html  css  js  c++  java
  • 集合

    java集合大致分为5种

    set:无序,不可以重复

    List:有序,可以重复

    Map:具有映射关系的集合

    Queue:java5新增。代表一种队列集合实现。

    java集合就像一个容器,把多个对像(实际上是对象的引用)丢进该容器中。

    java5之前,java集合会丢失容器中所有对象的数据类型,把所有对象都当成object处理,java5增加泛型以后,java集合可以记住容器中的对象类型。

    为了保存数量不确定的数据,以及保存具有映射关系的数据,java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也叫容器类。所有集合类都位于java.util包下,为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了有一些多线程的集合类。

    集合和数组的区别是数组元素可以是基本类型的值,也可以是对象(其实是保存对象的引用变量),集合只能报损对象。

    java集合类主要有两个接口派生而出:Collection和Map,是java集合框架的根接口,他们有包含了一些子接口或实现。

     

                                                                                              图1                                                                                                                                                                                                图2

    图1位Collection体系集合。Set和LIst接口是Collection接口派生出的两个子接口,分别代表有序集合和无序集合。Queue是java提供的队列实现,类似于List。图2位Map体系的继承树,Map保存的每项数据都是key-value对。key是不可重复的。

    两幅图中,Set,Queue,List,Map4个接口,可以分为3大类,Set集合无法记住元素顺序,因此对象不能重复,List集合像一个数组,可以记住每次添加元素的顺序,但是长度可变。Map每项数据都是键值对。

    其中最常用的集合为HashSet,TreeSet,ArrayList,ArrayDeque,LinkedLIst,HashMap,TreeMap。

    Collection接口是List,Set,Queue的父接口。接口里的方法可以操作以上集合。

    boolean add(object o)向集合添加一个类。成功返回true

    boolean addAll(Collection c)把c集合的所有元素添加到指定集合,成功返回true

    void clear()清除集合所有元素。

    。。。。。

    所有的Collection实现类都重写了toString()方法。

    传统模式下,把一个对象“丢进”集合中,集合会忘记这个对象的类型。都是object类型。java5以后,可以使用泛型来限制集合元素里的类型。

    java11增加了toArray(intFunction)方法。可以返回特定类型的数组。

    String[] ss=strColl.toArray(String::new);

    参数是一个lambda表达式,构造器引用。

    intFunction iF=a->new String[](a);

    toArray(iF)

    toArray(String[]::new)

    Iterable接口是Collection接口的父接口。

    Iterable接口在java8新增了一个forEach(Consumer action)默认方法。参数是一个函数式接口。所以在Collection集合里也可以直接调用该方法。

    当使用Iterable的forEach方法遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法(唯一的抽象方法),所以可以使用Lambda表达式来遍历集合元素。

    Consumer是函数式接口,forEach默认方法将集合元素一一传递给consumer类型的实例。而consumer类型是lambda表达式的目标类型,因此,集合元素一一传递给了lambda表达式的形参。

    consumer接口只有一个accept的抽象方法,而lambda表达式实现了这个方法,因此lambda表达式创建了一个consumer接口的实例,就是说lambda表达式的目标类型是consumer。

    Iterator接口主要用于遍历集合元素,并不存元素,定义了以下四个方法。

    boolean hasNext()如果被迭代的集合元素还没有被遍历完,则反回true。

    object next()返回集合里上一次next方法返回的元素。

    void remove 删除集合里上一次next方法返回的元素。

    void forEachRemainnig(consumer action)使用lambda表达式来遍历元素集合。

    Iterator必须依附于collection对象。遍历时,并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量。

    当使用它遍历时,不能改变集合里的元素。一旦在迭代过程中检测到该集合已经被修改,程序立马引发异常。可以避免共享资源而引发的潜在问题。

    使用foreach循环迭代访问集合元素时,同样不能改变集合。

    java8为Collection新增了removeIf(Predicate filter) Predicate是函数式接口,因此也可以用lambdda表示。

    Predicate有text方法。

     使用Stream操作集合

    java8新增了Sream,IntStream,LongStream,DoubleSream等流式API。这些api支持串行和并行聚焦操作的元素。

    有两种方法。

    第一,使用Builder创建对应流的builder。

    调用Builder的add方法,向流中添加元素。

    调用Builder的build方法,获取对应的流。

    调用流的方法。

    第二种,java8允许流来操作集合。

    Collection接口提供了一个默认的 Stream方法。该方法可以返回集合对应的流,接下来可以进行流式聚焦 操作。方便了集合操作,不用遍历集合。

    流式聚焦操作有很多方法。其中有中间方法,即方法的返回值是另外一个流。

    也有末端方法,对流的最终操作。

    例如collection.stream().mapToInt(ele->((string)ele).length()).forEach(System::println)中mapToInt是中间方法,返回一个Intstream流,forEach是末端方法,遍历Intstream流。

    Set集合

    set集合与Collection集合差不多,只是不允许存放相同的元素。

    HashSet

    是Set的典型实现,大多数使用Set就是使用这个实现类。他用Hash算法来存储集合中的元素。

    HashSet的特点:

    不能保证元素的顺序,与添加顺序不同,顺序也可能发生变化。

    不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上的线程同时修改了HashSet集合时,则必须通过代码来同步

    集合元素值可以是null。

    当向HashSet集合中加入元素时,会调用元素的hashcode方法,得到其hash值,根据他的hash值,用算法计算出内存位置。所以,hashset可以快速查找到被检索的对象。

    HasSet判断两个元素相等的方法时调用equals方法会hashcode方法,两个方法都相等,才会认为元素相等。

    如果只有hashcode不等,则会放入不同的位置,根据hashcode放。

    如果只有equals不等,则会放入相同的“桶”(bucket),如果多个元素的hashcode相等,则桶里会有多个元素,会导致性能下降。

    当向hashset中添加可变对象时,必须十分小心,如果修改hashset集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致hashset无法准确访问该对象。

    LinkedHashSet是hashset子集,它同样是根据hashcode值来决定元素存储的位置,但它同时使用链表来维护元素的次序,这样使得元素看起来以插入的顺序保存。因为需要维护插入顺序,所以性能略低于hashset,但是迭代访问set里全部元素时,有很好的性能。虽然有链表记录集合元素的添加顺序,但是仍然不允许重复元素。

     TreeSet是sortedset接口的实现类,可以自动排序。因此有另外的方法。

    import java.util.TreeSet;
    
    public class TreeSetTest {
        public static void main(String[] args) {
            var str=new TreeSet();
            str.add("你好");
            str.add("早上好");
            str.add("中午好");
            str.add("晚上");
            str.add("中间");
            str.add("早啊");
            System.out.println(str);
            System.out.println(str.first());
            System.out.println(str.last());
            System.out.println(str.lower("早上好"));
            System.out.println(str.higher("中午好"));
            System.out.println(str.subSet("中午好","早上好"));
            System.out.println(str.tailSet("早上好"));
            System.out.println(str.headSet("晚上"));
    
        }
    }
    
    [中午好, 中间, 你好, 早上好, 早啊, 晚上]
    中午好
    晚上
    你好
    中间
    [中午好, 中间, 你好]
    [早上好, 早啊, 晚上]
    [中午好, 中间, 你好, 早上好, 早啊]
    

     Hashset采用hash算法来决定元素的存储位置的不同,TreeSet采用红黑树的数据结构来存储集合元素。

    TreeSet支持两种排序:自然排序和定制排序。

    自然排序

    默认情况下,调用集合元素的compareto(object obj)方法·来比较元素直接的大小,然后将集合元素按升序排列。

    这个方法时Comparable接口的方法,java的一些常用类都实现了该接口。比如上面的String。如果用一个没有实现该方法的类的对象,放入TreeSet中,则会出现错误。

    同样的,该方法比较时,会将obj强制转换成同一类型的对象,因此当向TreeSet中添加不同类型的对象时,也会爆异常。

    当吧一个对象加入Treeset集合中时,调用该对象的compareto方法,与集合中其他对象比较大小,然后根据红黑树结构找到他的存储位置,如果比较大小后相等,则无法添加到Treeset中。

    如果两个对象通过compareto比较方法和equals方法不一致时会很麻烦,因为若前者相等,则不会让元素添加进去。这就和set集合的规则发生冲突。

    如果向Treeset中添加了可变对象,并且之后程序修改了对象引用的实例变量。那么集合不会改变他们的顺序,并且比较方法返回0。

     因此与hashset类似,如果存入可变对象的时候,尽量不要改版这些对象的实例,因为处理这些对象时,会非常复杂,而且容易出错。

    定制排序

    要想实现定制排序,需要通过Comparator接口的帮助,该接口是函数式接口,将该接口对象与集合关联,让该对象负责集合元素的排序逻辑。注意与集合元素类实现的Comparable接口是两回事。

    当集合实现了定制排序后,集合元素可以不实现Comparable接口。

    EnumSet

    EnumSet是一个专为枚举类设计的集合,集合元素也是有序的,按照在Enum类内的定义顺序来决定集合元素的顺序。

    EnumSet在内部以位向量的形式存储,非常紧凑,高效,因此处理速度非常快。

    不允许加入null元素。但可以删除判断null元素。

    没有构造器。必须调用类方法创建对象。

    当复制一个collection集合里的元素来创建EnumSet集合时,必须保证collection集合里的所有元素都是同一个枚举类的枚举值。

    hashset和treeset比较,hashset的性能较好,因为后者要额外的红黑树算法保证顺序。只有当需要顺序的集合时,才用treeset

    linkedhashset比hashset性能不好、但是遍历较快。

    EnumSet是所有set里最好的。但只能保存同一个枚举类的枚举值。

    hashset,treeset,enumset都是线程不安全的。

    List集合

    List集合代表一个元素有序,可重复的集合,集合中每个元素都有对应的顺序索引。默认按元素的添加顺序设置索引。

    import java.util.ArrayList;
    
    public class ListTest {
        public static void main(String[] args) {
            var books=new ArrayList();
            books.add("好好学习");
            books.add("天天向上");
            books.add("团结一心");
            books.add("攻抗疫情");
            books.add(1,new String("好好工作"));
            for(int i=0;i<books.size();i++){
                System.out.println(books.get(i));
            }
            books.remove(0);
            System.out.println(books);
            System.out.println(books.indexOf(new String("好好工作")));
            books.set(0,new String("锻炼身体"));
            System.out.println(books.subList(0,2));
        }
    
    好好学习
    好好工作
    天天向上
    团结一心
    攻抗疫情
    [好好工作, 天天向上, 团结一心, 攻抗疫情]
    0
    [锻炼身体, 天天向上]
    

      这里可以看出,对于两个对象,list判断相等时根据equals来判断的,两个对象,同样是“好好工作”,对于list来说,是一样的。

    java8为list集合增加了sort和replaceAll两个常用的默认方法。sort方法需要一个comparator对象来控制排序。因为是函数式接口,所以可以用lambda表达式作为参数。

    replace方法则需要一个UnaryOperator来替换所有集合元素。同样也是一个函数式接口。

    LIst额外提供了一个listIterator方法,返回一个ListIterator对象继承了Iterator接口,提供了专门操作list的方法。增加了几个方法。增加了向前迭代和add方法。

    ArryList和Vector实现类

    两者都是list类的典型实现,完全支持上面功能。

    都是基于数组实现的list类。封装了一个动态的允许再分配的object【】数组。,使用initialCapacity来设置数组长丢,当超出长度时,会自动增加。

    当大量添加元素时,可以使用ensureCapacity()来一次性增加长度·,减少分配次数,提高性能。

    如果创建空的,则默认长度为10.

    还可以调用trimToSize方法,调整数组 长度为当前元素个数,节省空间。

    ArryList和Vector几乎完全 相同,vector是java1.0就有了,还没有集合框架,因此有名字很长的方法,在2的时候,加入了框架,将vector改为list接口的实现,所以有了短的方法,其实没有区别。但是实际上vector有很多区别,尽量少用。

    两者最重要的区别是,ArryList是线程不安全的。vector是线程安全的。因此vector性能比前者底,但是即使使用线程安全,也不建议使用vector。

    stack集合继承了vector,同样是古老的集合。模拟栈,进出栈都是object元素,因此需要类型转换。 peek返回栈顶,但是不pop,pop返回栈顶并出栈。push入栈。尽量少使用,用ArryDeque替代。

    固定长度list

    数组deArrys提供了一个asList方法,该方法将一个数组或指定个数的对象转换为一个LIst集合,是Arry的一个内部类Arry.ArryList。该集合只能访问遍历。不能增加删除。

    Queue集合

    queue集合模拟队列。先进先出。新元素插入尾部,访问元素返回头部。不允许随机访问。

    add指定元素加入尾部。

    offer指定元素加入尾部,容量有限制时,比add好。

    element获取头部元素,不删除。

    peek获取头部,若为空,返回null

    poll返回头部,并删除。

    remove获取头部并删除。

    queue有一个PriorityQueue实现类,还有一个Deque接口。Deque代表一个双端队列,可以从两端来添加删除元素。可以当做队列和栈用。

    Deque有两个实现类,ArryDeque和Linkedlist。

    PriorityQueue类,并不是完全按照先进先出的原则,他为元素进行了排序。类似有TreeSet有两种方式排序。不允许插入null。若没有实现排序的函数式接口,不允许插入没有实现比较接口的类的实例。

    Deque接口与Arraydeque实现类。

    deque双端队列,即可用作队列也可用作栈。

    arraydeque是基于数组的双端队列。创建时可以指定长度。若不指定则默认16

    arraydeque和ArrayList一样,底层采用动态的,可以重新分配的object[]数组来存储集合元素。

    linkedList

    既是list接口的实现类又是queue的实现类。因此既可以根据索引来随机访问又可以当做队列或者栈使用。

    linkedlist是一个非常强大的类。

    linkelist内部是链表形式来保存集合元素美因茨随机访问性能差,但是插入,删除元素性能好,ArryList和ArryDeque都是内部数组实现的。随机访问性能好。vector因为实现了线程同步功能因此性能都比较差。

    对于基于数组集合实现,使用随机访问性能比使用iterator迭代访问的性能好,因为随机访问会被映射成对数组元素的访问。

    LIst是一个线性表接口,ArrayList是数组实现,linkedlist是链表线性表。queue代表队列

    大部分时候ArrayList性能比linkedlist好。

    对于遍历,ArrayList和vector应该用随机访问get来遍历。对于linkedlist应该采用Iterator来便利。

    如果要经常插入,删除来改变包含大量数据的list集合的大小,可以考虑使用linkedlist。

    如果有多个线程同时访问list集合的元素,开发者考虑使用Collections将集合包装成线程安全的集合。

  • 相关阅读:
    计算机语言的学习之道
    单麦克风远场语音降噪解决方案
    ESP8266 SPI 开发之软件驱动代码分析
    ESP8266 SPI 开发之软硬基础分析
    蓝牙5.0协议及下载地址
    python中往json中添加文件的方法
    Python isinstance() 函数含义及用法解析
    从一线方案商的角度来看高通QCC3020芯片
    Ubuntu18.04声卡配置问题解决
    python 音频通道分离的源码实现
  • 原文地址:https://www.cnblogs.com/tomato190/p/12342792.html
Copyright © 2011-2022 走看看