Java容器

问题及答案来源自《Java程序员面试笔试宝典》第四章 Java基础知识 4.9容器

 

 

 

1、Java Collections框架是什么?

Java Collections框架中包含了大量集合接口以及这些接口的实现类和操作它们的算法(比如排序、查找、反转、替换等)

具体而言主要提供了List(列表)、Queue(队列)、Set(集合)、Stack(栈)、Map(映射表)等数据结构

其中List(列表)、Queue(队列)、Set(集合)、Stack(栈)都继承自Collection接口

 

Collection的作用只是提供维护一组对象的基本接口而已

Collection是整个集合框架的基础,它里面存储一组对象,表示不同类型的Collections

 

Collection框架类图如下:

 

下面主要介绍Set、List和Map这三个接口:

Set:表示数学意义上的集合,主要特点是集合中的元素不能重复,因此存入Set的每个元素都必须定义equals方法来确保对象的

唯一性。该接口有两个实现类:HashSet和TreeSet,TreeSet中的元素是有序的

List:又称为有序的Collection,是按按对象进入的顺序保存对象,所以它对列表中每个元素的插入和删除位置进行精确的控制。

同时它可以保存重复的对象。LinkedList、ArrayList和Vector都实现了List接口

Map:提供一个从键映射到值得数据结构,用于保存键值对,值可以重复但键不能重复。实现它的类:HashMap、TreeMap、

LinkedHashMap、WeakHashMap、IdentityHashMap。虽然它们实现了相同的接口,但是执行效率不是完全相同。具体而言,

HashMap基于散列表实现,可以快速查询元素,LinkedHashMap采用列表来维护内部的顺序,TreeMap基于红黑树的数据结构

实现,内部元素是按需排列的

 

 

2、什么是迭代器?

迭代器是什么:

迭代器(Iterator)是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问容器对象中的元素,而又不必暴露

该对象内部细节的方法。通过迭代器,开发人员不需要了解容器底部结构就可以实现对容器的遍历

迭代器的使用:

  • 使用iterator()方法返回一个Iterator,然后通过Iterator的next方法返回第一个元素
  • 使用Iterator的hasNext方法判断是否还有元素,如果有可以使用next方法获取下一个元素

实例如下:

 1 public class IteratorDemo {
 2     public static void main(String[] args) {
 3         Collection<String> c = new ArrayList<String>();
 4         c.add("1");
 5         c.add("2");
 6         c.add("3");
 7         c.add("4");
 8         c.add("5");
 9         c.add("6");
10         Iterator<String> it = c.iterator();    // 获取集合的迭代器对象
11         while(it.hasNext()){    // 反复判断有没有下一个元素
12             String s = it.next();    // 取出下一个元素
13             System.out.println(s);
14         }
15     }
16 }

 

并发修改异常:

迭代的常规用法中我们要尽量避免在迭代过程中为集合添加/删除数据。否则会报错,原因是Java抛出了并发修改异常

迭代过程中并发修改异常的原因为:

迭代器中”记忆”的集合长度与集合中实际长度不同,而导致出现索引与实际元素不符甚至无限循环的情况发生

所以在使用Iterator时,避免类似操作,for循环底层为迭代器实现,所以也需要避免类似操作

有些迭代器避免了这样的问题,如ListIterator,但该类并不通用也不常用,实际开发中很少使用,只需要简单了解

 1 // java规定: 如果一个集合使用迭代器遍历,那么在遍历的过程中不允许修改集合的长度(增加或删除)
 2 public class ConcurrentModificationExceptionDemo {
 3     public static void main(String[] args) {
 4         Collection<String> c = new ArrayList<String>();
 5         c.add("1");
 6         c.add("2");
 7         c.add("itcast");
 8         c.add("4");
 9         c.add("5");
10         c.add("6");
11         Iterator<String> it = c.iterator();    // 获取集合的迭代器对象
12         while(it.hasNext()){    // 反复判断有没有下一个元素
13             String s = it.next();    // 取出下一个元素
14             if("itcast".equals(s)){
15                 // 如果相等添加一个大写ITCAST
16                 c.add("ITCAST");    // 报异常 => ConcurrentModificationException
17             }
18             System.out.println(s);
19         }
20     }
21 }

并发修改异常解决方法(单线程):

把要删除或添加的元素保存到一个集合中,遍历结束后调用removeAll方法或addAll方法来进行删除或添加

