Java常见的集合类及相关问题介绍

(一)常见的集合有哪些?

Map 接口和 Collection 接口是所有集合框架的父接口:

1. Collection 接口的子接口包括:Set 接口和 List 接口;

2. Map 接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap 以及 Properties 等;

3. Set 接口的实现类主要有:HashSet、TreeSet、LinkedHashSet 等;

4. List 接口的实现类主要有:ArrayList、LinkedList、Stack 以及 Vector 等。

(二)常见知识点总结

01.List 和 Set 以及 Map 之间的区别是什么?

List:Collection 下的子接口,保存有序的、可重复的数据 Set:Collection 下的子接口,保存无序的、不可重复的数据 Map:定义用来保存键-值对特点的数据。要求键不能重复。

02.List、Map、Set 三个接口存取元素时,各有什么特点?

答:List 以特定索引来存取元素,可以有重复元素。Set 不能存放重复元素(用对象的 equals() 方法来区分元素是否重复)。Map 保存键值对(key-value pair)映射,映射关系可以是一对一或 多对一。Set 和 Map 容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理 论存取时间复杂度为 O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的 键(key)构成排序树从而达到排序和去重的效果。

03. HashMap 和 Hashtable 的区别有哪些?

  1. HashMap 作为 Map 的主要实现类,没有考虑同步,是线程不安全的;可以存储 null 的 key 和 value。

  2. Hashtable 作为Map 的古老实现类使用了 synchronized 关键字,是线程安全的;不可以存储 null 的 key 和 value。

04. HashMap 的底层实现?

在 Java8 之前,其底层实现是数组 + 链表实现,Java8 使用了数组 + 链表 + 红黑树实现。画图分析:

05. HashMap 的工作原理

HashMap map = new HashMap();//底层创建了长度为 16 的 Entry 数组向 HashMap 中添加 entry1(key,value), 需要首先计算 entry1 中 key 的哈希值(根据 key 所在类的 hashCode()计算 得到),此哈希值经过处理以后,得到在底层 Entry[]数组中要存储的位置 i.如果位置 i 上没有元素,则 entry1 直接添加成功。如果位置 i 上已经存在 entry2(或还有链表存在的 entry3,entry4),则需要通过循环的方法,依 次比较 entry1 中 key 和其他的 entry 是否 equals.如果返回值为 true.则使用 entry1 的 value 去替换 equals 为 true 的 entry 的 value.如果遍历一遍以后,发现所有的 equals 返回都为 false,则 entry1 仍可添加成功。entry1 指向 原有的 entry 元素。

默认情况下,如果添加元素的长度 >= DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR (默认值为 12) 且新要添加的数组位置不为 null 的情况下,就进行扩容。默认扩容为原有长度的 2 倍。将原有的数据复制到 新的数组中。

06. ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。

HashMap 没有考虑同步,hashtable 考虑了同步的问题。但是 hashtable 在每次同步执行时都要锁住整个结构。

ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 将 hash 表分为 16 个桶(默认值),诸如 get,put,remove 等常用操作只锁当前需要用到的桶。

07.ConcurrentHashMap 的具体实现

  1. 该类包含两个静态内部类 HashEntry 和 Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;

  2. Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个 HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

08. HashMap 的长度为什么是 2 的幂次方?

  1. 通过将 Key 的 hash 值与 length-1 进行 & 运算,实现了当前 Key 的定位,2 的幂次方可以减少冲突(碰撞)的次数,提高 HashMap 查询效率;

  2. 如果 length 为 2 的次幂  则 length-1 转化为二进制必定是 11111……的形式,在于 h 的二进制与操作效率会非常的快,而且空间不浪费;

  3. 如果 length 不是 2 的次幂,比如 length 为 15,则 length-1 为 14,对应的二进制为 1110,在于 h 与操作,最后一位都为 0,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费相当大。

    更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。

09. List 和 Set 的区别是啥?

List 元素是有序的,可以重复;Set 元素是无序的,不可以重复。

10.List、Set 和 Map 的初始容量和加载因子

1)List

  • ArrayList 的初始容量是 10;加载因子为 0.5; 扩容增量:原容量的 0.5 倍 +1;一次扩容后长度为 16。

  • Vector 初始容量为 10,加载因子是 1。扩容增量:原容量的 1 倍,如 Vector 的容量为 10,一次扩容后是容量为 20。

2) Set

HashSet,初始容量为 16,加载因子为 0.75; 扩容增量:原容量的 1 倍; 如 HashSet 的容量为 16,一次扩容后容量为 32

3.)Map

HashMap,初始容量 16,加载因子为 0.75; 扩容增量:原容量的 1 倍; 如 HashMap 的容量为 16,一次扩容后容量为 32

11.阐述 ArrayList、Vector、LinkedList 的存储性能和特性。

1)ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 中的方法由于添加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较 ArrayList 差,因此已经是 Java 中的遗留容器。

2)LinkedList 使用 双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

3)Vector 属于遗留容器(Java 早期的版本中提供的容器,除此之外,Hashtable、 Dictionary、BitSet、Stack、Properties 都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具 类 Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这是对装潢模式 的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。

补充:遗留容器中的 Properties 类和 Stack 类在设计上有严重的问题,Properties 是一个键和值都 是字符串的特殊的键值对映射,在设计上应该是关联一个 Hashtable 并将其两个泛型参数设置为 String 类型,但是 Java API 中的 Properties 直接继承了 Hashtable,这很明显是对继承的滥用。这 里复用代码的方式应该是 Has-A 关系而不是 Is-A 关系,另一方面容器都属于工具类,继承工具 类本身就是一个错误的做法,使用工具类最好的方式是 Has-A 关系(关联)或 Use-A 关系(依 赖)。同理,Stack 类继承 Vector 也是不正确的。Sun 公司的工程师们也会犯这种低级错误,让 人唏嘘不已。

12.TreeMap 和 TreeSet 在排序时如何比较元素

TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。

TreeMap 要求存放的键值对映 射的键必须实现 Comparable 接口从而根据键对元素进行排序。

13.Collections 工具类中的 sort()方法如何比较元素

Collections 工具类的 sort 方法有两种重载的形式:

第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;

第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是 Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定 义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中 对函数式编程的支持)。

例子:

public class Student {
    private String name; // 姓名 
    private int age; // 年龄
    public Student(String name, int age) { 
        this.name = name;
        this.age = age;
    }
/**
* 获取学生姓名 
*/
    public String getName() { 
        return name;
    }
/**
* 获取学生年龄 
*/
    public int getAge() { 
        return age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
   }
}

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.Comparator; 
import java.util.List;
class Test02 {
    public static void main(String[] args) { 
        List<Student> list = new ArrayList<Student>();
        list.add(new Student("Hao LUO", 33));
        list.add(new Student("XJ WANG", 32));
        list.add(new Student("Bruce LEE", 60));
        list.add(new Student("Bob YANG", 22));
        // 通过 sort 方法的第二个参数传入一个 Comparator 接口对象
        // 相当于是传入一个比较对象大小的算法到 sort 方法中
        // 由于 Java 中没有函数指针、仿函数、委托这样的概念
        // 因此要将一个算法传入一个方法中唯一的选择就是通过接口回调
        Collections.sort(list, new Comparator<Student>() {
//            @Override
            public int compare(Student o1, Student o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        for (Student stu : list) {
            System.out.println(stu);
        }
    } 
}
输出结果:
Student [name=Bob YANG, age=22] 
Student [name=Bruce LEE, age=60] 
Student [name=Hao LUO, age=33] 
Student [name=XJ WANG, age=32]

14、Collection 和 Collections 的区别?

Collection 是一个接口,它是 Set、List 等容器的父接口;Collections 是个一个工具类,提供 了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

若有问题欢迎大家与我互动交流,可评论,可留言,希望我们大家能一起学习,共同进步。

猜你喜欢

转载自blog.csdn.net/myuhua/article/details/81285498