【成神之路】集合相关面试题

List 和 Set 区别

List,Set都是继承自Collection接口。

都是用来存储一组相同类型的元素的。

List特点:元素有放入顺序,元素可重复 。

有顺序,即先放入的元素排在前面。

Set特点:元素无放入顺序,元素不可重复。

无顺序,即先放入的元素不一定排在前面。

不可重复,即相同元素在set中只会保留一份。

所以,有些场景下,set可以用来去重。

不过需要注意的是,set在元素插入时是要有一定的方法来判断元素是否重复的。这个方法很重要,决定了set中可以保存哪些元素。

Arraylist与LinkedList默认空间是多少;

10,扩展的话1.5倍;

linkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。

HashMap 初始化大小是 16 ,扩容因子默认0.75,树化域值是8

ArrayList 与 Vector 区别

List主要有ArrayList、LinkedList与Vector几种实现。

这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。

ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.

LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.

当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.

Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。

Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.

而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.

注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

Arraylist与LinkedList区别与各自的优势

ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.

LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.

当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.

 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.

注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

List 和 Map 区别;

list和set是实现了collection接口的;

List:1.可以允许重复的对象。

    2.可以插入多个null元素。

        3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。

        4.常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

1.Map不是collection的子接口或者实现类。Map是一个接口。

2.Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。

3. TreeMap 也通过 Comparator  或者 Comparable 维护了一个排序顺序。

4. Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。

5.Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

ArrayList和LinkList的删除一个元素的时间复杂度;

ArrayList是O(N),LinkList是O(1);

如果存取相同的数据,ArrayList 和 LinkedList 谁占用空间更大?

ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指 向前一
个元素,一个指向下一个元素。也可以参考ArrayList vs. LinkedList。

HashMap 和 Hashtable 的区别

线程安全:

HashTable 中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用HashTable,但是要使用HashMap的话就要自己增加同步处理了。

继承关系:

HashTable是基于陈旧的Dictionary类继承来的。HashMap继承的抽象类AbstractMap实现了Map接口。

允不允许null值:

HashTable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。 HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。

默认初始容量和扩容机制:

HashTable中的hash数组初始大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数,每次扩充为原来的2倍。原因参考链接:全网把Map中的hash()分析的最透彻的文章,别无二家。-HollisChuang's Blog

哈希值的使用不同 :

HashTable直接使用对象的hashCode。HashMap重新计算hash值。

遍历方式的内部实现上不同 :

Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 HashMap 实现 Iterator,支持fast-fail,Hashtable的 Iterator 遍历支持fast-fail,用 Enumeration 不支持 fast-fail

HashSet 和 HashMap 区别 ;Set和hashCode以及equals方法的联系

HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。

Hashmap在什么时候扩容?超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

hashmap线程不安全,那我如果非要让他多线程访问,并且我接受他脏读数据。会出现什么情况?

多线程情况下HashMap死循环的问题

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。

HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是进行Resize。1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

jdk1.8版本中多线程put不会在出现死循环问题了,只有可能出现数据丢失的情况,因为1.8版本中,会将原来的链表结构保存在节点e中,然后依次遍历e,根据hash&n是否等于0,分成两条支链,保存在新数组中。jdk1.7版本中,扩容过程中会新数组会和原来的数组有指针引用关系,所以将引起死循环问题。

HashMap 的工作原理及代码实现,什么时候用到红黑树

    HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射;

    HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改;

    HashMap是非synchronized,所以HashMap很快;

    HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以)。

实现原理?

HashMap默认采用数组+单链表方式存储元素,当元素出现哈希冲突时,会存储到该位置的单链表中。但是单链表不会一直增加元素,当元素个数超过8个时,会尝试将单链表转化为红黑树存储。但是在转化前,会再判断一次当前数组的长度,只有数组长度大于64才处理。否则,进行扩容操作。

    HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。

    以下是HashMap初始化 ,简单模拟数据结构。

    Node[] table=new Node[16]  散列桶初始化,table
    class Node {
     hash;//hash值
          key;//键
    value;//值
    node next;//用于指向链表的下一层(产生冲突,用拉链法)
     
    }

    以下是具体的put过程(JDK1.8版)。

1、对Key求Hash值,然后再计算下标;

2、如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中);

3、如果碰撞了,以链表的方式链接到后面;

4、如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;

5、如果节点已经存在就替换旧值;

6、如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)。

    以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)

当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

手写简单的HashMap

有什么方法可以减少碰撞?

    扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode。)

    使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。为什么String, Interger这样的wrapper类适合作为键?因为String是final的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。

谈谈HashMap,哈希表解决hash冲突的方法;hashCode怎么实现?

    static final int hash(Object key) {
        if (key == null){
            return 0;
        }
         int h;
         h=key.hashCode();返回散列值也就是hashcode
          // ^ :按位异或
          // >>>:无符号右移,忽略符号位,空位都以0补齐
          //其中n是数组的长度,即Map的数组部分初始化长度
         return  (n-1)&(h ^ (h >>> 16));
    }

