集合

1. java 有哪些常用容器(集合)?

Java 容器分为 Collection 和 Map 两大类,各自都有很多子类。

2.ArrayList和Vector的联系和区别

相同点:
底层都使用数组实现

功能相同,实现增删改查等操作的方法相似

长度可变的数组结构

不同点:

Vector是早期JDK版本提供,ArrayList是新版本替代Vector的

Vector 的方法都是同步的,线程安全;ArrayList 非线程安全,但性能比Vector好

默认初始化容量都是10,Vector 扩容默认会翻倍,可指定扩容的大小;ArrayList只增加 50%    

3.Collection和Collections有什么区别?

Collection是JDK中集合层次结构中的最根本的接口。定义了集合类的基本方法。
Collections是一个包装类。它包含有各种有关集合操作的静态多态方法,不能实例化,像一个Collection集合框架中的工具类。

4.List、Set、Map 之间的区别是什么?

List:有序集合,元素可重复

Set:不重复集合,LinkedHashSet按照插入排序,SortedSet可排序,HashSet无序

Map:键值对集合,存储键、值和之间的映射;Key无序,唯一;value 不要求有序,允许重复

5.LinkedHashSet,HashSet,TreeSet的区别

LinkedHashSet会使用一个链表来维护元素的插入次序,当遍历集合的时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。

6.HashMap中的Hash算法

散列值优化函数

右移动16位,可以让高位和低位进行异或运算。混合了高位和低位,以此来增大hash码的随机性

取模运算,和长度减1进行与运算

这也正好解释了为什么HashMap的数组长度要取2的整次幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。

7.HashSet和HashTable的区别

JDK 1.8 中 HashMap 和 Hashtable 主要区别如下:

1)HashMap线程不安全;Hashtable 中的方法是Synchronize的。

2)HashMap的key和value都是可以是null,key只允许一个null;Hashtable的key和value都不可为null。

3)迭代器不同。HashMap的Iterator是fail-fast迭代器;Hashtable还使用了enumerator迭代器。

4)hash的计算方式不同。HashMap使用了扰乱函数;Hashtable使用了key的hashCode方法。

5)默认初始大小和扩容方式不同。HashMap默认初始大小16,容量必须是2的整数次幂,扩容时将容量变为原来的2倍;Hashtable默认初始大小11,扩容时将容量变为原来的2倍加1。

6)是否有contains方法。HashMap没有contains方法;Hashtable包含contains方法,类似于containsValue。

7)父类不同。HashMap继承自AbstractMap;Hashtable继承自Dictionary。

8.如何决定使用HashMap还是TreeMap?

1)HashMap基于散列桶(数组和链表)实现;TreeMap基于红黑树实现。

2)HashMap不支持排序;TreeMap默认是按照Key值升序排序的,可指定排序的比较器,主要用于存入元素时对元素进行自动排序。

3)HashMap大多数情况下有更好的性能,尤其是读数据。在没有排序要求的情况下,使用HashMap。

9.ArrayList和LinkedList的区别是什么?

1)ArrayList 基于动态数组实现,LinkedList 基于双向链表

2)扩容问题:ArrayList 使用数组实现,默认初始化长度为 10,数组扩容是会将原数组中的元素重新拷贝到新数组中,长度为原来的 1.5 倍(扩容代价高);LinkedList 不存在扩容问题,新增元素放到集合尾部,修改相应的指针节点即可。

3)LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用节点,一个指向前一个元素,一个指向下一个元素。

4)对于随机访问,一ArrayList 的速度要优于 LinkedList。新增和删除元素,一般 LinkedList 的速度要优于 ArrayList。因为 ArrayList 在新增和删除元素时,可能扩容和复制数组,还要进行移动

10.Array和ArrayList有何区别?

ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。

11.如何实现数组和List之间的转换? 

数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法。List 转数组,使用 List 的 toArray 方法。无参 toArray 方法返回 Object 数组,传入初始化长度的数组对象,返回该对象数组

12.Queue的add()和offer()方法有什么区别?

Queue 中 add() 和 offer() 都是用来向队列添加一个元素。

