面试准备系列——Java基础技术篇(9)/Java集合

1.(重点)Java Collections框架介绍一下

Java Collections框架中包含了大量集合接口以及这些接口的实现类和操作它们的算法(例如排序、查找、反转、替换、复制、取最小元素、取最大元素等),具体而言,主要提供了List (列表)、Queue (队列)、Set (集合)、Stack (栈)和Map ( 映射表,用于存放键值对)等数据结构。其中,List、 Queue、Set、 Stack 都继承自Collection 接口。
Collection的框架类图如图所示:
在这里插入图片描述

Collection是整个集合框架的基础,它里面储存一组对象,表示不同类型的Collections,它的作用只是提供维护一组对象的基本接口而已。

下面分别介绍 Set、List和Map 3个接口。

1)Set表示数学意义上的集合概念。其最主要的特点是集合中的元素不能重复,因此存入Set的每个元素都必须定义equals( )方法来确保对象的唯一性。该接口有两个实现类: HashSet和TreeSet。其中TreeSet实现了SortedSet 接口,因此TreeSet容器中的元素是有序的。

2)List又称为有序的Collection。它按对象进入的顺序保存对象,所以它能对列表中的每个元素的插人和删除位置进行精确的控制。同时,它可以保存重复的对象。LinkedList、 ArrayList和Vector都实现了List 接口。

3)Map提供了一个从键映射到值的数据结构。它用于保存键值对,其中值可以重复,但键是唯一的,不能重复。Java 类库中有多个实现该接口的类: HashMap、TreeMap、 LinkedHashMap、WeakHashMap和IdentityHashMap。虽然它们都实现了相同的接口,但执行效率却不是完全相同的。具体而言,HashMap 是基于散列表实现的,采用对象的HashCode可以进行快速查询。LinkedHashMap采用列表来维护内部的顺序。TreeMap基于红黑树的数据结构来实现的,内部元素是按需排列的。

常见题型:
在这里插入图片描述

2.什么是迭代器(重点关注一边遍历一遍删除问题)

迭代器(Iterator) 是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问一个容器( container)对象中的各个元素,而又不必暴露该对象内部细节的方法。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。

迭代器的使用主要有以下3个方面的注意事项:

  • 1)使用容器的iterator()方法返回一个Iterator, 然后通过Iterator 的next( )方法返回第一个元素。
  • 2)使用Iterator的hasNext( )方法判断容器中是否还有元素,如果有,可以使用next( )方法获取下一个元素。
  • 3)可以通过remove()方法删除迭代器返回的元素。

Iterator支持派生的兄弟成员。ListIterator 只存在于List 中,支持在迭代期间向List 中添加或删除元素,并且可以在List中双向滚动。

Iterator的使用方法如下例所示:
在这里插入图片描述
注意:使用集合经常会碰到一个问题就是,有时候想要一边遍历集合,一边删除集合中的一些元素(或者添加一些元素也会出现同样问题的),那么这种时候就需要使用另外的一个集合保存需要删除的元素,然后后边再使用remove方法删除。如:

		List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        //注意保存删除元素中的集合后边如果使用removeAll方法删除的话,
        // 那么删除集合中就只能保存要删除的元素而非该元素的下标了
        List<String> rmList = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            if (list.get(i).equals("2")) {
                rmList.add(list.get(i));
            }
        }
        list.removeAll(rmList);

下边详细说明:

在使用iterator( )方法时经常会遇到ConcurrentModificationException异常,这通常是由于在使用Iterator遍历容器的同时又对容器做增加或删除操作所导致的,或者由于多线程操作导致,当一个线程使用迭代器遍历容器的同时,另外一个线程对这个容器进行增加或删除操作。下例主要介绍单线程抛出ConcurrentModificationException 的情况:
在这里插入图片描述
抛出上述异常的主要原因是当调用容器的iterator()方法返回Iterator对象时,把容器中包含对象的个数赋值给了一个变量expectedModCount,在调用next( )方法时会比较变量expectedModCount与容器中实际对象的个数modCount的值是否相等,若二者不相等,则会抛出ConcurrentModificationException异常,因此在使用Iterator遍历容器的过程中,如果对容器进行增加或删除操作,就会改变容器中对象的数量,从而导致抛出异常

解决方法如下:在遍历的过程中把需要删除的对象保存到一个集合中,等遍历结束后在调用removeAll()方法来删除,或者使用iter. remove( )方法。

以上主要介绍了单线程的解决方案,那么多线程访问容器的过程中抛出ConcurrentModificationException异常又该怎么解决呢?

1)在JDK 1.5版本引人了线程安全的容器,比如ConcurrentHashMap和CopyOnWriteArrayList等。可以使用这些线程安全的容器来代替非线程安全的容器。
2)在使用迭代器遍历容器时对容器的操作放到synchronized代码块中,但是当引用程序并发程度比较高时,这会严重影响程序的性能。

引申: Iterator 与ListIterator有什么区别?
Iterator只能正向遍历集合,适用于获取移除元素。Listlerator 继承自Iterator, 专门针对List,可以从两个方向来遍历List,同时支持元素的修改。

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

ArrayList、Vector 、LinkedList 类均在java. util 包中,均为可伸缩数组,即可以动态改变长度的数组

ArrayList和Vector都是基于存储元素的Object[ ] array 来实现的,它们会在内存中开辟一块连续的空间来存储,由于数据存储是连续的,因此,它们支持用序号(下标)来访问元素,同时索引数据的速度比较快。但是在插入元素时需要移动容器中的元素,所以对数据的插人操作执行得比较慢。ArrayList 和Vector都有一个初始化的容量的大小,当里面存储的元素超过这个大小时就需要动态地扩充它们的存储空间。为了提高程序的效率,每次扩充容量,不是简单地扩充一个存储单元,而是一次增 加多个存储单元。Vector 默认扩充为原来的2倍(每次扩充空间的大小是可以设置的),而ArrayList默认扩充为原来的1.5倍(没有提供方法来设置空间扩充的方法)。

ArrayList与Vector最大的区别就是synchronization ( 同步) 的使用,没有一个ArrayList 的方法是同步的,而Vector的绝大多数方法( 例如add、insert、 remove、 set 、equals、 hashcode等)都是直接或者间接同步的,所以Vector是线程安全的,ArrayList 不是线程安全的。正是由于Vector 提供了线程安全的机制,其性能上也要略逊于ArrayList。

LinkedList是采用双向列表来实现的,对数据的索引需要从列表头开始遍历,因此用于随机访问则效率比较低,但是插入元素时不需要对数据进行移动,因此插入效率较高。同时,LinkedList是非线程安全的容器。

那么,在实际使用时,如何从这几种容器中选择合适的使用呢?

  • 当对数据的主要操作为索引或只在集合的末端增加、删除元素时,使用ArrayList 或Vector效率比较高;
  • 当对数据的操作主要为指定位置插入或删除操作时,使用LinkedList 效率比较高;
  • 当在多线程中使用容器时(即多个线程会同时访问该容器),选用Vector较为安全。

常见题型:
在这里插入图片描述

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

Java为数据结构中的映射定义了一个接口java. util. Map,它包括3个实现类: HashMap、HashTable和TreeMap。Map是用来存储键值对的数据结构,在数组中通过数组下标来对其内容索引的,而在Map中,则是通过对象来进行索引,用来索引的对象叫做key,,其对应的对象叫做value。

HashMap是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。由于HashMap与HashTable都采用了hash法进行索引,因此二者具有许多相似之处,它们主要有如下的一些区别:

  • 1)HashMap 是HashTable的轻量级实现( 非线程安全的实现),它们都完成了Map接口,主要区别在于HashMap允许空(null) 键值(key)(但需要注意,最多只允许一条记录的键为null,不允许多条记录的值为null),而HashTable不允许。
  • 2)HashMap 把HashTable的contains 方法去掉了,改成containsvalue和containsKey,因为contains方法容易让人引起误解。HashTable 继承自Dictionary 类,而HashMap是Java 1.2引进的Map interface的一个实现
  • 3)HashTable 的方法是线程安全的,而HashMap不支持线程的同步,所以它不是线程安全的。在多个线程访问HashTable时,不需要开发人员对它进行同步,而对于HashMap,开发人员必须提供额外的同步机制。所以,就效率而言,HashMap 可能高于HashTable。
  • 4)HashTable使用Enumeration, HashMap使用Iterator。
  • 5)HashTable和HashMap 采用的hash/ rehash 算法都几乎一样,所以性能不会有很大的差异。
  • 6)在HashTable中,hash数组默认大小是11,增加的方式是oldx2 +1。在HashMap中,hash数组的默认大小是16,而且一定是2的指数。
  • 7)hash值的使用不同,HashTable 直接使用对象的hashCode。

以上3种类型中,使用最多的是HashMap。HashMap里面存入的键值对在取出时没有固定的顺序,是随机的。一般而言,在Map中插入、删除和定位元素,HashMap是最好的选择。由于TreeMap实现了SortMap接口,能够把它保存的记录根据键排序,因此,取出来的是排序后的键值对,如果需要按自然顺序或自定义顺序遍历键,那么TreeMap会更好LinkedHashMap是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列。