简单来说就是

1、高16bt不变,低16bit和高16bit做了一个异或(得到的HASHCODE转化为32位的二进制,前16位和后16位低16bit和高16bit做了一个异或)

2、(n·1)&hash=->得到下标

为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

Hashmap的结构,1.7和1.8有哪些区别
不同点:

(1)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

(2)扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)

2、而在JDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法。但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出了扩容后的储存方式。

【成神之路】集合相关面试题_第1张图片
【成神之路】集合相关面试题_第2张图片
在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。

扩容流程对比图:
【成神之路】集合相关面试题_第3张图片
(3)JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)

【成神之路】集合相关面试题_第4张图片
(二)哈希表如何解决Hash冲突?

【成神之路】集合相关面试题_第5张图片
(三)为什么HashMap具备下述特点:键-值(key-value)都允许为空、线程不安全、不保证有序、存储位置随时间变化

【成神之路】集合相关面试题_第6张图片
(四)为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键

【成神之路】集合相关面试题_第7张图片
(五)HashMap 中的 key若 Object类型, 则需实现哪些方法?

【成神之路】集合相关面试题_第8张图片

Hashmap的hashcode相同是如何添加数据

在jdk1.8之前是插入头部的,在jdk1.8中是插入尾部的(达到域值8后树化)。

key通过hash%Entry[].length得到的index

HashMap里面用到链式数据结构的一个概念。
上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。
打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。
一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?
HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;
这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。
所以疑问不用担心。也就是说数组中存储的是最后插入的元素。
为什么1.8后改成尾插法

HashMap在jdk1.7中采用头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题。而在jdk1.8中采用尾插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

  • 1.7采用数组+单链表,1.8在单链表超过一定长度后改成红黑树存储
  • 1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置。
  • 1.7插入元素到单链表中采用头插入法,1.8采用的是尾插入法。

hashmap put 方法存放的时候怎么判断是否是重复的?

hashCode:

用于计算对象的hash值,计算结果为定长,返回int类型的结果;不重写默认使用Object的hashCode方法,重写后可以自定义hash函数,根据需要进行散列;

在已经实现了hashCode的类中,最后不要重写,自定义需要考虑如何散列降低hash碰撞;

equals:

比较两个对象是否相等,Object中调用hashcode方法;通过重写equals方法自定义比较的属性;在HashMap中通过equals和hashcode共同完成工作,具体表现在put和get时,如果hashcode相同,不代表是相同对象,还需要通过equals比较。

关系:

hashcode相同,equals不一定为true,两个对象不一定相等;

hashcode不同,equals一定为false,两个对象一定不相等;

equals为true,hashcode一定相同,两个对象一定相等;

equals为false,hashcode可能相同,两个对象不相等

HashMap出现Hash DOS攻击的问题

https://www.jianshu.com/p/5b99ae1ba9ce
Hashmap数组是什么时候new出来的?new时传17,数组有多大

在第一次put时候new出来;

默认bucket数组为16

传17时数组是32(扩容2倍)

put的元素(不论是桶里还是桶+链表里)达到容量乘负载因子的时候,默认16*0.75后扩容

HashMap 和 ConcurrentHashMap 的区别

【成神之路】集合相关面试题_第9张图片

【成神之路】集合相关面试题_第10张图片

ConcurrentHashmap是对什么分段

ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的。

1、最大特点是引入了 CAS(借助 Unsafe 来实现【native code】)

    CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

    Unsafe 借助 CPU 指令 cmpxchg 来实现

    使用实例:

    1、对 sizeCtl 的控制都是用 CAS 来实现的

    1、sizeCtl :默认为0,用来控制 table 的初始化和扩容操作。

        -1 代表table正在初始化;

        N 表示有 -N-1 个线程正在进行扩容操作;

        如果table未初始化,表示table需要初始化的大小;

        如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。

4、CAS 会出现的问题:ABA

    对变量增加一个版本号,每次修改,版本号加 1,比较的时候比较版本号。

ConcurrentHashMap使用什么技术来保证线程安全?

jdk1.7:Segment+HashEntry来进行实现的;只有16个Segment

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

jdk1.8:放弃了Segment臃肿的设计,采用Node+CAS+Synchronized来保证线程安全;

ConcurrentHashMap的get方法是否要加锁,为什么?

不需要,get方法采用了unsafe方法,来保证线程安全。

ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?

弱一致性,hashmap强一直性。

ConcurrentHashMap可以支持在迭代过程中,向map添加新元素,而HashMap则抛出了ConcurrentModificationException,

因为HashMap包含一个修改计数器,当你调用他的next()方法来获取下一个元素时,迭代器将会用到这个计数器。

ConcurrentHashMap1.7和1.8的区别:

