赞
踩
本系列文章:
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
集合框架:用于存储数据的容器。集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:
常见的集合对比:
Collection接口和Map接口是所有集合框架的父接口。
Collection接口的父接口是Iterable接口,Collection接口的子接口包括:Set接口、List接口和Queue接口。
//获取迭代器
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循环,也就是通过下标来遍历; 也可以用迭代器。 |
接口 | 常用实现类 |
---|---|
Map | HashMap、LinkedHashMap、Hashtable、ConcurrentHashMap |
Set | HashSet、LinkedHashSet |
List | ArrayList、LinkedList、Stack |
Queue | ArrayBlockingQueue、LinkedBlockingQueue |
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 | 红黑树(平衡二叉排序树) |
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);
}
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);
Iterator | ListIterator | |
---|---|---|
遍历 | 可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。 | 只能遍历List实现的对象,可以向前和向后遍历集合中的元素。 |
添加元素 | 无法向集合中添加元素 | 可以向集合添加元素 |
修改元素 | 无法修改集合中的元素 | 可以使用set方法修改集合中的元素 |
是否可以获取元素索引 | 无法获取集合中元素的索引 | 可以获取集合中元素的索引 |
简单来说,如果代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么就是线程安全的;反之则是线程不安全。
线程安全集合类可以分为三大类:
ConcurrentHashMap:线程安全的HashMap。
CopyOnWriteArrayList:线程安全的List,在读多写少的场合性能非常好,远远好于Vector。
ConcurrentSkipListMap: 跳表实现的Map,使用跳表的数据结构进行快速查找。
即没有使用同步或并发策略的集合。这类集合在日常开发中也使用的较多,如:HashMap、ArrayList、LinkedList、HashSet、TreeSet、TreeMap。
实现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,保证线程之间修改的可见性
。
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:
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。
List<String> list = new ArrayList<String>();
List<String> synchronizedList = Collections.synchronizedList(list);
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容器只能保证数据的最终一致性,不能保证数据的实时一致性
。
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 ++;
}
JUC目录下的集合用的是fail-safe机制,该机制允许在遍历集合元素的同时增删元素
。不过不同的集合,实现原理不一样,接下来以CopyOnWriteArrayList、ConcurrentHashMap为例解释。
JUC包下的容器都是fail-safe,都是在数据的一致性跟可用性中选择了可用性,允许出现数据的短期不一致,但是保证最终数据的一致性
。集合 | 数组 | |
---|---|---|
长度是否可变 | 可变 | 不可变 |
存储的数据类型 | 只能存储引用数据类型 | 可以存储基本数据类型,也可以存储引用数据类型 |
存储的元素是否类型一致 | 可以是不同类型的数据 | 必须是同一个类型的数据 |
可以使用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());
语义不明。因为Iterator的协议不能确保迭代的次序,添加元素时无法确定添加的位置。
确保同一集合里存储的是相同类型的元素,避免使用集合中的元素类型不一致时产生ClassCastException。
1、直接使用排好序的集合,如TreeSet或TreeMap。
2、也可以使用有顺序的的集合,如List,然后通过Collections.sort()排序。
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工具类常用方法:
1)排序。
2)查找、替换。
3)同步控制。不推荐,需要线程安全的集合类型时,可以使用JUC包下的并发集合。
//反转
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)
在JDK1.6中,Collections.sort方法使用的是MergeSort(归并排序);在JDK1.7中,Collections.sort的内部实现换成了TimeSort。
Timsort是结合了合并排序(merge sort)和插入排序(insertion 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可以认为是一个内比较器
,实现了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-张三
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]
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 |
耦合性 | 强 | 弱(不修改原有代码,扩展性好) |
//对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)
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。
1)使用正确的集合类。如不需要同步列表,使用ArrayList而不是Vector。
2)优先使用并发集合,而不是对集合进行同步。
3)使用接口代表和访问集合,如使用List存储ArrayList,使用Map存储HashMap等等。
4)使用迭代器来循环集合。
5)使用集合的时候使用泛型。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。