JavaSE(7续a)面试

5. List 的三个子类的特点
ArrayList 底层结构是数组,底层查询快,增删慢。
LinkedList 底层结构是链表型的,增删快,查询慢。
voctor 底层结构是数组 线程安全的,增删慢,查询慢。

6. List 和 Map、Set 的区别****

6.1 结构特点
List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并
且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无
序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的);
6.2 实现类
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。
Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null键;HashTable:线程安全,低效,不支持 null 值和 null 键;LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序;SortMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。
Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)。
6.3 区别
List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过
list.get(i)方法来获取集合中的元素;Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现Java.util.Comparator接口来自定义排序方式。
7. HashMap 和 HashTable 有什么区别?
HashMap 是线程不安全的,HashMap 是一个接口,是 Map 的一个子接口,是将键映射到值得对象,不允许键值重复,允许空键和空值;由于非线程安全,HashMap 的效率要较 HashTable 的效率高一些.HashTable 是线程安全的一个集合,不允许 null 值作为一个 key 值或者 Value 值;
HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步;
8. 数组和链表分别比较适合用于什么场景,为什么?
8.1 数组和链表简介
在计算机中要对给定的数据集进行若干处理,首要任务是把数据集的一部分(当数据量非常大时,可能只能一部分一部分地读取数据到内存中来处理)或全部存储到内存中,然后再对内存中的数据进行各种处理。
例如,对于数据集 S{1,2,3,4,5,6},要求 S 中元素的和,首先要把数据存储到内存中,然后再将内存中的数据相加。当内存空间中有足够大的连续空间时,可以把数据连续的存放在内存中,各种编程语言中的数组一般都是按这种方式存储的(也可能有例外),如图 1(b);当内存中只有一些离散的可用空间时,想连续存储数据就非常困难了,这时能想到的一种解决方式是移动内存中的数据,把离散的空间聚集成连续的一块大空间,如图 1(c)所示,这样做当然也可以,但是这种情况因为可能要移动别人的数据,所以会存在一些困难,移动的过程中也有可能会把一些别人的重要数据给丢失。另外一种,不影响别人的数据存储方式是把数据集中的数据分开离散地存储到这些不连续空间中,如图(d)。这时为了能把数据集中的所有数据联系起来,需要在前一块数据的存储空间中记录下一块数据的地址,这样只要知道第一块内存空间的地址就能环环相扣地把数据集整体联系在一起了。C/C++中用指针实现的链表就是这种存储形式。
在这里插入图片描述
由上可知,内存中的存储形式可以分为连续存储和离散存储两种。因此,数据的物理存储结构就有连续存储和离散存储两种,它们对应了我们通常所说的数组和链表,
8.2 数组和链表的区别
数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)
8.3 链表和数组使用场景
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。
链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。
8.4 跟数组相关的面试题
用面向对象的方法求出数组中重复 value 的个数,按如下个数输出:
1 出现:1 次 3 出现:2 次 8 出现:3 次 2 出现:4 次
int[] arr = {1,4,1,4,2,5,4,5,8,7,8,77,88,5,4,9,6,2,4,1,5};
9. Java 中 ArrayList 和 Linkedlist 区别?
ArrayList 和 Vector 使用了数组的实现,可以认为 ArrayList 或者 Vector 封装了对内部数组的操作,比如向数组中添加,删除,插入新的元素或者数据的扩展和重定向。
LinkedList 使用了循环双向链表数据结构。与基于数组的 ArrayList 相比,这是两种截然不同的实现技术,这也决定了它们将适用于完全不同的工作场景。
LinkedList 链表由一系列表项连接而成。一个表项总是包含 3 个部分:元素内容,前驱表和后驱表,如图所示:
在这里插入图片描述
在下图展示了一个包含 3 个元素的 LinkedList 的各个表项间的连接关系。在 JDK 的实现中,无论 LikedList 是否为空,链表内部都有一个 header 表项,它既表示链表的开始,也表示链表的结尾。表项 header 的后驱表项便是链表中第一个元素,表项 header 的前驱表项便是链表中最后一个元素。
在这里插入图片描述
10. List a=new ArrayList()和 ArrayList a =new ArrayList()的区别?
List list = new ArrayList();这句创建了一个 ArrayList 的对象后把上溯到了 List。此时它是一个 List 对象了,有些ArrayList 有但是 List 没有的属性和方法,它就不能再用了。而 ArrayList list=new ArrayList();创建一对象则保留了ArrayList 的所有属性。 所以需要用到 ArrayList 独有的方法的时候不能用前者。实例代码如下:
1.List list = new ArrayList();
2.ArrayList arrayList = new ArrayList();
3.list.trimToSize(); //错误,没有该方法。
4.arrayList.trimToSize(); //ArrayList 里有该方法。
11. 要对集合更新操作时,ArrayList 和 LinkedList 哪个更适合
1.ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
2.如果集合数据是对于集合随机访问 get 和 set,ArrayList 绝对优于 LinkedList,因为 LinkedList 要移动指针。
3.如果集合数据是对于集合新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。
ArrayList 和 LinkedList 是两个集合类,用于存储一系列的对象引用(references)。例如我们可以用 ArrayList 来存储一系列的 String 或者 Integer。那 么 ArrayList 和 LinkedList 在性能上有什么差别呢?什么时候应该用 ArrayList什么时候又该用 LinkedList 呢?
一.时间复杂度
首先一点关键的是,ArrayList 的内部实现是基于基础的对象数组的,因此,它使用 get 方法访问列表中的任意一个元素时(random access),它的速度要比 LinkedList 快。LinkedList 中的 get 方法是按照顺序从列表的一端开始检查,直到另外一端。对 LinkedList 而言,访问列表中的某个指定元素没有更快的方法了。
假设我们有一个很大的列表,它里面的元素已经排好序了,这个列表可能是 ArrayList 类型的也可能是 LinkedList类型的,现在我们对这个列表来进行二分查找(binary search),比较列表是 ArrayList 和 LinkedList 时的查询速度,
看下面的程序:

