您的位置:首页 > 编程语言 > Java开发

Java集合类常见问题(下)

2016-03-26 19:10 357 查看
21.HashMap和HashTable有何区别?

HashMap是HashTable的轻量级实现(非线程安全的实现),它们都继承自Map接口,HashTable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。它们采用的hash/rehash算法大概一样,所以性能上不会有很大的差异,但是它们也有以下不同点:

①HashMap允许key和value为null,而HashTable不允许。由于非线程安全,在单线程环境下,HashMap效率要高于HashTable。

②HashTable的方法是允许同步的,而HashMap不是,因此HashMap适合单线程环境,HashTable适合多线程环境。在多个线程访问HashTable时不需要自己为它的方法实现同步,而HashMap必须为它的方法提供外同步。

③HashTable有一个contains()方法,功能和containsValue()功能一样。HashMap 把 HashTable的contains 方法去掉了,改成 containsValue 和containsKey。因为contains 方法容易引起误解。

④ HashTable使用Enumeration,HashMap使用Iterator。

⑤ hash数组的初始化大小及增长方式不同。HashTable中hash数组的默认大小是11,增加方式的old*2+1,HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。

⑥ 哈希值的使用不同,HashTable直接使用对象的hashCode,而HashMap会重新计算hash值。

⑦ HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,HashTable提供了对键的列举(Enumeration)。一般认为HashTable是一个遗留的类。

[延伸]Java.util.Properties类是HashTable的一个子类,设计用于String keys和values。Properties对象的用法同HashTable的用法相像,但是增加了两个节省时间的方法:Store()方法把一个Properties对象的内容以一种可读的形式保存到一个文件中。Load()方法正好相反,用来读取文件,并设定Properties对象来包含keys和values。

注意,因为Properties扩展了Hashtable,你可以用超类的put()方法来添加不是String对象的keys和values。这是不可取的。另外,如果你将store()用于一个不包含String对象的Properties对象,store()将失败。作为put()和get()的替代,你应该用setProperty()和getProperty(),它们用String参数。

22.如何决定选用HashMap还是TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,如果需要对一个有序的key集合进行遍历,TreeMap是更好的选择。因为TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。

23.ArrayList和Vector有何异同点?

ArrayList和Vector在很多时候都很类似:

①两者都是基于索引的,内部由一个数组支持。

②两者维护插入的顺序,我们可以根据插入顺序来获取元素。

③ArrayList和Vector的迭代器实现都是fail-fast的。

④ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。

ArrayList 与 Vector 的区别,主要包括以下两方面:

①同步性:

Vector 是线程安全的,也就是说它的方法之间是线程同步的,而ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。

②数据增长:

ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来的两倍,而 ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。

24.Array和ArrayList有何区别?什么时候更适合用Array?

①Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。

②Array大小是固定的,ArrayList的大小是动态变化的。

ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等

对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,还是Array这种方式相对比较合适。下面列举了更适合Array的场景:

①如果列表的大小已经指定,大部分情况下是存储和遍历它们。

②对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。

③如果你要使用多维数组,使用[][]比
List<List<>>
更容易。

25.ArrayList和LinkedList有何区别?

ArrayList和LinkedList两者都实现了List接口,但是它们也有以下的不同点:

①ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。

②与此对应,LinkedList是以元素列表(链表)的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。

③相对于ArrayList,LinkedList的插入、添加、删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。

④LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

26.哪些集合类提供对元素的随机访问?

ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。

27.EnumSet是什么?

java.util.EnumSet是使用枚举类型的集合实现。当集合创建时,枚举集合中的所有元素必须来自单个指定的枚举类型,可以是显式的或隐式的。EnumSet是不同步的,不允许值为null的元素。它也提供了一些有用的方法,比如copyOf(Collection c)、of(E first,E…rest)和complementOf(EnumSet s)。

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

Vector、HashTable、Properties和Stack是同步类,所以它们是线程安全的,可以在多线程环境下使用。Java1.5并发API包括一些集合类,允许迭代时修改,因为它们都工作在集合的克隆上,所以它们在多线程环境中是安全的。

29.并发集合类是什么?

Java1.5并发包(java.util.concurrent)包含线程安全集合类,允许在迭代时修改集合。迭代器被设计为fail-fast的,会抛出ConcurrentModificationException。一部分类为:CopyOnWriteArrayList、 ConcurrentHashMap、CopyOnWriteArraySet。

