Java 集合

1. 集合结构图

在这里插入图片描述

2. Collection

2.0 Collection 接口方法

在这里插入图片描述

2.1 List 子接口

2.1.0 List 子接口的实现类

在这里插入图片描述

2.1.1 ArrayList

源码分析

  1. 几个属性参数
// 1. 默认容量
// 注意: 如果没有向集合中添加任何元素时, 容量为 0, 添加一个元素之后容量为 10
private static final int DEFAULT_CAPACITY = 10;

// 2. 存放元素的数组
transient Object[] elementData;

// 3. 实际元素个数
private int size;
  1. 添加元素和扩容
    如果没有向集合中添加任何元素时, 容量为 0, 添加一个元素之后容量为 10,
    扩容: 原来的 1.5 倍
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

2.1.2 LinkedList

其底层采用的双向链表结构

节点内部类

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2.2 Set 子接口

2.2.0 Set 子接口的实现类

在这里插入图片描述

2.2.1 HashSet

  1. 存储结构: 哈希表(数组 + 链表/红黑树)
  2. 存储过程(重复的依据)
    2.1 根据 hashcode 计算保存位置(数组位置) 如果此位置为空, 则直接保存, 如果不为空则只执行第二步
    2.2 再执行 equals方法, 如果 equals 方法为 true, 则认为是重复, 否则形成链表或红黑树.

对于自定义对象, 自定义重复依据时, 要重写 hashcode 和 equals 方法

2.2.2 TreeSet

  1. 存储结构: 红黑树
  2. 元素必须要实现 Comparable 接口, 实现 compareTo 方法, 若该方法的返回值为 0 , 认为是重复元素.

3. Map

3.0 Map 接口方法

05_Map 接口方法.xmind

3.1 HashMap

线程不安全, 运行效率快, 允许用 null 作为 key 或者是 Value

  1. 存储结构: 哈希表(数组 + 链表+红黑树)
  2. 键不可重复. 重复依据: key 的 hashcode 和 equals
  3. 源码分析

几个常量:

// 默认初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

// 最大容量 2 的 30 次方
static final int MAXIMUM_CAPACITY = 1 << 30;

// 默认扩容因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 当链表的节点数量大于 8, 并且数组的长度超过 64 时, 将链表改为红黑树
static final int TREEIFY_THRESHOLD = 8;

// 当红黑树节点数量小于 6 时, 改为链表
static final int UNTREEIFY_THRESHOLD = 6;

// 链表与红黑树切换的数组的数量界限
static final int MIN_TREEIFY_CAPACITY = 64;

// 链表节点定义
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;


// 数组
transient Node<K,V>[] table;

几个关键点

	// 构造函数
	// 刚创建 Hashmap 之后没有添加元素时 table = null, size = 0. 目的: 节省空间
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

1. HashMap 刚创建时, table 是 null, 为了节省空间, 当添加第一个元素时, table 容量调整为 16
2. 当元素个数大于阈值(16 * 0.75), 会进行扩容, 扩容后大小为原来的 2, 目的是减少调整元素的个数.
3. jdk1.8 当链表长度大于 8, 并且数组元素个数大于 64, 会调整为红黑树, 提高执行效率
4. jdk1.8 当链表长度小于 6, 调整成链表
5. jdk1.8 以前, 链表时头插入, 之后是尾插入

HashSet 与 HashMap 的关系

    public HashSet() {
        map = new HashMap<>();
    }
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
  1. hashSet 底层使用的就是 HashMap
  2. 当 向HashSet中添加元素时, 调用的是 HashMap 的 put 方法, 将要存入集合中的值作为 HashMap 的 key 以保证不可重复性.

3.2 TreeMap

  1. 存储结构 : 红黑树
  2. 要求 key 值可排序, 实现 Comparable 接口

面试题

