集合框架:用于存储数据的容器。
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达到“多态”。在面向对象编程语言中,接口通常用来形成规范。
实现:集合接口的具体实现,是重用性很高的数据结构。
算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。事实上,算法是可复用的函数。它减少了程序设计的辛劳。
集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于底层设计上。通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。 它提高了程序速度和质量。
集合的特点主要有如下两点:
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
Collection
Map
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出
ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
在遍历过程中,所有涉及到改变modCount值的地方全部加上synchronized。
使用CopyOnWriteArrayList来替换ArrayList
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang.
UnsupportedOperationException 异常。
Iterator 接口提供遍历任何 Collection 的接口。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出
ConcurrentModificationException 异常。
边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法
推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。
优点:随机访问快
缺点:插入删除需复制,耗费性能
数据结构实现:ArrayList 动态数组,而 LinkedList 双向链表随机访问效率:ArrayList 更好增加和删除效率:LinkedList 更好内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个应用线程安全:都不保证线程安全;综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
ArrayList、Vector 底层数组方式存储数据。
LinkedList 双向链表,LinkedList 插入速度较快。
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。
list: 有序 元素可重复 多个null 有索引 for和iterator 检索低效 插入删除高效
set: 无序 不可重复 一个null iterator 查找高效 插入删除低效
HashSet: 底层HashMap hashmap的value统一为PRESENT 底层调用hashmap的方法 hashset不允许重复
检查重复,不仅比较hash值,还要结合equals方法
值作为hashmap的key,所以不会重复
阻塞队列 在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间
相同点:返回第一元素,并删除
不同点:没有元素,poll返回null remove抛出异常NoSuchElementException
概述: HashMap是基于哈希表的Map接口的非同步实现
数组和链表的结合体
1.用key的hashcode作hash计算下标
2.(1)key相同,覆盖原始值;(2)key不同(出现冲突),key-value放入链表
Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
JDK1.8之前: 数据+链表; 之后:链表长度大于阈值(默认为8),链表转为红黑树
①.判断键值对数组table是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过,进行扩容。
①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
②.每次扩展的时候,都是扩展2倍;
③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
什么是哈希:就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值)
基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同
什么是哈希冲突:当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)
HashMap的数据结构:数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易
hash()函数:与自己右移16位进行异或运算(高低位异或)
可以,考虑一下几点:
1.重写了 equals() 方法,也应该重写 hashCode() 方法。
2.遵循与 equals() 和 hashCode() 相关的规则。
3.用户自定义 Key 类最佳实践是使之为不可变的
1.都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
2.内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范
重写hashCode()和equals()方法
hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间;哈希值可能不在数组大小范围内,进而无法匹配存储位置
hash%length==hash&(length-1)的前提是 length 是2的 n 次方;
1.线程安全 hashtable用synchronized修饰
2.效率 hashmap效率高
3.对null key的支持 hashmap可以 hashtable报错
4.Hashtable 默认大小11,之后扩充,容量为原来2n+1。HashMap 默认大小16。扩充,原来的2倍
对于在Map中插入、删除和定位元素,HashMap最好。然而,对一个有序的key集合进行遍历,TreeMap更好
1.JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。
2.hashmap允许null
1.底层数据结构,ConcurrentHashMap:数组+链表/红黑二叉树;Hashtable:数组+链表
2.实现线程安全的方式(重要):① 在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,竞争会越来越激烈效率越低。
JDK1.7: ConcurrentHashMap采用Segment + HashEntry的方式进行实现
JDK1.8: synchronized只锁定当前链表或红黑二叉树的首节点
Array 存储基本数据类型和对象,ArrayList 只能存储对象。Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。
第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。
面试造火箭,工作拧螺丝,希望对你有所帮助
多多转发、点赞、让更多人受益!!!