容器面试题

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/pectoralis_major/article/details/102549061

java 容器都有哪些?

数组,String,java.util下的集合容器

数组长度限制为 Integer.Integer.MAX_VALUE;

String的长度限制: 底层是char 数组 长度 Integer.MAX_VALUE 线程安全的

java.util下的集合容器
在这里插入图片描述

Collection 和 Collections 有什么区别?

1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。

2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

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

List、Set是实现了Collection接口的子接口;而Map是另一个集合接口;(Collection接口和Map接口是平级的)
在这里插入图片描述

HashMap 和 Hashtable 有什么区别?

1.继承父类不同

Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类;但二者都实现了Map接口。

2.线程的安全性

1. HashTable是同步(方法中使用了Synchronize)的;而HashMap是未同步(方法中缺省Synchronize)的。

2.Hashtable 线程安全,因为它每个方法中都加入了Synchronize,在多线程并发的环境下,可以直接使用Hashtable,不需自己在加同步;

HashMap线程不安全,因为HashMap底层是一个Entry数组,当发生hashmap冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。

3.是否有contains方法

1.HashTable有一个contains(Object value)方法,功能和containsValue方法(Object value)功能一样。

2.HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey。

4.可否允许有null值

key、value都是对象,但是不能拥有重复key值,value值可以重复出现。

1.Hashtable中,key和value都不允许出现null值。

2.HashMap允许null值(key和value都可以),因为在HashMap中null可以作为健,而它对应的值可以有多个null。

5.遍历方式内部实现不同

1.HashTable使用Enumeration,HashMap使用Iterator。

6.hash值不一样

1.HashTable直接使用对象的hashCode,如下:

//hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

2.HashMap要重新计算key值和hash值,如下:

int hash = hash(k);
int i = indexFor(hash, table.length);

static int hash(Object x) {
  int h = x.hashCode();

  h += ~(h << 9);
  h ^= (h >>> 14);
  h += (h << 4);
  h ^= (h >>> 10);
  return h;
}
static int indexFor(int h, int length) {
  return h & (length-1);
}

7.数组初始化和扩容不同

1.HashTable中hash数组默认大小是11,增加的方式是 arr*2+1。

2.HashMap中hash数组的默认大小是16,而且一定是2的指数。

如何决定使用 HashMap 还是 TreeMap?

TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排列的;TreeMap的实现也是基于红黑树结构。

而HashMap<K,V>的Key值实现散列hashCode(),分布是散列的均匀的,不支持排序,数据结构主要是桶(数组),链表或红黑树。

所以,查询的时候使用HashMap,增加、快速创建的时候使用TreeMap。

说一下 HashMap 的实现原理?

HashMap使用数组加链表实现。每个数组中储存着链表。

当使用put方法储存key-value键值对时,会先调用key的hashCode方法,得到此key经特定哈希运算后的值,然后将此值通过其他运算(?)得到一个值,将这个值与(length-1)做或操作(&),相当于对数组长度做取余操作。最终得到一个值作为此key在数组中的索引值,然后将key-value键值对储存进去。通过这种方法将储存的不同key-value键值对“散列”到数组的不同位置。

在储存的时候,如果索引位置尚无元素,那么直接储存。如果有元素,那么就调用此key的equals方法与原有的元素的Key进行比较。如果返回true,说明在这个equals定义的规则上,这两个Key相同,那么将原有的key保留,用新的value代替原来的value。如果返回false,那么就说明这两个key在equals定义的规则下是不同元素,那么就与此链表的下一个结点进行比较,知道最后一个结点都没有相同元素,再下一个是null的时候,就用头插法将此key-value添加到链表上。

HashMap对重复元素的处理方法是:key不变,value覆盖。

当使用get方法获取key对应的value时,会和储存key-value时用同样的方法,得到key在数组中的索引值,如果此索引值上没有元素,就返回null。如果此索引值上有元素,那么就拿此key的equals方法与此位置元素上的key进行比较,如果返回true。就返回此位置元素对应的value。如果返回false,就一直按链表往下比较,如果都是返回false,那么就返回null。

另外:HashMap在JDK1.8之后引入红黑树结构。HashMap是线程不安全的,线程安全的是CurrentHashMap,不过此集合在多线程下效率低。

说一下 HashSet 的实现原理?

HashSet是基于HashMap实现的,HashSet 底层使用HashMap来保存所有元素,
因此HashSet 的实现比较简单,相关HashSet 的操作,基本上都是直接调用底层HashMap的相关方法来完成,HashSet不允许有重复的值,并且元素是无序的。

ArrayList 和 LinkedList 的区别是什么?

ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
理论上来说在做新增和删除操作add和remove时,LinedList比较占优势,因为ArrayList要移动数据。

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

