【面试】集合相关-这一篇全了解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/w372426096/article/details/82936267

34、Java中的集合类有哪些?如何分类的?

35、Collection和Collections有什么区别?

解:

Collection 是一个集合接口。

它提供了对集合对象进行基本操作的通用接口方法。

Collection接口在Java 类库中有很多具体的实现。是list,set等的父接口。

Collections 是一个包装类。

它包含有各种有关集合操作的静态多态方法。

此类不能实例化,就像一个工具类,服务于Java的Collection框架。

日常开发中,不仅要了解Java中的Collection及其子类的用法,还要了解Collections用法。可以提升很多处理集合类的效率。

36、Java 中 Set 与 List 有什么不同?

解:

List,Set都是继承自Collection接口。

都是用来存储一组相同类型的元素的。

List特点:元素有放入顺序,元素可重复 。

有顺序,即先放入的元素排在前面。

Set特点:元素无放入顺序,元素不可重复。

无顺序,即先放入的元素不一定排在前面。

不可重复,即相同元素在set中只会保留一份。

所以,有些场景下,set可以用来去重。

不过需要注意的是,set在元素插入时是要有一定的方法来判断元素是否重复的。这个方法很重要,决定了set中可以保存哪些元素。

37、Set是如何保证元素不重复的?

解:

在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。

1、TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值

2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。

TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。

TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。

38、既然说了Set中的元素是不重复切无顺序的,那TreeSet中的元素是按照key的大小排序的又如何解释?

解:

Set 不保证顺序是指存入顺序与存储顺序的对应性。换句话说,Set 不保存插入的次序信息。以 TreeSet 为例,1 2 3 与 3 1 2 在存储的时候都是 1 2 3,没了插入次序信息,自然不能按插入顺序取数。List 则可以

Set中所说的无序中的顺序可以从两方面理解: 1、在Set中存入的元素和插入元素的顺序之间是否有关联。 2、能否实现通过指定的索引号获取某个位置我们想要获取的元素,比如ArrayList中的get(int index)方法。 TreeSet中的元素按照Key的大小排序是通过插入集合的元素属性通过compareTo方法进行比较来获取存储的位置的,我们并不知道插入的元素会被集合中哪个具体位置,所以TreeSet中的按照key值大小排序是从元素属性和元素类所属的compareTo方法而言的,不是相对于插入顺序而言的。

39、Java中的List有几种实现,各有什么不同?

解:

List主要有ArrayList、LinkedList与Vector几种实现。

这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。

ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.

LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.

当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.

Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。

Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.

而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.

注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

40、知道什么是synchronizedList吗?他和Vector有何区别?

解:

Vector是java.util包中的一个类。 SynchronizedList是java.util.Collections中的一个静态内部类。

在多线程的场景中可以直接使用Vector类,也可以使用Collections.synchronizedList(List list)方法来返回一个线程安全的List。

1.如果使用add方法,那么他们的扩容机制不一样。

2.SynchronizedList可以指定锁定的对象。即锁粒度是同步代码块。而Vector的锁粒度是同步方法。

3.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。

4.使用SynchronizedList的时候,进行遍历时要手动进行同步处理。

5.SynchronizedList可以指定锁定的对象。更多内容参见我的详细文章介绍:链接:SynchronizedList和Vector的区别-HollisChuang's

41、通过Array.asList获得的List有何特点,使用时应该注意什么?

1. asList 得到的只是一个 Arrays 的内部类,一个原来数组的视图 List,因此如果对它进行增删操作会报错

2. 用 ArrayList 的构造器可以将其转变成真正的 ArrayList

42、Java中的Collection如何迭代?

解:

1、传统的for循环遍历,基于计数器的:    

 遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。     

2、迭代器遍历,Iterator:     

每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取缔了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。     

3、foreach循环遍历:    

 根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码。

4、迭代器遍历:Enumeration

Enumeration 接口是Iterator迭代器的“古老版本”,从JDK 1.0开始,Enumeration接口就已经存在了(Iterator从JDK 1.2才出现)

