目录
JDK1.7对Java集合(Collections)的增强支持
key值不可重复,value值可以重复;键和值都可以为null
1、Collection接口
1.1 Collection与Collections的区别
- collection是集合类的上级接口,子接口主要有Set、List、Queue,提供了对集合对象进行基本操作的通用接口方法。是个java.util下的接口。
- Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。是个java.util下的类。
2. Arrays数组
2.1 Array与Arrays的区别
- Array提供了动态创建和访问Java数组的方法,效率搞,但是长度不可改变。无法判断其中实际存有多少元素,length只是告诉我们array的容量。
-
Arrays是一个与数组有关的类,专门用来操作array,提供了大量的静态方法来操作数组。 equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。 sort():用来对array进行排序。 binarySearch():在排好序的array中寻找元素。
2.2功能
asList()
将数组转换成java.util.ArrayList类型;
注意:返回的是Arrays的内部类java.util.ArraysArrayList,而不是java.util.ArrayList。java.util.ArraysArrayList, 而不是java.util.ArrayList。java.util.ArraysArrayList,而不是java.util.ArrayList。java.util.ArraysArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重了写这些方法而Arrays的内部类ArrayList没有重写,所以使用时会抛出异常。
sort()
数组排序
binarySearch()
在排好序的array中寻找元素,数组的二分查找。
equals()
两个数组的比较
fill()
给数组赋初值
3.Iterator
Iterator是所有集合的总接口,其他所有接口都继承于它,该接口定义了集合的遍历操作,Collection接口继承于Iterator,是集合的次级接口(Map独立存在,除外),定义了集合的一些通用操作。
java.util.Iterator接口定义如下:
public interface Iterator{
boolean hashNext(); //判断容器内是否还有可供访问的元素
Object next(); //返回迭代器刚越过的引用,返回值是Object,需要强制转换成自己需要的类型
void remove(); //删除迭代器刚越过的元素
}
使用
遍历List、Set、Map
Iterator iterator=list.Iterator();
while(iterator.hashnext()){
String s=iterator.next();
if(s.contains("1")){
iterator.remove();//安全删除
}
}
安全删除:Iterator支持从源集合中安全地删除对象(在Iterator上调用remove方法)。Iterator的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。
4.Set接口
java.util.Set
4.1 特点
- 不可重复(作用:询问某个对象是否在某个set)
- 允许null值
- 无序
4.2 方法
新建实例
Set<String> set=new LinkedHashSet<String>();
遍历
Iterator遍历;
删除
删除的是值为’a‘的项。不能按索引移除
4.3 AbstractSet抽象类
AbstractSet是一个抽象类,它继承于AbstractCollection.AbstractCollection实现了Set中的绝大部分函数,为Set的实现提供了便利。
HashSet依赖与HashMap,它实际上是通过HashMap实现的。HashSet中的元素是无序的。
TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。TreeSet中的元素是有序的。
LinkedHashSet继承于HashSet,是具有可预知迭代顺序的Set接口的哈希表和链接表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接表。此链接表定义了迭代顺序,即按照将元素插入到set中的(插入顺序)进行迭代
4.3 HashSet
1.概念
基于HashMap实现,非线程安全,地址不连续,查询慢增删快。适合插入和删除操作频繁的场景。底层用hashCode()算法实现,保证元素的无序唯一,自定义对象存进HashSet为了保证元素内容不重复需要覆盖hashCode()与equals()方法。
2.特点
a.能够最快的获取集合中的元素,效率非常高(以空间换时间)
b.元素不能重复
会根据hashcode和equals来判断是否是同一个对象,如果hashcode一样,并且equals返回true,则是同一个对象,不能重复存放。
c.无序
哈希表是通过使用称为散列法的机制来存储信息的。元素并没有以某种特定顺序来存放。
4.4 SortedSet接口
有序(Unicode升序 )(存放对象不能排序则报错,可指定排序规则)
唯一实现类:TreeSet
4.5 TreeSet
概念
继承SortedSet:要求元素有序,自定义的对象需要实现Comparable接口的compareTo(object o),基于红黑树实现,对象升序存储,访问和遍历快。
特点
- 非线程安全
- 有序(自动排序):可以按照自然顺序或者自定义舒徐自动排序,是J2SE中唯一可以实现自动排序的类型。TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
- 不允许插入null值。
4.6 LinkedHashSet
概念
根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,即当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
特点
有序(按照插入顺序输出)
5.List接口
List时一个接口,它继承于Collection的接口
5.1 特点
- 有序队列(怎么存怎么取):对象以线性方式存储(查询快,增删慢),只有一个开头和一个结尾。
- 可重复;
5.2 ArrayList
jdk1.2
- 底层的数据结构:动态数组(数组长度是可变的,百分之五十延长)地址连续,查询快,增删慢。适合查询操作频繁的场景。
- 非线程安全;
- 当ArrayList中的元素超过他的初始大小时,ArrayList只增加50%的大小
Double Brace Initialization
ArrayList<String> lists2=new ArrayList<String>(){//这个括号相当于派生自ArrayList<String>的匿名类。如果我们将该匿名类实例通过函数调用等方式传到该类型之外,那么对该匿名类的保持实际上会导致外层的类型无法被释放,进而造成内存泄露。
{//这个括号:由于匿名类中不能添加构造函数,因此这里的instance initializer实际上等于构造函数,用来执行对当前匿名类实例的初始化
add("test1");
add("test2");
}
}
该方法可读性强,推荐使用。
Double Brace Initialization方式初始化任何类型都可以通过它来执行初始化
JDK1.7对Java集合(Collections)的增强支持
在jdk1.7中,摒弃了JAVA集合接口的实现类,如ArrayList、HashSet和HashMap。而是直接采用[],{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象。如下:
List<String> list=["item"]; //向List集合中添加元素
String item=list[0];
Set<String> set={"item"};
Map<String,Integer> map={"key":1};
int value=map["key"];
5.3 LinkedList
1.底层数据结构:基于链表结构
是一个双向链表,常用堆栈于队列的实现,地址不连续,查询慢,增删快。
适合插入和删除操作频繁的场景。
2. 非线程安全
5.4 Vector
JDK1.0
底层数据结构:动态数组(数组长度时可变的百分之百延长,在内存中用连续的空间);
线程安全(是线程安全的ArrayList),在内存中占用连续的空间(查询、增删都很慢,效率低,被ArrayList替代了)
可设置增长因子,当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍。
5.5 Stack(栈)
它继承于Vector。它的特性是:先进后出(FILO)
Vector线程安全,所以Stack也是线程安全的。
6. Queue
在jdk5.0以前,通常的实现方式是使用java.util.List集合来模仿Queue。Queue的概念通过把对象添加到List的尾部,并通过从List的头部提取对象而从List中移除来模拟。你需要执行先进先出的动作时可以直接使用Queue接口就可以了。
非阻塞队列:LinkedList,PriorityQueue.
阻塞队列:见JUC
Arraylist | Vector | LinkedList | |
数据结构 | 动态数组(可变长:当元素个数超过数组的长度时,会产生一个新数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中) | 动态数组(可变长) | 双向链表 |
扩容机制 | 扩容为原来的1.5倍,不可设置容量增量 | 默认扩容为原来的2倍,可设置容量增量 | - |
线程安全性 | 非线程安全,用在单线程环境中 | 线程安全,即他的大部分方法都包含在关键字synchronized。 | 非线程安全 |
效率 | 如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是对其他指定位置的插入、删除操作,最好选择LinkedList. | ||
7.Map接口
7.1 Map特点
键值对存储
Map提供了一种映射关系,其中元素以键值对(key-value)存储。
key值不可重复,value值可以重复;键和值都可以为null
一个value值可以和很多key值形成对应关系,每个key最多能映射到一个value.
无序
7.2 操作
Object put(Object key,Object value)//添加,若关键字已经存在,那么取代旧值,返回关键字的旧值,若关键字原先不存在,则返回null
void putAll(Map t);//将来自特定映像的所有元素添加给该映像
Object remove(Object key);//从映像中删除与key相关的映射
void clear();//从映像中删除所有映像
Object get(Object key);//获得值,返回相关的对象,如果没有在该映像中找到该关键字,则返回null.
bollean containsKey(Object key);//判断映像中是否存在关键字key
bollean containsValue(Object value);//判断映像中是否存在值value
int size();//返回当前映像中映射的数量
boolean isEmpty();//判断映像中是否有任何映像
list=new ArrayList(map.value());//map转list
7.3 SortedMap接口
特点:key唯一,有序(Unicode升序)
实现类:TreeMap
7.4 TreeMap
概念
实现SortMap接口,能够把它保存的记录根据键排序,默认时按键值的升序排序,也可以指定排序的比较器,当用iterator遍历TreeMap时,得到的记录是排过序的。
特点
- 有序,非null插入,基于红黑树实现,可以按照自然顺序或者自定义顺序自动排序,不允许插入null值,查找效率较高,适合需要排序的场景。
- 非线程安全。
7.5 HashMap
概念
JDK1.2
定义:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>,Cloneable,Serializable
基于HashMap.Node数组加单向链表实现,是基于哈希表来实现的,用链表定址法来解决哈希冲突。根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据顺序是完全随机的。
HashMap实现了Map接口,继承AbstractMap。其中Map接口定义了键映射到值的规则,而AbstractMap类提供 Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作。
特点
- HashMap中的Entry对象是无序排列的。
- Key值和value值都可以为null,但是HashMap只能有一个key值为null的映射(key值不可重复,value可重复)对于key为null的值,在table[0]链表中存储。
- 非线程安全
- 基于数组实现,数组里的元素是一个单向链表。地址不连续,查询慢增删快;适合插入和删除操作频繁的场景
原理-哈希表拉链法
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);
Hash表的实质是构造记录的存储位置和其对应的关键字之间的映射函数f,关于Hash函数的构造方法,主要有如下几种:
(1)直接定址法,取关键字的某个线性函数作为Hash函数即Hash(key) = a*key+b。这种方法很少使用,虽然不会发生冲突,但是当key非常多的时候,整张Hash表也会非常大,毕竟是一一映射的。
(2)平方取中法,将key的平方的中间几位数作为得到的Hash地址。
(3)除留余数法,将key除以某个数,得到的余数作为Hash地址。
还有一些方法我们在此就不说了。当多个关键字经过这个Hash函数的映射而得到同一个值的时候,就发生了Hash冲突。解决Hash冲突主要有两种方法:
(1)开放定址法:
其中i=1,2,3。。。。,k(k<=m-1),H(key)为哈希函数,m为哈希表表长,di为增量序列,可能有下列2种情况:
当 di=1,2,3....,m-1时,称线性探测在散列;
当 时,称为二次探测再散列。
(2)链地址法:
即将所有关键字为同义词的记录存储在同一线性表中。假设某哈希函数产生的哈希地址在区间[0,m-1]上,则设立一个指针型向量 ChainHash[m];
其每个分量的初始状态都是空指针。凡是哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中。在列表中的插入位置可以在表头或表尾;也可以在中间,以保持同义词在同一线性表中按关键字有序。
例如:已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79),则按哈希函数H(key)=key MOD 13 和链地址法处理冲突构造所得的哈希表,如下图所示:
初始长度及扩容
初始长度是 16,每次扩展或者是手动初始化,长度必须是 2的幂。
因为HashMap的key是int类型,所以最大值是2^31次方,但是查看源码,当到达 2^30次方,即 MAXIMUM_CAPACITY
之后,便不再进行扩容。
优化
java7中 hashMap每个桶中放置的是链表,这样当hash碰撞严重时,会导致个别位置链表长度过长,从而影响性能。
在jdk1.8版本后,java对HashMap做了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。
7.6 HashTable
概念
jdk1.0
特点
- 不允许键或值为空
- 线程安全但效率低下,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtable在写入时会比较慢。
- properties是HashTable的子类,主键和值都是字符串
7.7 LinkedHashMap
有序(可设为true/false)
保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时用带参数,按照应用次数排序。
在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
输出的顺序与输入的相同
LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素
7.8 WeakHashMap
概念
继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap也是一个散列表,它的存储也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”,在WeakHashMap中,对于一个给定的键,其映射的存在并不组织垃圾回收器对该键的丢弃,这就使该键成为可终止,被终止,然后被回收。某个键被终止时,它对应的键值对也就从该映射中有效的移除了。
“弱键原理”:大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。
和HashMap一样,WeakHashMap是不同步的。
7.9 JUC(java.util.concurrent)
在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等;
list和set集合
map集合
ConcurrentHashMap
概念
- 是线程安全的哈希表,基于hash表实现(相当于线程安全的HashMap);继承于AbstractMap,并且实现ConcurrentMap
public class ConcurrentHashMap<K,V> extends AbstraceMap<K,V>
implements ConcurrentMap<K,V>, Serializable{
- "锁分段"来支持高效并发:使用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(),clear(),ConcurrentHashMap
ConcurrentHashMap 的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable 和同步的HashMap 一样了。
HashMap | HashTable | LinkedHashMap | WeakHashMap | ConcurrentHashMap | TreeMap | |
实现 | jdk1.7:标准链地址法(数组+链表) jdk1.8:数组+链表+红黑树 地址不连续
| 基本于hashmap一致 | 是HashMap的子类,基于拉链式散列结构。该结构由数组和链表+红黑树 在此基础上LinkedHashMap 增加了一条双向链表,保持遍历顺序和插入顺序一致的问题。 | 和HashMap一样,WeakHashMap也是一个散列表 | 在JDK1.7版本中,ConcurrentHashMap 的数据结构是由一个Segment 数组和多个HashEntry 组成,JDK1.8的实现已经摒弃了Segment 的概念,而是直接用Node 数组+链表+红黑树的数据结构来实现, | TreeMap 实现SortMap 接口,基于红黑二叉树的NavigableMap 的实现 |
线程安全性 | 非线程安全 | 线程安全(所有涉及到多线程操作的都加上了synchronized 关键字来锁住整个table, 任一时刻只有一个线程能写Hashtable,) | 非线程安全 | 非线程安全 | 线程安全(并发控制使用Synchronized 和CAS 来操作) | 线程安全 |
是否允许为null | 允许null作为key和value,key不可以重复 | 不允许有null的键和值 | 允许使用null值和null键, | 允许null 作为key 和value | 不允许null ,key 不可以重复,value 允许重复, | |
扩容 | 默认初始容量16,加载因子0.75,扩容为就容量2倍 | 默认初始容量11,扩容后的数组长度是之前数组长度的2倍+1 | ||||
适合场景 | 查询慢增删快;适合插入和删除操作频繁的场景 | - | 虽然底层使用了双线链表,但是增删相快了。因为他底层的Entity 保留了hashMap node 的next 属性。 | 特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到。 | 使用于按自然顺序或自定义顺序遍历键(Key)主要用于存入元素的时候对元素进行自动排序,迭代输出的时候就按排序顺序输出 | |
效率 | 效率稍高 | 效率比较低 | 效率稍高 | 比hashMap慢一点 |