1.public class TestList{
2. public static final int N=50000; //50000 个数
3. public static List values; //要查找的集合
4. //放入 50000 个数给 value; 5. static{
6. Integer vals[]=new Integer[N];
7. Random r=new Random();
8. for(int i=0,currval=0;i<N;i++)...{
9. vals=new Integer(currval);
10. currval+=r.nextInt(100)+1;
11. }
12. values=Arrays.asList(vals);
13. }
14. //通过二分查找法查找
15. static long timeList(List lst){
16. long start=System.currentTimeMillis();
17. for(int i=0;i<N;i++)...{
18. int index=Collections.binarySearch(lst, values.get(i));
19. if(index!=i)
20. System.out.println("***错误***");
21. }
22. return System.currentTimeMillis()-start;
23. }
24. public static void main(String args[])...{
25. System.out.println("ArrayList 消耗时间:"+timeList(new ArrayList(values)));
26. System.out.println("LinkedList 消耗时间:"+timeList(new LinkedList(values)));
27. }
28.}

得到的输出是:
12. ArrayList 消耗时间:15
13. LinkedList 消耗时间:2596
这个结果不是固定的,但是基本上 ArrayList 的时间要明显小于 LinkedList 的时间。因此在这种情况下不宜用LinkedList。二分查找法使用的随机访问(random access)策略,而 LinkedList 是不支持快速的随机访问的。对一个LinkedList 做随机访问所消耗的时间与这个 list 的大小是成比例的。而相应的,在 ArrayList 中进行随机访问所消耗的时间是固定的。
这是否表明 ArrayList 总是比 LinkedList 性能要好呢?这并不一定,在某些情况下 LinkedList 的表现要优于ArrayList,有些算法在 LinkedList 中实现 时效率更高。比方说,利用Collections.reverse 方法对列表进行反转时,其性能就要好些。看这样一个例子,加入我们有一个列表,要对其进行大量的插入和删除操作,在这种情况下 LinkedList就是一个较好的选择。请看如下一个极端的例子,我们重复的在一个列表的开端插入一个元素:

1.import java.util.*;
2.public class ListDemo { 3. static final int N=50000;
4. static long timeList(List list){
5. long start=System.currentTimeMillis();
6. Object o = new Object();
7. for(int i=0;i<N;i++)
8. list.add(0, o);
9. return System.currentTimeMillis()-start;
10. }
11. public static void main(String[] args) {
12. System.out.println("ArrayList 耗时:"+timeList(new ArrayList()));
13. System.out.println("LinkedList 耗时:"+timeList(new LinkedList()));
14. }
15.} 

这时我的输出结果是
14. ArrayList 耗时:2463
15. LinkedList 耗时:15
二.空间复杂度
在 LinkedList 中有一个私有的内部类,定义如下:

1.private static class Entry { 2. Object element;
3. Entry next;
4. Entry previous;
5. }