区别

  1. Array和ArrayList的区别,什么时候更合适用Array
  1. Array是数组,可以容纳基本类型和对象,而ArrayList是集合,只能容纳对象
  2. Array 是指定大小的, 而 ArrayList 的大小是动态的
  3. Array 没有提供 ArrayList 那么多的功能
  4. 适合 Array 的情况
    i. 如果列表的大小已经指定, 大部分情况下是存储和遍历它们
    ii. 对于遍历基本数据类型, 尽管 Collections 使用自动装箱来减轻编码任务, 在指定大小的基本类型的列表上 ArrayList 的工作也会变得很慢
    iii. 如果使用多维数组, 使用[][] 比<<>>简单
  1. ArrayList和Vector的区别
  1. Vector是线程同步的,所有它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因数,一般用ArrayList效率比较高。
  2. 如果集合中的元素数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而ArrayList增长率为目前数组长度的50%。在数据量变化比较大的情况下,用vector有一定优势
  1. ArrayList和LinkedList的区别
  1. ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表的数据结构
  2. 对于随机访问get和set,ArryList要优于LinkedList,因为LinkedList要移动指针
  3. 对于新增和删除操作add和remove,linkedList比较占优势,因为ArrayList要移动数据。这一点要看实际情况,如果只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但批量随机插入,则考虑LinkedList。因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。
  1. Lsit、set和map的区别
  1. List (实现了collection接口)
    i. 可以允许有重复的对象
    ii. 可以插入多个 null 值
    iii. 是一个有序容器, 保持了每个元素的插入顺序
  2. Set(实现了collection接口)
    i. 不允许重复对象
    ii. 无序容器, 无法保证每个元素的存储顺序, 而 TreeSet 可以通过 Comparable 或 Comparator 进行顺序排序
    iii. 只允许一个 null 元素
    iv. LinkedHashSet 按照元素的插入顺序对它们进行存储
  3. Map(不是collection的子接口或实现类。Map是一个接口)
    i. Map是以键值对的存储方式存放数据,key是唯一的,value可以重复
    ii. Map中可以有多个null值,但只能有一个null键
    iii. TreeMap也可以通过comparator或者comparable维护排序顺序
  1. HashMap和HashTable的区别
  1. Hashtable是线程安全的,同步的;而hashMap是线程不安全不同步的
  2. HashMap允许存在一个null的key,多个null的value;而hashtable的key和value都不允许为null
  1. HashMap和TreeMap的区别
  1. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果,你就应该使用TreeMap(HashMap中元素排列是无序的)
  2. 在Map中插入、删除和定位元素,HashMap的最好的选择,但如果您需要按自然顺序或自定义顺序遍历Map,那么就使用TreeMap。使用HashMap要求作为key的类中明确重写了hashCode()和equals()方法。
  1. Collection和Collections的区别
  1. Java…util.Collection是一个接口(集合顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类库中有很多集合的实现,其直接继承collection接口有List和Set
  2. Collections则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索及线程安全等操作。
  1. Enumeration和iterator接口的区别
  1. Enumeration接口作用与iterator接口相似,但只提供了遍历vector和hashTable类型集合元素的功能,不支持元素的移除操作
  2. Enumeration速度是iterator的2倍,同时占用更少的内存。但是,iterator远比enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,iterator允许调用者删除底层集合里面的元素。
  1. Listiterator有什么特点,与iterator区别
  1. Iterator是ListIterator的父类接口
  2. Iterator是单列集合(Collection)公共取出容器中元素的方式,而ListIterator是List集合的特有取出元素方式
  3. Iterator中具备的功能只有hashNext(),next(),remove();Listiterator中具备着对被遍历的元素进行增删查改的方法,可以对元素进行逆向遍历。
  1. Comparable和Comparator接口有何区别
  1. Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。
  2. 而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。
  3. 我们不难发现:Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
  1. ConcurrentHashMap和Hashtable的区别?
  1. ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。

Collection

  1. ArrayList集合加入1万条数据,应该怎么提高效率?

直接初始化ArrayList集合的初始化容量为1万。但达到100万以上乃至1000万以上时,初始化容量方法效率会下降

  1. Set里的元素是不能重复,那么用什么方法来区分重复与否呢?是用==还是用equals()?
  1. Set里的元素是不能重复的,用equals()方法来区分重复与否;
  2. == :
    i. 判断基本类型时,比较的是 值 是否相同
    ii. 引用类型时,比较的是地址是否相同
  3. equals:
    只能判断引用类型,默认情况下比较的是地址,可以进行重写,比较的是对象的成员变量值是否相同
  1. HashSet是如何保证数据不可重复的?
  1. HashSet的底层其实就是HashMap,由于HashMap的K值本身就不允许重复,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V,那么在HashSet中执行这一句话始终会返回一个false,导致插入失败,这样就保证了数据的不可重复性。

Map

  1. Map接口提供了哪些不同的集合视图?
  1. Set keyset():返回map中包含的所有key的一个Set视图。集合是受map支持的,map的变化会在集合中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
  2. Collection values():返回一个map中包含的所有value的一个Collection视图。这个collection受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个collection时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
  3. Set<Map.Entry<K,V>> entrySet():返回一个map钟包含的所有映射的一个集合视图。这个集合受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作,以及对迭代器返回的entry进行setValue外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。

HashMap

  1. HashMap 的 key 值要是为类对象, 该类需要满足什么条件?
  1. 需要重写equals()和hashCode()方法
  1. HashMap 的实现原理
  1. 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  2. 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
    获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
  3. 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
  1. HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现
  1. JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
  2. 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
  1. HashMap的put方法的具体流程

在这里插入图片描述

  1. 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
  2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
  3. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
  4. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
  5. 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
  6. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
  1. HashMap的扩容操作是怎么实现的?

  2. HashMap是怎么解决哈希冲突的?

  1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
  2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
  3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
  1. HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
  1. hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
  2. HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
  1. 为什么HashMap中String、Integer这样的包装类适合作为key?

String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

  1. 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
  2. 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;

工具类

  1. 如何实现 Array 和 List 之间的转换?
  1. Array 转 List: Arrays. asList(array) ;
  2. List 转 Array:List 的 toArray() 方法。
  1. Collection 和 Collections 有什么区别?

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

  1. TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。
  2. TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值