Loading

博主1

1.什么是迭代器(Iterator)?怎么用?有什么特点?
Iterator主要是用来遍历集合的,
方法如下:1.next()方法获得集合中的下一个元素
                    2.hasNext()检查集合中是否还有元素
                    3.remove()方法将迭代器新返回的元素删除
                    4.forEachRemaining()方法,遍历所有元素
特点:他的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出ConcurrentModificationException异常
 
2.Iterator和ListIterator的区别是什么?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
 
3.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
一:快速失败(fail—fast)          
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出Concurrent Modification Exception。  原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果结构发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。       
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。     
二:安全失败(fail—safe)       
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。       
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。       
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。           
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
 
List,set,Map的区别?
List 以索引来存储元素,有序的,元素是允许重复的,可以插入多个null
Set不能存放重复元素,无序的,只允许一个null
Map保存键值对映射,映射关系可以一对一,多对一
List有基于数组,链表实现的
Set,Map容器基于哈希存储和红黑树实现的
Set基于Map实现的
 
Poll()方法和remove()方法的区别?
Queue队列中,poll()和remove()都是从队列中取出一个元素,在队列元素为空的情况下,poll()返回null,remove()方法会返回异常
 
4.HashMap的底层结构?
4.1HashMap的扩容过程?什么时候扩容?
  第一步:把数组长度变为原来的两倍
  第二步:把旧数组的元素重新计算hash插入到新数组种
 第二步一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
 
4.2扩容时要重新计算hashcode值吗?
HashMap数据结构图:entry对象的数组+链表/红黑树(entry对象即键值对对象)
HashMap数据插入原理图:
1.判断数组是否为空,为空进行初始化;
2.不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index; 
3.查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中; 
4.存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false); 
5.如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;(如果当前节点是树型节点证明当前已经是红黑树了) 
6.如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8并且数组长度大于64, 大于的话链表转换为红黑树; 
7.插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。
 
4.1HashMap是怎么初始化容量的?
一般如果new HashMap() 不传值,默认大小是16,负载因子是0.75, 如果自己传入初始大小k,初始化大小为 大于k的 2的整数次方,例如如果传10,大小为16
 
4.2HashMap的哈希函数怎么设计的?为什么?
hash函数是先拿到 key 的hashcode,是一个32位的int值,然后让hashcode的高16位和低16位进行异或操作。
这个也叫扰动函数,这么设计有二点原因:
一定要尽可能降低hash碰撞,越分散越好; 
算法一定要尽可能高效,因为这是高频操作, 因此采用位运算;
 
 
5.HashMap和Hashtable有什么区别?HashTable是如何保证线程安全的?
HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。
他们有以下不同点:
HashMap允许键和值是null,而Hashtable不允许键或者值是null。 
Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。 
HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。
另一方面,Hashtable提供了对键的列举(Enumeration)。 一般认为Hashtable是一个遗留的类。
 
6.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。 
Array大小是固定的,ArrayList的大小是动态变化的。 
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。 
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
 
7.ArrayList和LinkedList有什么区别?
ArrayList和LinkedList都实现了List接口
他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。
LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。 
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。 
 
ArrayList和Vector的区别是什么?
ArrayList线程是不安全的,Vector线程是安全的
ArrayList在底层数组不够用的时候,在原来的基础上扩展0.5倍,Vector扩展1倍
Vector方法前面都加了synchronized关键字,来保证线程的安全性吗
 
8.你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?
大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是一个渐进上界 。大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
 
9.如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。
 
10.Enumeration接口和Iterator接口的区别有哪些?
Enumeration速度是Iterator的2倍,同时占用更少的内存。
但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。
同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。
 
HashSet是如何保证不重复的?
HashSet的add方法,元素E作为HashMap的key,我们都知道HashMap是不可以允许重复的
Public boolean add(E e){
   return map.put(e,PRESENT)==null;
}
 
11.HashSet和TreeSet有什么区别?
HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。
TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。
 
