【java】Collection集合框架的总结

近期在巩固关于java集合的知识,因此写下总结一篇。参考书籍为梁勇的《Java语言程序设计与数据结构进阶篇》和林信良《Java JDK9学习笔记》,若描述有误请多多指教。

1.Iterable接口

Collection接口继承自Iterable接口,Iterable中定义了iterator()方法,能够返回一个迭代器。除了iterator(),接口中还实现了两个默认方法:forEach(Consumer<? super T> action)和spliterator()。前者使集合能用增强for循环遍历,后者用于并行遍历集合。以下是用Iterator,forEach,和重写forEach遍历元素的示例,重写forEach用了lambda表达式,此例只是简单的做了打印操作,还可通过在表达式中增加其他处理语句。

public void test(){
        Set<String> set = new HashSet<>();
        set.add("one");
        set.add("two");
        set.add("three");
        // Iterator
        Iterator iterable = set.iterator();
        System.out.print("Iterator: ");
        while (iterable.hasNext()){
            System.out.print(iterable.next()+" ");
        }
        // forEach
        System.out.print('\n'+"forEach: ");
        for(String e: set){
            System.out.print(e+" ");
        }
        // Override forEach
        System.out.print('\n'+"Override forEach: ");
        set.forEach(e->System.out.print(e+" "));
}
//控制台输出:
Iterator: one two three 
forEach: one two three 
Override forEach: one two three 

2.Iterator接口

迭代器(Iterator)是一种经典的设计模式,用于在不需要暴露数据结构实现细节的情况下遍历数据结构。Iterator下定义的方法有:

hasNext():boolean 判断迭代器中是否有下个元素
next():E 返回迭代器的下个元素
remove():void 从集合中移除此迭代器返回的最后一个元素
forEachRemaining(Consumer<? super E> action):void 对剩余的每个元素执行给定操作,直到处理完所有元素或操作引发异常。

线性表迭代器ListIterator继承了Itrator接口,ListIterator增加了对表进行双向遍历的能力。定义了hasPrevious和previous,previousIndex等方法。

Iterable接口和Iterator接口的关系

Iterable:在java.lang包中。实现了该接口的对象是可迭代的,能有使用foreach语句的能力。Iterable中定义的方法的操作对象是Iterator。

Iterator:在java.util包中。迭代器,提供迭代机制的对象。通过实现这个接口来提供标准的迭代器。

 

3.Collection接口

Java集合框架中定义的所有接口和类都组织在java.util包中。Collection是处理对象集合的根接口,Collection继承了Iterable接口,部分常用的方法定义如下所示。

add(E e):boolean 添加一个新元素e到该集合中
addAll(Collection<? extends E> c):boolean 将集合c中所有元素添加到该集合中
clear():void 删除该集合的所有元素
contains(Object o):boolean 判断该集合中是否包含元素o
containsAll(Collection<? extends E> c):boolean 判断该集合中是否包含集合c中的所有元素
isEmpty():boolean 判断该集合是否为空 
remove(Object o):boolean 从该集合中移除元素o
removeAll(Collection<?> c):boolean 从该集合中移除在集合c中也存在的元素
retainAll(Collection<?> c):boolean 取该集合和c的交集
size():int 返回该集合中元素的数目
iterator():Iterator 返回迭代器(即返回包含该集合所有元素的数组)
toArray():Object[] 返回一个包含该集合中所有元素的数组
toArray(T[] a):T[] 返回一个包含该集合中所有元素的指定类型数组
equals(Object o):boolean
hashcode():int

Java8之后接口里能够添加实现方法,称为默认方法,需在方法前增加default进行修饰。这一修改目的是为了解决接口的修改与现有的实现不兼容的问题,即在接口中添加新方法时不需修改所有的已有实现类。此外,java8也能在接口中声明(并且可以提供实现)静态方法。

AbstractCollection抽象类提供了Collecttion接口的大部分实现,除了add,size,iterator方法之外,实现了Collection的其他所有方法。该类也称为便利抽象类,用户可以继承它来很方便地实现自己的数据结构。

除了java.util.PriorityQueue没有实现Cloneable外,集合框架中的其他具体实现类都实现了java.util.Cloneable和java.io.Serializable接口,因此除了优先队列,Collection其他实例都是可克隆的。克隆能够创建该集合的副本,且在副本上做的修改不会影响到原集合。

 

4.List

List又称为线性表,List接口继承了Collection接口,定义了一个用于顺序存储元素的接口。除了Collection中定义的方法,还包含的常用方法如下:

sort(Comparator<? super E> c):void 排序,源码中将该集合转换为Array数组,再用Arrays.sort()排序
get(int index):E 取出索引为index的元素
set(int index, E element):E 将索引为index的元素用element取代并返回原来的元素
indexOf(Object o):int 返回线性表中第一个与指定元素匹配的元素的索引
lastIndexOf(Object o):int 返回线性表中最后一个与指定元素匹配的元素的索引
listIterator():ListIterator<E> 返回线性表的迭代器
listIterator(int index):ListIterator<E> 返回索引从index开始的迭代器
subList(int fromIndex, int toIndex):List<E> 截取索引从fromIndex到toIndex的子线性表

List具体实现类有ArrayList和LinkedList。前者继承了AbstractList抽象类,后者继承了AbstractSequentialList抽象类。AbstractList提供了List接口的部分实现,AbstractSequentialList扩展了AbstractList以提供对链表的支持。

ArrayList用数组存储元素,并且是动态扩容,若元素个数超过数组的容量就创建一个更大的新数组并将当前数组中所有元素都复制到新数组,这是比较耗废时间和内存的操作,因此若大致知道元素所需容量,在创建表时可以指定容量,可以节省扩容成本。ArrayList默认初始容量为10,当加入第一个元素时容量由0初始化为10,当新增元素后容量会超,则新建容量为原始容量1.5倍的新数组(通过位运算计算),并将原始数据拷贝到新数组。

LinkedList在一个链表中存储元素。

ArrayList和LinkedList的选择:

当需要频繁对线性表进行插入或删除操作应该选择Linkedlist,需要随机存取元素应选择ArrayList。因为ArrayList使用数组存储,数组在内存中是连续的线性空间,根据索引随机存取速度快,调整索引表现差。LinkedList在新增对象时才新增Node对象形成链状结构,不会事先耗费内存。Linkedlist有get方法来获取元素,但是效率很低,是通过从第一个对象开始计数来取得指定索引的元素。应该使用增强for循环或ForEach方法

Vector和Stack

Vector是AbstractList的子类。与ArrayList相比,Vector包含用于访问和修改向量的同步方法。对于不需要同步的应用程序来说应该选择更高效的ArrayList。

栈是一种先进后出的数据结构,Stack(栈)是Vector的子类,包含了:

empty(): boolean 判断栈是否为空
peek(): E 返回栈顶元素
pop(): E 返回并移除栈顶元素
push(Object o): E 往栈顶中添加新元素
search(Object o):int 返回o在栈中离栈顶最近的位置,不存在返回-1

Queue

队列是一种先进先出的数据结构。Queue接口继承自Collection接口,加入了更适合队列的插入,获取和检验等操作。

Deque(双端队列)继承了Queue接口,LinkeList实现了Deque接口,因此可以使用LinkeList创建队列,能够高效的在两端进行插入或移除。

PriorityQueue(优先队列)实现了Queue接口,优先级最高的元素最先被移除。优先级的判定又Comparable或Comparator决定。

5.Set

set是用于存储和处理无重复元素的高效数据结构。Set接口扩展了Collection接口,它没有引入新的方法或常量,只是规定Set实例中不能出现重复元素。AbstractSet类继承了AbstractCollection类并部分实现了Set接口。AbstractSet类提供了equals方法和hashcode方法的具体实现。Set接口的具体类是HashSet,LinkedHashSet,TreeSet。遍历Set中的元素可使用foreach循环。

Hashset

HashSet是基于HashMap实现的,将元素保存于map的key中,而value使用一个static final的Object对象标识。插入新元素时通过判断HashMap中是否有key与新元素相等,若无则添加,若有则跳过,以此保证不重复性。相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成。

HashSet默认初始容量为16,负载系数为0.75,即HashSet中元素超过现容量的0.75倍时自动扩容为现容量的2倍。构造方法中可添加参数修改初始容量和负载系数。负载系数0.75是在时间开销和空间开销上一个好的均衡。

HashSet中元素是无序的。

LinkedHashSet

LinkedHashSet用一个链表实现来扩展HashSet类,LinkedHashSet是有序的,可以按照插入先后来有序存储。若无需元素有序,应选择HashSet,它比LinkedHashSet更为高效。

TreeSet

SortedSet是Set的子接口,能够确保元素有序。此外它还提供了first()和last()方法以返回set中第一个元素和最后一个元素,以及方法headSet(toElement)和tailSet(fromElement)分别返回小于toElement和大于等于fromElement的部分。

NevigableSet扩展了SortedSet接口,提供了元素比较并返回元素,删除第一或最后一个元素并返回等方法。