List转数组:toArray(arraylist.size()方法

数组转List:Arrays的asList(a)方法

ArrayList 和 Vector 的区别是什么?

List接口下一共实现了三个类:ArrayList,Vector和LinkedList

LinkedList主要保持数据的插入顺序的时候使用,采用链表结构;ArrayList,Vector都是使用的是长度可变的数组存储

ArrayList,Vector主要区别为以下几点:

(1)同步性:Vector是线程安全的,用synchronized实现线程安全,而ArrayList是线程不安全的,如果只有一个线程会访问到集合,那最好使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们再去考虑和编写线程安全的代码。

(2)数据容量增长:二者都有一个初始容量大小,采用线性连续存储空间,当存储的元素的个数超过了容量时,就需要增加二者的存储空间,Vector增长原来的一倍,ArrayList增加原来的0.5倍。

Array 和 ArrayList 有何区别?

  1. Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大小),而ArrayList可以只是先声明。
  2. Array只能存储同构的对象,而ArrayList可以存储异构的对象。
    同构的对象是指类型相同的对象,若声明为int[]的数组就只能存放整形数据,string[]只能存放字符型数据,但声明为object[]的数组除外。
    而ArrayList可以存放任何不同类型的数据(因为它里面存放的都是被装箱了的Object型对象,实际上ArrayList内部就是使用"object[] _items;"这样一个私有字段来封装对象的)

3.在CLR托管对中的存放方式
Array是始终是连续存放的,而ArrayList的存放不一定连续。

4 初始化大小
Array对象的初始化必须只定指定大小,且创建后的数组大小是固定的,
而ArrayList的大小可以动态指定,其大小可以在初始化时指定,也可以不指定,也就是说该对象的空间可以任意增加。

5 Array不能够随意添加和删除其中的项,而ArrayList可以在任意位置插入和删除项。

Array和ArrayList的相似点

1 都具有索引(index),即可以通过index来直接获取和修改任意项。
2 他们所创建的对象都放在托管堆中。
3 都能够对自身进行枚举(因为都实现了IEnumerable接口)。

在 Queue 中 poll()和 remove()有什么区别?

Queue 中 remove() 和 poll()都是用来从队列头部删除一个元素。在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。

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

线程安全(Thread-safe)的集合对象:

  • Vector 线程安全:
  • HashTable 线程安全:
  • StringBuffer 线程安全:

非线程安全的集合对象:

  • ArrayList :
  • LinkedList:
  • HashMap:
  • HashSet:
  • TreeMap:
  • TreeSet:
  • StringBulider:

迭代器 Iterator 是什么?

迭代器是一种模式、详细可见其设计模式,可以使得序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部
Iterable 实现这个接口的集合对象支持迭代,是可以迭代的。实现了这个可以配合foreach使用~
Iterator 迭代器,提供迭代机制的对象,具体如何迭代是这个Iterator接口规范的。

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

  1. boolean hasNext() ; 判断迭代器中是否还有下一个元素,有则返回true
  2. Object next(); 返回迭代器中下一个元素
  3. void remove() ; 删除集合里上一个next方法调用的时候返回的对象元素
  4. void forEachRemaining(Consumer action); 使用Lambdda表达式的形式输出Iterator中所以的元素。注意该方法其实是间接调用next()方法进行遍历,所以再次是next()方法的时候Iterator中的对象已经被遍历完了。

特点:

  1. Iterator遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出ConcurrentModificationEception的异常。
  2. Iterator遍历集合元素的过程中可以通过remove方法来移除集合中的元素
  3. Iterator必须依附某个Collection对象而存在,Iterator本身不具有装载数据对象的功能。
  4. Iterator.remove方法删除的是上一次Iterator.next()方法返回的对象。
  5. 强调一下next()方法,该方法通过游标指向的形式返回Iterator下一个元素。

Iterator 和 ListIterator 有什么区别?

List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:

  1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能

  2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

  3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

  4. 都可实现删除对象,但是ListIterator的set()方法可以实现对象的修改。Iterator仅能遍历,不能修改。

因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。

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

我们很容易想到用final关键字进行修饰,我们都知道

final关键字可以修饰类,方法,成员变量,final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的成员变量必须初始化值,如果这个成员变量是基本数据类型,表示这个变量的值是不可改变的,如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变的

那么,我们怎么确保一个集合不能被修改?首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。

我们可以做一个实验:

可以看到:我们用final关键字定义了一个map集合,这时候我们往集合里面传值,第一个键值对1,1;我们再修改后,可以把键为1的值改为100,说明我们是可以修改map集合的值的。

那我们应该怎么做才能确保集合不被修改呢?
我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。

同理:Collections包也提供了对list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)

面试题答案

猜你喜欢

转载自blog.csdn.net/pectoralis_major/article/details/102549061