| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> Java集合容器面试题(精心整理100家互联网企业面试题,史上最全面试题,面试必过) -> 正文阅读 |
|
[数据结构与算法]Java集合容器面试题(精心整理100家互联网企业面试题,史上最全面试题,面试必过) |
文章目录
集合的特点简述Java中的集合
集合和数组的区别数组是固定长度的;集合可变长度的。 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。 数据结构:就是容器中存储数据的方式。 对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。 集合容器在不断向上抽取过程中,出现了集合体系。在使用一个体系的原则:参阅顶层内容。建立底层对象。 栈和队列的区别
那些集合元素提供对元素的随机访问ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。 使用集合框架的好处
集合框架中的泛型有什么优点?Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。 泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编 译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使 得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类 型检查的字节码指令。 说说常见的集合有哪些吧?Map接口和Collection接口是所有集合框架的父接口:
hashcode有什么用?hashcode的作用详解(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址; (2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同; (3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。 (4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写。 List,Set,Map三者的区别?List、Set、Map 是否继承自 Collection 接口?List、Map、Set 三个接口存取元素时,各有什么特点?首先,List与Set具有相似性,它们都是单列元素的集合,所以,它们有一个共同的父接口,叫Collection。 Set里面不允许有重复的元素即不能有两个相等(注意,不是仅仅是相同)的对象,即假设Set集合中有了一个A对象,现在我要向Set 集合再存入一个B对象,但B对象与A对象equals相等,则B对象存储不进去,所以, Set 集合的 add 方法 有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时,则返回 true , 当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加入该元素,返回结果为 false **。**Set取 元素时,不能细说要取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。 List表示有先后顺序的集合注意,不是那种按年龄、按大小、按价格之类的排序。当我们多次调用add(Obje)方法时,每次加入的对 象就像火车站买票有排队顺序一样,按先来后到的顺序排序。有时候,也可以插队,即调用 add(intindex,Obj e)方法,就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进List 中,每调用一次add方法,这个对象就被插入进集合中一次,其实,并不是把这个对象本身存储进了集 合中,而是在集合中用一个索引变量指向这个对象,当这个对象被add多次时,即相当于集合中有多个 索引指向了这个对象。List除了可以用Iterator接口取得所有的元素,再逐一遍历各个元素之外,还可以 调用get(index i)来明确说明取第几个。 Map与List和Set不同它是双列的集合,其中有put方法,定义如下:put(obj key,obj value),每次存储时,要存储一对 key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。取则可以根据key获得相应 的value,即get(Object key)返回值为key所对应的value。另外,也可以获得所有的key的结合,还可以 获得所有的value的结合,还可以获得key和value组合成的Map.Entry对象的集合。 总结 List以特定次序来持有元素,可有重复元素。Set无法拥有重复元素,内部排序。Map保存key-value值, value可多值。 当一个集合被作为参数传递给一个函数时,如何才能让函数不能修改它在作为参数传递之前,我们可以使用Collections.unmodifiableCollection(Collection c)方法创建一个只读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。 Collection
哪些集合类是线程安全的?
BlockingQueue是什么Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。 Java集合的快速失败机制 “fail-fast”?是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) 方法创建的集合。
该静态方法内部返回了Collections的静态内部类UnmodifiableCollection对象,该内部类又实现了Collection集合接口。 二、使用Arrays.asList创建的集合。
通过Arrays.asList方法创建了一个集合,这个集合不是我们传统中使用的ArrayList集合,两者继承同一个父类,但是内部却又不同的实现,Arrays.asList创建的ArrayList中没有重写其父类的add、remove方法,所以不持支新增和删除。 Collection接口List接口迭代器 Iterator 是什么?Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。 Iterator 怎么使用?有什么特点?Iterator 使用代码如下:
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。 如何边遍历边移除 Collection 中的元素?边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。 Iterator 和 ListIterator 有什么区别?Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。 在迭代一个集合的时候,避免ConcurrentModificationException在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。 Enumeration和Iterator的区别
为什么Iterator接口没有具体的实现1. Iterator接口定义了遍历集合的方法,但它的实现则是集合实现类的责任。每个能够返回用于遍历的Iterator的集合类都有它自己的Iterator实现内部类。 2. 这就允许集合类去选择迭代器是fail-fast还是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的。 ArrayList和Vector的相同点和不同点ArrayList和Vector在很多时候都很类似。 (1)两者都是基于索引的,内部由一个数组支持。 (2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。 (3)ArrayList和Vector的迭代器实现都是fail-fast的。 (4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。 以下是ArrayList和Vector的不同点。 (1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。 (2)ArrayList比Vector快,它因为有同步,不会过载。 (3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。 Array和ArrayList的区别和联系Array可以容纳基本类型和对象,而ArrayList只能容纳对象。 Array是指定大小的,而ArrayList大小是固定的。 Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。 (1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。 (2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。 (3)如果你要使用多维数组,使用[][]比List<List<>>更容易。 ArrayList和LinkedLis的区别和联系ArrayList和LinkedList两者都实现了List接口,但是它们之间有些不同。 (1)ArrayList是由Array所支持的基于一个索引的数据结构,所以它提供对元素的随机访问,复杂度为O(1),但LinkedList存储一系列的节点数据,每个节点都与前一个和下一个节点相连接。所以,尽管有使用索引获取元素的方法,内部实现是从起始点开始遍历,遍历到索引的节点然后返回元素,时间复杂度为O(n),比ArrayList要慢。 (2)与ArrayList相比,在LinkedList中插入、添加和删除一个元素会更快,因为在一个元素被插入到中间的时候,不会涉及改变数组的大小,或更新索引。 (3)LinkedList比ArrayList消耗更多的内存,因为LinkedList中的每个节点存储了前后节点的引用。 遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?遍历方式有以下几种: for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。 foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。 说一下 ArrayList 的优缺点
如何实现数组和 List 之间的转换?数组转 List:使用 Arrays. asList(array) 进行转换。
说一下Vector和ArrayList,LinkedList的联系和区别?分别使用场景从两点回答:
2、使用场景
List和Set有什么区别?
插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。 Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差。 LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。 多线程场景下如何使用 ArrayList?ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样: 如果要保证线程安全ArrayList应该如何做?
List 和 Set 的区别List , Set 都是继承自Collection 接口
另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。 Set和List对比
Set接口set接口Set 接口:存储无序的、不可重复的数据
说一下 HashSet 的实现原理?HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。 HashSet如何检查重复?HashSet是如何保证数据不可重复的?向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。 HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。 以下是HashSet 部分源码:
hashCode()与equals()的相关规定:如果两个对象相等,则hashcode一定也是相同的 如何理解Set接口的无序性与不可重复性以 HashSet 为例说明:
在 Queue 中 poll()和 remove()有什么区别?相同点:都是返回第一个元素,并在队列中删除返回的对象。
Set接口的使用要求Set 接口中没有额外定义新的方法,使用的都是 Collection 中声明过的方法
Map接口Map 常用的实现类如下
Hashtable:Java 早期提供的一个哈希表实现,它是线程安全的,不支持 null 键和值,因为它的性能不如 ConcurrentHashMap,所以很少被推荐使用。 HashMap:最常用的哈希表实现,如果程序中没有多线程的需求,HashMap 是一个很好的选择,支持 null 键和值,如果在多线程中可用 ConcurrentHashMap 替代。 TreeMap:基于红黑树的一种提供顺序访问的 Map,自身实现了 key 的自然排序,也可以指定 Comparator 来自定义排序。 LinkedHashMap:HashMap 的一个子类,保存了记录的插入顺序,可在遍历时保持与插入一样的顺序。 说一下 HashMap 的实现原理?HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 HashMap自动扩容如果在初始化HashMap中没有指定初始容量,那么默认容量为16,但是如果后来HashMap中存放的数 量超过了16,那么便会有大量的hash冲突;在HashMap中有自动扩容机制,如果当前存放的数量大于 某个界限,HashMap便会调用resize()方法,扩大HashMap的容量。 当hashmap中的元素个数超过数组大小loadFactor loadFactor 0.75 16 hashmap 160.75=12的时候,就 把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常 消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高 hashmap的性能。 HashMap 基于 Hash 算法实现的当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 HashMap线程安全吗?HashMap是非线程安全的,如果在多线程环境下,可以使用HashTable,HashTable中所有CRUD操作都是线程同步的,同样的,线程同步的代价就是效率变低了。再Java 5以后,有了一个线程安全的HashMap——ConcurrentHashMap,ConcurrentHashMap相对 于HashTable来说,ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常 用操作只锁当前需要用到的桶。试想,原来只能一个线程进入,现在却能同时16个写线程进入(写线程 才需要锁定,而读线程几乎不受限制,并发性的提升是显而易见。 HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。 JDK1.8之前 JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8之后 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 HashMap 底层的数据是数组被成为哈希桶,每个桶存放的是链表,链表中的每个节点,就是 HashMap 中的每个元素。在 JDK 8 当链表长度大于等于 8 时,就会转成红黑树的数据结构,以提升查询和插入的效率。 HashMap 数据结构,如下图: 1)添加方法:put(Object key, Object value) 执行流程如下:
源码及说明:
put() 执行流程如下:
源码及说明:
HashMap是怎么解决哈希冲突的?答:在解决这个问题之前,我们首先需要知道什么是哈希冲突,而在了解哈希冲突之前我们还要知道什么是哈希才行; 什么是哈希?Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 所有散列函数都有如下一个基本特性**:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同**。 什么是哈希冲突?当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。 HashMap的数据结构在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做链地址法的方式可以解决哈希冲突: hash()函数上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下:
这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动); JDK1.8新增红黑树 通过上面的链地址法(使用散列表)和扰动函数我们成功让我们的数据分布更平均,哈希碰撞减少,但是当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn); 总结 简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:
能否使用任何类作为 Map 的 key?可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点: 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。 为什么HashMap中String、Integer这样的包装类适合作为K?答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 如果使用Object作为HashMap的Key,应该怎么办呢?答:重写hashCode()和equals()方法 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?答:hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置; 那怎么解决呢?
HashMap 的长度为什么是2的幂次方为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。 这个算法应该如何设计呢?我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。 那为什么是两次扰动呢?答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的; HashMap 与 HashTable 有什么区别?HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了Map接口,
就 HashMap 与 HashTable 主要从三方面来说。
HashMap 和 ConcurrentHashMap 的区别ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。) ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; ① 在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,竞争会越来越激烈效率越低。 如何决定选用HashMap还是TreeMap?对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序 的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元 素会更快,将map换为TreeMap进行有序key的遍历。 ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?JDK1.7
JDK1.8
poll()方法和remove()方法区别?poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。 LinkedHashMap和PriorityQueue的区别PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证, 但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。 TreeMap是实现原理TreeMap是一个通过红黑树实现有序的key-value集合。 TreeMap继承AbstractMap,也即实现了Map,它是一个Map集合 TreeMap实现了NavigableMap接口,它支持一系列的导航方法, TreeMap实现了Cloneable接口,它可以被克隆 TreeMap本质是Red-Black Tree,它包含几个重要的成员变量:root、size、comparator。其中root是 红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、 value、left、right、parent和color。Entry节点根据根据Key排序,包含的内容是value。Entry中key比 较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。 comparable 和 comparator的区别?comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序 Collection 和 Collections 有什么区别?java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。 TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。 Collections 工具类的 sort 方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较; 第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。 源码分析遍历ArrayList时如何正确移除一个元素 错误写法示例一:
错误写法示例二:
要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注 意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是 怎么实现的:
按一般执行路径会走到else路径下最终调用faseRemove方法:
可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在 遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动 (也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。 针对这种情况可以倒序删除的方式来避免:
因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。 而错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在 上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器 (该代码在其父类AbstractList中):
这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的next 方法:
第一行checkForComodification方法:
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才 会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用 ArrayList的remove,改为用Iterator的remove即可。
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 1:47:35- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |