集合

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将集合包装成线程安全的集合。

posted @ 2020-02-21 19:58  小甲点点  阅读(225)  评论(0编辑  收藏  举报