面试官常问的 Java 基础题 51-60
- 51.设计 4 个线程,其中两个线程每次对 j 增加 1,另外两个线程对 j 每次减少 1。写出程序。
- 52.ArrayList 和 Vector 的区别
- 53.HashTable 和 HashMap 的区别
- 54.List 和 Map 的区别?
- 55.List, Set, Map 是否继承自 Collection 接口?
- 56.List、Map、Set 三个接口,存取元素时,各有什么特点?
- 57.说出 ArrayList,Vector, LinkedList 的存储性能和特性
- 58.去掉一个 Vector 集合中重复的元素
- 59.Collection 和 Collections 的区别。
- 60.Set 里的元素是用什么方法来区分重复的呢? 是用==还是 equals()? 它们有何区别?
51.设计 4 个线程,其中两个线程每次对 j 增加 1,另外两个线程对 j 每次减少 1。写出程序。
52.ArrayList 和 Vector 的区别
ArrayList 、Vector 都实现了 List 接口
ArrayList 线程不安全,线程异步,查询快,增长率为目前数组长度的 1.5 倍,不可设置增长空间大小;
Vector 线程安全,线程同步,增删改快,增长率为目前数组长度的 2 倍,可设置增长空间大小。
ArrayList | Vector |
---|---|
线程不安全,线程异步 | 线程安全,线程同步 |
增长率为目前数组长度的 1.5 倍 | 增长率为目前数组长度的 2 倍 |
不可设置增长的空间大小 | 可设置增长的空间大小 |
增删改慢,查询快 | 查询慢,增删改快 |
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是 有序集合,即存储在这两个集合中的元素的位置都是有顺序的, 相当于一种 动态的数组,我们以后可以 按位置索引号 取出某个元素,并且其中的 数据允许重复,这是 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。
(本来题目问的与 hashset 没有任何关系,但 为了说清楚 ArrayList 与 Vector 的功能,我们使用对比方式,更有利于说明问题) 。
接着才说 ArrayList 与 Vector 的区别,这主要包括两个方面:.
-
(1) 同步性: Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它的方法之间是线程不同步的。
如果只有一个线程会访问到集合,建议使用 ArrayList,因为它不考虑线程安全,效率更高;
如果有多个线程会访问到集合,建议使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。 -
(2) 数据增长: ArrayList 与 Vector 都有一个初始的容量大小;
当存储进它们里面的元素的个数超过了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要 增加存储空间 时,不是只增加一个存储单元,而是 增加多个存储单元 ,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。
Vector 默认增长为原来两倍,而 ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是 增长为原来的 1.5 倍)。
ArrayList 与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。
53.HashTable 和 HashMap 的区别
Hashtable 产生于JDK 1.1,基于 Dictionary 类,线程安全,线程同步,效率较低,不可将空值作为一个表单条目的key或value。HashMap 是 Java1.2 引进的 Map 接口的一个实现 ,线程不安全,线程不同步,可以将空值作为一个表单条目的key或value。
HashTable产生于JDK 1.1,而HashMap产生于JDK 1.2。
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口,主要区别在于 HashMap 允许空(null)键值(key) , 由于非线程安全,在只有一个线程访问的情况下,HashMap效率要高于 Hashtable。
HashMap 允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey,因为 contains 方法容易让人引起误解。
Hashtable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Mapinterface 的一个实现。
最大的不同是,Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable 时,不需要自己为它的方法实现同步, 而 HashMap 就必须为之提供外同步。 Hashtable 和 HashMap 采用的 hash/rehash 算法都大概一样,所以性能不会有很大的差异。
虽然 Hashtable 比 HashMap 出现的早一些,但是现在 Hashtable 基本上已经被弃用了。而 HashMap 已经成为应用最为广泛的一种数据类型了。原因:一方面是因为 Hashtable 是线程安全的,效率比较低。也可能是Hashtable开始设计的时候没有遵循驼峰命名法(手动)。
-
1、父类不同:
HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary(已被废弃,详情看源代码)。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
Hashtable比HashMap多提供了elments() 和contains() 两个方法。
elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。
-
2、null值问题
Hashtable既不支持Null key也不支持Null value。Hashtable的put()方法的注释中有说明 。
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
-
3、线程安全性
Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步
HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。具体的原因在下一篇文章中会详细进行分析。使用HashMap时就必须要自己增加同步处理,
虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。这样设计是合理的。在我们的日常使用当中,大部分时间是单线程操作的。HashMap把这部分操作解放出来了。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
tip:HashMap是JDk1.2之后有的,而在JDK1.5中,伟大的Doug Lea给我们带来了concurrent包,从此Map也有安全的了。也就就是有了ConcurrentHashMap(关于这个的理解下次有机会再写,或自行百度)
-
4、遍历方式不同
Hashtable、HashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加,删除,修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。
JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,Hashtable也是使用fast-fail的。(此处可以去看一下1.5和1.8JDK源码的对比)
-
5、初始容量不同
Hashtable的初始长度是11,之后每次扩充容量变为之前的2n+1(n为上一次的长度)
而HashMap的初始长度为16,之后每次扩充变为原来的两倍
创建时,如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。
-
6、计算哈希值的方法不同
为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置
Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。 然而除法运算是非常耗费时间的。效率很低
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
54.List 和 Map 的区别?
List 是存储单列数据的集合,存储的数据有序且可重复;
map 是双列数据集合,存储的数据无序,键不可重复,值可重复。
List是存储单列数据的集合,Map是存储键和值这样的双列数据的集合;
List 中存储的数据是有顺序,并且允许重复;Map 中存储 的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
55.List, Set, Map 是否继承自 Collection 接口?
List,Set 是继承自 Collection 接口,Map 不继承自 Collection 接口。
Collection集合的子接口有 Set、List、Queue 三种子接口 。 我们比较常用的是Set、List,还有Map接口,不过Map不是collection的子接口。
List:
- 可以允许重复的对象。
- 可以插入多个null元素。
- 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
- 常用的实现类有 ArrayList、LinkedList 和 Vector。
Set:
- 不允许重复对象
- 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
- 只允许一个 null 元素
- Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。
Map:
- Map不是collection的子接口或者实现类。Map是一个接口。
- Map 的 每个 Entry 都持有两个对象,也就是一个键一个值(键值对),Map 可能会持有相同的值对象但键对象必须是唯一的。
- TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
- Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
- Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
56.List、Map、Set 三个接口,存取元素时,各有什么特点?
List 按序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存键值对,value可多值。
List :元素有放入顺序,元素可重复
Set :元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
Map :元素按键值对存储,无放入顺序,键不可重复,值可重复
List 接口有三个实现类:LinkedList,ArrayList,Vector
LinkedList :底层基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢。
ArrayList 和Vector 的区别:ArrayList是非线程安全的,效率高;Vector是基于线程安全的,效率低。
Set 接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
SortedSet 接口有一个实现类:TreeSet(底层由平衡二叉树实现)
Query 接口有一个实现类:LinkList
Map 接口有三个实现类:HashMap,HashTable,LinkeHashMap
HashMap 非线程安全,高效,支持null;HashTable线程安全,低效,不支持null
SortedMap 有一个实现类:TreeMap
57.说出 ArrayList,Vector, LinkedList 的存储性能和特性
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快,插入数据慢;
Vector 由于使用了synchronized 方法(线程安全),通常性能上较ArrayList 差;
LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
ArrayList 和 Vector 他们底层的实现都是一样的,都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector 中的方法由于添加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较 ArrayList 差,因此已经是Java中的遗留容器。
LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
补充:Vector 属于遗留容器
(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类 Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。
58.去掉一个 Vector 集合中重复的元素
59.Collection 和 Collections 的区别。
Collection是个java.util下的接口,它是各种集合结构的父接口,继承与他的接口主要有 Set 和 List。
Collections是个java.util下针对集合类的一个帮助类,它包含各种与集合有关的搜索、排序、线程安全化等操作的静态方法。
60.Set 里的元素是用什么方法来区分重复的呢? 是用==还是 equals()? 它们有何区别?
Set里的元素可以通过迭代器遍历出来,Set判断元素是否重复是使用 equals()方法进行判断的。
equals() 和 == 方法决定引用值是否指向同一对象,equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。