30.BlockingQueue是什么?

Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。

BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。

Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。

31.队列和栈是什么,它们的区别呢?

栈和队列两者都被用来预存储数据。java.util.Queue是一个接口,它的实现类在Java并发包中。队列允许先进先出(FIFO)检索元素,但并非总是这样。双向队列(Deque)接口允许从两端检索元素。栈与队列很相似,但它允许对元素进行后进先出(LIFO)进行检索。Stack是一个扩展自Vector的类,而Queue是一个接口。

32.Collections类是什么?

Collections是个java.util下的类,Collections是针对集合类的一个帮助类,他提供一系列有关集合操作的静态方法,实现对各种集合的搜索、排序、线程安全化等操作。它包含操作集合的多态算法,返回一个由指定集合支持的新集合和其它一些内容。这个类包含集合框架算法的方法,比如折半搜索、排序、混编和逆序等。

33.Comparable和Comparator接口是什么?

如果想使用Array或Collection的排序方法时,需要在自定义类里实现Java提供Comparable接口。Comparable接口有compareTo()方法,它被排序方法所使用。应该重写这个方法,如果“this”对象比传递的对象参数更小、相等或更大时,它返回一个负整数、0或正整数。

但是,在大多数实际情况下,我们想根据不同参数进行排序。比如,作为一个CEO,想对雇员基于薪资进行排序,一个HR想基于年龄对他们进行排序。这就是我们需要使用Comparator接口的情景,因为Comparable.compareTo(Object obj)方法实现只能基于一个字段进行排序,我们不能根据对象排序的需要选择字段。

Comparator接口的compare(Object obj1, Object obj2)方法的实现需要传递两个对象参数,若第一个参数比第二个小,返回负整数;若第一个等于第二个,返回0;若第一个比第二个大,返回正整数。

34.Comparable和Comparator接口有何区别?

Comparable和Comparator接口被用来对对象集合或者数组进行排序。Comparable接口被用来提供对象的自然排序,我们可以使用它来提供基于单个逻辑的排序。

Comparator接口被用来提供不同的排序算法,我们可以选择需要使用的Comparator来对给定的对象集合进行排序。

35.我们如何对一组对象进行排序?

如果我们需要对一个对象数组进行排序,我们可以使用Arrays.sort()方法。如果我们需要排序一个对象列表,我们可以使用Collection.sort()方法。两个类都有用于自然排序(使用Comparable)或基于标准的排序(使用Comparator)的重载方法sort()。Collections内部使用数组排序方法,所有它们两者都有相同的性能,只是Collections需要花时间将列表转换为数组。

36.当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?

在作为参数传递之前,我们可以使用Collections.unmodifiableCollection(Collection c) 方法创建一个只读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。

37.我们如何从给定集合那里创建一个synchronized的集合?

我们可以使用Collections.synchronizedCollection(Collection c)根据指定集合来获取一个synchronized(线程安全的)集合。比如HashMap可以这样来实现线程安全:

Map m = Collections.synchronizedMap(new HashMap);


38.集合框架里实现的通用算法有哪些?

Java集合框架提供常用的算法实现,比如排序和搜索。Collections类包含这些方法实现。大部分算法是操作List的,但一部分对所有类型的集合都是可用的。部分算法有排序、搜索、混编、最大最小值。

39.大写的O是什么?举几个例子?

大O符号描述的是就数据结构中的一系列元素而言,一个算法的性能在最坏的场景下有多么好。O也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。比如:

①ArrayList的get(index i)是一个常量时间操作,它不依赖list中元素的数量。所以它的性能是O(1)。

②一个对于数组或列表的线性搜索的性能是O(n),因为需要遍历所有的元素来查找需要的元素。

40.与Java集合框架相关的有哪些最好的实践?

根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:

①假如元素的大小是固定的, 而且能事先知道,我们就应该用Array而不是ArrayList。如果我们想根据插入顺序遍历一个Map,我们需要使用TreeMap。如果我们不想重复,应该使用Set。

②有些集合类允许指定初始容量,所以如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容(大小调整)。

③为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。

④使用JDK提供的不变类(immutable class)作为Map的键,可以避免为我们自己的类实现hashCode()和equals()方法。

⑤基于接口编程,而非基于实现编程,因为它允许后面可以更容易地改变实现。

⑥底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。

⑦尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: