数据结构
1.
基础类型(Primitives)与封装类型(Wrappers)的区别在哪里
集合框架
- HashMap的工作原理是什么
- 内部的数据结构是什么
- HashMap 的 table的容量如何确定?loadFactor 是什么? 该容量如何变化?这种变化会带来什么问题?
- HashMap 实现的数据结构是什么?如何实现
- HashMap 和 HashTable、ConcurrentHashMap 的区别
- HashMap的遍历方式及效率
- HashMap、LinkedMap、TreeMap的区别
- 如何决定选用HashMap还是TreeMap
- 如果HashMap的大小超过了负载因子(loadfactor)定义的容量,怎么办
- HashMap 是线程安全的吗?并发下使用的 Map 是什么,它们内部原理分别是什么,比如存储方式、 hashcode、扩容、 默认容量等
hashcode+equals方法的作用
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。
当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
h & (length - 1) == h % length 这是table为2的幂次方的原因,位运算提升效率
jdk1.8之前头插法,因为
后插入的Entry被查找的可能性更大
jdk1.8之后使用尾插法,因为这个时候如果数量大于8会变成红黑树
内部数据结构就是bucket,桶的数据结构,数组加链表的形式
内部维护一个size变量,HashMap的大小
HashMap的table是
大于 initialCapacity 的最小的 2 的 n 次方值。
设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
threshold = (
int
) (capacity *
loadFactor);
扩容为原来的2倍,resize要进行rehash。
loadFactor负载因子初始化时0.75,也可以根据传入参数指定。
HashMap的遍历方式:
map.keySet() 得到一个key的set ,使用
iterator,
map
.
get
(
key
)
,或者
map.entrySet() 得到entry的set,iterator,
entry
.
getKey
(
),
entry
.
getValue
(
)
;
keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法。
Map.foreach本质仍然是entrySet
以自定义对象为key使用hashMap
public
class
StringOther
{
private
String name;
public
StringOther(String name)
{
this
.name = name;
}
@Override
public
int
hashCode()
{
return
name.hashCode();
}
@Override
public
boolean
equals(Object obj)
{
if
(obj ==
this
)
return
true
;
if
(!(obj
instanceof
StringOther))
return
false
;
StringOther so = (StringOther)obj;
return
so.getName().equals(name);
}
public
String getName()
{
return
this
.name;
}
@Override
public
String toString()
{
return
"["
+
this
.name+
":"
+
this
.hashCode()+
"]"
;
}
}
注意重写equals方法的时候一定要写成:
1
|
@Override
public
boolean
equals(Object obj)
|
的形式,
注意注解Override和参数类型Object obj
,如写成@Override public boolean equals(StringOther obj)这样会出现意想不到的错误,如果对其不解,可在下方留言。
并且在每个覆盖了equals方法的类中也必须覆盖hashCode方法,如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运转,这样的集合包括HashMap, HashSet和Hashtable.
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:
线程安全性,同步(synchronization),以及
速度
。
- HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
- HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
- 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
- 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
- HashMap不能保证随着时间的推移Map中的元素次序是不变的。
HashMap和currentHashMap的区别
- 就像上面所说他们之间的第一个重要的区别就是ConcurrentHashMap是线程安全的和在并发环境下不需要加额外的同步。虽然它不像Hashtable那样需要同样的同步等级(全表锁),但也有很多实际的用途。
- 你可以使用Collections.synchronizedMap(HashMap)来包装HashMap作为同步容器,这时它的作用几乎与Hashtable一样,当每次对Map做修改操作的时候都会锁住这个Map对象,而ConcurrentHashMap会基于并发的等级来划分整个Map来达到线程安全,它只会锁操作的那一段数据而不是整个Map都上锁。
- ConcurrentHashMap有很好的扩展性,在多线程环境下性能方面比做了同步的HashMap要好,但是在单线程环境下,HashMap会比ConcurrentHashMap好一点。
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
currentHashMap的结构和HashMap结构相似
分段锁机制
是由Segment数组结构和HashEntry数组结构组成,Segment是一种可重入锁,在ConcurrentHashMap中扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里面包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里面包含一个HashEntry数组,每个HashEntry是一个链表结构的元素。每个Segment守护者一个HashEntry数组里的元素,党对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。
put():先定位segment,加锁,先判断是否超过容量,如果超出将当前segment扩容两倍,再散列后插入到新的。
get():先定位segment,count和value都定义成volatile类型的变量。不会读到过期的是因为JMM的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值。
remove():删除某个元素,会新建一个新的链表,然后在链表中修改,顺序会反过来。
clear():清除
size():统计size时,先尝试2次通过不锁住Segment的方式来统计各个Segment大小。如果统计的过程中,容器的count发生变化,再采用对Segment的put remove clean加锁的方式来统计所有Segment的大小。
初始容量:
segment长度ssize初始化为16(13 14 15都等于16),sshift = 4 loadfactor=0.75
cap是segment里HashEntry的长度 = initialCapacity/ssize = 16/16 = 1(把所有数据分段)
threshold = (int) cap * loadFactor = 0
ConcurrentHashMap的get操作为什么不加锁
因为把count和value都设置为了volatile,happens before原则保证写操作先于读操作。除非读到的值是空值才会加锁重读
ConcurrentHashMap 是一个并发散列映射表的实现,它允许完全并发的读取,并且支持给定数量的并发更新。相比于
HashTable 和
用同步包装器包装的 HashMap(Collections.synchronizedMap(new HashMap())),ConcurrentHashMap 拥有更高的并发性。在
HashTable 和由同步包装器包装的 HashMap 中,使用一个全局的锁来同步不同线程间的并发访问。同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器。这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成
串行化
的了。
在使用锁来协调多线程间并发访问的模式下,减小对锁的竞争可以有效提高并发性。有两种方式可以减小对锁的竞争:
减小请求 同一个锁的 频率。
减少持有锁的 时间。
ConcurrentHashMap 的高并发性主要来自于三个方面:
用分离锁实现多个线程间的更深层次的共享访问。
用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。
使用分离锁,减小了请求
同一个锁
的频率。
通过 HashEntery 对象的不变性及对同一个 Volatile 变量的读 / 写来协调内存可见性,使得 读操作大多数时候不需要加锁就能成功获取到需要的值。由于散列映射表在实际应用中大多数操作都是成功的 读操作,所以 2 和 3 既可以减少请求同一个锁的频率,也可以有效减少持有锁的时间。
通过减小请求同一个锁的频率和尽量减少持有锁的时间
,使得 ConcurrentHashMap 的并发性相对于 HashTable 和
用同步包装器包装的 HashMap
有了质的提高。
2、HashSet
- HashSet和TreeSet有什么区别
- HashSet 内部是如何工作的
- WeakHashMap 是怎么工作的?
HashSet是使用HashMap来实现的,只是vlaue是PREZENT
对于 HashSet 中保存的对象,注意正确重写其 equals 和 hashCode 方法,以保证放入的对象的唯一性。
Set
- Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals()? 它们有何区别?
- TreeMap:TreeMap 是采用什么树实现的?TreeMap、HashMap、LindedHashMap的区别。TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
- TreeSet:一个已经构建好的 TreeSet,怎么完成倒排序。
- EnumSet 是什么
set中的元素用equals区分,我们需要重写equals和hashCode方法。
关于==和equals的区别
java中的数据类型,可分为两类:
1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
他们之间的比较,应用双等号(==),比较的是他们的值。
2.复合数据类型(类)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
abc.intern()检查字符串池里是否存在"abc"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。
Hash算法
- Hashcode 的作用
- 简述一致性 Hash 算法
- 有没有可能 两个不相等的对象有相同的 hashcode?当两个对象 hashcode 相同怎么办?如何获取值对象
- 为什么在重写 equals 方法的时候需要重写 hashCode 方法?equals与 hashCode 的异同点在哪里
- a.hashCode() 有什么用?与 a.equals(b) 有什么关系
- hashCode() 和 equals() 方法的重要性体现在什么地方
- Object:Object有哪些公用方法?Object类hashcode,equals 设计原则? sun为什么这么设计?Object类的概述
- 如何在父类中为子类自动完成所有的 hashcode 和 equals 实现?这么做有何优劣。
- 可以在 hashcode() 中使用随机数字吗?
一致性hash算法:
不可以在hashCode中使用随机数字,因为同一对象的 hashcode 值必须是相同的
LinkedHashMap
- LinkedHashMap 和 PriorityQueue 的区别是什么
PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。
List
- List, Set, Map三个接口,存取元素时各有什么特点
- List, Set, Map 是否继承自 Collection 接口
- 遍历一个 List 有哪些不同的方式
- LinkedList
- LinkedList 是单向链表还是双向链表
- LinkedList 与 ArrayList 有什么区别
- 描述下 Java 中集合(Collections),接口(Interfaces),实现(Implementations)的概念。LinkedList 与 ArrayList 的区别是什么?
- 插入数据时,ArrayList, LinkedList, Vector谁速度较快?
LinkedList是双向链表
- ArrayList
- ArrayList 和 HashMap 的默认大小是多少
- ArrayList 和 LinkedList 的区别,什么时候用 ArrayList?
- ArrayList 和 Set 的区别?
- ArrayList, LinkedList, Vector的区别
- ArrayList是如何实现的,ArrayList 和 LinkedList 的区别
- ArrayList如何实现扩容
- Array 和 ArrayList 有何区别?什么时候更适合用Array
- 说出ArraList,Vector, LinkedList的存储性能和特性
以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。
按数组下标访问元素—get(i)/set(i,e) 的性能很高,这是数组的基本优势。
直接在数组末尾加入元素—add(e)的性能也高,但如果按下标插入、删除元素—add(i,e), remove(i), remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是基本劣势。
Vector
非常类似ArrayList,但是
Vector
是同步的。由
Vector
创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为
Vector
是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了
Vector
的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
存储内容比较:
- Array数组可以包含基本类型和对象类型,
- ArrayList却只能包含对象类型。
但是需要注意的是:Array数组在存放的时候一定是同种类型的元素。ArrayList就不一定了,因为ArrayList可以存储Object。
空间大小比较:
- Array的空间大小是固定的,空间不够时也不能再次申请,所以需要事前确定合适的空间大小。
- ArrayList的空间是动态增长的,如果空间不够,它会创建一个空间比原空间大一倍的新数组,然后将所有元素复制到新数组中,接着抛弃旧数组。而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。(比较麻烦的地方)。
方法上的比较:
ArrayList作为Array的增强版,当然是在方法上比Array更多样化,比如添加全部addAll()、删除全部removeAll()、返回迭代器iterator()等。
Map
- Map, Set, List, Queue, Stack
- Map 接口提供了哪些不同的集合视图
- 为什么 Map 接口不继承 Collection 接口
Collection
├List
│├LinkedList
│├ArrayList
│└
Vector
│ └Stack
└Set
|-HashSet
Map
├
Hashtable
├HashMap
└WeakHashMap
Collections
- 介绍Java中的Collection FrameWork。集合类框架的基本接口有哪些
- Collections类是什么?Collection 和 Collections的区别?Collection、Map的实现
- 集合类框架的最佳实践有哪些
- 为什么 Collection 不从 Cloneable 和 Serializable 接口继承
- 说出几点 Java 中使用 Collections 的最佳实践?
- Collections 中 遗留类 (HashTable、Vector) 和 现有类的区别