JAVA面试题合集——精髓(一)

190 阅读5分钟

1、hashcode相等两个类一定相等吗?equals呢?相反呢?

hashCode相等,equals也不一定相等, 两个类也不一定相等。
原因:hashCode相同,可能是多个键,值相同而已,那么equals也可能不相同;equal相等,那么必定
是同一个对象,即hashcode必定相同

2、介绍一下集合框架?

Java中的集合框架的实现是Java容器类相关类库,容器类有两种划分:
  (1)、Collection接口:一个包含独立元素的序列,序列的每一个位置都包含一个独立的元素,
	且各个元素之间是无序的,是可重复的,是可以为null的。
	a、继承了Collection接口的接口有:List、Queue、Set;
	b、继承list接口的有:AbstractList 及LinkedList,常用的ArrayList继承自AbstractList。
	    ArrayList是基于数组实现的,其底层实现为一个长度动态增长的Object[]数组,
	    因此其具有访问快,增删慢的特点。
	c、Queue 不允许随机访问其中间的元素,只能从队首访问的Collection,且一般来说队列都
	    应该是FIFO(先进先出)的。
	d、Set是元素不重复的Collection。实现了Set接口的有HashSet、LinkedHashSet、
	    SortedSet接口继承了Set接口。
	
  (2)、Map接口:一个每一组数据都是键值对的容器,并能够通过其键来查找其对应值;
	a、基于Map接口实现的类有很多,常用的有:TreeMap,HashMap,LinkedHashMap
	b、TreeMap存储key-value对(节点)时,需要根据key对节点进行排序,
	可以保证所有的key-value对处于有序状态
	c、HashMap用于快速访问
	d、LinkedHashMap能够保持元素插入的顺序,也提供快速访问的能力。

3、hashmap hastable 底层实现什么区别?hashtable和concurrenthashtable呢?

    (1)、HashTable:
	底层数组+链表实现,无论key还是value都不能为null,
	线程安全,实现线程安全的方式是在修改数据时synchronized锁住整个HashTable,效率低,
	初始size为11,扩容:newsize = olesize*2+1,
	出现hash冲突时采用的是将新元素加入到链表的开头,
	寻址方式采用的是求余数:index = (hash & 0x7FFFFFFF) % tab.length
    (2)、HashMap:
	jdk1.8之前底层数组+链表实现,1.8之后数组+链表+红黑树结构
	可以存储null键和null值,线程不安全
	初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
	扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
	出现hash冲突时,如果链表节点数小于8时是将新元素加入到链表的末尾
	寻址方法采用的是位运算按位与:index = hash & (tab.length – 1)
    (3)、Concurrenthashtable:
	(a)、jdk1.8之前采用Segment + HashEntry的方式进行实现,
	默认size为16,Segment在实现上继承了ReentrantLock,自带了锁的功能。
	    PUT:
		当执行put操作时,会进行第一次key的hash来定位Segment的位置,
		如果该Segment还没有初始化,即通过CAS操作进行赋值,
		然后进行第二次hash操作,找到相应的HashEntry的位置,
		这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),
		会通过继承ReentrantLock的tryLock()方法尝试去获取锁,
		如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,
		那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,
		超过指定次数(在多处理器环境下,重复次数为64,单处理器重复次数为1)就挂起,
		等待唤醒。
	    SIZE:
		先采用不加锁的方式,连续计算元素的个数,最多计算3次:
		如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;
		如果前后两次计算结果都不同,则给每个Segment进行加锁,再计算一次元素的个数;
	(b)、jdk1.8之后采用Node + CAS + Synchronized来保证并发安全进行实现。
	    PUT:
		1、如果没有初始化就先调用initTable()方法来进行初始化过程
		2、如果没有hash冲突就直接CAS插入
		3、如果还在进行扩容操作就先进行扩容
		4、如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,
			一种是链表形式就直接遍历到尾端插入,
			一种是红黑树就按照红黑树结构插入
		5、最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,
		    break再一次进入循环
		6、如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容
	    SIZE:
		使用一个volatile类型的变量baseCount记录元素的个数,当插入新数据或则删除数
		据时,会通过addCount()方法更新baseCount。
			
总结:1.7:
	Concurrenthashtable存储结构数组+链表,同步机制采用了”分段锁”策略,
	将map分为N个segment,默认提升16倍,
	键值对为HashEntry,涉及到的共享变量都使用volatile修饰,volatile可以保证内存可见性	
      1.8:
	Concurrenthashtable存储结构数组+链表+红黑树,同步机制采用“CAS + synchronized”,
	键值对为Node,源码中,部分使用sychronizeded关键字控制,防止多个线程操作。