目录
1、Java中集合的种类
2、Arrarlist与LinkedList的区别
3、Arrarlist与Vector的区别
4、list的遍历方式
5、HashMap
6、ConcurrentHashMap
7、HashTable
8、TreeMap(可排序)
9、LinkHashMap(记录插入顺序)
10、HashMap和Hashtable的区别
11、HashMap和HashSet的区别
12、HashSet如何检查重复
13、TreeMap和TreeSet的区别
14、泛型是什么,及其使用场景?
主要包括Collection接口,Map接口以及Collection接口的三个子接口Set,List,Queue。
Java集合大致可以分为Set、List、Queue和Map四种体系,其中
Set代表无序、不可重复的集合;
List代表有序、重复的集合;list包括Arrarlist、Vector、LinkedList;
Map代表具有映射关系的集合,使用键值对来存储;
Java 5 又增加了Queue体系集合,代表一种队列集合实现。
Java集合就像一种容器,可以把多个对象(实际上是对象的引用,但习惯上都称对象)“丢进”该容器中。从Java 5 增加了泛型以后,Java集合可以记住容器中对象的数据类型,使得编码更加简洁、健壮。
1)都不能保证线程安全:两者都是不同步的,都不能保证线程安全;
2)底层数据结构:ArrayList底层使用的是Object数组;LinkedLIst底层是双向链表(JDK1.6之前微循环链表,1.7取消了循环)
3)插入和删除元素是否受元素位置的影响
4)是否支持快速随机访问
5)内存空格占用
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的代码要在同步操作上消耗大量的时间,所以访问比ArrayList慢;
ArrayList不是同步的,所以在不需要保证线程安全时建议使用ArrayList。
1)实现了RandomAccess接口的list,优先选择普通for循环,其次foreach
2)为实现RandomAccess接口的list,优先选择foreach遍历(foreach循环底层也是通过iterator实现的),大size的数据,不要使用普通的for循环
底层原理:JDK1.8之前是:数组和链表(链表散列)
JDK1.8之后是:数组、链表和红黑树
HashMap的结构:
java7: 数组+单向链表
1)capacity:当前数组容量,始终保持2的n次方,可以扩容,扩容后数组为当前的2倍;(始终保持2的N次方的原因是为了减少hash碰撞)
2)loadFactor:负载因子,默认为0.75;
3) threshold: 扩容的阀值,等于capacity*loadFactor。
java8: 数组+单向链表 +红黑树
查找的时间复杂度取决于链表的长度,为O(n);当链表的元素超过了8个以后,会将链表转换为红黑树,在这些位置查找时可以降低时间复杂度为O(logN)。
为什么设置的是8呢?
如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。
在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,当长度为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。
HashMap的存储方式与查询效率:
HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
HashMap最多只允许一条记录的键为null,允许多条记录的值为null;
HashMap的线程安全性:
HashMap非线程安全,即任一时刻允许多个线程同时写HashMap,可能会导致数据的不一致。
保证线程安全:1)使用Collections的synchronizedMap方法是HashMap具有安全的能力;
2)使用ConcurrentHashMap。
jdk1.7:
整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。
安全性:
ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
并行度:
concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,
也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。
jdk1.8:
采用CAS和Synchronized锁
线程安全性:线程安全,任一时间只有一个线程能写HashTable;底层是hash表,使用了同步方法
并发性不如 ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。
Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换 。
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按健值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。
LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历
LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序
(1)HashMap是线程不安全,Hashtable是线程安全;
(2)效率:因为线程安全的问题,HashMap要比HashTable效率更高一点。
(3)HashMap中,null可以作为键值也可以value值,在HashTable中,不允许键值为null。
(4) 初始容量大小和每次扩充的容量大小都不同:不指定初始容量时:HashMap的初始容量W为16,每次扩充是2倍;Hashtable的初始容量是11,每次扩充是2n+1倍。 当指定初始容量时:Hashtable会直接使用给定的大小,而HashMap会将其扩充为2的幂次方大小;
(5)底层数据结构:1.8之后,HashTable在解决哈希冲突时有了较大的变化,当链表长度大于阈值8时,将链表转化为红黑树,用来减少搜索时间;HashTable没有这样的机制。
HashSet的底层就是Hashmap来实现的;
HashMap实现的是Map接口,存储键值对,put添加元素,使用key计算Hashode;HashSet实现的是Set接口,存储对象,add添加元素,使用成员对象计算HashCode值,对于两个对象来说hashcoad可能相同,所以用equels方法来判断对象的相等性;
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。
TreeSet
1.TreeSet)是使用二又树的原理对新add0的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二又树指定的位置。
2.Integer和String对象都可以进行默认的TreeSet排序,而自定义类的对象是不可以的,自己定义的类必须实现Comparable接口,并且覆写相应的compareTo)函数,才可以正常使用。
3.在覆写compare0函数时,要返回相应的值才能使TreeSet 按照一定的规则来排序
4.比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
TreeMap
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用lterator 遍历TreeMap时,得到的记录是排过序的。
如果使用排序的映射,建议使用TreeMap。
在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
泛型就是一种未知的类,将未知的类型声明在集合、对象上,泛型的默认类型为Object。
泛型只能定义引用数据类型,而不能使用基本数据类型
泛型类、泛型方法、泛型接口、泛型通配符
例如:作用在类上时( public class Animal { E pet; } ) , 泛型跟在类后边,可以指定用了泛型的类内部的 pet 的类型。
作用在对象上时( Animal str = new Animal(); ) , 泛型跟在类后边 , 使得对象类的的 pet 属性为 Dog类型。
作用在方法上时( public Animal getPet(){ return E ; } ), 如在类上没有声明泛型时,必须在返回值和访问修饰符之间声明。
作为方法入参时( public void setPet(E pet){ this.pet = pet ; } ), 如在类上没有声明泛型时,必须在返回值和访问修饰符之间声明。