43、Enumeration和Iterator接口的区别

解:

函数接口不同       

Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。       

Iterator只有3个函数接口。Iterator除了能读取集合的数据之外,也能数据进行删除操作。

Iterator支持fail-fast机制,而Enumeration不支持。       

Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。       

而Iterator 是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

注意:Enumeration迭代器只能遍历Vector、Hashtable这种古老的集合,因此通常不要使用它,除非在某些极端情况下,不得不使用Enumeration,否则都应该选择Iterator迭代器。

44、Iterator和ListIterator之间有什么区别?

解:

Iterator可用于遍历Set、List,ListIterator只可用于List。

Iterator只能向后遍历[next(), hasNext()],ListIterator可向前或向后遍历[previous(), hasPrevious()]。

ListIterator实现了Iterator的接口,并增加了

①add()方法,可向List添加对象

②set()方法,可修改对象

③定位索引位置,nextIndex(), previousIndex()

45、什么是fail-fast,什么是fail-safe,有什么区别吗?

解:

一:快速失败(fail—fast)         

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出ConcurrentModificationException。         

        如以下代码,会抛出ConcurrentModificationException:        

for (Student stu : students) {

          if (stu.getId() == 2)

          students.remove(stu);

          }

          原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

      注意:这里异常的抛出条件是检测到 modCount=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

      场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

相关文章:链接:Java中的增强for循环(for each)的实现原理与坑-HollisChuang's Blog   

二:安全失败(fail—safe)

      采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

      原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。

      缺点:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

      场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

46、如何在遍历的同时删除ArrayList中的元素。

解:

常见的删除有以下几种:

/**
  * 使用增强的for循环
 * 在循环过程中从List中删除非基本数据类型以后,继续循环List时会报
 *ConcurrentModificationException
  */

public void listRemove() {
    List<Student> students = this.getStudents();
    for (Student stu : students) {
        if (stu.getId() == 2)
        students.remove(stu);
    }
}
/**
 * 像这种使用增强的for循环对List进行遍历删除,但删除之后马上就跳出的也不会出现异常
*/
public void listRemoveBreak() {
    List<Student> students = this.getStudents();
    for (Student stu : students) {
        if (stu.getId() == 2) {
            students.remove(stu);
            break;
        }
    }
}

/**
* 这种不使用增强的for循环的也可以正常删除和遍历,
* 这里所谓的正常是指它不会报异常,但是删除后得到的
* 数据不一定是正确的,这主要是因为删除元素后,被删除元素后
* 的元素索引发生了变化。假设被遍历list中共有10个元素,当
* 删除了第3个元素后,第4个元素就变成了第3个元素了,第5个就变成
* 了第4个了,但是程序下一步循环到的索引是第4个,
* 这时候取到的就是原本的第5个元素了。
*/
public void listRemove2() {
    List<Student> students = this.getStudents();
    for (int i=0; i<students.size(); i++) {
        if (students.get(i).getId()%3 == 0) {
            Student student = students.get(i);
            students.remove(student);
        }
    }
}

/**
* 使用Iterator的方式可以顺利删除和遍历
*/
public void iteratorRemove() {
    List<Student> students = this.getStudents();
    System.out.println(students);
    Iterator<Student> stuIter = students.iterator();
    while (stuIter.hasNext()) {
        Student student = stuIter.next();
        if (student.getId() % 2 == 0)
            stuIter.remove();
        //这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现                        ConcurrentModificationException 
        }
        System.out.println(students);
    }

建议使用最后一种,即使用Iterator的方式在遍历过程中删除元素

47、如何对一组对象进行排序?

解:

Java.util包中的List接口继承了Collection接口,用来存放对象集合,所以对这些对象进行排序的时候,要么让对象类自己实现同类对象的比较,要么借助比较器进行比较排序。

举例:学生实体类,包含姓名和年龄属性,比较时先按姓名升序排序,如果姓名相同则按年龄升序排序。

第一种:实体类自己实现比较

