Java 15:集合

来源:互联网 发布:恐怖美术馆ib知乎 编辑:程序博客网 时间:2024/06/10 00:19

Java类中,集合类的基本接口是Collection接口,该接口中有一个Iteration<E> iterator();返回一个实现了Iterator接口的对象,可以用这个迭代器对象一次访问集合中所有的元素。

public interface Iterator<E>有三个方法:next()、hasNext()、remove()

通过反复调用next()可以逐个访问集合中的所有元素。现在可以用for each来代替迭代器的显式循环,foreach可以和任何实现了Iterable接口的对象一起工作,因为:

public interface Collection<E> extends Iterable<E>,因此标准库中所有的集合都可以使用“foreach”进行循环


循环访问的顺序取决于集合类型,ArrayList这种会按索引递增访问,而HashSet这一类则会按照某种随机的顺序出现,

Java集合类库的迭代器和那种按索引访问的迭代器不同,可以将迭代器理解为在两个对象之间,调用next()时,迭代器越过一个元素,并返回其引用。(这一层面上,Iterator.next()和InputStream.read是等效的,从数据流中读取一个字节就会“消耗掉”这个字节,返回该字节)


Iterator接口的remove()将会删除上次调用next方法返回的元素,如果没有执行过next()就直接删除就会报错


具体集合:

在Java集合类中,以Map结尾的类实现了Map接口,其他集合类都实现了Collection接口, 

ArrayList可以动态增减的索引序列LinkedList可以在任何位置高效插入删除的有序序列ArrayDeque用循环数组实现的双端队列HashSet没有重复元素的有序集合TreeSet一种有序集EnumSet包含枚举类型值的集合LinkedHashSet可以记住元素插入顺序的集合PriorityQueue可以高效删除最小元素的集合HashMap存储键值关系的存储结构TreeMap键值有序排列的映射表EnumMap键值属于枚举类型的映射表LinkedHashMap记住键值添加顺序的映射表WeakHashMap值没有用处以后可以被垃圾回收器回收的映射表IdentityHashMap用==而不用equals比较键值的映射表

链表linked list:

数组和ArrayList在连续的存储位置上存放对象引用,有一个重大缺陷,就是从数组的中间位置删除一个元素需要很大的代价,因为删除以后数组中后面的所有元素要向前移动,插入以后则是向后整体移动

链表则可以解决这个问题,链表将每个对象存放在独立的节点中,每个节点还存放着序列中下一个节点的引用。在Java中,所有的链表其实都是双向的,即每一个还存放着前驱节点的引用,在链表中删除元素只需要更新该元素前后节点的引用即可

在LinkedList中调用迭代器的next()和remove()和前面说的标准一样,在删除元素之前必须用next()跨越过它(删除第i个元素执行i次next,然后remove)

LinkedList是有序的集合,add方法想尾部添加对象,需要向链表中间添加对象时,因为迭代器就是用来描述集合中位置的,因此这种依赖于位置的add方法将由迭代器负责。

只有自然有序的集合使用迭代器添加元素才有意义,因此Iterator接口中没有add方法,而子接口ListIterator中包含了add,ListIterator同时包含next和hasNext对应的previous和hasPrevious方法。

LinkedList的iterator()方法返回的自然是ListIterator

注意,使用LinkedList对象的add是插入尾部,而使用迭代器的add会插入迭代器当前位置,这是再调用一次next得到的不是刚插入的元素,而是原来情况下的下一个元素

add只依赖与迭代器的位置,而remove还依赖迭代器的状态,无法连续调用两次remove

多个迭代器同并发处理可能会不一致

链表不支持快速地随机访问,如果想要查看链表的第n个元素只能从头越过n-1个元素,因此需要采用整数索引访问元素时通常不会使用链表。尽管如此,LinkedList还是提供了访问特定位置元素的get方法,虽然效率不高(get方法有一个优化在于,如果查找的位置大于一半,就从尾部反过来查找)。

使用链表的唯一理由就是尽可能减少在中间插入和删除的代价


散列集:

链表和数组可以按照人们的意愿排列元素的次序,但是如果忘记某个指定元素的位置,就需要依次访问所有元素,直到找到。如果不在意元素的顺序,可以有几种快速查找元素的数据结构,其缺点就是无法控制元素出现的顺序,它们按照有利于其操作目的的原则组织元素

散列表:为每个对象根据其实例域计算一个整数,称为散列码(Hash码),如果自定义类就要负责四先这个类的hashCode(注意,第五章的内容,自己实现的hashCode必须与equals方法兼容,equals为true,则hashCode必须相同)