jdk1.8的实现降低锁的粒度,jdk1.7锁的粒度是基于Segment的,包含多个HashEntry,而jdk1.8锁的粒度就是Node

数据结构:jdk1.7 Segment+HashEntry;jdk1.8 数组+链表+红黑树+CAS+synchronized

Concurrenthashmap和hashmap扩容

    如果超过阀值,数组进行扩容。值得一提的是,Segment的扩容判断比HashMap更恰当,因为HashMap是在插入元素后判断元素是否已经到达容量的,如果到达了就进行扩容,但是很有可能扩容之后没有新元素插入,这时HashMap就进行了一次无效的扩容。

    Concurrenthashmap如何扩容。扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再hash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

ConcurrentHashMap 的工作原理及代码实现,如何统计所有的元素个数;ConcurrentHashMap segment是干嘛的?数据结构是什么?

https://blog.csdn.net/justloveyou_/article/details/72783008

若hashcode方法永远返回1或者一个常量会产生什么结果?

hashCode()方法获取对象的散列值,并不能表现其唯一性,但是有离散性,其意义在于类似于进行hashMap等操作时,加快对象
比较的速度,进而加快对象搜索的速度。

所以,当hashCode()返回常量时,所有对象都出现hash冲突,而hashCode()本身的性能也会降级。

做hash的key的时候效率会极度变低;变量比较也会变慢.

HashSet方法里面的hashcode存在哪,如果重写equals不重写hashcode会怎么样?

HashSet内部是HashMap存的

要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

1、如果两个对象相同,那么它们的hashCode值一定要相同;2、如果两个对象的hashCode相同,它们并不一定相同     上面说的对象相同指的是用eqauls方法比较。   
你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。  

Set内存放的元素为什么不可以重复,内部是如何保证和实现的?

在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。

1、TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值

2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。

TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。

TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。

说一下TreeMap的实现原理?红黑树的性质?红黑树遍历方式有哪些?如果key冲突如何解决?setColor()方法在什么时候用?什么时候会进行旋转和颜色转换?

TreeMap底层是由红黑树实现的。关于TreeMap及红黑树的详细原理可以参考:链接:TreeMap - Java 提高篇 - 极客学院Wiki

HashMap在什么时候时间复杂度是O(1),什么时候是O(n),什么时候又是O(logn);

O(1):链表的长度尽可能短,理想状态下链表长度都为1

O(n):当 Hash 冲突严重时,如果没有红黑树,那么在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为O(N)。

O(logn):采用红黑树之后可以保证查询效率O(logn)

Java Collections和Arrays的sort方法默认的排序方法是什么;

java的Collections.sort算法调用的是合并排序,它是稳定排序,当数据接近有序的时候,效率更高,collections中的数据在排序前需要输入到array中,接着调用Arrays.sort函数来完成对象排序,最近通过迭代器将数组中排好序的对象些人到collection中,这也要求collection必须为mutable类型的。

Arrays.sort() 采用了2种排序算法 -- 基本类型数据使用快速排序法,对象数组使用归并排序

为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

之所以选择红黑树是为了解决二叉查找树的缺陷:二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成层次很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋、右旋、变色这些操作来保持平衡。引入红黑树就是为了查找数据快,解决链表查询深度的问题。我们知道红黑树属于平衡二叉树,为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少。所以当长度大于8的时候,会使用红黑树;如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

红黑树:

每个节点非红即黑
根节点总是黑色的
如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
每个叶子节点都是黑色的空节点(NIL节点)
从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

TreeSet 对存入对数据有什么要求呢?

TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。

TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。

Set 存的顺序是有序的吗?

在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。

1、TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值

2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。

TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。

TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。

Set 不保证顺序是指存入顺序与存储顺序的对应性。换句话说,Set 不保存插入的次序信息。以 TreeSet 为例,1 2 3 与 3 1 2 在存储的时候都是 1 2 3,没了插入次序信息,自然不能按插入顺序取数。List 则可以

Set中所说的无序中的顺序可以从两方面理解: 1、在Set中存入的元素和插入元素的顺序之间是否有关联。 2、能否实现通过指定的索引号获取某个位置我们想要获取的元素,比如ArrayList中的get(int index)方法。 TreeSet中的元素按照Key的大小排序是通过插入集合的元素属性通过compareTo方法进行比较来获取存储的位置的,我们并不知道插入的元素会被集合中哪个具体位置,所以TreeSet中的按照key值大小排序是从元素属性和元素类所属的compareTo方法而言的,不是相对于插入顺序而言的。

常见 Set 的实现有哪些?

HashSet,ThreeSet
TreeSet 底层源码有看过吗?
HashSet 是不是线程安全的?为什么不是线程安全的?

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的
Java 中有哪些线程安全的 Map?

Hashtable、 synchronizedMap、 ConcurrentHashMap。

 

 

你可能感兴趣的:(面试)