(实现comparable接口:public interface Comparable<T> ,里面就一个方法声明:public int compareTo(T o); )

代码:

public class Student implements Comparable<Student>{
    private String name;
    private int age;
    @Override
    public int compareTo(Student o) {
     // TODO Auto-generated method stub
     int flag = this.name.compareTo(o.name);
     if(flag == 0) {
         flag = this.age - o.age;
     }
    return flag;
    }
}

然后利用List类的sort(Comparator<? super E> c)方法或java.util.Collections工具类的sort(List<T> list) 进行排序:

List<Student> students = new ArrayList<Student>();
students.add(new Student("a",10));
students.add(new Student("b",12));
students.add(new Student("b",11));
students.add(new Student("ac",20));
students.sort(null);
//Collections.sort(students);

第二种:借助比较器进行排序。

public class Student {
    private String name; private int age;
}

比较器java.util.Comparator类是一个接口(public interface Comparator<T> ),包含int compare(T o1, T o2);等方法。

我们的比较器要实现该接口并实现compare方法:

private class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
// TODO Auto-generated method stub
int flag = o1.getName().compareTo(o2.getName());
    if(flag == 0) {
        flag = o1.getAge() - o2.getAge();
    }
    return flag;
    }
}

比较的时候可以利用List的sort(Comparator<? super E> c)方法(或者java.util.Collections工具类的sort(List<T> list, Comparator<? super T> c)方法)进行排序。

List<Student> students = new ArrayList<Student>();
students.add(new Student("a",10));
students.add(new Student("b",12));
students.add(new Student("b",11));
students.add(new Student("ac",20));
Test t = new Test();
students.sort(t.new StudentComparator());
//Collections.sort(students, t.new StudentComparator());
for(Student student : students) {
    System.out.println(student.getName()+" "+student.getAge());
}

48、Comparable和Comparator接口有何区别?

解:

参见第047,二者都是总来对集合排序支持的接口。

Comparable用于使某个类具备可排序能力。

如047期中的Student类,实现该接口后覆盖其compareTo方法,即可具备可排序的能力。

Comparator是一个比较器接口,可以用来给不具备排序能力的对象进行排序。如047期中对不具备排序能力的Student进行排序。

49、Java中的集合使用泛型有哪些好处,

解:

Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。

集合使用泛型之后,可以达到元素类型明确的目的,避免了手动类型转换的过程,同时,也让我们更加明确容器保存的是什么类型的数据

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

解:

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

51、如何通过给定集合得到一个synchronized的集合?

解:

使用Collections.synchronizedXXX()方法,可以获得一个synchronized的集合。

52、Java中的Map主要有哪几种?之间有什么区别。

解:

Java中的Map主要有HashMap、HashTable、LinkedHashMap、ConcurrentHashMap这几种常用的。

简单的区别如下:

HashMap 是非线程安全的。

HashTable 是线程安全的。

LinkedHashMap 是 HashMap 的一个子类,它保留插入的顺序。

ConcurrentHashMap 也是线程安全的,在加锁粒度上比HashTable要细,性能会更好一些。

53、Java中遍历Map的几种方式。

解:

方法一 在for-each循环中使用entries来遍历

这是最常见的并且在大多数情况下也是最可取的遍历方式。在键值都需要时使用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

注意:for-each循环在java 5中被引入所以该方法只能应用于java 5或更高的版本中。如果你遍历的是一个空的map对象,for-each循环将抛出NullPointerException,因此在遍历前你总是应该检查空引用。

方法二 在for-each循环中遍历keys或values。

如果只需要map中的键或者值,你可以通过keySet或values来实现遍历,而不是用entrySet。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//遍历map中的键
for (Integer key : map.keySet()) {
    System.out.println("Key = " + key);
}
//遍历map中的值
for (Integer value : map.values()) {
    System.out.println("Value = " + value);
}

该方法比entrySet遍历在性能上稍好(快了10%),而且代码更加干净。

方法三使用Iterator遍历