WeakHashMap与HashMap类似,二者的不同之处在于WeakHashMap中key采用的是“弱引用”的方式,只要WeakHashMap中的key不再被外部引用,它就可以被垃圾回收器回收。而HashMap中key采用的是“ 强引用的方式”,当HashMap中的key没有被外部引用时,只有在这个key从HashMap中删除后,才可以被垃圾回收器回收。

常见题型:
在这里插入图片描述

5.(重点)用自定义类作为HashMap或HashTable的key需要注意哪些问题

HashMap与HashTable是用来存放键值对的一种容器, 在使用这两个容器时有一个限制:不能用来存储重复的键。也就是说,每个键只能唯一映射 一个值,当有重复的键时,不会创建新的映射关系,而会使用先前的value值。为了更好地说明这个问题,我们首先来看一段示例代码:
在这里插入图片描述

从上面的例子可以看出,首先向HashMap中添加< " aa", “bbb” >,接着添加< " aa", “cc”>的时候由于与前面已经添加的数据有相同的key:" aa",因此会用新的值" cc"替换" bbb"。式但当用自定义的类的对象作为HashMap的key时,有时候会给人造成-种假象-一-key是可以重复的,示例如下:
在这里插入图片描述
从表面上看,向HashMap中添加的两个键值对的key值是相同的,可是为什么在后面添加的键值对没有覆盖前面的value呢?
为了说明这个问题,下面首先介绍HashMap添加元素的操作过程。具体而言,在向HashMap中添加键值对<key, value>时, 需要经过如下几个步骤:
首先,调用key的hashCode( )方法生成一个hash值h1,如果这个h1在HashMap中不存在,那么直接将< key , value >添加到HashMap中;如果这个h1已经存在,那么找出HashMap中所有hash值为h1的key,然后分别调用key的equals()方法判断当前添加的key值是否与已经存在的key值相同。如果equals()方法返回true,说明当前需要添加的key已经存在,那么HashMap会使用新的value值来覆盖掉旧的value值;如果equals()方法返回false,说明新增加的key在HashMap中不存在,因此会在HashMap中创建新的映射关系。当新增加的key的hash值已经在HashMap中存在时,就会产生冲突。一般而言, 对于不同的key值可能会得到相同的hash值,因此就需要对冲突进行处理。一般而言,处理冲突的方法有开放地址法、再hash法、链地址法等。HashMap 使用的是链地址法来解决冲突,具体操作方法如图所示。
在这里插入图片描述

向HashMap中添加元素时,若有冲突产生,其实现方式如下图所示。
在这里插入图片描述

从HashMap中通过key查找value时, 首先调用的是key的hashCode()方法来获取到key对应的hash值h,这样就可以确定键为key的所有值存储的首地址 。如果h对应的key值有多个,那么程序接着会遍历所有key,通过调用key的equals( )方法来判断key的内容是否相等。只有当equals()方法的返回值为true时,对应的value才是正确的结果。在上例中,由于使用自定义的类作为HashMap的key,而没有重写hashCode()方法和equals( )方法,默认使用的是Object类的hashCode( )方法和equals( )方法。Object类的equals()方法的比较规则如下:当参数obj引用的对象与当前对象为同一个对象时,就返回true,否则返回false。hashCode( )方法会返回对象存储的内存地址。由于在上例中创建了两个对象,虽然它们拥有相同的内容,但是存储在内存中不同的地址,因此在向HashMap中添加对象时,调用equals( )方法的返回值为false, HashMap会认为它们是两个不同的对象,就会分别创建不同的映射关系,因此为了实现在向HashMap中添加键值对,可以根据对象的内容来判断两个对象是否相等,这就需要重写hashCode( )方法和equals( )方法,示例如下:

在这里插入图片描述
由此可以看出,开发者在使用自定义类作为HashMap的key时,需要注意以下几个问题:

  • 1)如果想根据对象的相关属性来自定义对象是否相等的逻辑,此时就需要重写equals()方法,一旦重写了equals()方法, 那么就必须重写hashCode( )方法。
  • 2)当自定义类的多项作为HashMap(HashTable)的key时,最好把这个类设计为不可变类。
  • 3)从HashMap的工作原理可以看出,如果两个对象相等,那么这两个对象有着相同的hashCode,反之则不成立。

6.Collection和Collections有什么区别

Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。实现该接口的类主要有List和Set,该接口的设计目标是为各种具体的集合提供最大化的统一的操作方式。

Collections是针对集合类的一个包装类,它提供一系列静态方法以实现对各种集合的搜索、排序、线程安全化等操作,其中大多数方法都是用来处理线性表。Collections 类不能实例化,如同一个工具类,服务于Collection框架。若在使用Collections类的方法时,对应的colleetion的对象为null,则这些方法都会抛出NullPointerException。

使用Collections的示例如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值