集合

原文出自:https://zhuanlan.zhihu.com/p/267545042

1、常见集合

 

线程安全:

Vector、HashTable

线程不安全:

HashMap、TreeMap、HashSet、ArrayList、LinkedList

 

2、 Arraylist与 LinkedList 区别

  1. Arraylist 底层是数组;LinkedList 底层是双向循环链表数据结构;(后面的区别也是因为底层数据结构不同引发的

  2. ArrayList查询快,增删慢;LinkedList查询慢,增删快

  3. ArrayList的空间浪费主要体现在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在存放直接后继和直接前驱以及数据)。

 

3、 HashMap 和 Hashtable 的区别:

  1. 同步性:Hashtable 的方法是 Synchronize 的,线程安全;而 HashMap 是线程不安全的,不是同步的。所以只有一个线程的时候使用hashMap效率要高。(线程安全性也附带着效率的问题)

  2. 值:HashMap对象的key、value值均可为null。HahTable对象的key、value值均不可为null。

  3. 容量:HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。(当容量使用75%时就进行扩容

  4. HashMap扩容时是当前容量翻倍即:capacity * 2,Hashtable扩容时是容量翻倍+1 即:capacity * 2+1。

 

HashMap可以通过下面的语句进行同步:(collections:集合的工具类

Map m = Collections.synchronizeMap(hashMap);

 

4、 HashSet 和 HashMap 区别

HashSet 底层就是基于 HashMap 实现的。只不过HashSet里面的HashMap所有的value都是同一个Object而已(key值就成为了存储数据,不再是指向标),因此HashSet也是非线程安全的。

 

img

 

 

5、ConcurrentHashMap和Hashtable的区别(没看明白)

JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类 似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

 

6、Set和List的区别

  1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。都可以存储null值,但是set不能重复所以最多只能有一个空元素。

  2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。

  3. List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。

 

7、 ArrayList 与 Vector 的区别:

共同点: 都实现了List接口,都是有序的集合,我们可以按位置的索引号取出元素,其中数据都是可以重复的,这是与hashSet最不同的,hashSet不可以按照索引号去检索其中的元素,也不允许有重复的元素。

区别:

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

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

 

8、HaspMap与TreeMap的区别:

  1. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。

  2. 在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。

 

9、Collection和Collections的区别

Collection是单列集合的顶层接口,Map是双列集合的顶层接口

Collections是一个集合的工具类,提供了排序、查找等操作集合的一些常用方法。

 

10、 Collection框架中实现比较要怎么做?

第一种,实体类实现Comparable<T>接口,并实现 compareTo(T t) 方法,我们称为内部比较器

第二种,创建一个外部比较器,这个外部比较器要实现Comparator接口的 compare(T t1, T t2)方法。

 

comparable 和 comparator的区别?

  • comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序

  • comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序

一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().

 

例子:

public class ComparatorTest {
   public static void main(String[] args) {
       Student xiaoming = new Student("xiaoming", 20);
       Student xiaohong = new Student("xiaohong", 21);
       Student xiaogang = new Student("xiaogang", 19);

       List<Student> list = new ArrayList<Student>();
       list.add(xiaohong);
       list.add(xiaoming);
       list.add(xiaogang);
       //因为Student类已经重写了compareTo方法
       System.out.println("已重写后的list --->>>" + list);
       //自定义排序
       Collections.sort(list, new Comparator<Student>() {
           @Override
           //升序
           public int compare(Student o1, Student o2) {
               return o1.getAge().compareTo(o2.getAge());
          }
      });
       //自定义排序
       System.out.println("自定义排序list --->>>" + list);

  }
}

class ComparatorTest2 implements Comparator<Student> {

   public static void main(String[] args) {
       Student xiaoming = new Student("xiaoming", 20);
       Student xiaohong = new Student("xiaohong", 21);
       Student xiaogang = new Student("xiaogang", 19);
       List<Student> list = new ArrayList<>();
       list.add(xiaohong);
       list.add(xiaoming);
       list.add(xiaogang);
       System.out.println("已重写后的list --->>>" + list);

       ComparatorTest2 comparatorTest = new ComparatorTest2();
       System.out.println("自定义比较--->>>"+comparatorTest.compare(xiaoming, xiaohong));

  }

   //重写compare方法,用于单独比较
   @Override
   public int compare(Student o1, Student o2) {
       return o1.getAge().compareTo(o2.getAge());
  }


}

@Data
class Student implements Comparable<Student> {
   String name;
   Integer age;

   Student(String name, int age) {
       this.name = name;
       this.age = age;
  }

   @Override
   public int compareTo(Student o2) {
       //降序
       return this.getAge().compareTo(o2.getAge());
  }
}

第一个main输出:

已重写后的list --->>>[Student(name=xiaohong, age=21), Student(name=xiaoming, age=20), Student(name=xiaogang, age=19)]
自定义排序list --->>>[Student(name=xiaogang, age=19), Student(name=xiaoming, age=20), Student(name=xiaohong, age=21)]

第二个main输出:

已重写后的list --->>>[Student(name=xiaohong, age=21), Student(name=xiaoming, age=20), Student(name=xiaogang, age=19)]
自定义比较--->>>-1

 

11、ConcurrentHashMap分段锁(不理解)

jdk1.7中:

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。Segment 是一种可重入锁 ReentrantLock,在 ConcurrentHashMap 里扮演锁的角色,HashEntry 则用于存储键值对数据。

Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样。

 

img

 

 

1.8 中:

放弃了Segment,直接用 Node数组+链表+红黑树 的数据结构来实现,并发控制使用Synchronized + CAS来操作,整个看起来就像是优化过且线程安全的HashMap。

 

img

 

 

12、遍历一个 List 有哪些不同的方式?

  1. for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。

  2. 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。

  3. foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

 

 

13、如何实现数组和 List 之间的转换?

  • 数组转 List:使用 Arrays. asList(array) 进行转换。

  • List 转数组:使用 List 自带的 toArray() 方法。

        // list to array
       List<String> list = new ArrayList<String>();
       list.add("HaC");
       list.add("HelloCoder");
       Object[] str = list.toArray();

       // array to list
       String[] array = new String[]{"HaC", "HelloCoder"};
       List<String> mylist = Arrays.asList(array);

 

14、hashCode()与equals()

  1. 如果两个对象相等,则hashcode一定也是相同的

  2. 两个对象相等,对两个equals方法返回true

  3. 两个对象有相同的hashcode值,它们也不一定是相等的(哈希冲突)

  4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖

  5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

 

15、HashMap什么时候进行扩容?

1.添加元素的时候会检查容器当前元素个数。当HashMap的容量值超过了临界值(默认16 * 0.75=12)时扩容。

2.HashMap将会重新扩容到下一个2的指数幂(16->32->64)。

3.调用resize方法,定义长度为新长度(32)的数组,然后对原数组数据进行再Hash。这个过程是一个性能损耗点。

 

posted @ 2021-10-31 16:26  G5佩奇  阅读(18)  评论(0)    收藏  举报