使用泛型:

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
    while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
        System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
    }

不使用泛型:

Map map = new HashMap();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry entry = (Map.Entry) entries.next();
    Integer key = (Integer)entry.getKey();
    Integer value = (Integer)entry.getValue();
    System.out.println("Key = " + key + ", Value = " + value);
}

你也可以在keySet和values上应用同样的方法。

该种方式看起来冗余却有其优点所在。首先,在老版本java中这是惟一遍历map的方式。另一个好处是,你可以在遍历时调用iterator.remove()来删除entries,另两个方法则不能。根据javadoc的说明,如果在for-each遍历中尝试使用此方法,结果是不可预测的。

从性能方面看,该方法类同于for-each遍历(即方法二)的性能。

方法四、通过键找值遍历(效率低)

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Integer key : map.keySet()) {
     Integer value = map.get(key);
     System.out.println("Key = " + key + ", Value = " + value);
}

作为方法一的替代,这个代码看上去更加干净;但实际上它相当慢且无效率。因为从键取值是耗时的操作(与方法一相比,在不同的Map实现中该方法慢了20%~200%)。如果你安装了FindBugs,它会做出检查并警告你关于哪些是低效率的遍历。所以尽量避免使用。

总结

如果仅需要键(keys)或值(values)使用方法二。如果你使用的语言版本低于java 5,或是打算在遍历时删除entries,必须使用方法三。否则使用方法一(键值都要)。

参考资料:链接:Java中如何遍历Map对象的4种方法 - CSDN博客

54、HashMap和HashTable有何不同?

解:

线程安全:

HashTable 中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用HashTable,但是要使用HashMap的话就要自己增加同步处理了。

继承关系:

HashTable是基于陈旧的Dictionary类继承来的。HashMap继承的抽象类AbstractMap实现了Map接口。

允不允许null值:

HashTable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。 HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。

默认初始容量和扩容机制:

HashTable中的hash数组初始大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数,每次扩充为原来的2倍。原因参考链接:全网把Map中的hash()分析的最透彻的文章,别无二家。-HollisChuang's Blog

哈希值的使用不同 :

HashTable直接使用对象的hashCode。HashMap重新计算hash值。

遍历方式的内部实现上不同 :

Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 HashMap 实现 Iterator,支持fast-fail,Hashtable的 Iterator 遍历支持fast-fail,用 Enumeration 不支持 fast-fail

55、HashMap 和 ConcurrentHashMap 的区别?

解:

ConcurrentHashMap和HashMap的实现方式不一样,虽然都是使用桶数组实现的,但是还是有区别,ConcurrentHashMap对桶数组进行了分段,而HashMap并没有。

ConcurrentHashMap在每一个分段上都用锁进行了保护。HashMap没有锁机制。所以,前者线程安全的,后者不是线程安全的。PS:以上区别基于jdk1.8以前的版本。

56、同样是线程安全的Map,HashTable和ConcurrentHashMap之间有什么区别?

解:

HashTable 通过使用synchronized 加锁保证线程安全,也就是说,在进行同步操作的时候,HashTable会把整个结构都锁住。

ConcurrentHashMap使用分段锁保证线程安全, ConcurrentHashMap默认情况下将hash表分为16个桶(分片),在加锁的时候,针对每个单独的分片进行加锁,其他分片不受影响。锁的粒度更细。

它们都可以用于多线程的环境,但是当HashTable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。