12.你知道哪些线程安全的集合实现类?
Vector:
Vector和ArrayList类似,是长度可变的数组,与ArrayList不同的是,Vector是线程安全的,它给几乎所有的public方法都加上了synchronized关键字。由于加锁导致性能降低,在不需要并发访问同一对象时,这种强制性的同步机制就显得多余,所以现在Vector已被弃用
HashTable:
HashTable和HashMap类似,不同点是HashTable是线程安全的,它给几乎所有public方法都加上了synchronized关键字,还有一个不同点是HashTable的K,V都不能是null,但HashMap可以,它现在也因为性能原因被弃用了
 
java.util.concurrent包中的集合:
ConcurrentHashMap:
ConcurrentHashMap和HashTable都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable的加锁方法是给每个方法加上synchronized关键字,这样锁住的是整个Table对象。而ConcurrentHashMap是更细粒度的加锁 在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响 JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率
 
CopyOnWriteArrayList和CopyOnWriteArraySet:
它们是加了写锁的ArrayList和ArraySet,锁住的是整个对象,但读操作可以并发执行
 
除此之外还有ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque等,至于为什么没有ConcurrentArrayList,原因是无法设计一个通用的而且可以规避ArrayList的并发瓶颈的线程安全的集合类,只能锁住整个list,这用Collections里的包装类就能办到
 
13.ConcurrentHashMap和HashTable的区别?
ConcurrentHashMap和HashTable都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable的加锁方法是给每个方法加上synchronized关键字,这样锁住的是整个Table对象。而ConcurrentHashMap是更细粒度的加锁 在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响 JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率
 
14.LinkedHashMap的使用场景?实现原理?
LinkedHashMap内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry内部除了继承HashMap的Node属性,还有before 和 after用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。
 
15.TreeMap怎么实现有序的?
TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序,内部是通过红黑树来实现。所以要么key所属的类实现Comparable接口,或者自定义一个实现了Comparator接口的比较器,传给TreeMap用于key的比较。
 
16.JDK1.8对于HashMap的优化?为什么?
数组+链表改成了数组+链表或红黑树;
链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7将新元素放到数组中,原始节点作为新节点的后继节点,1.8遍历链表,将元素放置到链表的最后; 
扩容的时候1.7需要对原数组中的元素进行重新hash定位在新数组的位置,1.8采用更简单的判断逻辑,位置不变或索引+旧容量大小; 
在插入时,1.7先判断是否需要扩容,再插入,1.8先进行插入,插入完成再判断是否需要扩容;
 
Collection和Collections的区别是什么?
Collection 是Java集合框架中的基本接口。
Collections是java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法
 
如何决定使用HashMap还是TreeMap?
这个主要考察HashMap和TreeMap的区别:TreeMap多了对集合中的元素根据键排序的能力以及对集合内元素的搜索能力
比如当用Iterator遍历TreeMap时,得到的记录是排过序的
 
如何实现数组和List之间的转换?
List转Array,必须使用集合的toArray(T[ ] array) 如下:
List<String> list = new ArrayList<String>();
list.add("jay");
list.add("tianluo");

//使用泛型,无需显式类型转换
String[] array = list.toArray(new String[list.size()]);
System.out.println(array[0]);
 Array转List,使用Arrays.asList()把数组转换成集合时候,不能使用修改集合相关的方法,如下:
String[] str = new String[]{"jay","tianlou"};
List<String> list = Arrays.asList(str);
list.add("boy");
运行结果:

Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.kun.Demo.Demo02.main(Demo02.java:11)

因为Arrays.asList不是返回java.util.ArrayList,而是一个内部类ArrayList

解决方式:

ArrayList<String> arrayList = new ArrayList<String>(str.length);
Collections.addAll(arrayList,str);


什么是Java优先级队列(Priority Queue)?
优先队列是Queue接口的实现,可以对其中元素进行排序
1.优先队列中元素默认排列顺序是升序排列
2.但对于自己定义的类来说,需要自己定义比较器
特点:1.基于优先值堆
2.不允许null值
3.线程不安全
4.出入队时间复杂度O(log(n))
5.调用remove()返回堆最小值

posted @   远乡人  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示