在Java中,散列表用链表数组实现,每一个链表称为一个“桶”,要想查找表中对象的位置,首先计算其散列码,然后与桶的总数取余,得到的结果就是桶的索引,如果桶中没有其他元素,直接插入到桶中,如果桶占了,就发生“散列冲突”,这时就需要用新对象和桶中的对象进行比较,看是否已经存在,

如果想要更多地控制散列表的运行性能,就要指定一个初始的桶数,桶数是指具有相同散列值的桶的数目,如果大致知道有多少元素要插入到散列表中,就可以设置桶数,标准类库设置的是2的幂数,默认16

如果无法估计,散列表太满,就需要再散列,就是创建一个桶数更多的表,再将所有元素插入到新表,装填因子决定何时进行“再散列”,比如设为0,75就是超过75%已经填入元素,这个表就会用双倍的桶数自动进行再散列。

散列表可以用于实现几个重要的数据结构,其中最简单的是set类型,set是没有重复元素的元素集合。set的add方法首先确认集合中不存在然后才添加,contain是方法也被重新定义,用来快速查找某个元素是否存在,只会在某个桶中查找而不会查找所有元素

在更改集合中元素时要小心,如果元素的散列码发生了变化,则元素在数据结构中的位置也会相应变化

试了下在HashSet加入Integer或者String元素以后,修改他们引用的值,HashSet里面的东西包括对应的值都没有改变,可能由于他们是不可变对象

换成Date,它的HashCode是有time计算的,修改某一个对象的time,HashSet中的值相应改变,

问题又来了:如果将某一个对象的hash值有关的域改成一样,此时HashSet有重复的元素,但是循环输出时,不会主动给他去除重复,而是将一样的也输出出来


树集:

树集可以以任意顺序插入到集合中,对集合进行遍历时,每个值将自动按照排序后的顺序呈现。

TreeSet<Integer> ts=new TreeSet();ts.add(2);ts.add(4);ts.add(1);for (Integer integer : ts) {System.out.println(integer);}
不论是整数还是字符串,都以排好序的形式呈现,TreeSet使用的是红黑树,每一次一个元素添加到树中,总会放置到正确的位置。添加到树中要比添加到散列中慢,比添加到数组或链表中要快。如果树中包含n个元素,查找新元素的正确位置平均需要log2n次比较。


TreeSet怎么知道元素以哪种方式排序呢,在默认情况下,树集假定插入的元素实现了Comparable接口,接口中定义了comparable方法,如果要插入自定义的对象,就必须通过实现Comparable接口,自定义排序规则。Object类中没有compareTo的默认实现。

可以通过给TreeSet构造函数传递Comparator,用于解决:可能对于一个类型有多种排序方法,但是只能实现Comparable接口一次,或者某一类没有实现Comparable接口


是否应该使用树集取代散列集?要看是否有排序需求。


队列和双端队列:

队列(Queue)可以有效地在尾部添加元素,在头部删除元素。

双端队列(Deque)可以在头尾有效地添加、删除,但是不能在中间添加元素。

Java中提供了Deque接口,并有ArrayDeque和LinkedDeque两种实现。在队列满时add,队列空时remove、get会报错,使用offer、pool、peek方法不会报错


优先级队列:可以按照任意的顺序插入,但总是按照排序的顺序检索,无论何时调用remove方法,总是获得当前队列中优先级最小的元素。优先级队列使用堆数据结构,堆是一个自我调整的二叉树,对树进行添加、删除操作,可以让最小的元素移动到根,不用花费时间排序。和TreeSet一样,一个优先队列既可以保存实现了Comparable接口的对象,也可以保存在构造器中提供比较器的对象。没有提供这两种比较法方法,直接将对象加入到优先级队列中,会报运行时错误。


映射集:集是一个集合,可以快速地查找现有的元素,但是要查看一个元素,需要有查找元素的精确副本,这不是非常通用的查找方式,通常我们知道一些键的信息,并查到与之对应的元素,映射表就是为此设计的,存放键值对。Java中提供了HashMap和TreeMap,都实现了Map接口,HashMap对键进行散列,TreeMap用键的整体顺序。都是用的键作为散列和比较,值不用于散列和比较。

键必须是唯一的,不能对一个键存放两个值,如果连续put两次,第一次put返回null,第二次返回第一次的值,并覆盖。

对于可变对象,在加入HashMap以后,修改对象其域,再从HashMap中获取,状态也是改变以后的。String、Integer等不可变类型则保留原始put结果。


链接散列集和链接映射表:LinkedHashSet和LinkedHashMap,可以保存数据插入的顺序


找出两个HashSet的交集(Map中没有该方法):

Set<Integer> result=new HashSet<>(hs);result.retainAll(hs2);for (Integer integer : result) {System.out.println(integer);}




原创粉丝点击