java程序员面试笔试宝典4.9容器
程序员文章站
2022-04-21 19:44:56
...
1.Java Collections框架是什么?
Java Collections框架中包含了大量集合接口以及这些接口的实现类和操作他们的算法
例如:排序,查找,替换,复制,取最小元素,取最大元素等。
具体而言,主要提供了List列表,Queue队列,Set集合,Stack栈,和Map映射表,存放键值对。
其中List,Queue,Set,Stack都继承自Collection接口。
Collections是整个集合框架的基础,它里面存储一组对象,表示不同类型的Collections,它的作用是提供维护一组对象的基本接口而已。
下面分别介绍Set,List和Map3个接口。
1)Set表示数学意义上的集合概念。主要特点是集合中的元素不能重复
因此存入Set中的每个元素都必须定义equals()方法确保对象的唯一性。
该类有两个实现类HashSet和TreeSet
其中TreeSet实现了SortedSet接口,因此TreeSet容器中的元素是有序的。
2)List又称为有序的Collection,按对象进入的顺序保存对象
所以它能对列表中的每个元素的插入和删除位置进行精确的控制。
同时可以保存重复的对象。
LinkedList,ArrayList和Vector都实现了此接口。
3)Map提供了一个从键映射到值的数据结构,用于保存键值对,其中值可以重复,但键是唯一的,不能重复。
java类库中有多个实现该接口的类:HashMap,TreeMap,LinkedHashMap,WeakHashMap,和IdentityHashMap
虽然他们都实现了相同的接口,但是执行效率却不相同,
具体而言,
HashMap是基于散列表实现的,采用对象的HashCode可以进行快速查询
LinkedHashMap采用列表来维护内部的顺序
TreeMap基于红黑树的数据结构来实现的,内部元素是按需排列的。
笔1:下面哪种创建map集合的方式是 正确的?D
A:Map m = new Map();
B:Map m = new Map(int capacity,increment capacity)
C:Map m = new Map(new Collection())
D:Map是接口,所以不能实例化。
说明:
接口不能直接实例化,但是可以实例化实现Map接口的类的对象。
2.什么是迭代器?
迭代器(Iterator)是一个对象,它的工作是遍历并选择序列中的对象,
它提供了一种访问一个容器对象中各个元素,而不必暴露该对象内部细节的方法。
由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。
注意:
1)使用容器的Iterator()方法返回一个Iterator,然后通过Iterator的next()方法返回第一个元素。
2)使用Iterator的hasNext()方法判断容器中是否还有元素,如果有,可以使用next()方法获取下一个元素。
3)可以通过remove()方法删除迭代器返回的元素。
Iteator支持派生的兄弟成员.ListIteator只存在于List中,支持在迭代器期间向List中添加或删除元素,并且可以在List中双向滚动。
例1:
public class IteatorTest {
public static void main(String[] args) {
List<String> ll = new LinkedList<String>();
ll.add("first");
ll.add("second");
ll.add("third");
ll.add("fourth");
for (Iterator<String> iter=ll.iterator();iter.hasNext();) {
String str = iter.next();
System.out.println(str);
}
}
}
结果:
first
second
third
fourth
在使用iteator()方法时经常会遇到ConcurrentModificationException异常,
这通常是由在使用Iteator遍历容器的同时又对容器做增加或删除操作所导致的,
或者由于多线程操作导致
当一个线程使用迭代器遍历容器的同时,另外一个线程对这个容器进行增加或删除操作。
例2:单线程抛出ConcurrentModificationException的情况
public class IteatorTest {
public static void main(String[] args) {
List<String> ll = new LinkedList<String>();
ll.add("first");
ll.add("second");
ll.add("third");
ll.add("fourth");
for(Iterator<String> iter = ll.iterator();iter.hasNext();){
String str = iter.next();
System.out.println(str);
if(str.equals("second")){
ll.add("five");
}
}
}
}
结果:
first
second
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:953)
at java.util.LinkedList$ListItr.next(LinkedList.java:886)
at com.zcl.test.IteatorTest.main(IteatorTest.java:17)
抛出上述异常的主要原因是当调用容器的iterator方法返回Iteator对象时,把容器中包含对象的个数赋值给了一个变量expectedModCount
在调用next()方法时会比较变量expectedModCount与容器中实际对象的个数modCount的值是否相等。
若二者不相等,就抛出ConcurrentModificationException异常。
因此在使用Iteator遍历容器的过程中,如果对容器进行增加或删除操作,就会改变容器中对象的数量,从而导致抛出异常。
解决方法如下:
在遍历的过程中把需要删除的对象保存到一个集合中,等遍历结束后在调用remove all()方法来删除,或者使用iter.remove()方法。
以上是单线程的解决方案,多线程访问容器的过程中抛出C异常该如何解决呢?
1)jdk1.5之后引入了线程安全的容器,比如ConcurentHashMap和CopyOnWriteArrayList等。
可以使用这些线程安全的容器来代替非线程安全的容器
2)在使用迭代器遍历容器时对容器的操作放到synchronized代码块中,但是当引用程序并发程度比较高时,这会严重影响程序的性能。
引申:Iterator与ListIterator有什么区别?
Iteator只能正向遍历集合,适用于获取移除元素。
ListIteator继承自Iteator,专门针对List,可以从两个方向来遍历list,同时支持元素的修改。
3.ArrayList,Vector和LinkedList有什么区别?
ArrayList,Vector和LinkedList类均在java.util包中,均为可伸缩数组,即可以动态改变长度的数组。
ArrayList和Vector都是基于存储元素的Object[] array来实现的,会在内存中开辟一块连续的空间来存储,支持下标访问,索引块,增删慢。
ArrayList和Vector都有初始大小;
ArrayList每次扩容为1.5倍,Vector为两倍;
ArrayList每次扩充不可设置,Vector可以设置。
区别:
ArrayList没有一个方法是同步的
Vector的绝大多数方法(add,insert,remove,set,equals,hashcode等)都是同步的。线程安全的。因此性能逊色于ArrayList
LinkedList是采用双向列表实现的,对数组的索引需要从头开始遍历,用于随机访问效率低,但是增删效率高。同时是非线程安全的。
使用:
当对数据的主要操作是索引或只在集合的末端增加,删除元素时,使用ArrayList和Vector效率高;
当对数据的操作主要是指定位置的增删时,使用LinkedList效率高
当在多线程中使用容器时,使用Vector更安全。
笔1:若线性表最常用的操作是存取第i个元素及其前驱的值,则采用(D)存储方式节省时间。
A:单链表
B:双链表
C:单循环链表
D:顺序表
说明:
顺序适合在随机访问的场合使用。时间复杂度为0(1)
而列表的随机访问操作的时间复杂度为0(n)
笔2:对于import java.util包,下列说法中,错误的是(C)
A:Vector类属于java.util包
B:Vector类放在.../java/util/目录下
C:Vector类放在java.util文件中
D:Vector类是Sun公司的产品
说明:
java.util是包名,实质是一个目录结构
4.HashMap,Hashtable,TreeMap和WeakHashMap有哪些区别?
java为数据结构中的映射定义了一个接口java.util.Map,它包含三个实现类:HashMap,Hashtable,TreeMap
Map采用存储键值对的数据结构
数组中通过数组下标进行索引,map通过对象key进行索引,其对应的对象叫做value.
hashMap和hashtable都采用了hash法进行索引。区别如下:
1)hashMap是hashTable的轻量级实现(非线程安全实现)
hashmap最多只允许一条纪录的键为null,hashtable不允许
2)hashmap去掉了hashtable的contains方法,引入了containsKey和containsValue。
hashtable继承自Dictionary类
hashmap是java1.2引入的map接口的一个实现
3)hashtable线程安全,效率低
4)hashtable使用Enumeration,HashMap使用Iterator
5)两者采用的hash算法几乎相同,性能不会有太大差异。
6)在hashtable中,hash数组默认大小是11,增加方式是old*2+1,
hashmap中默认大小是16,而且一定是2的指数。
7)hash值的使用不同,hashtable直接使用对象的hashCode.
使用最多的是hashMap。
hashMap里面存入的键值对在取出时没有固定的顺序,是随机的。
TreeMap实现了SortMap接口,能够把它保存的纪录根据键排序,因此的取出来的是排序后的键值对。如果需要按照自然顺序或者自定义顺序遍历键,使用treeMap.
LinkedHashMap是hashMap的一个子类,如果需要输出的顺序和输入的顺序相同,那么用LinkedHashMap可以实现,还可以实现按读取顺序来排序。
WeakHashMap和HashMap的区别:
WeakHashMap中的key采用弱引用的方式,只要key不再被外部引用,就可被gc回收。
HashMap中的key采用强引用方式,当HashMap中的key没有被外部引用时,只有这个key从HashMap中删除后,才可以被gc回收。
笔1:在Hashtable上下文中,同步指的是什么?
同步意味着在一个时间点只能有一个线程可以修改hash表,任何线程在执行Hashtable的更新操作前都需要获取对象锁,其他线程则等待锁的释放。
笔2:如何实现HashMap的同步?
HashMap可以通过
Map m = Collections.synchronizedMap(new HashMap());
来达到同步的效果。
具体而言,该方法返回一个同步的Map,该Map封装了底层的HashMap的所有方法,使得底层的HashMap即使在多线程的环境下也是安全的。
5.用自定义类型作为HashMap或Hashtable的key需要注意哪些问题?
HashMap与Hashtable是用来存放键值对的一种容器,在使用这种容器的时候有一种限制:不能用来存放重复的键。
即每个键只能唯一映射唯一的一个值,当有键重复时,不会 创建新的映射关系,而是会使用先前的键值。
例1:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public class Test{
public static void test1(){
System.out.println("User user defined class as key:");
HashMap<String, String> hm = new HashMap<String,String>();
hm.put("aaa", "bbb");
hm.put("aaa", "ccc");
Iterator<Entry<String, String>> iterator = hm.entrySet().iterator();
while(iterator.hasNext()){
Entry<String, String> next = iterator.next();
String key = next.getKey();
String value = next.getValue();
System.out.println(key+" "+value);
}
}
public static void main(String[] args) {
test1();
}
}
结果:
User user defined class as key:
aaa ccc
说明:会用新值"ccc"代替"bbb"
但是使用自定义的对象作为key的时候会造成一种假象好像key是可以重复的
例2:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
class Person{
String id;
String name;
public Person(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
public class Test{
public static void test2(){
System.out.println("User String as key:");
HashMap<Person, String> hm = new HashMap<Person,String>();
Person p1 = new Person("111", "name1");
Person p2 = new Person("111", "name1");
hm.put(p1, "address1");
hm.put(p2, "address1");
Iterator<Entry<Person, String>> iterator = hm.entrySet().iterator();
while(iterator.hasNext()){
Entry<Person, String> next = iterator.next();
Person key = next.getKey();
String value = next.getValue();
System.out.println("key="+key+" value="+value);
}
}
public static void main(String[] args) {
test2();
}
}
结果:
User String as key:
key=Person [id=111, name=name1] value=address1
key=Person [id=111, name=name1] value=address1
说明:
从表面上看好像没有覆盖
HashMap添加元素的步骤:
1)调用key的hashCode()生成一个hash值h1,
如果h1在hashMap中不存在,直接将<key,value>添加到hashMap中
如果h1已经存在,找出所有hash值为h1的key,分别调用key的equals()方法判断key是否相同
如果返回true,说明key已经存在,hashmap会使用新值覆盖旧的
返回false,说明key不存在,创建新的映射关系。
如果h1已经存在,就会产生冲突,一般而言,对于不同的key值可能会得到相同的hash值,因此就需要对冲突进行处理。hashMap采用链地址法。
上例中,使用自定义的类作为key,没有重写hashCode方法和equals方法,默认使用Object的hashCode和equals方法。
Object的equals方法当引用同一个对象时,返回true。否则返回false.
创建了两个对象,虽然拥有相同的内容,但是在内存中地址不同,因此调用equals返回false,hashMap认为是不同的对象,会分别创建不同的映射关系。
因此为了实现在向HashMap中添加键值时,可以根据对象的内容来判断两个对象是否相等,这就需要重写hashCode方法和equals方法。
例3:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
class Person{
String id;
String name;
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
Person p = (Person) obj;
if(p.id.equals(this.id)){
return true;
}else {
return false;
}
}
public Person(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
public class Test{
public static void test2(){
System.out.println("User String as key:");
HashMap<Person, String> hm = new HashMap<Person,String>();
Person p1 = new Person("111", "name1");
Person p2 = new Person("111", "name1");
hm.put(p1, "address1");
hm.put(p2, "address1");
Iterator<Entry<Person, String>> iterator = hm.entrySet().iterator();
while(iterator.hasNext()){
Entry<Person, String> next = iterator.next();
Person key = next.getKey();
String value = next.getValue();
System.out.println("key="+key+" value="+value);
}
}
public static void main(String[] args) {
test2();
}
}
结果:
User String as key:
key=Person [id=111, name=name1] value=address1
结论:开发者使用自定义类作为HashMap的key时,需要注意以下几个问题:
1)如果根据对象的相关属性来自定义对象是否相等的逻辑,此时就需要重写equals方法,一旦重写了equals方法,那么就必须重写hashCode方法。
2)当自定义类的多项作为HashMap(Hashtable)的key时,最好把这个类设计为不可变类。
3)从hashMap的工作原理来看,如果两个对象相等,那么这两个对象有着相同的hashCode,反之 则不成立。
6.Collection和Collections有什么区别?
Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。
实现该接口的类主要有List和Set,为各种具体的集合提供共最大化的统一的操作方式。
Collections是针对集合类的一个包装类,它提供一系列静态方法以实现对各种集合的搜索,排序,线程安全化等操作,其中大多数方法都是用来处理线性表的。
Collections类不能实例化,如同一个工具类,服务于Collection框架。
若在使用Collections类的方法时,对应的collection对象为null,则这些方法都会抛出NullPointerException。
例1:
package com.zcl.test;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class Test{
public static void main(String[] args) {
List<Integer> list = new LinkedList<Integer>();
int[] array = {1,7,3,2};
for (int i = 0; i < array.length; i++) {
list.add(new Integer(array[i]));
}
Collections.sort(list);
for (int i = 0; i < array.length; i++) {
System.out.println(list.get(i));
}
}
}
结果:
1
2
3
7
下一篇: PHP中级工程师面试题