面试总结1—集合问题
文章目录
1.基本概念
集合主要的功能是用来保存对象的,不同的集合类有不同的特性。比如LIST存储的对像是有序且可重复的,Set是无序且不可重复的。因为集合是对数组做的封装,所以,数组永远比任何一个集合要快。
JAVA将集合类划分两大类:
- Collection接口:一个独立元素的序列,这些元素服从一些规则。List,Set,Queue都继承了这个接口。
- Map接口:一组成对的“键值对”对象,允许通过键来找值。
问题1:Collection接口和Collections类的区别:
Collection是集合类的根接口,Collection 表示一组对象,这些对象也称为 collection 的元素。定义在不同Collection的子接口(List,Set,Queue)的元素有不同的规则。
Collections是集合类的工具类,里面定义了一些静态方法用来操作集合。他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作 。就好像Arrays类是用来操作数组的一样。
通过一个add例子来查看
public class AddingGroups {
public static void main(String args[]){
Collection<Integer> collection =new ArrayList<Integer> (Arrays.asList(1,2,3,4,5));
Integer[] modiy={6,7,8,9};
collection.addAll(Arrays.asList(modiy));
Collections.addAll(collection,10,11,12,13);//比Collection.addAll()快
Collections.addAll(collection, modiy);
List<Integer> list=Arrays.asList(14,15,16,17);
/*
* 用Arrays.asList做输出的话 因为其底层表示的是数组,因此不能改变大小
* list.add(1,99)抛错!
* 可以通过new ArrayList(Arrays.asList(14,15,16,17));这样就能够改变list结构
*/
list.set(1, 99);
for(Integer i:collection)
System.out.println(i);
for(Integer ii:list)
System.out.println(ii);
}
}
/*
Collections.addAll(Collection<? super T> c,T... element)
java.util.Collecton.addAll(Collection<? extends E> c)
通过上面可以看出Collection只能添加一个Collection对象 Collections.addAll()操作的参数是可变的T,
官方API中写道:将所有指定元素添加到指定 collection 中。可以分别指定要添加的元素,或者将它们指定为一个数组。此便捷方法的行为与 c.addAll(Arrays.asList(elements)) 的行为是相同的,但在大多数实现下,此方法运行起来可能要快得多。
*/
2.List接口(有序可重复)
public interface List<E> extends Collection<E> ,Iterable<E>
从List接口的定义中我们可以看出继承自Collection接口和Iterable接口(迭代器)
有两种类型的List:
- ArrayList:基于数组实现
- LinkedList:双向循环链表的数据结构
- Vector:现在基本不用了
问题2:ArrayList和LinkedList的区别
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
相同点:都不是线程安全的。
问题3:ArrayList和Vector的区别(是否有序、是否重复、数据结构、底层实现)
ArrayList和Vector都实现了List接口,他们都是有序集合,并且存放的元素是允许重复的。它们的底层都是通过数组来实现的,因此列表这种数据结构检索数据速度快,但增删改速度慢。
而ArrayList和Vector的区别主要在两个方面:
第一,线程安全。Vector是线程安全的,而ArrayList是线程不安全的。因此在如果集合数据只有单线程访问,那么使用ArrayList可以提高效率。而如果有多线程访问你的集合数据,那么就必须要用Vector,因为要保证数据安全。
第二,数据增长。ArrayList和Vector都有一个初始的容量大小10,当存储进它们里面的元素超过了容量时,就需要增加它们的存储容量。ArrayList每次增长原来的0.5倍,而Vector增长原来的一倍。ArrayList和Vector都可以设置初始空间的大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法
问题4:如何遍历List集合
方式一:使用迭代器
由List的继承可知List继承了Iterable接口,在API中说到:实现这个接口允许对象成为 “foreach” 语句的目标。java.lang.Iterable
其中的方法:
Iterator<T> iterator()
返回一个在一组 T 类型的元素上进行迭代的迭代器。
java.util.Iterator :对 collection 进行迭代的迭代器。迭代器是一个对象,他的工作是遍历并选择序列中的对象。
其中有三个方法:
boolean hasNext():如果仍有元素可以迭代,则返回 true。
E next() :返回迭代的下一个元素。
void remove():从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
使用迭代器遍历:
List<String> list=new ArrayList<String>(Arrays.asList("a","b","c"));
Iterator<String> it=list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//iterator有个子接口ListIterator比Iterator接口更加强大可以双向移动,但是只能用来遍历List集合。
这里注意一个问题如果我们想遍历的时候将list集合中的元素删除,只能通过迭代器的方式,其他方式都不可以会报错!
List<String> list=new ArrayList<String>(Arrays.asList("a","b","c"));
Iterator<String> it=list.iterator();
while(it.hasNext()){
it.next();
it.remove();
}
方式二:foreach语句(只有实现了Iterable接口才能使用)
List<String> list=new ArrayList<String>(Arrays.asList("a","b","c"));
for(String s:list){
System.out.println(s);
}
方式三:使用for循环
List<String> list=new ArrayList<String>(Arrays.asList("a","b","c"));
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
3.Set集合(无序且不可重复)
Set里面不允许有重复的元素,所谓重复,即不能有两个相等(注意,不是仅仅是相同)的对象 ,即假设Set集合中有了一个A对象,现在我要向Set集合再存入一个B对象,但B对象与A对象equals相等,则B对象存储不进去。所以,Set集合的add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true,当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。Set取元素时,没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。
有两种类型的Set:
- HashSet:采用hash表(实际上是一个HashMap实例)实现
- TreeSet:将元素存储在红—黑树数据结构中
- LinkedHashSet (extends HashSet):也是一个Hash表,但同时维护了一个双链表来维护插入顺序。(插入顺序与读取顺序一致)
问题5:HashSet、TreeSet、LinkedHashSet的区别
HashSet是采用hash表来实现的。其中的元素没有按顺序排列,add()、remove()以及contains()等方法都是复杂度为O(1)的方法,存储速度快。不是同步的,集合中的元素可以为null,但只能有一个。
TreeSet是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是add()、remove()以及contains()等方法都是复杂度为O(log (n))的方法。****
LinkedHashSet介于HashSet和TreeSet之间。它也是一个hash表,但是同时维护了一个双链表来记录插入的顺序。基本方法的复杂度为O(1)。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
有关于区别:https://www.cnblogs.com/wl0000-03/p/6019627.html这篇文章很详细。
问题6:遍历Set
和遍历List一样三种方式!
4.Map集合(键值对)
Map与List和Set不同,它是双列的集合,其中有put方法,定义如下:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。取则可以根据key获得相应的value,get(Object key)返回值为key 所对应的value。另外也可以获得所有的key的集合map.keySet(),还可以获得所有的value的结合(map.values()),还可以获得key和value组合成的Map.Entry对象的集合(map.entrySet())。
有两种类型的Map:
- HashMap:基于哈希表的Map接口的实现。
- TreeMap:基于红黑树的Map实现。
- LinekedHashMap extends HashMap:Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。
问题7:HashMap、TreeMap、LinkedHashMap、HashTable的区别
(1)HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为null,不允许多条记录的值为null。HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要同步,可以用Collections.synchronizedMap(HashMap map)方法使HashMap具有同步的能力。
(2)Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,然而,这也导致了Hashtable在写入时会比较慢。
(3)LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的。在遍历的时候会比HashMap慢。有HashMap的全部特性。
(4)TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iteraor遍历TreeMap时,得到的记录是排过序的。TreeMap的键和值都不能为空。
问题8:如何遍历Map集合
public class MapOfList {
public static Map<Integer, List<? extends String>> map=new HashMap<Integer,List<? extends String>>();
static{
map.put(1, Arrays.asList("1a","1b","1c"));//这里的值是List类型的容器。
map.put(2, Arrays.asList("2d","2e","2f"));
map.put(3, Arrays.asList("3g","3h","3i"));
map.put(4, Arrays.asList("4j","4k","4l"));
}
public static void main(String args[]){
//方式一:在for-each循环中遍历keys或values。
for(Integer in:map.keySet()){
System.out.println(in+"Integer has:");
for(String str:map.get(in))
System.out.println(" "+str);
}
//方式二:foreach常见方法
for(Map.Entry<Integer,List<? extends String>> ent:map.entrySet()) {
System.out.println("key:"+ent.getKey()+"-values:"+ent.getValue());
}
//方式三:迭代器
Set hs=map.entrySet();
Iterator it=hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//方式四:通过键查找值(效率低)
for (Integer key : map.keySet()) {
List<? extends String> value = map.get(key);
System.out.println("Key = " + key + ", Value = " + value);
}
}
}
注意:在Map集合中允许值对象为null,因此查看是否存在键对象应该用cotainsKey而不是get方法。