每个 Entry 对象 reference 列表 中的一个元素,同时还有在 LinkedList 中它的上一个元素和下一个元素。一个有 1000 个元素的 LinkedList 对象将有 1000 个链接在一起 的 Entry 对象,每个对象都对应于列表中的一个元素。这样的话,在一个 LinkedList 结构中将有一个很大的空间开销,因为它要存储这 1000 个 Entity 对象的相关信息。
ArrayList 使用一个内置的数组来存 储元素,这个数组的起始容量是 10.当数组需要增长时,新的容量按如下公式获得:新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增长 50%。 这就意味着,如果你有一个包含大量元素的 ArrayList 对象,那么最终将有很大的空间会被浪费掉,这个浪费是由 ArrayList 的工作方式本身造成 的。如果没有足够的空间来存放新的元素,数组将不得不被重新进行分配以便能够增加新的元素。对数组进行重新分配,将会导致性能急剧下降。如果我们知道一个 ArrayList 将会有多少个元素,我们可以通过构造方法来指定容量。我们还可以通过 trimToSize 方法在 ArrayList 分配完毕之后去掉浪 费掉的空间。
三.总结
ArrayList 和 LinkedList 在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:

1.对 ArrayList 和 LinkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList 而言,主
要是在内部数组中增加一项,指向所添加的元素,偶 尔可能会导致对数组重新进行分配;而对 LinkedList 而言,这
个开销是统一的,分配一个内部 Entry 对象。 2.在 ArrayList
的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在 LinkedList 的中间 插入或删除一个元素的开销是固定的。
3.LinkedList 不支持高效的随机元素访问。 4.ArrayList 的空间浪费主要体现在在 list
列表的结尾预留一定的容量空间,而LinkedList
的空间花费则体现在它的每一个元素都需要消耗相当的空间可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList
会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用 LinkedList 了。

16. 请用两个队列模拟堆栈结构
两个队列模拟一个堆栈,队列是先进先出,而堆栈是先进后出。模拟如下
队列 a 和 b (1)入栈:a 队列为空,b 为空。例:则将”a,b,c,d,e”需要入栈的元素先放 a 中,a 进栈为”a,b,c,d,e” (2)出栈:a 队列目前的元素为”a,b,c,d,e”。将 a 队列依次加入 Arraylist 集合 a 中。以倒序的方法,将 a 中的集合取出,放入 b 队列中,再将 b 队列出列。代码如下:

1. public static void main(String[] args) {
2. Queue<String> queue = new LinkedList<String>(); //a 队列
3. Queue<String> queue2=new LinkedList<String>(); //b 队列
4. ArrayList<String> a=new ArrayList<String>(); //arrylist 集合是中间参数
5. //往 a 队列添加元素
6. queue.offer("a"); 
7. queue.offer("b");
8. queue.offer("c");
9. queue.offer("d");
10. queue.offer("e");
11. System.out.print("进栈:");
12. //a 队列依次加入 list 集合之中
13. for(String q : queue){
14. a.add(q);
15. System.out.print(q); 
16. }
17. //以倒序的方法取出(a 队列依次加入 list 集合)之中的值,加入 b 对列
18. for(int i=a.size()-1;i>=0;i--){
19. queue2.offer(a.get(i)); 
20. } 
21. //打印出栈队列
22. System.out.println("");
23. System.out.print("出栈:");
24. for(String q : queue2){ 
25. System.out.print(q);
26. } 
27. }

打印结果为(遵循栈模式先进后出):
进栈:a b c d e
出栈:e d c b a
17. Collection 和 Map 的集成体系
Collection:在这里插入图片描述
Map:在这里插入图片描述
18. Map 中的 key 和 value 可以为 null 么?
HashMap 对象的 key、value 值均可为 null。
HahTable 对象的 key、value 值均不可为 null。
且两者的的 key 值均不能重复,若添加 key 相同的键值对,后面的 value 会自动覆盖前面的 value,但不会报错。
测试代码如下:

19. public class Test { 
2.
20. public static void main(String[] args) { 
21. Map<String, String> map = new HashMap<String, String>();//HashMap 对象
22. Map<String, String> tableMap = new Hashtable<String, String>();//HashTable 对象
6.
23. map.put(null, null); 
24. System.out.println("hashMap 的[key]和[value]均可以为 null:" + map.get(null)); 
9.
25. try { 
26. tableMap.put(null, "3"); 
27. System.out.println(tableMap.get(null)); 
28. } catch (Exception e) { 
29. System.out.println("【ERROR】:hashTable 的[key]不能为 null"); 
30. } 
16.
31. try { 
32. tableMap.put("3", null);
  System.out.println(tableMap.get("3")); 
34. } catch (Exception e) { 
35. System.out.println("【ERROR】:hashTable 的[value]不能为 null"); 
36. } 
37. } 
38. }
运行结果:
hashMap 的[key]和[value]均可以为 null:null
【ERROR】:hashTable 的[key]不能为 null
【ERROR】:hashTable 的[value]不能为 null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值