在容量已满的情况下,add() 方法会抛出IllegalStateException异常,offer() 方法只会返回 false 。

13.Queue的remove()和poll()方法有什么区别?

Queue 中 remove() 和 poll() 都是用来从队列头部删除一个元素。

在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。

14.Queue的element()和peek()方法有什么区别?

Queue 中 element() 和 peek() 都是用来返回队列的头元素,不删除。

在队列元素为空的情况下,element() 方法会抛出NoSuchElementException异常,peek() 方法只会返回 null。

15.哪些集合类是线程安全的?

Vector,Stack,Hashtable,java.util.concurrent 包下所有的集合类 ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque...

16.迭代器Iterator是什么?

首先说一下迭代器模式,它是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。

集合可以通过iterator()方法获取一个Iterator

Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。

缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。

17.Iterator怎么使用?有什么特点?

java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象

next() 方法获得集合中的下一个元素

hasNext() 检查集合中是否还有元素

remove() 方法将迭代器新返回的元素删除

forEachRemaining(Consumer<? super E> action) 方法,遍历所有元素

18.Iterator和 ListIterator有什么区别?

ListIterator 继承 Iterator,ListIterator 比 Iterator多方法

1)使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类

2)ListIterator 有 add和set 方法

3)ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以

4)ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以

19.怎么确保一个集合不能被修改?

使用 JDK中java.util.Collections 类,unmodifiable*** 方法赋值原集合。

当再修改集合时,会报错 java.lang.UnsupportedOperationException。从而确保自己定义的集合不被其他人修改。

20.为什么基本类型不能做为HashMap的键值?

Java中是使用泛型来约束 HashMap 中的key和value的类型的,HashMap<K, V>

泛型在Java的规定中必须是对象Object类型的,基本数据类型不是Object类型,不能作为键值

map.put(0, "ConstXiong")中编译器已将 key 值 0 进行了自动装箱,变为了 Integer 类型

21.HashMap的键值需要注意什么

key相不相同是根据equals方法来的,查找位置是按照hashcode来的。以自定义类作为 HashMap 的 key,需要注意按照自己的设计逻辑,重写自定义类的 hashCode() 方法和 equals() 方法,减少Hash冲突。

22.Java中已经数组类型,为什么还要提供集合?

数组的优点:

数组的效率高于集合类

数组能存放基本数据类型和对象;集合中只能放对象

数组的缺点:

数组长度固定且无法动态改变;集合类容量动态改变

数组无法判断其中实际存了多少元素,只能通过length属性获取数组的申明的长度

数组存储的特点是顺序的连续内存;集合的数据结构更丰富

JDK 提供集合的意义:

集合有多种数据结构,不同类型的集合可适用于不同场合

弥补了数组的一些缺点,比数组更灵活、实用,可提高开发效率

23.TreeSet的原理是什么?使用需要注意什么?

TreeSet 基于 TreeMap 实现,TreeMap 基于红黑树实现

TreeSet 默认构造方法,存入对象的类未实现 Comparable 接口,抛出 ClassCastException

24.HashSet实现原理是什么?有什么特点?

1)HashSet 是基于 HashMap 实现的,查询速度特别快

2)HashMap 是支持 key 为 null 值的,所以 HashSet 支持添加 null 值

3)HashSet 存放自定义类时,自定义类需要重写 hashCode() 和 equals() 方法,确保集合对自定义类的对象的唯一性判断(具体判断逻辑,见 HashMap put() 方法,简单概括就是 key 进行 哈希。判断元素 hash 值是否相等、key 是否为同个对象、key 是否 equals。第 1 个条件为 true,2、3 有一个为 true,HashMap 即认为 key 相同)

4)无序、不可重复

25.Map的实现类中,哪些是有序的,哪些是无序的,如何保证其有序性?

Map 的实现类有 HashMap、LinkedHashMap、TreeMap

HashMap是有无序的

LinkedHashMap 和 TreeMap 是有序的。LinkedHashMap 记录了添加数据的顺序;TreeMap 默认是升序

LinkedHashMap 底层存储结构是哈希表+链表,链表记录了添加数据的顺序

