Java 集合总结

集合类

Java 集合,也成为容器,
主要有两大接口派生出来:Collection 和 Map
Collection 主要存放单一元素;
Map 存放 K-V 键值对
集合所有的类在 java.util.* 下

Collections

是所有集合类的工具类

Collection 的方法

add
addAll
remove()
removeAll
contains()
containsAll
size
toArray()

List

是有序的,代表添加的顺序和遍历的顺序是一致的;
有下标;
元素可以重复
与SET 集合正好相反
List 的实现方式有 ArrayList 和 LinkedList

ArrayList

ArrayList 是一种数组列表,
当我们装载的是基本数据类型
boolean、byte、short、char、int 、float、double、long 时
我们只能存储他们的包装类型
它的底层实现是一个 Object[] 数组实现的
与它类似的LinkedList 相比,它的访问速度快,增删慢,线程不安全,使用频率高

为啥 ArrayList 线程不安全还要用?

因为我们平时的使用场景中,都是用来查询的,不会涉及太频繁的增删,如果涉及频繁的增删,那么可以使用LinkedList,如果考虑到线程安全,那么使用 Vector. 这是他们三者的区别。实际开发过程中,还是使用 ArrayList 多一点。

ArrayList 底层是数组,如果不断添加元素会不会有问题?

ArrayList 可以通过构造方法在初始化的时候指定底层数组的大小
无参构造方法默认数组大小是0,当add 元素时,才会分配数组大小为默认的10 ,add 会有一个扩容操作
有参构造方法通过参数指定大小

ArrayList 数组长度不受限制,它是怎么实现的?

arrayList 通过对数组扩容方式实现的
假如我们有一个数组长度是10,这时候需要添加元素时
ArrayList 会重新定义一个数组,长度为原来的1.5倍,也就是15
这时候,将原来的元素放入新数组,把指向原来数组的地址换成新数组的地址

ArrayList 的数组默认大小为啥是 10?

应该是 10 这个长度是数组最常用的最有效率的

ArrayList 在增删时慢,为什么慢?

它有指向index的新增和直接新增的,直接新增是在尾部新增
创建时如果没有指定长度,新增之前会有一个数组长度的校验,如果长度不够是需要扩容的,第一次新增会初始化长度为10。当添加第11个元素时,会扩容为15,添加第12个元素时不会扩容,添加到第16个元素时,再次扩容为22.
jdk 8 和老版本的比较,8扩容效率更高,采用位运算,每次右移一位加上原来的长度,其实就是除以2的操作。
之后就是调用 arrays.copy()

ArrayList 的删除是怎么实现的?

其实是一个system.arraycopy()

ArrayList 是不是线程安全的?

不是线程安全的,线程安全的版本是 vector
vector 的实现是将对数据操作的方法都加了synchornized
也可以使用 Collections.synchornizedList(), 底层跟vector 是一样的

ArrayList 可以用来做队列吗?

队列是先进先出读的,如果使用arrayList 做队列,需要注意的是:
在尾部追加元素,在头部删除元素
但那时这个是耗时的操作,所以不建议使用

ArrayList 不适合作队列,那数组适合吗?

比如 ArrayBlockingQueue, 是一个无界阻塞队列,是一个定长队列

ArrayList 和LinkedList 的遍历性能比较如何?

ArrayList 性能更好,最大优势是内存是连续的,CPU内存结构会缓存连续的内存片段,大幅度降低了内存开销

LinkedList 特点是什么?

LinkedList 底层是双向链表实现的,实现了Queue 接口,经常被用来做队列

LinkedList 的主要变量有哪些?

双向链表的节点数量
双向链表的第一个节点 Node 类
双向链表的最后一个节点

LinedList 的内部类Node 是怎样的?

Node 类有三个属性
E item 存放节点值,是一个对象类型
Node pre 该节点的下一个节点
该节点的上一个节点

LinkedList 是如何实现的?

调用add 方法时,将指定元素添加到链表的尾部,将链表的最后一个节点的next指向新增节点,新增节点的前一个节点指向最后一个节点
调用get(index) 方法时,检查下标是否越界,如果越界,抛出下标越界异常
如果不越界,判断下标距离头部还是尾部近,判断的时候是比较index 是否小于size的1/2,如果小于从头部开始,从头或者尾部开始遍历,找到指定的位置返回
调用remove(o) 方法时,从链表中删除第一次出现的元素。如果元素为空,遍历节点找到元素为null 的删除,否则,遍历找到该元素的节点删除
poll() 是返回并删除第一个元素,如果是null ,返回null
peek() 是返回但是不删除第一个元素,如果是null,返回null

HashMap 的结构和底层实现是怎样的?