并发修改异常解决方法(多线程):

  • 使用线程安全的容器:比如ConcurrentHashMap和CopyOnWriteArrayList等
  • 使用迭代器遍历容器时对容器的操作放到synchronized代码块中

 

引申 - Iterator和ListIterator有什么区别?

Iterator只能正向遍历,适用于获取移除元素。而ListIterator继承自Iterator,专门针对List,可以从两个方向来遍历List,

同时也支持元素的修改

 

 

3、ArrayList、Vector和LinkedList有什么区别?

三者共同点:

三者均在java.util包中,均可以作为伸缩数组,即可以动态改变长度的数组

ArrayList和Vector的相同点:

都基于存储元素的Object[] array来实现的,会在内存中开辟一块连续的空间来存储,由于数据存储是连续的,因此它们

支持用下标访问元素,访问数据速度比较快、插入数据比较慢(要移动元素)

ArrayList和Vector的不同点: 

  • Vector扩充空间默认扩充为原来的2倍(每次扩充空间的大小是可以设置的)
  • ArrayList扩充空间默认扩充为用来的1.5倍(没有提供方法来设置空间扩充的方法)
  • Vector是线程安全的(Vector的大部分方法是直接或间接同步的)
  • ArrayList不是线程安全的(没有一个ArrayList的方法是同步的)

关于LinkedList:

LinkedList是采用双向列表来实现的,对数据的索引需要从列表头开始遍历,因此用于随机访问则效率比较低,但是插入元素

不需要对数据进行移动,故插入效率比较高,同时LinkedList是非线程安全的容器

实际使用选择:

  • 当对数据的主要操作为索引或只在集合的末端增加、删除元素时:ArrayList或Vector
  • 当对数据的操作主要为指定位置的插入或删除操作时:LinkedList
  • 当在多线程中使用容器时(即多个线程会访问容器):Vector

 

 

4、HashMap、HashTable、TreeMap和WeakHashMap有哪些区别?

Map是什么:

Java为数据结构中的映射定义了一个接口java.util.Map,它包括三个实现类:HashMap、HashTable和TreeMap

Map是用来存储键值对的数据结构,在数组中通过数组下标来对其内容索引的,而在Map中则是通过对象来进行

索引,用来索引的对象叫做key,其对于的对象叫做value

HashMap:

HashMap是最常用的Map,是根据键的HashCode值存储数据,根据键可以直接获得它的值,具有很快的访问速度

HashMap和HashTable的区别:

  • HashMap是HashTable的轻量级实现(非线程安全的实现),都实现了Map接口
  • HashMap中允许存在null键值(最多只能有一条),而HashTable中不允许存在null键值的
  • HashMap把HashTable的contains方法改成了containsValue和containsKey
  • HashTable是线程安全的,而HashMap不支持线程的同步(不是线程安全的)
  • HashMap效率比HashTable高(但多线程访问HashMap时开发人员要提供额外的同步机制)
  • HashTable使用Enumeration,而HashMap使用Iterator

HashMap和TreeMap和LinkedHashMap:

  • HashMap里存入的键值对在取出时是随机的,一般而已在Map中插入、删除和定位元素最好用HashMap
  • TreeMap实现了SortMap接口,能把它保存的记录根据键排序,因此需要排序的键值对可以使用TreeMap
  • LinkedHashMap是HashMap的子类,如果需要输出的顺序和输入的顺序相同,可以使用LinkedHashMap

WeakHashMap和HashMap: 

WeakHashMap里面的key采用的是弱引用的方式,只要key不再被外部引用就可以被垃圾回收期回收,而HashMap

中的key采用的是强引用的方式,就算没有被外部引用,但只有这个key从HashMap删除后才能被垃圾回收器回收

 

在HashTable上下文中,同步指什么?

同步意味着一个时间点只能有一个线程可以修改hash表,任何线程在执行HashTable的更新操作前都需要获取

对象锁,其他线程则等待锁的释放

 

如何实现HashTable的同步?

可以通过Map m = Collections.synchronizedMap(new HashMap())来达到同步的效果。具体而言,该方法返回一个

同步的Map,该Map封装了底层的HashMap的所有方法,使得底层的HashMap即使是在多线程的环境中也是安全的

 

 

5、用自定义类型作为HashMap或HashTable的key需要注意哪些问题?

HashMap和HashTable是用来存放键值对的一种容器,在使用这个容器时不能存储重复的键,也就是说每个键只能

映射一个值,当有重复的键时不会创建新的映射关系,而会使用先前的键值 