TreeSet实现了NevigableSet接口,元素是有序的,是通过元素实现Comparable接口或在构造TreeSet时指定Comparator来排序。

线性表(list)和规则集(set)性能比较:仅对比list和set的查询速度和删除速度,set都比list要快很多。

6.Collections

Collections类包含了集合和线性表中的通用操作的静态方法。主要包含了用于线性表的sort,binarySearch,reverse,shuffle,copy,nCopies和fill(以指定元素替换线性表中所有元素)方法,以及集合的max,min,disjoint(判断两个集合中是否有重复元素)和frequency方法。其中,copy方法是浅复制,复制的只是引用。nCopies(int n,T o)创建了一个包含o的n个副本的不可变线性表,所有元素都有相同引用。

上述的ArrayList,LinkedList,HashSet,TreeSet,LinkedHashSet,Hashmap,LinkedHashMap,TreeMap都是线程不安全的,即在多线程的情况下,不提供数据访问保护,导致数据不一致或脏数据的产生。Collection中还有一类用于提供线程安全的集合。主要有:

synchronizedCollection(Collection<T> c):<T> Collection<T> 返回线程安全的collection
synchronizedSet(Set<T> s):<T> Set<T> 返回线程安全的set
synchronizedList(List<T> list):<T> List<T> 返回线程安全的list
synchronizedMap(Map<K,V> m): <K,V> Map<K,V> 返回线程安全的map

Tips:

传统方法主要采用上述包装类来获得同步集合,但是上述方法会锁住整个集合,因此降低了并发性能。从java5 开始,在java.util.concurrent包下,提供了大量支持高效并发访问的集合接口和实现类,主要包括以Concurrent开头(ConcurrentHashMap等)的集合类和以CopyOnWrite开头(CopyOnWriteArrayList的)的集合类。前者引入了分段的概念,适用于写多读少的情况,访问时不会锁住整个集合而是只对相关片段上锁,未上锁部分依旧支持多线程访问。后者采用读写分离的思想,允许多个线程以非同步的方式读,适合在读多写少的情况下使用,因为有线程写的时候它会将整个集合复制一个副本来进行操作,频繁复制会使性能变差。此外,ConcurrentHashMap不允许null作为键或值。

写多读少,有实时性要求选择Concurrent

读多写少,无实时性要求选择CopyOnWrite

7.Map

map是一个存储键值对集合的容器对象,它提供了通过键快速获取,删除和更新键值对的功能。map中不能存在重复键。map可以通过forEach方法和entrySet等方法进行遍历。后者能够返回一个Set对象,对象中元素都是Map.Entry实例,可以直接调用getKey(),getValue()来获取每个Map.Entry的键和值。Map中的常用方法如下:

size():int 返回map的大小
isEmpty(): boolean 判断是否为空
containsKey(Object key):boolean 判断key是否存在
containsValue(Object value):boolean 判断值是否存在
get(Object key):V 按照key值获取value
put(K key, V value):V 加入键值对
remove(Object key):V 按照key值删除键值对
putAll(Map<? extends K, ? extends V> m):void 将m中所有键值对加入到本map
clear():void 删除所有键值对
keySet():Set<K> 返回包含所有键的set
values():Collection<V>  返回包含所有值的集合
entrySet():Set<Map.Entry<K, V>> 返回包含所有键值对的Map.Entry Set
forEach(BiConsumer<? super K, ? super V> action):void 对每条键值对执行操作

AbstractMap是一个便利抽象类,实现了Map中除了entrySet之外的所有方法。map的具体实现类有HashMap,LinkedHashMap和TreeMap。

HashMap

HashMap对于定位一个值,插入键值对以及删除键值对是高效的。Java2以前一般使用Hashtable来存储键值对,它实现了Map接口,除了更新方法是同步的以外,它与HashMap的使用方法是相同的。

LinkedHashMap

LinkedHashMap用链表来扩展HashMap类,元素可以按照插入的先后算徐或最后一次被访问的顺序来排序。可在构造方法中指定顺序,默认按照插入顺序。

TreeMap

SortedMap是map的子接口,它能够确保map中的键值对是有序的,还提供了firstKey和lastKey方法返回map中第一个和最后一个键。headMap(toKey)和tailMap(fromKey)分别返回键小于toKey的部分和大于等于fromKey的部分。

NavigableMap继承了SortedMap,提供了键比较并返回键,删除第一或最后一个键值对并返回等方法

TreeMap在遍历排好序的键时很高效,是按照键的比较来排序的。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值