因为ConcurrentHashMap引入了分片(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而HashTable则会锁定整个map。

PS:以上区别基于jdk1.8以前的版本。

57、hashCode()和equals()方法的作用,二者有什么关系?

解:

参考链接:Java中的equals()和hashcode()之间关系-HollisChuang's Blog

hashCode:

用于计算对象的hash值,计算结果为定长,返回int类型的结果;不重写默认使用Object的hashCode方法,重写后可以自定义hash函数,根据需要进行散列;

在已经实现了hashCode的类中,最后不要重写,自定义需要考虑如何散列降低hash碰撞;

equals:

比较两个对象是否相等,Object中调用hashcode方法;通过重写equals方法自定义比较的属性;在HashMap中通过equals和hashcode共同完成工作,具体表现在put和get时,如果hashcode相同,不代表是相同对象,还需要通过equals比较。

关系:

hashcode相同,equals不一定为true,两个对象不一定相等;

hashcode不同,equals一定为false,两个对象一定不相等;

equals为true,hashcode一定相同,两个对象一定相等;

equals为false,hashcode可能相同,两个对象不相等

58、HashMap和TreeMap的区别是什么?

解:

HashMap和TreeMap都是非线程安全的。也就是说,如果涉及到多线程的场景,这两者都需要开发者自己通过加锁方式避免并发访问。

HashMap和TreeMap 最大的区别就是TreeMap具备排序功能。默认情况下,存入TreeMap中的KV,会按照KEY进行升序排列。当然,开发者可以自定义比较器来改变这种默认排序行为。

在定义TreeMap的时候,可以传入一个比较器:TreeMap(Comparator<? super K> comparator)

在Java 8之前,HashMap 底层是通过Hash表实现的,Java 8以后引入了红黑树,会在一定条件下由Hash表转换成红黑树存储。这个后面专门讲。

TreeMap底层是由红黑树实现的。关于TreeMap及红黑树的详细原理可以参考:链接:TreeMap - Java 提高篇 - 极客学院Wiki

59、ConcurrentHashMap和LinkedHashMap有什么区别

60、所有的类都可以作为Map的key吗?有什么需要注意的吗?

解:

理论上,是可以使用任何类作为HashMap的Key的,因为在语法上并没有任何限制。但是,一般我们在使用自定义的类作为HashMap的参数时,需要考虑以下问题:

1、如果重写了equals方法,别忘了重写hashcode方法。

2、用户自定义的Key类最佳实践是使用不可变类型。这样,hashcode可以被缓存起来,拥有更好的性能。不可变类也可以确保hashCode和equals两个方法的结果在未来不会发生变化。

61、你了解Java的并发编程包么?并发集合类是什么?有哪些。

解:

Java1.5并发包(java.util.concurrent)包含线程安全集合类,允许在迭代时修改集合。

Java并发集合类主要包含以下几种:

1. ConcurrentHashMap

2. ConcurrentLinkedDeque

3. ConcurrentLinkedQueue

4. ConcurrentSkipListMap

5. ConcurrentSkipSet

6. CopyOnWriteArrayList

7. CopyOnWriteArraySet

62、请简答介绍下CopyOnWriteArrayList,和普通的ArrayList存在哪些区别?

解:

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

CopyOnWriteArrayList相当于线程安全的ArrayList,CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素add到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

注意:CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。也就是说add方法是线程安全的。

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景。

和ArrayList不同的是,它具有以下特性:

支持高效率并发且是线程安全的

因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大

迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等操作

使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照

63、什么是ConcurrentSkipListMap?他和ConcurrentHashMap有什么区别?

解:

ConcurrentSkipListMap是一个内部使用跳表,并且支持排序和并发的一个Map,是线程安全的。一般很少会被用到,也是一个比较偏门的数据结构。

简单介绍下跳表(

跳表是一种允许在一个有顺序的序列中进行快速查询的数据结构。 在普通的顺序链表中查询一个元素,需要从链表头部开始一个一个节点进行遍历,然后找到节点。如图1。

跳表可以解决这种查询时间过长,其元素遍历的图示如图2,跳表是一种使用”空间换时间”的概念用来提高查询效率的链表。

);

ConcurrentSkipListMap 和 ConcurrentHashMap 的主要区别:

a.底层实现方式不同。ConcurrentSkipListMap底层基于跳表。ConcurrentHashMap底层基于Hash桶和红黑树。b.ConcurrentHashMap不支持排序。ConcurrentSkipListMap支持排序。

猜你喜欢

转载自blog.csdn.net/w372426096/article/details/82936267
今日推荐