TreeMap 底层存储结构是红黑树,二叉树的中序遍历保证了数据的有序性

26.TreeMap和TreeSet在排序时如何比较元素?

TreeMap 会对 key 进行比较,有两种比较方式,第一种是构造方法指定 Comparator,使用 Comparator#compare() 方法进行比较;第二种是构造方法未指定 Comparator 接口,需要 key 对象的类实现 Comparable 接口,用 Comparable #compareTo() 方法进行比较

TreeSet 底层是使用 TreeMap 实现

27.Collections工具类中的sort方法如何比较元素?

第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较

第二种不强制性的要求容器中的元素必须可比较,但要求传入参数 Comparator 接口的子类,需要重写 compare() 方法实现元素的比较规则,其实就是通过接口注入比较元素大小的算法,这就是回调模式的应用

28.Map的遍历方式

Map 的 keySet() 方法,单纯拿到所有 Key 的 Set

Map 的 values() 方法,单纯拿到所有值的 Collection

keySet() 获取到 key 的 Set,遍历 Set 根据 key 找值(不推荐使用,效率比下面的方式低,原因是多出了根据 key 找值的消耗)

获取所有的键值对集合,迭代器遍历

获取所有的键值对集合,for 循环遍历

29.List、Set和Map接口的特点与常用的实现类

List 和 Set 实现了 Collection 接口。

List:

允许重复的对象,可以插入多个 null 元素,是有序容器,保持了每个元素的插入顺序

常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList,它提供了使用索引的随意访问,LinkedList 更合适经常添加或删除元素的场景

Set:

不允许重复对象,只允许一个 null 元素

Set 接口最常用的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。HashSet 基于 HashMap 实现;LinkedHashSet 按照插入排序;TreeSet 通过 Comparator 或 Comparable 接口实现排序

Map:

是单独的顶级接口,不是 Collection 的子接口

Map 的 每个 Entry 都持有两个对象,key 和 value,key 唯一,value 可为 null 或重复

Map 接口常用的实现类有 HashMap、LinkedHashMap、Hashtable 和 TreeMap

Hashtable 和 未指定 Comparator 的 TreeMap 不可为 null;HashMap、LinkedHashMap、指定 Comparator 的 TreeMap 的 key 可以为 null

30.说一下HashMap的实现原理

HashMap 基于 Hash 算法实现,通过 put(key,value) 存储,get(key) 来获取 value

基于链地址法解决Hash冲突

当传入 key 时,HashMap 会根据 key,调用 hash(Object key) 方法,计算出 hash 值,根据 hash 值将 value 保存在 Node 对象里,Node 对象保存在数组里

当计算出的 hash 值相同时,称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value

当 hash 冲突的个数:小于等于 8 使用链表;大于 8 时,使用红黑树解决链表查询慢的问题

31.ConcurrentHashMap了解吗?说说实现原理。

HashMap 是线程不安全的,效率高;put操作可能会出现死循环

HashTable 是线程安全的,效率低。

ConcurrentHashMap 可以做到既是线程安全的,同时也可以有很高的效率,得益于使用了分段锁。

JDK 1.7:

ConcurrentHashMap 是通过数组 + 链表实现,由 Segment 数组和 Segment 元素里对应多个 HashEntry 组成

value 和链表都是 volatile 修饰,保证可见性

ConcurrentHashMap 采用了分段锁技术,分段指的就是 Segment 数组,其中 Segment 继承于 ReentrantLock

理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发,每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment

put 方法的逻辑较复杂:

尝试加锁,加锁失败 scanAndLockForPut 方法自旋,超过 MAX_SCAN_RETRIES 次数,改为阻塞锁获取

将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry

遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value

不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容

最后释放所获取当前 Segment 的锁

get 方法较简单:

将 key 通过 hash 之后定位到具体的 Segment,再通过一次 hash 定位到具体的元素上

由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了其内存可见性

JDK 1.8:

抛弃了原有的 Segment 分段锁,采用了 CAS + synchronized 来保证并发安全性

HashEntry 改为 Node,作用相同

val next 都用了 volatile 修饰

put 方法逻辑:

