面试必备基础知识 — 容器

介绍

容器主要包括 CollectionMap 两种,Collection 存储着对象的集合,而Map 存储着键值对的映射表。

集合类中存储的仅仅是对象的引用,并不存储对象本身。

Collection

继承关系

Set

Set是不允许元素重复的。判断元素的重复需要根据对象的hash方法和equals方法来决定。这也是我们通常要为集合中的元素类重写hashCode方法和equals方法的原因。

  • TreeSet :基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet 查找的时间复杂度为O(1),TreeSet 为O(logN)。
  • HashSet : 基于哈希表实现,支持快速查找,但不支持有序性操作,并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历HashSet 得到的结果是不确定的。
  • LinkedHashSet :具有HashSet 的查找效率,并且内部使用双向列表维护元素的插入顺序,也就是LinkedHashSet可以保证元素插入集合的顺序与输出顺序保持一致。

List

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List集合默认按元素的添加顺序设置元素的索引,例如第一个添加的元素索引为0,第二个添加的元素索引为1…
List作为Collection接口的子接口,可以使用Collection接口里的全部方法。而且由于List是有序集合,因此List集合里增加了一些根据索引来操作集合元素的方法。

  • ArrayList :基于动态数组实现,非线程安全,支持随机访问,实现了RandomAccess接口

    • RandomAccess 接口中什么都没有定义,或许可以理解为,该接口是一个标识符,标识实现这个接口的类具有随机访问的功能。
  • Vector :和ArrayList类似,但它是线程安全的

  • LinkedList :基于双向列表实现,只能顺序访问,但是可以快速的在链表中插入和删除元素

ArrayList 的扩容机制

首先底层数组的默认容量是 10 :

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。

	//需要分配的最大数组的大小
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

     //扩容核心方法
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //oldCapacity右移一位,(oldCapacity/2)
        //将新容量更新为旧容量的1.5倍
        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);
    }

扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

Java中的 length、length()、size

  • lenght 属性 ,针对数组
  • length() 方法,针对字符串
  • size() 方法,针对泛型集合

通过源码一步一步分析ArrayList 扩容机制

Queue

  • LinkedList :可以用它来实现双向队列
  • PriorityQueue :基于堆结构实现,可以用它来实现优先队列

Map

在这里插入图片描述

  • TreeMap :基于红黑树实现
  • HashMap :基于哈希表实现
  • HashTable :和 HashMap 类似,但它是线程安全的
  • LinkedHashMap :使用双向链表来维护元素的顺序

HashMap

HashMap 是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。
JDK1.8 对HashMap 底层实现进行了优化,例如 引入红黑树的数据结构扩容的优化等。

  • HashMap 它根据键的 HashCode值 存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序是不确定的
  • HashMap 的键不能重复,值可以
  • HashMap 非线程安全,即任意时刻可以有多个线程同时写HashMap,可能会导致数据不一致
  • 如果需要线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
java.util.Collection 是一个集合接口。
它提供了对集合对象进行基本操作的通用接口方法。
java.util.Collections 是一个包装类。
它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。
此类不能实例化,就像一个工具类,服务于Java的Collection框架。

从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的
底层实现
HashMap 使用 拉链法 来解决冲突:拉链法就是将数组和链表结合,也就是重建一个链表数组,数组中的每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
拉链法
图片来源

Put方法

图解:
Put方法
图片来源

Put() 源码分析:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //判断键值对数组table 是否为空或为null值,执行resize()进行扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //计算index,并对null进行处理(直接新建节点添加)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //节点存在,直接覆盖value(HashCode和equals都相等)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //判断table[i]是否为红黑树
            else if (p instanceof TreeNode)
            	//直接在树种插入键值对
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //为链表
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //判断链表长度是否大于8,是 链表转为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果key已经存在,直接覆盖value
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //插入成功后,判断实际存在的键值对 size是否超过最大容量 threshold,如果超过 扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

扩容机制

扩容(resize)就是重新计算容量,HashMap对象内部的数组无法装载更多的元素时,HashMap对象就需要扩大内部数组的长度。
Java里面的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组。也就是把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。
HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。

Java 8系列之重新认识HashMap

迭代器

Iterator接口 经常被称作迭代器,它是Collection接口的父接口。但Iterator主要用于遍历集合中的元素。
Iterator接口中主要定义了2个方法:

  • hasNext() :如果仍有元素可以迭代,则返回true.(boolean)
  • next() :返回迭代的下一个元素。
ArrayList<> array = new ArrayList<>();
//迭代器遍历集合
Iterator<String> it = array.iterator();//返回的是子类对象
while(it.hasNext()) {  //判断是否有下一个元素
    String s = it.next();//取出该元素
    System.out.println(s);
}
 
//增强for
for(String s : array) {
    System.out.println(s);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值