HashMap 是非常常用的数据结构
是有数组和链表组合构成的数据结构
数组中存放k-v这样的实例,在 java 7叫做 Entry, 在Java 8 叫做 Node
比如:存入一个键值堆(“k”,“v”)当调用put 方法时,先调用hash(k),得到的值就是数组中的位置
当链表长度超过8,数组长度超过64 就会将链表转为红黑树,当链表长度小于6时,再转为链表
在这里插入图片描述

HashMap 为啥需要链表?

因为数组是有长度的,两个不同的key 做hash取到的值有可能是一样的,这样就形成了链表,每一个 node 都保存 hash值 、key value 下一个节点

hashMap 的链表是怎么插入元素的?

jdk 8 之前是头插法,就是说新来的值会取代原来的值,原来的值就会顺推到链表,这样设计可能考虑到新值会被查找的几率更大,提升查询效率
jdk 8 以后,采用尾插法

hashMap 数组达到一定量会扩容,具体原理是什么?

hashMap 的数组扩容也就是resize
resize 的两个因素是:hashMap 的当前长度,负载因子0.75f
就是说,比如当前容量大小为100,当存入第76个元素的时候,就需要resize。
扩容分为两步:第一:创建一个新的entry 数组,长度为原来的2倍
遍历原来的entry数组,将所有的元素重新进行一次hash() 存放到新数组中

hashmp 扩容的时候,重新hash的目的是什么?为什么不直接复制过来?

是因为数组长度变化后,hash 规则也变了
hash 方法的公式是:hashcode(key) & (length - 1)

hashMap jdk 8 以后为啥插入链表改为尾插法?

因为在并发插入场景下,可能会造成环形链表,这样可能会出现死循环,原因是:扩容转移后前后链表顺序倒置
JDK 8 之后改进了这个问题,使用了尾插法,在扩容时,会保持链表元素原来的位置,就不会出现循环链表的情况了

HashMap 的默认初始化长度是多少?

默认初始化长度是16

为什么重写equals 时,也要重写hashCode ?

在Java 中,所有的类都继承 Object 类,Object 类有两个方法,
一个是 equals(), 另一个是 hashCode(), 都是用来比较两个对象是否相等的
在没有重写equals 方法时,equals 方法是比较两个对象的地址是否相等,
new 出来的两个对象地址不想等,对于值对象,比较的是值,对于引用对象,比较的是地址
HashMap 存放的时候,不同的key 有可能存放相同的位置,取值的时候是根据 hashCode(key) 查找到对应的下标。那如何才能找到具体的某一个值呢?
equals, 所以对equals() 进行重写,比较值是不是相等,建议一定要对hasCode() 重写,以保证相同对象返回相同的hashCode值,不同对象返回不同hachCode 值
比较的时候先比较hashCode 值是否相等,如果不想等,直接返回,这样也提高了效率。

如何处理HashMap 线程安全问题?

采用 hashTable 或者 ConcurrentHashMap 或者 Collections.SynchornizedMap()
由于并发度的问题,一般采用ConcurrentHashMap,它的性能和效率高于其余两个

HashMap 为啥是线程不安全的?

jdk 8 之后多线程环境下,可能会造成覆盖key 的情况

HashMap 和HashTable 区别?

hashTable put null 的时候会报空指针
hashMap 做了特殊处理,如果是null ,计算hash值就会返回0
所以 hashTbale 不允许null 键值,因为hashTable 使用快速失败机制,
HashTable 实现decoratory ,hashMap 集成 abstractMap
hashMap 初始化容量为16 ,hashTable 为11
扩容机制也不一样
当其他线程改变了hashmap 的值,会抛出ConcurrentModificationException
而hashTable 不会

快速失败机制是啥?

是迭代器在遍历集合时,如果改变了元素,那么会抛出异常
原理是:
迭代器在迭代过程中,会维护一个modeCout 的变量,这个变量表明在迭代过程中集合被修改的次数,如果修改次数与初始化次数不一样,就会抛出异常
在每次做增删改时都会对这个变量加1,在迭代器调用hasNext() 或者next() 时,会去判断这个值有没有被改变

线程安全的集合类的比较?

ConcurrentHashMap 的并发度为啥很高?
HashMap 底层结构在JDK 7 和JDK 8 有所不同
原理上来讲concurrentHashMap 采用分段锁技术,不会像hashTable 那样方法上加锁

CopyOnWriteArrayList 是一种写入时复制;
与 Vector 的区别:
Vector 的 add() 是 Synchornized 修饰的, CopyOnWrite 是 Lock 锁实现的,效率高!

arrayList 和 hashmap 初始容量是多少?

arrayList 初始容量是10,扩容1.5倍
hashmap 初始容量为16,扩容2倍。超过64且链表长度超过8会转红黑树,低于6转为链表,加载因子是0.75f ,也就是3/4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值