Java集合(一)集合框架概述
本系列文章:
Java集合(一)集合框架概述
Java集合(二)List、ArrayList、LinkedList
Java集合(三)CopyOnWriteArrayList、Vector、Stack
Java集合(四)Map、HashMap
Java集合(五)LinkedHashMap、TreeMap
Java集合(六)Hashtable、ConcurrentHashMap
Java集合(七)Set、HashSet、LinkedHashSet、TreeSet
Java集合(八)BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue
一、集合框架
集合框架:用于存储数据的容器。集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:
- 1、接口
表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达到“多态”。在面向对象编程语言中,接口通常用来形成规范。 - 2、实现
集合接口的具体实现,是重用性很高的数据结构。 - 3、算法
在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。
常见的集合对比:
1.1 集合体系*
Collection接口和Map接口是所有集合框架的父接口。
Collection接口的父接口是Iterable接口,Collection接口的子接口包括:Set接口、List接口和Queue接口。
- 1、Collection体系
由上图可知:Collection继承了Iterable,Iterable中有3个方法:
//获取迭代器
Iterator<T> iterator();
//for each循环
default void forEach(Consumer<? super T> action);
//并行迭代器循环
default Spliterator<T> spliterator();
Iterator是迭代器接口,定义了几个迭代操作的方法,也就是需要具体的实现类去实现的迭代器逻辑:
//询问是否还有下一个元素,返回true表示有,反之表示没有
boolean hasNext();
//获取下一个元素
E next();
//删除上次调用next()方法时返回的元素
default void remove();
//遍历所有元素
default void forEachRemaining(Consumer<? super E> action)
可以看出,实现了Collection接口的实现类(即List、Set、Queue的实现类),都既支持for each循环;又支持迭代器循环(要自己实现迭代器逻辑)
。
-
2、Map体系
-
3、List、Set、Map的特征
集合类 | 特点 | 遍历 |
---|---|---|
List | 有序(元素存入集合的顺序和取出的顺序一致); 元素可以重复; 可以插入多个Null元素; 元素都有索引。 | 支持for循环,也就是通过下标来遍历; 也可以用迭代器遍历。 |
Set | 无序(存入和取出顺序有可能不一致); 元素不可以重复; 只允许存入一个null元素; 必须保证元素唯一性。 | 只能用迭代方式遍历,因为无序,无法用下标来取得想要的值。 |
Map | 键值对集合,存储键、值和之间的映射。 Key无序,唯一; value不要求有序,允许重复。 | 底层实现也是List,所以也支持for循环,也就是通过下标来遍历; 也可以用迭代器。 |
- 4、List、Set、Map的常见实现类
接口 | 常用实现类 |
---|---|
Map | HashMap、LinkedHashMap、Hashtable、ConcurrentHashMap |
Set | HashSet、LinkedHashSet |
List | ArrayList、LinkedList、Stack |
Queue | ArrayBlockingQueue、LinkedBlockingQueue |
- 5、List、Set、Map的底层实现方式
List实现类底层实现方式:
集合类 | 底层实现 |
---|---|
Arraylist | Object数组 |
Vector | Object数组 |
LinkedList | 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) |
Set实现类底层实现方式:
集合类 | 底层实现 |
---|---|
HashSet | 基于HashMap实现,用HashMap来保存元素 |
LinkedHashSet | LinkedHashSet继承HashSet,基于LinkedHashMap实现 |
TreeSet | 红黑树(自平衡的排序二叉树) |
Map实现类底层实现方式:
集合类 | 底层实现 |
---|---|
HashMap | JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在。JDK1.8以后链表在元素较多时,可以转化为红黑树 |
LinkedHashMap | LinkedHashMap继承HashMap,所以它的底层仍然是由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序(LRU)相关逻辑 |
Hashtable | 和HashMap相似,由数组+链表组成,数组是Hashtable的主体,链表则是主要为了解决哈希冲突而存在 |
TreeMap | 红黑树(平衡二叉排序树) |
1.2 迭代器
1.2.1 Iterator迭代器
Iterator对象称为迭代器,迭代器可以对集合进行遍历(只能即单向遍历)。
迭代器在代码里就是一个接口:
public interface Iterator<E> {
//集合中是否还有元素,有则返回true,否则返回fasle
boolean hasNext();
//第一次调用Iterator的next()方法时,它返回序列的第一个元素;
//其他情况使用next()获得序列中的下一个元素.
E next();
//删除进一次通过next()方法获取的元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
Iterator使用示例:
//迭代器循环
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(o);
}
1.2.2 ListIterator迭代器
ListIterator是Collection框架中的一个接口;是用于扩展Iterator接口的。ListIterator,可以向前和向后遍历集合的元素,还可以添加、删除或修改集合中的任何元素
。
ListIterator迭代器包含的方法:
//如果列表迭代器后面还有元素,则返回 true,否则返回false
boolean hasNext();
//返回列表中ListIterator指向位置后面的元素
E next();
//以逆向遍历列表,列表迭代器前面还有元素,则返回 true,否则返回false
boolean hasPrevious();
//返回列表中ListIterator指向位置前面的元素
E previous();
//返回列表中ListIterator所需位置后面元素的索引
int nextIndex();
//返回列表中ListIterator所需位置前面元素的索引
int previousIndex();
//从列表中删除next()或previous()返回的最后一个元素
void remove();
//从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e
void set(E e);
//将指定的元素插入列表,插入位置为迭代器当前位置之前
void add(E e);
1.2.3 两者的区别
Iterator | ListIterator | |
---|---|---|
遍历 | 可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。 | 只能遍历List实现的对象,可以向前和向后遍历集合中的元素。 |
添加元素 | 无法向集合中添加元素 | 可以向集合添加元素 |
修改元素 | 无法修改集合中的元素 | 可以使用set方法修改集合中的元素 |
是否可以获取元素索引 | 无法获取集合中元素的索引 | 可以获取集合中元素的索引 |
1.3 线程安全*
简单来说,如果代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么就是线程安全的;反之则是线程不安全。
1.3.1 线程安全的集合
线程安全集合类可以分为三大类:
- 1、遗留的线程安全集合
Hashtable、Vector(直接使用synchronized来保证线程安全,不建议使用,性能差)。
Stack。 - 2、使用Collections封装的线程安全集合
如:Collections.synchronizedList、Collections.synchronizedMap、Collections.synchronizedSet(不建议使用,性能差)。 - 3、java.util.concurrent.*
即JUC目录下的并发容器,包含三类关键词:Blocking、CopyOnWrite、Concurrent(建议使用,性能好)。
1、Blocking:大部分实现基于锁,并提供用来阻塞的方法,如BlockingQueue阻塞队列,非常适合用于作为数据共享的通道。
2、CopyOnWrite:拷贝读写,用空间换时间;
3、Concurrent:内部很多操作使用CAS优化,一般可以提供较高吞吐量。
ConcurrentHashMap:线程安全的HashMap。
CopyOnWriteArrayList:线程安全的List,在读多写少的场合性能非常好,远远好于Vector。
ConcurrentSkipListMap: 跳表实现的Map,使用跳表的数据结构进行快速查找。
1.3.2 线程不安全的集合
即没有使用同步或并发策略的集合。这类集合在日常开发中也使用的较多,如:HashMap、ArrayList、LinkedList、HashSet、TreeSet、TreeMap。
1.4 两种fail机制
1.4.1 fail-fast机制*
实现fail-fast机制的集合都是强一致性的,在各种遍历(不管是forEach遍历,还是iterator获取迭代器进行迭代)之前,都会提取保存modCount的值,用于后面每一次迭代/遍历前进行比较,不一致则抛出并发修改异常(ConcurrentModificationException),终止遍历。
除了JUC目录下的并发集合,Collection中所有Iterator的实现类(如常见的ArrayList、LinkedList、HashMap)都是按fail-fast来设计的,这些集合都是线程不安全的
。
以ArrayList为例,有多处代码都能抛出ConcurrentModificationException:
private class Itr implements Iterator<E> {
//迭代器游标
int cursor;
//最后一个元素索引为-1
int lastRet = -1;
//初始化"期望的modCount"expectedModCount,如果在迭代过程中集合元素
//发生了变化,则modCount就会跟着变化,此时用expectedModCount和modCount
//比较,就能知道这次集合元素变化
int expectedModCount = modCount;
//是否还有下一个元素
public boolean hasNext() {
return cursor != size;
}
//获取下一个元素
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
//删除元素
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//省略一些代码
//检测在迭代过程中,集合元素是否发生了变化
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
modCount表示对ArrayList中元素真正的修改次数,对ArrayList内容的修改都将增加这个值,在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。
在迭代过程中,会判断modCount跟expectedModCount是否相等,如果不相等就表示ArrayList里的元素已经被修改,此时就会抛出ConcurrentModificationException异常。
上述代码中,modCount声明为volatile,保证线程之间修改的可见性
。
1.4.2 不同情形下的fail-fast
- 多线程下的fail-fast
常见的情形:存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2增删了集合A中的数据,那么这个时候程序就会抛出ConcurrentModificationException异常,从而产生fail-fast事件。示例:
public class FailFastTest {
public static List<String> list = new ArrayList<>();
private static class MyThread1 extends Thread {
@Override
public void run() {
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String s = iterator.next();
System.out.println(this.getName() + ":" + s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
super.run();
}
}
private static class MyThread2 extends Thread {
int i = 0;
@Override
public void run() {
while (i < 10) {
System.out.println("thread2:" + i);
if (i == 2) {
list.remove(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i ++;
}
}
}
public static void main(String[] args) {
for(int i = 0 ; i < 10;i++){
list.add(i+"");
}
MyThread1 thread1 = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread1.setName("thread1");
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
启动两个线程,其中一个线程1对list进行迭代,另一个线程2在线程1的迭代过程中remove一个元素,结果也是抛出了ConcurrentModificationException:
- 单线程下的fail-fast
当然,不仅是多个线程,单个线程也会出现fail-fast机制,因为在单线程下也可能会修改集合里的元素数量,导致modCount改变。
单线程下的fail-fast示例:
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
list.remove(3);
}
System.out.println(iterator.next());
i ++;
}
并使用迭代器遍历ArrayList集合,在遍历过程中,remove一个元素,这个时候,就会发生fail-fast。
1.4.3 解决fail-fast的方法
- 1、将线程不安全的集合转换成线程安全的集合
比如使用Collections.synchronizedXxx将线程不安全的集合转换成线程安全的集合。示例:
List<String> list = new ArrayList<String>();
List<String> synchronizedList = Collections.synchronizedList(list);
- 2、直接使用线程安全的集合
使用JUC目录下的线程安全类。如CopyOnWriteArrayList替换ArrayList,CopyOnWriteArrayList的所有可变操作(add、set等)都是通过对底层数组进行一次新的复制来实现的。示例:
private static List<String> list = new ArrayList<String>();
//替换为
private static List<String> list = new CopyOnWriteArrayList<String>();
CopyOnWriterArrayList在是使用上跟 ArrayList几乎一样, CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于 CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性
。
- 3、 不要使用集合自身的删除方法,而使用集合中迭代器的删除方法
以ArrayList为例,迭代器的remove方法代码:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
可以看到,迭代器里的remove方法不会修改modCount的值,并且不会对后面的遍历造成影响。但是该方法remove不能指定元素,只能remove当前遍历过的那个元素,这也是该方法的局限性。使用迭代器里remove方法示例:
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
iterator.remove(); //迭代器的remove()方法
}
System.out.println(iterator.next());
i ++;
}
1.3.3 fail-safe机制*
JUC目录下的集合用的是fail-safe机制,该机制允许在遍历集合元素的同时增删元素
。不过不同的集合,实现原理不一样,接下来以CopyOnWriteArrayList、ConcurrentHashMap为例解释。
- CopyOnWriteArrayList
CopyOnWriteArrayList的实现方式是每次更新操作都会复制一份出来并替换原来的Object数组,不会影响到创建迭代器时拿到的Object数组的迭代遍历,写操作也不会阻塞读操作。
CopyOnWriteArrayList的迭代器和for遍历并没有检测并发修改异常的操作,但是迭代器的set()、add()、remove()会抛出UnsupportedOperationException的异常。也就是说遍历只支持读操作,读和写操作的是两个集合。
CopyOnWriteArrayList是弱一致性的,允许迭代时修改数据,代价是迭代时看到的可能不是最新的数据,保证了可用性。 - ConcurrentHashMap
ConcurrentHashMap的keys()、values()、entrySet()同样也没有检测并发修改的操作。
keySet()、entrySet()支持添加删除操作。
ConcurrentHashMap中节点的val和next都是volatile修饰的,如果变化发生在已遍历过的部分,迭代器就不会反映出来,而如果变化发生在未遍历过的部分,迭代器就会发现并反映出来。也就是说用volatile保证了数据在多线程情况下的可见性。 - 总结
JUC包下的容器都是fail-safe,都是在数据的一致性跟可用性中选择了可用性,允许出现数据的短期不一致,但是保证最终数据的一致性
。
1.5 集合相关问题
1.5.1 集合和数组的区别
集合 | 数组 | |
---|---|---|
长度是否可变 | 可变 | 不可变 |
存储的数据类型 | 只能存储引用数据类型 | 可以存储基本数据类型,也可以存储引用数据类型 |
存储的元素是否类型一致 | 可以是不同类型的数据 | 必须是同一个类型的数据 |
1.5.2 怎么确保一个集合不能被修改
可以使用Collections.unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出Java. lang. UnsupportedOperationException异常。 示例:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
1.5.3 为何没有像Iterator.add()这样的方法,向集合中添加元素
语义不明。因为Iterator的协议不能确保迭代的次序,添加元素时无法确定添加的位置。
1.5.4 集合框架中的泛型有什么优点
确保同一集合里存储的是相同类型的元素,避免使用集合中的元素类型不一致时产生ClassCastException。
1.5.5 用哪两种方式来实现集合的排序
1、直接使用排好序的集合,如TreeSet或TreeMap。
2、也可以使用有顺序的的集合,如List,然后通过Collections.sort()排序。
1.5.6 集合框架使用注意事项
- 1、根据需要选择正确的集合类型
- 2、集合初始化时,指定集合初始值大小
如HashMap使用HashMap(int initialCapacity) 初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。这样可以减少由于扩容导致的性能损耗。 - 3、使用JDK提供的不可变类(如Integer、String)作为Map的key
可以避免自己实现hashCode()和equals()。 - 4、使用集合转数组的方法,必须使用集合的toArray(T[ ] array),传入的是类型完全一致、长度为0的空数组
反例:直接使用toArray无参方法存在问题,此方法返回值只能是Object[ ]类,在进行类型转换时,可能会出现ClassCastException错误。
正例:
List<String> list = new ArrayList<>(2);
list.add("123");
list.add("qwe");
String[] array = list.toArray(new String[0]);
说明:使用toArray带参方法,数组空间大小的length:
1) 等于0,动态创建与size相同的数组,性能最好。
2) 大于0但小于size,重新创建大小等于size的数组,增加GC负担。
3) 等于size,在高并发情况下,数组创建完成之后,size正在变大的情况下,负面影响与2相同。
4) 大于size,空间浪费,且在size处插null值,存在NPE隐患。
二、Collections工具类
Collections是一个集合工具类,作用就是为集合框架提供某些功能实现。
Collections工具类常用方法:
1)排序。
2)查找、替换。
3)同步控制。不推荐,需要线程安全的集合类型时,可以使用JUC包下的并发集合。
2.1 排序相关方法
//反转
void reverse(List list)
//随机排序
void shuffle(List list)
//按自然排序的升序排序
void sort(List list)
//定制排序,由Comparator控制排序逻辑
void sort(List list, Comparator c)
//交换两个索引位置的元素
void swap(List list, int i , int j)
2.1.1 Collections.sort的底层实现
在JDK1.6中,Collections.sort方法使用的是MergeSort(归并排序);在JDK1.7中,Collections.sort的内部实现换成了TimeSort。
Timsort是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法。
- Timsort的核心过程
1、数组个数小于32的情况使用二分插入排序。
2、数组大于32时, 先算出一个合适的大小,在将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的分区。其中每一个分区叫一个run。针对这些run序列,每次拿一个run出来按规则进行合并。每次合并会将两个run合并成一个run。合并的结果保存到栈中。合并直到消耗掉所有的run,这时将栈上剩余的 run合并到只剩一个run为止。这时这个仅剩的run便是排好序的结果。
2.1.2 Collections.sort的两种形式
Collections.sort方法有两种重载的形式:第一种要求传入的待排序容器中存放的对象必须实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个定义的排序规则。
这两种排序方式对集合元素的要求,在Collections的源码中也可以看出:
public static <T extends Comparable<? super T>> void sort(List<T> list)
public static <T> void sort(List<T> list, Comparator<? super T> c)
- Comparable和Comparator
Comparable出自java.lang包,有个compareTo(Object obj)方法用来排序。
Comparator出自java.util包,有个compare(Object obj1, Object obj2)方法用来排序。
一般需要对一个集合使用自定义排序时,就至少要实现上面的2个接口之一。
2.1.3 Comparable*
Comparable可以认为是一个内比较器
,实现了Comparable接口的类所产生的对象,就可以和同类型对象比较。实现Comparable接口需要重写compareTo方法,compareTo方法也被称为自然比较方法
。compareTo方法的返回值是int,有三种情况:
- 对象a大于对象b(compareTo方法里面的对象),那么返回正整数。
- 对象a等于对象b(compareTo方法里面的对象),那么返回0。
- 对象a小于对象b(compareTo方法里面的对象),那么返回负整数。
Comparable接口使用示例:
//实体类
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//重写compareTo方法实现按年龄来排序
@Override
public int compareTo(Person o) {
if (this.age > o.getAge()) {
return 1;
}
if (this.age < o.getAge()) {
return -1;
}
return 0;
}
}
测试类:
public static void main(String[] args) throws InterruptedException {
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("张三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("赵六", 5), "xiaohong");
// 得到key的值的同时得到key所对应的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}
结果:
5-赵六
10-王五
20-李四
30-张三
2.1.4 Comparator*
Comparator可以认为是一个外比较器
,有两种情况可以使用实现Comparator接口的方式:
- 一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较。
- 一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式。
Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2。这两个参数是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:
- o1大于o2,返回正整数。
- o1等于o2,返回0。
- o1小于o3,返回负整数。
示例:
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
System.out.println("原始数组:");
System.out.println(arrayList);
Collections.sort(arrayList);
System.out.println("原始排序:");
System.out.println(arrayList);
// 定制排序
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序:");
System.out.println(arrayList);
结果:
原始数组:
[-1, 3, 3, -5, 7, 4, -9, -7]
原始排序:
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序:
[7, 4, 3, 3, -1, -5, -7, -9]
2.1.5 Comparable和Comparator的区别*
Comparable和Comparator接口都可以用来实现集合中元素的比较、排序。
像Integer、String等这些基本类型的Java封装类都已经实现了Comparable接口,这些类对象本身就支持元素比较,直接调用Collections.sort()就可以对集合中元素的排序。
而自定义类的List序列,当这个对象不支持元素比较或者元素比较函数不能满足要求时,就需要写一个比较器(即写一个实现了Comparator的类)来完成两个对象之间大小的比较。
Comparable和Comparator的具体区别:
参数 | Comparable | Comparator |
---|---|---|
排序逻辑 | 排序逻辑必须在待排序对象的类中,所以称为自然排序 | 排序逻辑在另一个类中实现 |
实现接口 | 实现Comparable接口 | 实现Comparator接口 |
排序方法 | int compareTo(Object o1) | int compare(Object o1,Object o2) |
触发排序方式 | Collections.sort(List list) | Collections.sort(List list, Comparator com) |
接口所在包 | java.lang | java.util |
耦合性 | 强 | 弱(不修改原有代码,扩展性好) |
2.2 查找替换方法
//对List进行二分查找,返回索引,List必须是有序的
int binarySearch(List list, Object key)
//根据元素的自然顺序,返回最大的元素
int max(Collection coll)
//根据定制排序,返回最大元素,排序规则由Comparatator类控制
int max(Collection coll, Comparator c)
//用指定的元素代替指定list中的所有元素
void fill(List list, Object obj)
//统计元素出现次数
int frequency(Collection c, Object o)
//统计target在list中第一次出现的索引,找不到则返回-1
int indexOfSubList(List list, List target)
//用新元素替换旧元素
boolean replaceAll(List list, Object oldVal, Object newVal)
2.3 同步控制方法
Collections提供了多个synchronizedXxx()方法,该方法可以将指定集合(如:HashSet、TreeSet、ArrayList、LinkedList、HashMap、TreeMap等)包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。 示例:
//返回指定Collection支持的线程安全的Collection
synchronizedCollection(Collection<T> c)
//返回指定List支持的线程安全的List
synchronizedList(List<T> list)
//返回由指定Map支持的线程安全的Map
synchronizedMap(Map<K,V> m)
//返回指定Set支持的线程安全的set
synchronizedSet(Set<T> s)
不过不建议这些方法,因为效率低,需要线程安全的集合类型时请考虑使用JUC包下的并发集合,如:CopyOnWriteArrayList 、ConcurrentHashMap 、CopyOnWriteArraySet。
2.4 使用Collections的最佳实践
1)使用正确的集合类。如不需要同步列表,使用ArrayList而不是Vector。
2)优先使用并发集合,而不是对集合进行同步。
3)使用接口代表和访问集合,如使用List存储ArrayList,使用Map存储HashMap等等。
4)使用迭代器来循环集合。
5)使用集合的时候使用泛型。