根据 key 计算出 hash 值

判断是否需要进行初始化(table初始化操作会延缓到第一次put行为)但是put是可以并发执行的,那么是如何实现table只初始化一次的?

    1)如果sizectl=-1,说明被其他线程抢了初始化的操作,则直接让出自己的cpu时间片

    2)通过cas操作让sizectl=-1,标识当前线程抢到了初始化资格

    3)初始化数组,长度为16,或者初始化在构造ConcurentHashMap的时候传入的参数

    4)计算扩容的阈值,实际就是当前容量的0.75倍,这里使用了右移来计算

                                    sc = n - (n >>> 2);

    5)//设置sizeCtl为sc,如果默认是16的话,那么此时sc = 16*0.75=12

根据 key 定位出的 桶,如果为桶内没有元素的话,利用 CAS 尝试写入,失败重新开始。

如果此时桶内已经有元素了,那么判断桶内第一元素的hash值是 hashcode == MOVED == -1,说明正在扩容,那么此线程就会调用helpTransfer()帮助扩容。

如果不是的话,则对第一个元素synchronized上锁,上锁之后还要对桶内第一个元素判断是否发生了改变,发生变化了就再从头再来。

如果第一元素是链表节点,那么遍历链表查询是否key相等,若查到把value更改为我们要put的value值,没找到的话新建节点插入到链表尾部。若第一个节点是树节点,那么就以树的方式插入。

get 方法逻辑:

根据key得到具体哪个桶中没有数据,那么直接返回null。那么桶中第一个元素的hash值如果大于0说明为链表节点,那么遍历链表查询即可,小于0说明是树节点或者正在扩容,那么调用Node子类的find函数进行查询。


JDK 1.7 到 JDK 1.8 中的 ConcurrentHashMap 最大的改动:

链表上的 Node 超过 8 个改为红黑树,查询复杂度 O(logn)

ReentrantLock 显示锁改为 synchronized,说明 JDK 1.8 中 synchronized 锁性能赶上或超过 ReentrantLock

32.红黑树

红黑树定义和性质

红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:

性质1:每个节点要么是黑色,要么是红色。

性质2:根节点是黑色。

性质3:每个叶子节点(NULL)是黑色。

性质4:每个红色结点的两个子结点一定都是黑色。

性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点

红黑树并不是一个完美平衡二叉查找树,从图1可以看到,根结点P的左子树显然比右子树高,但左子树和右子树的黑结点的层数是相等的,也即任意一个结点到到每个叶子结点的路径都包含数量相同的黑结点(性质5)。所以我们叫红黑树这种平衡为黑色完美平衡。

自平衡靠的是什么?左旋、右旋和变色。

左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。如图3。

右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。如图4。

变色:结点的颜色由红变黑或由黑变红。

33.如何构建红黑树

新节点为x

将新插入的节点标记为红色

如果 X 是根结点(root),则标记为黑色

如果 X 的 parent是红色。分为两种情况

1) 如果 X 的 uncle (叔叔) 是红色 

3.1.1 将 parent 和 uncle 标记为黑色

3.1.2 将 grand parent (祖父) 标记为红色

3.1.3 x为红色,


2)如果 X 的 uncle (叔叔) 是黑色,我们要分四种情况处理

3.2.1 左左 (P 是 G 的左孩子,并且 X 是 P 的左孩子)

    

g,x为红色

3.2.2 左右 (P 是 G 的左孩子,并且 X 是 P 的右孩子)


p,g为红色z

3.2.3 右右 (和 3.2.1 镜像过来,恰好相反)


3.2.4 右左 (和 3.2.2 镜像过来,恰好相反)



34.完全二叉树的性质

具有n个节点的完全二叉树的深度为 k=log2n 。

【满二叉树】i层的节点数目为:2^i

【满二叉树】节点总数和深度的关系:节点数和深度的关系2^(k+1)-1,

【完全二叉树】最后一层的节点数为:n−(2k−1)=n+1−2kn−(2k−1)=n+1−2k (因为除最后一层外,为【满二叉树】)

 n=n0+n1+n2 

n-1=2n2+n1

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270