——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
Java集合简介
集合结构类型在java设计之初就已经做了一些实现,但是由于需要满足更多需求,在java1.2中良好设计的Java Collections Framework(JCF) 出现了,带来了更优的体系结构和性能更高的实现。在java1.5泛型提出之后,整个Java Collections Framework按泛型架构重写了,至此,java的集合框架已具有相当好的可用度。
今天,首先大致了解一下JCF的体系结构,然后按接口逐个了解每个常用集合类的特点。
以下实例均以java1.8实现运行。
Java Collections Framework 大致接口层次:
Iterable : Iterable接口提供了for : 简便遍历的支持
- Collections
- List
- Set
- SortedSet
- Queue
- Deque
- Map
- SortedMap
Java Collections Framework 的常见集合:
容器接口 | 抽象类 | 常用实现 | 其他实现 |
---|---|---|---|
List<E> | AbstractList<E> | ArrayList<E> | LinkedList<E> ,Vector<E> ,Stack<E> |
Set<E> | AbstractSet<E> | HashSet<E> | TreeSet<E> |
Map<K,V> | AbstractMap<K,V> | HashMap<K,V> | TreeMap<K,V> ,Hashtable<K,V> ,Properties |
JCF之前的集合类
Vector,Stack,Hashtable,Properties 在JCF出现之前,这4位元老就已经服务了2个java版本了,而JCF一出现,也把他们加入了JCF体系,与1.2才出现的新集合类不同的是,他们的实现默认是线程同步的,故相对效率较低。
Collection接口:
Collection是元素容器的抽象,JCF中没有直接实现Collection接口的类
Collection接口只定义了不多的方法:
添加元素的boolean add(E e)
,boolean addAll(Collection<? extends E> c)
清空容器的void clear()
移除元素的boolean remove(Object o)
,boolean removeAll(Collection<?> c)
,boolean removeIf(Predicate<? super E> filter)
,boolean retainAll(Collection<?> c)
(类似于交集运算)
判断元素存在的boolean contains(Object o)
,boolean containsAll(Collection<?> c)
判断容器是否为空的boolean isEmpty()
查询容器元素数量的int size()
提供迭代器的 Iterator<E> iterator()
转为数组的Object[] toArray()
,<T> T[] toArray(T[] a)
以及1.8新加入的流操作Stream<E> stream()
,Stream<E> parallelStream()
,Spliterator<E> spliterator()
List接口
List是元素列表的抽象,比Collection特殊的是,列表是有序的
List基于特性添加了以下方法:
在指定位置插入元素void add(int index, E element)
在指定位置插入集合addAll(int index, Collection<? extends E> c)
取得指定位置的元素E get(int index)
设置指定位置的元素E set(int index, E element)
移除指定位置的元素E remove(int index)
查找元素在列表中的位置int indexOf(Object o)
(从头部开始,不存在返回-1),
int lastIndexOf(Object o)
(从尾部开始)
提供特有的列表迭代器 ListIterator<E> listIterator()
,ListIterator<E> listIterator(int index)
(从某个位置开始)
指定头尾的子列表 List<E> subList(int fromIndex, int toIndex)
(含头不含尾)
流操作:排序void sort(Comparator<? super E> c)
,替换replaceAll(UnaryOperator<E> operator)
ArrayList数组列表
ArrayList是列表的数组实现,其内部结构是数组,特点是查询快O(1),增删效率低O(~n/2)+可能的数组重建操作。
LinkedList链表
LinkedList是列表的链接列表实现,其内部结构是链接节点(Node)组成的链,特点是增删效率高O(1),查询效率稍低O(~n/2)
Vector矢(这个译法不好)
Vector本意是向量,在N维空间中表示一个矢量需要N个值,所以Vector也就成了顺序数据结构的名称,它类似于ArrayList,是数组实现,但是同步的。
List的简单测试
构造长度为100000的三种List,分别测试添加,修改,插入,删除
结果如下:单位纳秒
ArrayList add cost: 12995692
LinkedList add cost: 11672940
Vector add cost: 8200942
ArrayList set cost: 7496400
LinkedList set cost: 6867191702
Vector set cost: 19437996
ArrayList Insert cost: 1243475741
LinkedList Insert cost: 20157028
Vector Insert cost: 1311598985
ArrayList Remove cost: 2497509350
LinkedList Remove cost: 20006097
Vector Remove cost: 2410710609
可见,在添加大量数据时,Vector最快,这是因为Vector的数组调整是倍增的,而ArrayList是50%增加,链表是线性增加
在查询修改数据时,链表的速度比另外两种慢2个数量级以上,这就是数据结构不同造成的差异
但是在插入和删除时,链表又表现出超过大约2个数量级的速度,同样也是因为数据结构不同
可见在实际使用中应该按需求选择容器。
Set接口
Set是元素集合的抽象,集合的特征是没有重复,Set也一样,它实现了容器内元素唯一性的机制。
Set的方法完全参照Collection,没有增加任何方法
HashSet哈希集合
HashSet是集合的哈希表实现,其内部结构是哈希表,其效率有赖于hashCode()方法的设计,良好的设计可以使查询和修改的效率都接近O(1)。
TreeSet二叉树集合
TreeSet实现了SortedSet,所以这是一个有序集合,依赖于元素自身实现Comparable自然排序或者外部比较器Comparator,如果都不存在将出现异常。其内部结构是二叉树,查询和修改效率与内容有关,在O(log(n))-O(n)之间。
Set的简单使用:
HashSet
HashSet必须依赖于hashCode以及equals方法,当然Object有默认实现:
TreeSet必须依赖于Comparable或者Comparator:
final int count=10;
//HashSet
Set<UserC> hs=new HashSet<UserC>();
byte[] rs=new byte[10];
Random r=new Random();
for (int i = 0; i < count; i++) {
r.nextBytes(rs);
hs.add(new UserC(new String(rs), r.nextLong(), r.nextBoolean()));
}
//test Unique
sp(hs.size());
for (UserC userC : hs) {
hs.add(userC);
}
sp(hs.size());
//TreeSet
//without comparable
Set<UserComparator> tsuc=new TreeSet<UserComparator>();
/* the next without comparable || comparator causes
* java.lang.ClassCastException cannot be cast to java.lang.Comparable */
// tsuc.add(new UserComparator());
//comparable
Set<UserC> tsu=new TreeSet<UserC>();
for (int i = 0; i < count; i++) {
r.nextBytes(rs);
tsu.add(new UserC(new String(rs), r.nextLong(), r.nextBoolean()));
}
//sorted by birth
for (UserC userC : tsu) {
sp(userC);
}
sp();
//comparator
Set<UserC> tsu2=new TreeSet<UserC>(new UserComparator());
for (int i = 0; i < count; i++) {
r.nextBytes(rs);
tsu2.add(new UserC(new String(rs), r.nextLong(), r.nextBoolean()));
}
//sorted by name
for (UserC userC : tsu2) {
sp(userC);
}
Map接口
Map是映射容器的抽象,其包含的不是单个元素而是键值对(Entry)。
Entry
Entry记录一个键值对。
实现:
HashMap
HashMap是映射容器的哈希表实现,其内部结构是哈希表,其效率有赖于hashCode()方法的设计,良好的设计可以使查询和修改的效率都接近O(1)。
Hashtable
Hashtable类似于HashMap,同样是哈希表实现,但是是JCF之前出现的,线程同步的容器。
Map的简单使用
HashMap类似于HashSet,依赖于hashCode以及equals方法,键和值中仅键参与哈希。
final int count=10;
//HashMap
Map<UserC,Integer> hs=new HashMap<UserC,Integer>();
byte[] rs=new byte[10];
Random r=new Random();
for (int i = 0; i < count; i++) {
r.nextBytes(rs);
hs.put(new UserC(new String(rs), r.nextLong(), r.nextBoolean()),r.nextInt(1000));
}
//test modify
for (Entry<UserC, Integer> e : hs.entrySet()) {
sp(e.getKey());
sp(e.getValue());
}
for (UserC userC : hs.keySet()) {
Integer i=hs.put(userC,-100);
sp(i);
}
for (Entry<UserC, Integer> e : hs.entrySet()) {
sp(e.getKey());
sp(e.getValue());
}
参与JCF的其他接口和特定方法
Iterator
Iterator是Collection的迭代器,它代表一个能遍历所属容器的工具,需要注意的是Iterator并没有一个当前元素,指针在两个元素之间:
方法很简单:
是否能迭代到下一个元素:boolean hasNext()
下一个 :E next()
流操作:void remove()
,void forEachRemaining(Consumer<? super E> action)
ListIterator
ListIterator是List的特有迭代器,由于列表索引index存在所以功能更强大:
相比Iterator添加了方法:E next()
是否能迭代到前一个元素:boolean hasPrevious()
前一个:E previous()
下一个的列表索引:int nextIndex()
前一个的列表索引:int previousIndex()
设定当前元素:void set(E e)
在下一个元素之前添加元素:void add(E e)
Comparator
Comparator是某类型元素的外部比较器,它能为各种集合比较和排序提供支持。各种有序集合和排序方法可以利用这个进行排序。
看似很多方法,实际常用的只有2个:
元素对比:int compare(T o1, T o2)
元素是否相等:boolean equals(Object obj)
此外还有流处理方法:
逆向比较器:Comparator<T> reversed()
词典式先后比较器系列方法:Comparator<T> thenComparing
以及一些静态方法:
自然顺序比较器:<T extends Comparable<? super T>> Comparator<T> naturalOrder()
逆向比较器:<T extends Comparable<? super T>> Comparator<T> reverseOrder()
null前置的允许null比较器:<T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
null后置的允许null比较器:<T> Comparator<T> nullsLast(Comparator<? super T> comparator)
内部成员比较器系列方法:Comparator<T> comparing
Comparable
Comparable是针对元素的自然排序接口,实现它的元素能进行自然排序和比较。各种有序集合和排序方法可以利用这个进行自然排序。
方法只有一个:
与另一个元素比较 public int compareTo(T o)
int hashCode()
和 boolean equals(Object obj)
方法
这两个方法覆盖了Object基类中的方法,目的是为元素提供哈希算法支持,Object默认实现是hashCode返回地址,equals比较引用对象是否是同一个。在许多情况下我们需要比较元素实际内容,实现更有效的哈希算法,这样就必须覆盖重写这两个方法。覆盖时有两个原则:1.equals返回true的对象哈希值必须相等;2.哈希值应该体现元素的状态,减少碰撞,均匀散布。
JCF中的工具类
Collections
这个工具类包装了大量用于操作集合的静态方法,包括诸如:排序,逆序,二分查找,置换,随机移位,填充容器,封装线程同步容器,封装不可修改的容器,单例容器等等,预置了相当多的方法和内部类。值得随时查阅调用。
Arrays
这个工具类包装了大量用于操作数组的静态方法,类似Collections,其方法包括:排序,并行排序,并行累加,二分查找,填充,比较,拷贝,切割,转向流等。
流式操作
流式操作是java1.8的新特性,是函数式编程的体现,这种语法能将集合以流的方式进行串联操作,可以有多个中间步骤和一个最终步骤。能简单的完成一系列复杂操作而且语法很有可读性。
例子:
List<String> stringCollection = new ArrayList<>();
stringCollection.add("d432a43dd2");
stringCollection.add("aadfaa2");
stringCollection.add("bb3a2b1");
stringCollection.add("a1aa1");
stringCollection.add("b2w3343bb3");
stringCollection.add("cwcc");
stringCollection.add("bbafsb2");
stringCollection.add("qdd1");
//demo1
stringCollection.stream()
.filter((s) -> s.length()>4)//filter
.sorted()//sort
.forEach(cc.sisel.util.Quic::sp);
//demo2
Optional<String> result=
stringCollection.stream()
.map(String::toUpperCase)//toUpper
.filter((s) -> s.contains("A"))//collect A
.limit(8)//length<=8
.reduce((s1,s2) -> s1+"#@#"+s2);//merge
sp(result.get());
List测试代码:
final int count =100000;
List<Integer> larr=new ArrayList<>(),
llin=new LinkedList<>(),lv=new Vector<>();
int[] source=new int[count];
ArrayList<Integer> rd=new ArrayList<Integer>(count);
Random r=new Random();
for (int i = 0; i < source.length; i++) {
source[i]=r.nextInt(count);
rd.add(i, i);
}
Collections.shuffle(rd);
long before,after;
//add test
before=System.nanoTime();
for (int i = 0; i < count; i++) {
larr.add(source[i]);
}
after=System.nanoTime();
sp("ArrayList add cost:\t"+(after-before));
before=System.nanoTime();
for (int i = 0; i < count; i++) {
llin.add(source[i]);
}
after=System.nanoTime();
sp("LinkedList add cost:\t"+(after-before));
before=System.nanoTime();
for (int i = 0; i < count; i++) {
lv.add(source[i]);
}
after=System.nanoTime();
sp("Vector add cost:\t"+(after-before));
//set test
before=System.nanoTime();
for (int i = 0; i < count; i++) {
larr.set(rd.get(i), -1);
}
after=System.nanoTime();
sp("ArrayList set cost:\t"+(after-before));
before=System.nanoTime();
for (int i = 0; i < count; i++) {
llin.set(rd.get(i), -1);
}
after=System.nanoTime();
sp("LinkedList set cost:\t"+(after-before));
before=System.nanoTime();
for (int i = 0; i < count; i++) {
lv.set(rd.get(i), -1);
}
after=System.nanoTime();
sp("Vector set cost:\t"+(after-before));
//insert test
before=System.nanoTime();
for (ListIterator<Integer> iterator = larr.listIterator(); iterator.hasNext();) {
if(iterator.hasNext()){
iterator.add(-99);
}
iterator.next();
}
after=System.nanoTime();
sp("ArrayList Insert cost:\t"+(after-before));
before=System.nanoTime();
for (ListIterator<Integer> iterator = llin.listIterator(); iterator.hasNext();) {
if(iterator.hasNext()){
iterator.add(-99);
}
iterator.next();
}
after=System.nanoTime();
sp("LinkedList Insert cost:\t"+(after-before));
for (ListIterator<Integer> iterator = lv.listIterator(); iterator.hasNext();) {
if(iterator.hasNext()){
iterator.add(-99);
}
iterator.next();
}
after=System.nanoTime();
sp("Vector Insert cost:\t"+(after-before));
//remove test
before=System.nanoTime();
for (ListIterator<Integer> iterator = larr.listIterator(larr.size()/2);larr.size()>0 ; ) {
if(iterator.hasPrevious()){
iterator.previous();
iterator.remove();
}
if(iterator.hasNext()){
iterator.next();
iterator.remove();
}
}
after=System.nanoTime();
sp("ArrayList Remove cost:\t"+(after-before));
before=System.nanoTime();
for (ListIterator<Integer> iterator = llin.listIterator(llin.size()/2);llin.size()>0 ; ) {
if(iterator.hasPrevious()){
iterator.previous();
iterator.remove();
}
if(iterator.hasNext()){
iterator.next();
iterator.remove();
}
}
after=System.nanoTime();
sp("LinkedList Remove cost:\t"+(after-before));
for (ListIterator<Integer> iterator = lv.listIterator(lv.size()/2);lv.size()>0 ; ) {
if(iterator.hasPrevious()){
iterator.previous();
iterator.remove();
}
if(iterator.hasNext()){
iterator.next();
iterator.remove();
}
}
after=System.nanoTime();
sp("Vector Remove cost:\t"+(after-before));
User 类以及UserComparator
class UserC implements Comparable<UserC>{
String name;
long birth;
boolean isMale;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (birth ^ (birth >>> 32));
result = prime * result + (isMale ? 1231 : 1237);
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UserC other = (UserC) obj;
if (birth != other.birth)
return false;
if (isMale != other.isMale)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public UserC(String name, long birth, boolean isMale) {
super();
this.name = name;
this.birth = birth;
this.isMale = isMale;
}
@Override
public String toString() {
return "UserC [name=" + name + ", birth=" + birth + ", Male="
+ isMale + "]";
}
@Override
public int compareTo(UserC o) {
long span=this.birth-o.birth;
if(span==0){
int n =this.name.compareTo(o.name);
if(n==0){
if(this.isMale^o.isMale){
return this.isMale?1:-1;
}
return 0;
}
return n;
}
return (int) span;
}
}
class UserComparator implements Comparator<UserC>{
@Override
public int compare(UserC o1, UserC o2) {
int n =o1.name.compareTo(o2.name);
if(n==0){
long span=o1.birth-o2.birth;
if(span==0){
if(o1.isMale^o2.isMale){
return o1.isMale?1:-1;
}
return 0;
}
return (int) span;
}
return n;
}
}