java容器
集合容器概述
什么是集合
用于存储数据的容器
集合的特点
对象封装数据,对象多了也需要存储,集合用于存储对象
对象的个数可以确定使用数组,对象的个数不确定可以使用集合,因为集合的长度是可变的
集合和数组的区别
数组长度固定;集合的长度可变
数组可以存储基本数据类型和引用数据类型;集合只能存储引用数据类型
数组存储的元素必须是相同的数据类型;集合存储的对象可以是不同的数据类型
使用集合框架的好处
容量自增长、灵活
提供了高性能的数据结构和算法,使编码更简单,提高程序的速度和质量
允许不同的api相互操作、api之间可以来回传递集合
常用的集合类有哪些及集合框架底层数据结构
collection
Set
hashSet
基于hashMap实现,底层采用hashMap保存元素(不可重复无序)
treeSet
红黑树(自平衡的排序二叉树) 不可重复、有序
linkedHashSet
继承于hashSet,内部是通过linkedHashMap实现的
List
ArrayList
Object数组
LinkedList
双向循环链表
stack
vector
Object数组
Map
HashMap
jdk1.8之前是数组+链表【数组是hashMap的主体,链表则是为了解决hash冲突,使用《拉链法》解决】;jdk1.8之后在解决hash冲突时有了较大的变化,当链表长度大于阈值(默认是8)时,将链表转为红黑树,以减少搜索的时间,小于阈值会在退为链表
treeMap
红黑树(自平衡的排序二叉树)
hashTable
数组+链表
concurrentHashMap
子主题 1
propertes
linkedHashMap
继承与hashMap,在其基础上增加了一个双向链表,使其上面的结构可以保持插入时的顺序,同时通过对链表进行相对应的操作,实现了访问顺序相关上的逻辑
List、Set、Map区别,List、Set、Map是否都继承与Collection接口,三者在存取元素时有什么区别
List
一个有序的容器、元素可以重复、可以插入多个null,通过索引获取
Set
一个无序的容器、不允许存在重复的元素、只能存一个null
Map
是一个键值对集合,存储键、值之间的映射。key唯一,value不要求有序可重复。
哪些集合是线程安全的
vector
stack
hashTable
enumeration(枚举)
快速失败机制,fail-fast
java集合的一种错误检测机制,当多个线程对集合进行结构上的改变时,有可能会产生;如在循环时remove();
解决办法
1.在遍历过程中,所有涉及到改变modCount值得地方全部加上同步锁(synchronized)
2.使用copyOnWriteArrayList替换ArrayList
创建一个只读的集合
Collection.unmodifiableCollection(Collection coll) 方法创建一个只读的集合
Collection接口
List
Iterator 是什么
【迭代器】提供了任何遍历Collection的接口
iterator怎么使用?有什么特点?
使用
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String str = iterator.next();
System.out.println(str);
}
特点
只能单向遍历,但是更加安全,他可以确保在遍历集合元素被更改时会抛出异常
如何边遍历边移除Collection中的对象
使用iterator.remove()
iterator和ListIterator区别
iterator可以遍历list和set;而ListIterator只能遍历list
iterator单向遍历,ListIterator可以双向遍历(向前、向后遍历)
ListIterator实现了iterator接口,然后添加了额外的功能,如:添加一个元素、替换一个元素、获取前面、后面的元素索引位置
遍历一个List有几种方法,最佳实践是什么?
collection中提供了一个RandomAccess接口用来标记List是否支持randomAccess;支持的列表可以用for循环遍历,不支持的简历使用iterator或者foreach遍历
说一下ArrayList的优缺点
优点
底层以数组实现,是一种随机访问模式,实现了randomAccess接口,查询效率高
在顺序添加一个元素时非常方便
查询效率高
缺点
删除元素时需要一次元素复制操作,如果复制的元素很多会比较耗时
插入时也一样
新增删除效率低
如何实现数组和集合的相互转换
数组转集合
Arrays.asList()
集合转数组
.toArray()
ArrayList和LinkedList区别
数据结构实现
arrayList是数组,linkedList是双向链表
随机访问效率
ArrayList比linkedList随机访问效率要高,因为linkedList是线性数据存储方式,需要移动指针从前往后一次查找
增加和删除效率
linkedlist效率比arrayList效率高
内存空间占用
linkedList比较占用空间,因为他还存着前后两个元素的引用
ArrayList和Vector区别
线程安全
vector是线程安全的
性能
ArrayList比vector高
扩容
vector每次扩容增加一倍,ArrayList只会增加50%
插入数据时ArrayList、LinkedList、vector谁速度更快?阐述下他们之间的存储性能和特性
底层都是数组的方式存储数据,vector是线程安全的,他内部的所有方法都加上了同步锁,但是性能比arrayList差,linkedList使用双向链表实现存储,查询需要一个一个遍历,但是插入只需记录当前项前后引用即可,所以插入linkedList快
多线程场景下怎么使用arrayList
使用Collection.synchronizedList()方法将其转换成线程安全的容器后使用
为什么ArrayList的elementData上加上了transient修饰
arrayList实现了序列化接口,transient的作用是不希望elementData数据被序列化,因为每次序列化都会先调用defaultWirteObject()方法序列化ArrayList中的非transient元素,然后遍历elementData,只序列化已存入的元素,这样加快了序列化的速度也减少了序列化之后的文件大小
List和Set的区别
一个有序一个无序,一个可重复一个不可重复,set检索元素效率低,删除和插入效率高,删除和插入不会引起元素位置的改变;List查询效率高,删除和插入效率低,会影响其他元素位置的改变.
Set
说一下hashSet的实现原理
hashSet怎么检查重复及如何保证数据不可重复
add()时会比较hash值,同事还会结合equles方法比较,
hashSet的add是hashMap的put
hashMap的key是唯一的,hashSet加入的值就是hashMap的key,并且在相同时会用新的key覆盖旧的key然后返回旧的key。所以不会重复(先比较hash在比较equals)
hashCode和equals
如果两个对象相等,hashCode也是相同
hashCode相同,两个对象不一定相同
如果两个对象相同equals也是返回true
如果equals被覆盖那个hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashcode(),则改class的两个对象无论如何都不会相等
==与equals的区别
==是判断两个变量或者实例是不是指向同一个内存空间;equals比较的是所指向内存空间的值是不是相同
==是引用是否相同;equals()是指值是否相同
Queue
blockingQueue是什么?
阻塞队列
在queue中poll()和remove()有什么区别
相同点
都是返回第一个元素并在队列中删除返回的对象
不同点
如果没有元素poll()会返回null;remove会抛出异常
Map接口
HashMap实现原理
概述
HashMap是基于哈希表的Map接口的非同步实现。提供所有可选的映射操作,并允许使用null值和null键,不不保证映射的顺序
数据结构
数组+链表+红黑树
基于hash算法实现的
1.put时,利用key的hashCode重新计算hash计算当前对象元素在数组中的下标
2.存储时,如果hash值相同的key,此时有两种情况:1)如果key相同则覆盖原始值;2)如果key不同(哈希冲突),则将当前的key-value放入链表中
3.获取时,直接找到hash值对应的下标,再进一步判断key是否相同 从而拿到对应值
解决hash冲突的核心就在于:使用数组的存储方式,将冲突的key的对象放入到链表中,发生冲突就在链表中寻找
jdk1.8中当链表中的节点超过8时,改链表会转换为红黑树,当小于6时会回退为链表
jdk1.7和jdk1.8HashMap区别
概述
在java中,保存数据有两种比较简单的数据结构:数组和链表。
数组:寻址容易,但是插入和删除困难
链表:寻址困难,但是插入和删除容易
将数组和链表的结合在一起,发挥两者各自的优势,使用拉链法解决哈希冲突
jdk1.8之前
解决哈希冲突:拉链法:将链表和数组结合,创建一个链表数组,数组中的每一格就是一个链表,若遇到哈希冲突,将冲突的值加到链表中
如果链表长度太长影响查询效率
jdk1.8之后
解决哈希冲突:对拉链法进行了优化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,提高查询效率
优化点
1.resize扩容优化
2.引入和红黑树,【避免单条链表过长而影响查询效率】
3.决了多线程死循环问题,但仍非线程安全的,多线程可能导致数据丢失问题
put方法的具体流程
路程图
描述
1.判断键值对数组table是否为空或者null,否则执行resize()
2.根据key计算hash值得到插入的数组索引i,如果table[i]== null直接新建节点添加,进入第6步,否者进入第三步
3.判断table[i]的首个元素是否和key一样,如果相同则覆盖,不同进入第4步【相同指 hashcode以及equals比较】
4.判断table[i]是否为红黑树treeNode,如果是则在树中插入键值对,否则进入第5步
5.遍历table[i],判断链表长度是否大于阈值,大于将链表转化为红黑树,在红黑树中插入,否则进入链表进行插入;遍历中如果key已经存在则直接覆盖
6.插入成功后,判断实际存在的键值对数量size是否超出最大容量threshold,如果超过进行扩容【resize()】
扩容是怎么实现的
1.在jdk1.8中,resize()方法是在hashMap中的键值对大于阈值时或者初始化时,调用热size方法进行扩容
2.每次扩容的时候,都是扩展2倍
3.扩展后node的位置要么是在原位置,要么是移动到原偏移量2倍的位置
在purVal()中,有两次使用了resize(),在进行初始化时会进行扩容或者当该数组的实际大小大于其临界值(第一次为12 16*0.75),这个时候扩容的同时也会伴随的桶上面的元素进行重新分发,这也是jdk1.8优化的一个点;在1.7中扩容之后需要重新计算hash值,根据hash值重新分发,但是在1.8中则是根据同一个桶的位置中进行判断【e.hash & oldCap)是否为0,重新进行分配后,该元素要么停留在原位,要么移动到原始位置+增加的数据大小这个位置上
HashMap是怎么解决哈希冲突
什么是哈希?
一般称为“散列”;就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值)【简单说:就是讲任意长度的消息压缩成固定长度消息的函数】
基本特性:同一散列函数计算出的散列值如果不同,那么输入值肯定也不同,但是如果相同,那么输入值也不一定相同
什么是哈希冲突?
当两个不同的输入值,根据同一个散列函数计算出相同的散列值
哪些方法解决?
1.使用链地址法(散列表)来链接拥有相同hash值得数据
2.使用2次扰动函数(hash())来降低hash冲突的概率,是的数据分布更平均
3.引入红黑树进一步降低遍历的时间复杂度,提升遍历速度
能否使用任意类作为Map的key
可以使用任意的类型作为key,但是要考虑以下几点!
如果类重写了equals(),一定也要重写hashCode()方法
类的所有实例都要遵循与equals()和hashCode()先关规则
如果一个类中没有使用equals(),不应该在hashCode()中使用他
用户自定义key类最佳实践是使之为不可变的,这样hashCode()值可以被缓存起来,拥有更好的性能。【不可变的类可以确保hashCode()和equals()在未来不会改变,这样就可以解决与可变相关的问题了】
为什么String、Integer这样的包装类适合作为key
它能够保证Hash值不可更改性和及计算准确性,能够有效的减少Hash碰撞的概率
内部已重写了equals()和hashCode()等方法;遵循了HashMap内部的规范,不容易出现hash值计算错误的情况
如果使用了Object作为HashMap的key应该怎么办?
重写equals()和hashCode()方法
HashMap为什么不直接使用hashcode()处理后的哈希值作为下标
hashCode方法返回的是int整数类型,其取值范围大约有40亿,而hashMap的最大容量范围取不到最大值,会导致计算出的哈希值不在数组的大小范围,进而无法匹配存储位置
解决方案
HashMap实现了自己的hash()方法
HashMap的长度为什么是2的幂次方
为了能让HashMap存取高效,尽量减少碰撞,也就是尽量把数据分配均匀,每个链表、红黑树的长度大致相同
HashMap和HashTable有什么区别
线程安全
hashMap非线程安全,hashTable是线程安全的,其内部的方法都经过synchronized修饰
效率
因为线程安全问题,hashMap比hashTable效率更高,且hashTable基本被淘汰
为null key和null value的支持
hashMap可以支持,但只能有一个,hashTable不支持,只要有null直接回抛出异常
初始容量大小和每次扩容的大小
1.如果创建时未指定容量初始值,hashtable默认值为11,之后每次扩容容量为原来的2n+1;hashMap为16,之后每次扩容都是原来的两倍
2.创建时如果给了默认长度,hashTable会直接使用给的长度;而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂次方作为哈希表的大小
底层数据结构
hashMap在1.8中当链表长度到达阈值会转换为红黑树,hashTable不会
推荐使用:hashTable作为保留类不建议使用,推荐在单线程的环境下使用hashMap,如果多线程使用ConcurrentHashMap
如何决定使用HashMap还是TreeMap
如果你需要一个有序的key集合进行遍历使用treeMap
在map中插入、删除、定位元素这类操作HashMap是最好的选择
ConCurrentHashMap和HashMap的区别
线程安全
ConcurrentHashMap对整个桶数组进行分割分段(segment),然后在每一个分段上都用lock锁进行保护,相对于hashTable的同步锁(synchronized)锁粒度更精细,并发性能更好。【jdk1.8之后利用了cas算法】
HashMap没有锁机制,不是线程安全的。
键值对允许null
HashMap的键值对允许有null,但是concurrentHashMap都不允许
ConCurrentHashMap和HashTable的区别
底层数据结构
ConcurrentHashMap
JDK1.8之前采用分段数组+链表实现,JDK1.8之后和HashMap的结构一样,采用数组+链表+红黑树
HashTable
数组+链表;数组是主体,链表是为解决哈希冲突而存在
实现线程安全的方式
ConcurrentHashMap
在jdk1.7时,concurrentHashMap(分段锁)对整个桶数组进行了分割分段(segment),每一把锁只锁住容器的其中一部分数据,多线程访问容器的不同数据,就不会存在锁竞争,提高并发效率。【默认分配16个segment,比hashTable性能提高了16倍】。到了jdk1.8时已经摒弃了segment的概念,而是直接使用node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和CAS来操作。(JDK1.6之后对synchronized锁做了很多优化;整个看起来就是一个线程安全的HashMap,虽然在JDK1.8中还能看到segment的数据结构,但是已经简化了属性,只是为了兼容低版本
HashTable(同一把锁)使用synchronized来保证线程安全,效率低下,当一个线程访问同步方法是,其他线程无法访问同步方法,可能会进入阻塞或者轮询状态;如使用put添加元素,另一个线程不能使用put添加,也不能使用get,会导致竞争越来越激烈,效率越低。
ConCurrentHashMap的实现原理
JDK1.7
概述:
将数据分为一段一段的存储,然后每一个段都配一把锁,当一个线程占用锁访问其中一段数据时,其他的段数据也能被其他线程访问
实现:采用segment+ hashEntry
一个ConcurrentHashMap里包含一个segment数组,segment结构和hashmap类似,是一种数组+链表的结构,一个segment包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个segment守护着HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须先获取对应的segment的锁
JDK1.8
概述
放弃了segment臃肿的设计,采用Node+CAS+Synchronized来保证并发安全。synchronized只锁住当前链表或者红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升了n倍
实现
1.该类包含两个静态内部类HashEntry(封装映射表的键值对)和Segment(充当锁的角色);
2.Segment是一种可重入的锁ReentrantLock,每个segment守护一个HashEntry数组里的元素,当要对HashEntry数组的数据进行修改时,必须先获取对应得segment锁
什么是CAS
CAS是compare and swap 比较交换
CAS是一种基于锁的操作;而且是乐观锁。CAS操作包含三个操作数;内存位置(V)、预期原值(A)和新值(B),如果内存地址的值和A值是一样的,那么就将内存里的值更新为B,CAS是通过无限循环来获取数据的,如果在第一轮中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能有机会执行
java集合概述
最新推荐文章于 2024-05-17 14:46:30 发布