但是当用自定义的类的对象作为HashMap中的key时会给人造成一种假象:key是可以重复的,示例如下:

 1 class Person {
 2     String id;
 3     String name;
 4 
 5     public Person(String id, String name) {
 6         this.id = id;
 7         this.name = name;
 8     }
 9 
10     public String toString() {
11         return "id=" + id + ", name=" + name;
12     }
13 
14 }
15 
16 public class HashMapTest {
17     public static void main(String[] args) {
18         System.out.println("Use String as key: ");
19         HashMap<Person, String> hm = new HashMap<Person, String>();
20         Person p1 = new Person("111", "name1");
21         Person p2 = new Person("111", "name1");
22         hm.put(p1, "address1");
23         hm.put(p2, "address2");
24 
25         Iterator iter = hm.entrySet().iterator();
26         while (iter.hasNext()) {
27             Map.Entry entry = (Map.Entry) iter.next();
28             Person key = (Person) entry.getKey();
29             String val = (String) entry.getValue();
30             System.out.println(key + " - " + val);
31             // 输出结果如下:
32             // id=111, name=name1 - address1
33             // id=111, name=name1 - address2
34         }
35     }
36 }

上面这种现象的原因是:

虽然这两个对象有着同样的内容,但是存在在内存中不同的地址里,向hashMap中添加对象时调用equals方法

的返回值为false,HashMap会认为它们是两个不同的对象,就会分别创建不同的映射关系,因此为了实现在向

HashMap中添加键值对时可根据对象的内容来判断两个对象是否相等,就需要重写equals方法和hashCode方法:

 1 class Person {
 2     String id;
 3     String name;
 4     
 5     public int hashCode(){
 6         return id.hashCode();
 7     }
 8     
 9     public Person(String id, String name) {
10         this.id = id;
11         this.name = name;
12     }
13 
14     public String toString() {
15         return "id=" + id + ", name=" + name;
16     }
17     
18     public boolean equals(Object obj){
19         Person p = (Person)obj;
20         if(p.id.equals(this.id)){
21             return true;
22         } else{
23             return false;
24         }
25     }
26 
27 }
28 
29 public class HashMapTest {
30     public static void main(String[] args) {
31         System.out.println("Use String as key: ");
32         HashMap<Person, String> hm = new HashMap<Person, String>();
33         Person p1 = new Person("111", "name1");
34         Person p2 = new Person("111", "name1");
35         hm.put(p1, "address1");
36         hm.put(p2, "address2");
37 
38         Iterator iter = hm.entrySet().iterator();
39         while (iter.hasNext()) {
40             Map.Entry entry = (Map.Entry) iter.next();
41             Person key = (Person) entry.getKey();
42             String val = (String) entry.getValue();
43             System.out.println(key + " - " + val);
44             // 输出结果如下:
45             // id=111, name=name1 - address2
46         }
47     }
48 }

 

总结 - 开发者在使用自定义类作为HashMap的key时,需要注意以下几点: 

  • 如果想根据对象的相关属性来自定义对象是否相等的逻辑就要重写equals方法和hashCode方法
  • 当自定义类的对象作为HashMap(hashTable)的key时,最好把这个类设置为不可变类
  • 从HashMap的工作原理可以看出如果两个对象相等,那么这两个对象有相同的hashCode值,反之则不成立

 

 

6、Collection和Collections有什么区别?

Collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法。实现该接口的类主要有List和Set,

该接口的主要设计目标是为各种具体的集合提供最大化统一的操作方式

Collections是针对集合类的一个包装类,它提供了一系列的静态方法以实现对各种集合的搜索、排序、线程安全化

等操作,其中绝大多数方法都是用来处理线性表,Collections类如同工具类,不能被实例化(类似Math类)

使用示例:

 1 public class CollectionsDemo {
 2     public static void main(String[] args) {
 3         List<Integer> list = new LinkedList<Integer>();
 4         int array[] = {1, 7, 3, 2};
 5         for(int num: array){
 6             list.add(new Integer(num));
 7         }
 8         
 9         for(int i=0; i< array.length; i++){
10             System.out.print(list.get(i));
11         }
12         System.out.println();
13         // 1732
14         
15         Collections.sort(list);
16         for(int i=0; i< array.length; i++){
17             System.out.print(list.get(i));
18         }
19         System.out.println();
20         // 1237
21     }
22 }

 

posted @ 2019-02-02 21:29  woz333333  阅读(138)  评论(0编辑  收藏  举报