java8新特性。stream里有一个collect(Collector c)方法,这个方法里面接收一个Collector的实例。可以将一个管道流的结果集到一个list中。
a.Collection:在Java.util下的一个接口,是所有集合的root接口。Collection接口继承于Iterator.
b.Collections: Java.util下的一个专用静态类,它包含有各种有关集合操作的静态方法。
java中同步容器主要包括2 类:
区别在于他们对加锁的范围不同,HashTable 对整张Hash表进行加锁,而ConcurrentHashMap将Hash表分为16桶(segment),每次只对需要的桶进行加锁。
在对Vector 等容器进行迭代修改时,会报ConcurrentModificationException 异常。但是在并发容器中(如ConcurrentHashMap,CopyOnWriteArrayList 等)不会出现这个问
题。
Collections.synchronizedXXX(),可以将指定的集合包装成线程同步的集合。比如,
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
提供了动态创建和访问 Java 数组的方法。其中的元素的类型必须相同。
效率高,但容量固定且无法动态改变。
它无法判断其中实际存有多少元素,length只是告诉我们array的容量。
此静态类专门用来操作array ,提供搜索、排序、复制等静态方法。
equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
sort():用来对array进行排序。
binarySearch():在排好序的array中寻找元素。
int [] a;//一开始不定义长度
int [] a = new int[10];
将数组转换成java.util.ArrayList类型;
注意:返回的是Arrays的内部类java.util.Arrays A r r a y L i s t , 而 不 是 j a v a . u t i l . A r r a y L i s t 。 j a v a . u t i l . A r r a y s ArrayList, 而不是java.util.ArrayList。java.util.Arrays ArrayList,而不是java.util.ArrayList。java.util.ArraysArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重了写这些方法而Arrays的内部类ArrayList没有重写,所以使用时会抛出异常。
数组的排序;
数组的二分查找;
两个数组的比较;
给数组赋初值。
类似C语言中的memset()应用
char *p = new char[90];
memset((void *)p, -2, 90);
//把90个char都赋成-2,因为C++里的char是一个byte(8bit);
java写法:
java.util.Arrays.fill( float[], float)
数组名不等价于指针,只有数组名作为函数参数时,才退化为指针,此时数组名的sizeof()就是指针大小,除了这种情况外,均是整个指整个数组的大小。
char *string_a=(char *)malloc(100*sizeof(char));//对于64位机:sizeof(string_a)为8
char string_b[100];//sizeof(string_b)为100.
①三维数组:数组A[0…4,-1…-3,5…7]共有5*3*3=45
个元素。
Iterator是所有集合的总接口,其他所有接口都继承于它,该接口定义了集合的遍历操作,Collection接口继承于Iterator,是集合的次级接口(Map独立存在,除外),定义了集合的一些通用操作。
java.util.Iterator其接口定义如下:
public interface Iterator {
boolean hasNext(); //判断容器内是否还有可供访问的元素
Object next(); //返回迭代器刚越过的元素的引用,返回值是Object,需要强制转换成自己需要的类型
void remove(); //删除迭代器刚越过的元素
}
Iterator iterator = list.iterator();
while(iterator.hasNext()){
String s = iterator.next();
if (s.contains("1")) {
iterator.remove();//安全删除
}
}
Iterator支持从源集合中安全地删除对象(在Iterator上调用remove方法)。
Iterator的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。
如果在循环的过程中调用集合的remove()方法,就会导致循环出错,引发ConcurrentModificationException异常。
原因:Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。正确的做法是不用list.remove(),使用Iterator.remove();
Java.util.Set。
①不可重复(作用:询问某个对象是否在某个Set);
②允许null值;
③无序。
①新建实例
Set set = new LinkedHashSet();
②遍历
Iterator遍历;
③删除
移除的是值为"a"的项。不能按索引移除。
set.remove(“a”);
父类:
java.lang.Object
java.util.AbstractCollection
java.util.AbstractSet
public abstract class AbstractSet
extends AbstractCollection
implements Set
AbstractSet 是一个抽象类,它继承于AbstractCollection。AbstractCollection实现了Set中的绝大部分函数,为Set的实现类提供了便利。
HastSet 和 TreeSet 是Set的两个实现类。
HashSet依赖于HashMap,它实际上是通过HashMap实现的。HashSet中的元素是无序的。
TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。TreeSet中的元素是有序的。
LinkedHashSet继承于HashSet,是具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。
基于HashMap实现,非线程安全,地址不连续,查询慢增删快。适合插入和删除操作频繁的场景。
底层用hashCode()算法实现,保证元素的无序唯一,自定义对象存进HashSet为了保证元素内容不重复需要覆盖hashCode()与equals()方法。
a.能够最快的获取集合中的元素,效率非常高(以空间换时间).
b.元素不能重复
会根据hashcode和equals来判断是否是同一个对象,如果hashcode一样,并且equals返回true,则是同一个对象,不能重复存放。
c.无序
哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放。
有序(Unicode升序)。(存放对象不能排序则报错、可指定排序规则)
唯一实现类:TreeSet.
继承SortedSet;要求元素有序,自定义的对象需要实现Comparable接口的 compareTo(object o)方法。基于红黑树实现,对象以升序存储,访问和遍历快。
i>非线程安全
ii>有序(自动排序)
可以按照自然顺序或者自定义顺序自动排序,是J2SE中唯一可实现自动排序的类型。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
iii>不允许插入null值。
根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
有序(按插入顺序输出)
List 是一个接口,它继承于Collection的接口。
①有序队列(怎么存怎么取);
对象以线性方式存储(查询快,增删慢),只有一个开头和一个结尾。
②可重复;
JDK1.2。
①底层的数据结构:动态数组(数组长度是可变的百分之五十延长)
地址连续,查询快,增删慢。适合查询操作频繁的场景。
②非线程安全;
③当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小。(节约内存)
初始容量默认为10,扩容:((旧容量 * 3) / 2) + 1
除了常见的初始化方法,还有double brace initialization方法进行初始化:
ArrayList lists2 = new ArrayList(){ //这个括号相当于派生自ArrayList的匿名类。如果我们将该匿名类实例通过函数调用等方式传到该类型之外,那么对该匿名类的保持实际上会导致外层的类型无法被释放,进而造成内存泄露。
{ //这个括号:由于匿名类中不能添加构造函数,因此这里的instance initializer实际上等于构造函数,用来执行对当前匿名类实例的初始化
add("test1");
add("test2");
}
};
这种方法可读性强,推荐使用。
Double Brace Initialization并不仅仅局限于对集合类型的初始化。实际上,任何类型都可以通过它来执行预初始化:
NutritionFacts cocaCola = new NutritionFacts() {{
setCalories(100);
setSodium(35);
setCarbohydrate(27);
5 }};
在JDK1.7中,摒弃了Java集合接口的实现类,如:ArrayList、HashSet和HashMap。而是直接采用[]、{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象,如下 :
List list=["item"]; //向List集合中添加元素
String item=list[0]; //从List集合中获取元素
Set set={"item"}; //向Set集合对象中添加元素
Map map={"key":1}; //向Map集合中添加对象
int value=map["key"]; //从Map集合中获取对象
①底层的数据结构:基于链表结构;
是一个双向链表,常用堆栈与队列的实现,地址不连续,查询慢,增删快。
适合插入和删除操作频繁的场景。
②非线程安全
JDK1.0。
①底层是数据结构:动态数组(数组长度是可变的百分之百延长。在内存中占用连续的空间);
②线程安全;(是线程安全的ArrayList)
在内存中占用连续的空间(查询、增删都很慢,效率低,被ArrayList替代了)
③可以设置增长因子;
当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍。
它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。
Vector线程安全,所以Stack也是线程安全的。
int size();
boolean isEmpty();
boolean contains(Object o);
//以正确的顺序返回list中元素的迭代器
Iterator iterator();
boolean add(E e);
boolean addAll(Collection extends E> c);
//在指定位置插入指定集合
boolean addAll(int index, Collection extends E> c);
//如果指定元素存在list中,移除list中第一次出现的指定元素(实现类可以选择具体的实现)
boolean remove(Object o);
//判断list中是否包含某个集合
boolean containsAll(Collection> c);
//删除list中包含的Collection中的所有元素
boolean removeAll(Collection> c);
//保留list中包含的Collection中的所有元素
boolean retainAll(Collection> c);
//将该列表的每个元素替换为将该运算符应用于该元素的结果。
default void replaceAll(UnaryOperator operator);
//对list中的元素排列
default void sort(Comparator super E> c);
void clear();
boolean equals(Object o);
int hashCode();
E get(int index);
E set(int index, E element);
//在指定位置上增加指定元素
void add(int index, E element);
//删除指定索引上的元素
E remove(int index);
//获取对象的第一个索引
int indexOf(Object o);
//获取对象的最后一个索引
int lastIndexOf(Object o);
//返回list的list 迭代器
ListIterator listIterator();
//从指定位置返回list的迭代器
ListIterator listIterator(int index);
//返回list的子list
List subList(int fromIndex, int toIndex);
list转为数组:
Object[] toArray();
//在list的末尾插入元素(实现类可以选择插入的位置)
String[] array=list.toArray(new String[list.size()]);//可以指定类型
在jdk5.0以前,通常的实现方式是使用java.util.List集合来模仿Queue。Queue的概念通过把对象添加(称为enqueuing的操作)到List的尾部(即Queue的后部)并通过从List的头部(即Queue的前部)提取对象而从 List中移除(称为dequeuing的操作)来模拟。你需要执行先进先出的动作时可以直接使用Queue接口就可以了。
元素并没有以某种特定顺序来存放。
非阻塞队列:LinkedList,PriorityQueue。
阻塞队列:见JUC
与Collection接口无关,有一个子接口SortedMap。
Map提供了一种映射关系,其中元素以键值对(key-value)存储。
一个value值可以和很多key值形成对应关系,每个key最多只能映射到一个value。
Object put(Object key, Object value)
: 添加。若关键字已存在,那么取代旧值,返回关键字的旧值。若关键字原先不存在,则返回null
void putAll(Map t): 将来自特定映像的所有元素添加给该映像
Object remove(Object key)
: 从映像中删除与key相关的映射
void clear()
: 从映像中删除所有映射
Object get(Object key)
: 获得值,返回相关的对象,如果没有在该映像中找到该关键字,则返回null。
boolean containsKey(Object key)
: 判断映像中是否存在关键字key
boolean containsValue(Object value)
: 判断映像中是否存在值value
int size(): 返回当前映像中映射的数量
boolean isEmpty() :判断映像中是否有任何映射
Map中的键值对以Entry类型的对象实例形式存在。Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。通过这个集合的迭代器,您可以获得每一个条目(唯一获取方式)的键或值并对值进行更改。
list = new ArrayList(map.value());
AbstractMap 的成员变量
transient volatile Set keySet;//keySet, 保存 map 中所有键的 Set
transient volatile Collection values;//values, 保存 map 中所有值的集合
他们都是 transient, volatile, 分别表示不可序列化、并发环境下变量的修改能够保证线程可见性。
需要注意的是 volatile 只能保证可见性,不能保证原子性,需要保证操作是原子性操作,才能保证使用 volatile 关键字的程序在并发时能够正确执行。
特点: key唯一,有序(Unicode升序)
实现类:TreeMap
实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
i>有序,非null插入;
基于红黑树实现,可以按照自然顺序或者自定义顺序自动排序,不允许插入null值,查找效率比较高,适合需要排序的场景。
ii>非线程安全
JDK1.2。
定义:
public class HashMap
extends AbstractMap
implements Map, Cloneable, Serializable
基于HashMap.Node数组加单向链表实现,是基于哈希表来实现的,用链表定址法来解决哈希冲突。
根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。
i>HashMap中的Entry对象是无序排列的。
ii>Key值和value值都可以为null,但是一个HashMap只能有一个key值为null的映射(key值不可重复,value可重复)。
对于key为null的值,在talbe[0]链表中存储。
iii>非线程安全;
iv>基于数组实现,数组里的元素是一个单向链表。
地址不连续,查询慢增删快;适合插入和删除操作频繁的场景。
HashMap基于数组实现,数组里的元素是一个单向链表。
拉链法:哈希表是由数组 + 链表组成。
数组:长度为16,每个元素存储的是一个链表的头结点。e = next, e是null,才能跳出循环。上面e永远不会空,死循环了。
哈希函数:hash(key)&(len-1):
实现了均匀的散列,但比直接%len效率高,因为len为2的整数次幂,所以len-1为奇数(最后一位为1),保证了hash值通过&运算后,得到和原hash的低位相同,减少碰撞。
HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。
一般情况下(理想情况下冲突比较少),HashMap的插入和查找的时间复杂度都是O(1);
计算哈希值,根据哈希值与数组容量计算它所在的索引,根据索引查找它所在的链表。
在单向链表中查找该元素
计算哈希值,根据哈希值与数组容量计算它所在的索引,根据索引查找它所在的链表。
从起始节点开始遍历,查找要删除的元素,删除该节点,将节点的后继添加为它前驱的后继
根据key计算hash值,并根据hash值和数组容量,找到索引值,该位置即为存储该元素的链表所在处。
遍历table[i]位置的链表,查找相同的key,若找到则用新的value替换掉oldValue.
若没有查找到相同的key,则添加key到table[i]位置,新添加的元素总是添加在单向链表的表头位置,后面的元素称为它的后继。
新来的Entry节点插入链表时,使用的是“头插法”。至于为什么不插入链表尾部,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大。且插入到最后的话,那时间复杂度也会上升。
java7中 hashMap每个桶中放置的是链表,这样当hash碰撞严重时,会导致个别位置链表长度过长,从而影响性能。
在jdk1.8版本后,java对HashMap做了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。
初始长度是 16,每次扩展或者是手动初始化,长度必须是 2的幂。
index = HashCode(Key) & (length - 1), 如果 length是 2的 幂的话,则 length - 1就是 全是 1的二进制数,比如 16 - 1 = 1111,这样相当于是 坐落在长度为 length的hashMap上的位置只和 HashCode的后四位有关,这只要给出的HashCode算法本身分布均匀,算出的index就是分布均匀的。
因为HashMap的key是int类型,所以最大值是2^31次方,但是查看源码,当到达 2^30次方,即 MAXIMUM_CAPACITY
之后,便不再进行扩容。
默认HashMap的初始长度是16,比较小,每一次push的时候,都会检查当前容量,如果需要扩容,整个表里的所有元素都需要按照新的hash算法被算一遍,这个代价较大。
ReHash过程:每个元素重新算hash值,将链表翻转(遍历每个bucket上的链表,然后用头插法插入对应的bucket上的链表中)。对于同时准备扩容的两个线程1和2,如下图:
源码如下:
while(null != e) {
Entry next = e.next; //线程1还没有执行这句 中断了
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);//计算hash值
}
int i = indexFor(e.hash, newCapacity);//获取扩容后这个结点对应索引位
e.next = newTable[i];//这三行代码表示头插法插入
newTable[i] = e;
e = next;
}
(1)线程1,中断,线程2 reHash
(2)线程2将原表 bucket 1 处的链表分发到 新表 bucket 1 和 bucket 3 上(hash值的后2位,第一位不同,则不是01就是11),分散到 bucket 3上的值有两个, key(3), key(7),遍历原表Bucket 1 上的 链表,采用头插法,结果就是 链表反转且还属于新表此bucket的元素放到 此bucket上。此时 key(7) -> key(3) -> null
(3)此时线程 2 被中断,线程 1调度。
此时线程 1 中 e 是 key(3)-> null,根据源码继续将e插入对应的table[3]链表,头插法,结果为:key(3)->key(7)->key(3),这是一个循环链表。
e = next, e是null,才能跳出循环。上面e永远不会空,死循环了。
JDK1.0。
i>不允许键或值为空;
ii>线程安全但效率低下;
即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
Properties是HashTable的子类,主键和值都是字符串。
hashmap的底层是数组+链表的结构,是线程不安全的。
hastable是在hashmap的基础上,对整个哈希表(数组)加锁 sychronized实现线程安全。–>因为加锁效率低
HashMap是线程序不安全的(所以效率高),不是同步的.
Hashtable是线程安全的(Synchronize),也就是说是同步的。
HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
迭代HashMap采用快速失败机制,而HashTable不是。
快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
都实现了Map接口。
HashMap继承AbstractMap类。
HashTable继承了Dictionary类。
都使用了Iterator。
由于历史原因,Hashtable还使用了Enumeration的方式。
Enumeration接口:
可以枚举(一次获得一个)对象集合中的元素。
这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。
HashTable直接使用对象的hashCode;
HashMap重新计算hash值,用与代替求模。
HasnTable中的hash数组默认大小是11,增加的方式是old*2 + 1。
HashMap中的hash数组的默认大小是16,而且一定是2的指数。
保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时用带参数,按照应用次数排序。
在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。
是Android内部特有的api,标准的jdk是没有这个类的。在Android内部用来替代HashMap
目的是提高效率,其核心是折半查找函数(binarySearch)。
使用SparseArray更加节省内存空间的使用,SparseArray也是以key和value对数据进行保存的。使用的时候只需要指定value的类型即可。而且key不需要封装成对象类型.
Android开发中官方推荐:当使用HashMap(K, V),如果K为整数类型时,使用SparseArray的效率更高。
i>优点:
key保存在int mKeys[]数组中,相对于HashMap不再对key进行自动装箱,避免资源消耗。但是vaule是保存在Object[] mValues数组中还是需要自动装箱的。
相对于HashMap,不再使用额外的Entry对象来存储数据,减少了内存开销。
数据量小的情况下,随机访问效率更高。
ii>缺点
插入操作需要复制数组,增删效率低。
数据量巨大时,复制数组成本巨大,gc()成本也巨大。
数据量巨大时,查询效率也会明显下降。
SpareArray的key是一个int有序数组,查找过程使用的二分查找。
i>插入流程
用二分查找法查找元素的key。
如果插入的数据冲突了,则直接覆盖原则。
如果当前插入的key上的数据为DELETE,则直接覆盖。
如果前面几步都失败了,则检查是否需要gc()并且在索引上插入数据。
继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。
和HashMap一样,WeakHashMap是不同步的。
①ArrayList、Vector底层都采用数组方式存储数据。Vector效率低,ArrayList查询快,增删慢。
LinkedList底层采用链表存储数据。查询慢,增删快。
②对于线程安全
Vector是线程安全的,也就是说是同步的,但效率很低;
ArrayList是非线程安全的;
③数据增长
当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
排序对象通过实现java.lang.Comparable接口,并且重写comparaTo()方法(自然比较方法),返回0则表示是同一个对象,否则为不同对象。
package com.set;
import java.util.Set;
import java.util.TreeSet;
class Student1 implements Comparable{
int id;
public Student1(int id) {
this.id = id;
}
@Override
public String toString() {
return this.id+"";
}
@Override
public int hashCode() {
return this.id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student1){
Student1 stu = (Student1) obj;
if (stu.id == this.id)
return true;
}
return false;
}
public int compareTo(Student1 o) {
return (this.id-o.id);
}
}
public class TreeSetTest {
public static void main(String[] args) {
Set set = new TreeSet();
Student1 s1 = new Student1(5);
Student1 s2 = new Student1(1);
Student1 s3 = new Student1(2);
Student1 s4 = new Student1(4);
Student1 s5 = new Student1(3);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
for (Student1 s : set) {
System.out.println(s);
}
}
}
建立一个第三方类并实现java.util.Comparator接口。并重写方法。
Comparable接口将比较代码嵌入自身类中,而后者在一个独立的类中实现比较。然后排序:Collections.sort(list, new MySort ());的第二个参数返回一个int型的值,就相当于一个标志,告诉sort方法按什么顺序来对list进行排序。
如:定义集合形式为TreeSet ts = new TreeSet(new 第三方类());
Collections.sort(mOldFileInfos, new Comparator() {
@Override
public int compare(FileItem lhs, FileItem rhs){
//返回值:
//0表示相等,左值排在上面
//负数:左值小于右值,排在上面
//正数:左值大于右值,排在下面
}
} );
package com.set;
import java.util.Set;
import java.util.TreeSet;
class MySort implements java.util.Comparator{
public int compare(Student2 o1, Student2 o2) {
return o2.id-o1.id; //2个数相等差就是0,大于0就表示大于,小于0就表示小于 。即:返回:o1.id - o2.id ,表示正序排序。
}
}
class Student2{
int id;
public Student2(int id) {
this.id = id;
}
@Override
public String toString() {
return this.id+"";
}
@Override
public int hashCode() {
return this.id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student2){
Student2 stu = (Student2) obj;
if (stu.id == this.id)
return true;
}
return false;
}
}
public class TreeSetTest2 {
public static void main(String[] args) {
Set set = new TreeSet(new MySort());
Student2 s1 = new Student2(5);
Student2 s2 = new Student2(1);
Student2 s3 = new Student2(2);
Student2 s4 = new Student2(4);
Student2 s5 = new Student2(3);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
for (Student2 s : set) {
System.out.println(s);
}
}
}
常用场景:
dataList.sort(new ListEntityComparable());
1)使用优化过的数据集合SparseArray代替HashMap,HashMap为每个键值都提供一个对象入口,使用SparseArray可以免去基本对象类型转换为引用数据类想的时间。
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
fail-fast是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等;
JUC集合包中的List和Set实现类包括: CopyOnWriteArrayList, CopyOnWriteArraySet和ConcurrentSkipListSet。
CopyOnWrite 容器也是一种读写分离的思想,读和写在不同的容器上进行。
相当于线程安全的ArrayList,它实现了List接口。
是支持高并发的。
这个类的整个add操作都是在锁的保护下进行的。
CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
add方法是加锁的(ReentrantLock),所以适用于读多写少的场景。
public boolean add(E e) {
final ReentrantLock lock = this.lock;//加的是lock 锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);//将原容器的引用指向新的容器;
return true;
} finally {
lock.unlock();
}
}
读的时候不需要加锁, 如果读的时候有线程正在向CopyOnWriteArrayList 添加数据,读还是会读到旧的数据(在原容器中进行读)。
适合使用在读操作远远大于写操作的场景里,比如缓存。
慎用!
因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc.
因为CopyOnWrite 的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。
针对内存占用问题,可以:
1)通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10 进制的数字,可以考虑把它压缩成36 进制或64 进制。
2) 不使用CopyOnWrite 容器, 而使用其他的并发容器, 如ConcurrentHashMap。
CopyOnWrite 容器只能保证数据的最终一致性,不能保证数据的实时一致性。
不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求.
相当于线程安全的HashSet,它继承于AbstractSet类。
内部包含一个CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。
是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发。
JUC集合包中Map的实现类包括: ConcurrentHashMap和ConcurrentSkipListMap。
a)是线程安全的哈希表:
基于hash表实现,(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。
public class ConcurrentHashMap extends AbstractMap
implements ConcurrentMap, Serializable {
b)“锁分段”来支持高效并发:
使用segment来分段和管理锁(分段锁,提高了效率),segment继承自ReentrantLock。故ConcurrentHashMap使用ReentrantLock来保证线程安全。
方法访问数组中的数据,可能只涉及到数组的部分,对整个数组加锁会降低线程并发执行的效率,所以如果对数组分段加锁,使用segment分片锁,这样一个线程只会锁住数组的一片,其他线程仍可以访问数组的其他片进行写操作,具有这样的分片锁的机制是就是ConcurrentHashMap。
ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。
线程安全且高效。
ConcurrentHashMap与HashMap一样用数组加链表存储元素,用链表定址法来解决哈希冲突,不同之处在于当链表长度大于8的时候会将链表转换为一棵红黑树,查找时间复杂度由O(N)变成O(lgN)。
ConcurrentHashMap并发控制的关键在于一个变量,如下所示:
private transient volatile int sizeCtl;
sizeCtl被volatile关键字修饰是一个多线程共享的变量,当它的值为负数的时候说明某个线程正在操作这个Map,想要去操作这个Map的线程就要一直去竞争这个sizeCtl,没有得到这个变量的值就要一直自旋等待这个变量,当占用这个变量的线程操作完成后,要将这个变量的值设置回来,以便让其他线程走出自旋,竞争到该变量。
这种同步进制事实上是一种CAS的做法。
正是因为get 操作几乎所有时候都是一个无锁操作( get 中有一个readValueUnderLock 调用,不过这句执行到的几率极小),使得同一个Segment 实例上的put 和get 可以同时进行,这就是get 操作是弱一致的根本原因。
因为没有全局的锁,在清除完一个segment 之后,正在清理下一个segment 的时候,已经清理的segment 可能又被加入了数据,因此clear返回的时候,ConcurrentHashMap 中是可能存在数据的。因此,clear 方法是弱一致的。
在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException 异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap 迭代器弱一致的表现。
在这种迭代方式中,当iterator 被创建后,集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据,这样iterator 线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。
总结,ConcurrentHashMap 的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable 和同步的HashMap 一样了。
区别在于HashTable是对整个代码块加锁,而ConcurrentHashMap是使用分片锁,粒度较小,不用对整个代码块加锁,提高了读写速率。
是线程安全的有序的哈希表(相当于线程安全的TreeMap); 它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。
JUC集合包中Queue的实现类包括: ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDeque, ConcurrentLinkedQueue和ConcurrentLinkedDeque。
是数组实现的线程安全的有界的阻塞队列。
在读写操作上都需要锁住整个容器,因此吞吐量与一般的实现是相似的,适合于实现“生产者消费者”模式。
ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中
的位置。
生产者和消费者共用一把锁。
是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。
能够高效的处理并发数据:对生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
Put()和take()方法:都可以实现阻塞的功能。
Put()方法:把元素加入到阻塞队列中,如果阻塞队列没有空间,则调用此方法的线程被阻塞,直到有空间的时候再继续。
take()方法:取出排在阻塞队列首位的对象,若阻塞队列为空,则调用此方法的线程被阻塞,直到有新的对象被加入的时候再继续。
offer()和poll()方法:不具有阻塞的功能。
offer()方法:把元素加入到阻塞队列中,如果可以容纳,则返回true。如果不可以容纳,则返回false。
poll()方法:取出排在阻塞队列首位的对象,若阻塞队列为空,则返回null,如果不为空,则返回取出来的那个元素。
Node
进行插入或移除,会生成一个额外的Node 对象,这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC 的影响还是存在一定的区别,会影响性能。是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。
是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。
是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
此阻塞队列为基于数组的无界阻塞队列。它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,不会阻塞生产者,但会阻塞消费者。
PriorityBlockingQueue 里面存储的对象必须是实现Comparable 接口,队列通过这个接口的compare 方法确定对象的priority。
队列的元素并不是全部按优先级排序的,但是队头的优先级肯定是最高的。每取一个头元素时候,都会对剩余的元素做一次调整,这样就能保证每次队头的元素都是优先级最高的元素。
DelayQueue 是一个无界阻塞队列,用于放置实现了Delayed 接口的对象,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed 元素。这个队列里面所存储的对象都带有一个时间参数,采用take 获取数据的时候,如果时间没有到,取不出来任何数据。而加入数据的时候,是不会阻塞的(不会阻塞生产者,但会阻塞消费者)。
DelayQueue 内部使用PriorityQueue 实现的。DelayQueue 是一个使用PriorityQueue实现的BlockingQueue,优先队列的比较基准值是时间。本质上即:DelayQueue = BlockingQueue +PriorityQueue + Delayed。
优势:
如果不使用DelayQueue,那么常规的解决办法就是:使用一个后台线程,遍历所有对象,挨个检查。这种笨笨的办法简单好用,但是对象数量过多时,可能存在性能问题,检查间隔时间不好设置,间隔时间过大,影响精确度,过小则存在效率问题。而且做不到按超时的时间顺序处理。
应用场景:
缓存系统的设计。缓存中的对象,超过了有效时间,需要从缓存中移出。使用一个线程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期到了。
对锁进行更精确的控制。
对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如:
假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,“读线程"需要等待。
如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
// 造成当前线程在接到信号或被中断之前一直处于等待状态。
void await()
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 对应Object的wait();
boolean await(long time, TimeUnit unit)
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout)
// 造成当前线程在接到信号之前一直处于等待状态。
void awaitUninterruptibly()
// 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
boolean awaitUntil(Date deadline)
// 唤醒一个等待线程。对应Object的notify();
void signal()
// 唤醒所有等待线程。对应Object的notifyAll()
void signalAll()
不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
控制某个资源可被同时访问的个数,通过构造函数设定一定数量的许可,通过acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。
下面的例子只允许5 个线程同时进入执行acquire()和release()之间的代码:
public class SemaphoreTest {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5 个线程同时访问
final Semaphore semp = new Semaphore(5);
// 模拟20 个客户端访问
for (int index = 0; index < 20; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + NO);
Thread.sleep((long) (Math.random() * 10000));
// 访问完后,释放,如果屏蔽下面的语句,则在控制台只能打印5 条记录,之后线程一直阻塞
semp.release();
} catch (InterruptedException e) {
}
}
};
exec.execute(run);
} // 退出线程池
exec.shutdown();
}
}
具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大;
表示异步计算的结果;
可以用来在一个线程中等待多个线程完成任务的类。
CountDownLatch 是通过“共享锁”实现的。
CountDownLatch latch = new CountDownLatch(3);
创建时传入一个int 类型参数,表示该“共享锁”最多能被count 个线程同时获取, 这个值只能被设置一次, 而且CountDownLatch 没有提供任何机制去重新设置这个计数值。
让线程阻塞等待其他线程,直到CountDownLatch 的计数值变为0,才继续执行之后的操作。
主线程必须在启动其他线程后立即调用await()方法:
WorkerThread t1 = new WorkerThread(latch);
WorkerThread t2 = new WorkerThread(latch);
WorkerThread t3 = new WorkerThread(latch);
t1.start();
t2.start();
t3.start();
latch.await();
这个函数用来将CountDownLatch 的计数值减1,如果计数达到0,则释放所有等待的线程。
由其他线程调用:latch.countDown();
一个任务,它需要等待其他的一些任务都执行完毕之后它才能继续执行。
比如:开5 个多线程去下载,当5 个线程都执行完了才算下载成功。
这个类是一个可以重复利用的屏障类。它允许一组线程相互等待,直到全部到达某个公共屏障点,然后所有的这组线程再同步往后执行。
await()函数:每被调用一次,计数便会减少1(CyclicBarrier 设置了初始值),并阻塞住当前线程。当计数减至0 时,阻塞解除,所有在此CyclicBarrier 上面阻塞的线程开始运行。
(1) CountDownLatch 的作用是允许1 个线程等待其他线程执行完成之后,它才执行;而CyclicBarrier 则是允许N 个线程相互等待到某个公共屏障点,然后这一组线程再同时执行。
(2) CountDownLatch 的计数器的值无法被重置,这个初始值只能被设置一次,是不能够重用的;CyclicBarrier 是可以重用的。
private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() {
public void run()
{
System.out.println("寝室四兄弟一起出发去球场");
}
});
//cb传入线程中,线程中通过
//线程通过cb.await();使cb计数器减一
class Producer implements Runnable {
private final BlockingQueue sharedQueue;
public Producer(BlockingQueue sharedQueue) {
this.sharedQueue = sharedQueue;
}
public void run() {
for (int i = 0; i < 10; i++) {
try {
System.out.println("Produced: " + i);
sharedQueue.put(i);
} catch (InterruptedException ex) {
System.out.println(ex);
}
}
}
}
class Consumer implements Runnable {
private final BlockingQueue sharedQueue;
public Consumer(BlockingQueue sharedQueue) {
this.sharedQueue = sharedQueue;
}
public void run() {
while (true) {
try {
int i = (Integer) sharedQueue.take();
System.out.println("Consumed: " + i);
} catch (InterruptedException ex) {
System.out.println(ex);
}
}
}
}
public class ProducerConsumerPattern {
public static void main(String args[]) {
BlockingQueue sharedQueue = new LinkedBlockingQueue();
Thread prodThread = new Thread(new Producer(sharedQueue));
Thread consThread = new Thread(new Consumer(sharedQueue));
prodThread.start();
consThread.start();
}
}
PriorityQueue queue = new PriorityQueue(10);//充当缓冲区
class Consumer extends Thread {
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == 0) {//队列空的条件下阻塞
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.poll(); // 每次移走队首元素
queue.notify();
}
}
}
}
class Producer extends Thread {
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == 10) {//队列满了的条件下阻塞
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.offer(1); // 每次插入一个元素
queue.notify();
}
}
}
}
private PriorityQueue queue = new PriorityQueue(10);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
class Consumer extends Thread {
public void run() {
while (true) {
lock.lock();
try {
while (queue.size() == 0) {
try {
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll(); // 每次移走队首元素
notFull.signal();
} finally {
lock.unlock();
}
}
}
}
class Producer extends Thread {
public void run() {
while (true) {
lock.lock();
try {
while (queue.size() == 10) {
try {
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1); // 每次插入一个元素
notEmpty.signal();
} finally {
lock.unlock();
}
}
}
}