Java集合

1Java集合框架有哪些?

  1. collection结合的层级接口。一个集合代表一组对象,这些对象叫做元素。
  2. List有序集合,数组格式,可重复,通过下标获取元素。
  3. Set无序集合,数组格式,不可重复,通过下标获取元素。
  4. Map无需,key-value,vlaue可以重复,key不可以重复,支持空key,空value。

2.Collection和Collections的区别?

  1. Collection是定义集合的接口。
  2. Collections是集合的工具类包含如排序,sort等方法。

3.List、Set、Map都是实现Collection接口吗?

  1. List、Set实现Collection接口、Map不实现。
  2. Map是键值对映射关系,Set存储的零散的元素是不允许重复的,List是线性结构的容器,适用于按数值索引查询的情况。

4.Collections.sort排序的原理?

  1. 1.6以前Arrays.sort和Collections.sort都是MergeSort,1.7更更新为TimeSort。

5.List、Set、Map的区别?

名称类别线程安全优点缺点
ArrayListList非线程安全集合有序、可以重复、自动扩容、扩容默认1*1.5、随机访问效率高插入和删除的效率比较低、根据内容查找元素的效率较低。
linkedListList非线程安全双向链表,不必保证内存上的连续,所以增删快(非首尾位置)查询时必须要经历从头到尾的遍历,所以查询慢
VectorList线程安全可以重复、自动扩容、默认扩种1*2、可以null插入和删除效率低,复杂度高f(n)
HashSetSet线程不安全元素不可以重复、自动扩容、无序
HashMapMap线程不安全key-value映射存储、支持空key空value、key不可以重复、自动扩容(数组大小为16,开始扩容的大小=原来的数组大小*loadFactor(loadFactor的默认值为0.75))、1.7之前首插、1.8尾插获取只能通过Key获取,不能随机获取

6.HashMap、和HashTable的区别

  1. hashMap线程不安全、支持Key和Value为null,HashTable线程安全不支持key和value为null;

7.如何决定使用HashMap还是TreeMap?

  1. 插入、删除、定位使用HashMap,有序遍历用TreeMap。

8.HashMap的实现原理

  1. hashMap是基于hash实现的、通过put存入、get key获取。当传入key是通过key.HashCode()方法计算Hash值,根据Key的Hash将Value保存在Bucket(桶)中,当两个对象的Hash重复时,被称为Hash冲突,Map通过链表和红黑树的方式来处理。
  2. HashMap底层是数组+链表+红黑树(1.8之后、1.7之前没有红黑树、加入红黑树是为了提高查询效率)当链表的长度大于8时就会转换成红黑树(因为红黑树需要进行左旋,右旋操作, 而单链表不需要,如果元素小于8个,查询成本高,新增成本低、低于8个、新增成本高、查询成本低);
  3. HashMap
    1. put过程
static final int **DEFAULT_INITIAL_CAPACITY** = 1 << 4; // aka 16 // 默认数组大小 table Node<K,V>
		static final int **MAXIMUM_CAPACITY** = 1 << 30; // 数组最大大小
		static final float **DEFAULT_LOAD_FACTOR** = 0.75f; //负载因子 默认大小  影响 扩容后大小
		static final int **TREEIFY_THRESHOLD** = 8; // 数组上的链表转树华的 默认值 大于8 转为树华
		static final int **UNTREEIFY_THRESHOLD** = 6; // 树转回链表的默认值
		static final int **MIN_TREEIFY_CAPACITY** = 64; // 数组内的所有元素超过64位的时候才能允许树华
		Node<key,value>[] table // 哈希桶
		transient int size; //当前哈希表中元素个数
		transient int modCount; // 当前哈希表中 结构修改次数 指增删 修改不计数
		int threshold; // 扩容系数 当哈希表中的元素 大于 系数时 扩容 默认为 **DEFAULT_INITIAL_CAPACITY**
 		final float loadFactor; // 负载因⼦ ,默认 0.75f  **DEFAULT_LOAD_FACTOR** threshold = size*loadFactor)
// 计算 hash值   高位运算 (目的让hash值大于16的也参与路由运算)
// 路由运算 (length - 1 & hash )
// 如果key位null 默认 hash 为0 放在数组 0 的位置 因为 0 参与运算结果 也为0 
 static final int hash(Object key) {
        int h; // 如果 key == null 那么hash为0 
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value   key是否存在
     * @param evict if false, the table is in creation mode. 
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab;  //引用当前 hashMap的散列表
        Node<K,V> p;  // 当前散列表的元素
        int n, i; // n 为 当前散列表长度  i 路由计算的结果  
		// 延迟初始化  节省内存空间  hashMap 只有在你 put数据的时候第一次调用 putVal 才会初始化 。 节省内存空间
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
	// (n-1)& hash  路由算法  计算 value应该存放在散列表中的哪个位置 如果 该位置 是 null 那么 创建 node 存入数据 
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; //临时的散列表元素
            K k; // 临时的元素key 
           // p 路由计算后的哈希桶内的 node 元素 
           // 表示 我当前要插入的 key 与我 现在map中的 key重复  e = p 后续要进行替换操作
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            
			else if (p instanceof TreeNode)  // 如果 p 元素 所在的 桶位置 已经 树化
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {  // 处理链表的情况  1. 我和散列表上的元素的key不同 但是 hash值相同 我要便利 链表 看看链表上的key是否有与我相同的 如果有 替换 没有插入末尾
                for (int binCount = 0; ; ++binCount) {
                	// e 临时 散列表 元素  = 当前的散列表元素的 下一位 结果如果是 0  那么 说明当前已经到了 末尾 我可以直接 插入进去
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // binCount 如果 = 7 说明我这个链表的长度 已经是 8位了 在插入 数据 我就是9位 要进行 树化 
     				             // TREEIFY_THRESHOLD  默认缺省值 表示 链表长度超过 多少 树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 我的hash值 于 链表上的 某个元素的 hash值相同 那么我要替换他
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //表示 e不是 null   需要做替换操作
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount; // 不做替换的操作时 我的表结构改动次数  +1 modCount 结构改动次数
        //如果我操作后的 大小 大于我设置的 阈值 那么 出发扩容机制 
        if (++size > threshold)
            resize(); // 扩容机制 
        afterNodeInsertion(evict);
        return null;
    }
  1. 扩容机制
    为什么要扩容 1. 当前数组容量不足以存储 2. 解决链表过长 查询效率过低
    实现思路 常见一个更大的数组 来 容纳现在小的数组 然后回收小数组内存
final Node<K,V>[] resize() {
//	 oldTab 扩容前的 哈希数组 
// oldThr  扩容前的Node数组长度 
        Node<K,V>[] oldTab = table;
        // 扩容前的数组 == null 说明没有初始化 那么长度就为0 : 否则就是 扩容前数组的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // oldThr 扩容前的 阈值 默认 16 
        int oldThr = threshold;
         // 扩容后的哈希数组长度  和 阈值
        int newCap, newThr = 0;
        // >0说明 散列表 已经初始化过了 正常 扩容
        if (oldCap > 0) {
        // 说明 当前 大小已经达到了 最大值 将 阈值扩大到 最大 返回当前列表
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 没有到达最大  newCap = 当前的 2倍 小于 最大值 并且 当前的数组大小 大于等于 16时  那么 阈值翻倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //  oldCap 不大于0 说明 散列表还没有创建 , 但是创建 HashMap时调用了 有参构造函数的情况下  设置散列表长度 = 阈值
        //public HashMap(int initialCapacity)  public HashMap(int initialCapacity)  public HashMap(Map<? extends K, ? extends V> m) 
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
            // 通过无参构造函数时 创建的 对象 第一次扩容 
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;  16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  //阈值  = 负载因子 (默认 0.75f)  * 默认长度 16 
        }
        // 手动 指定了 负载因子大小 计算 阈值大小
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        // 创建新的 哈希数组
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // 将 老的 table 替换成新的哈希数组
        table = newTab;
        // 旧的 阈值 不是null  说明已经初始化 哈希数组
        if (oldTab != null) {
            // 根据老数组的 长度遍历
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // e 临时的 元素 
                // 这里判断 元素 有值
                if ((e = oldTab[j]) != null) {
                // 清空 老数组内的内容 方便内存被 GC回收
                    oldTab[j] = null;
                    // 这里说明 该节点上只有一个单元素 没有发生过哈希冲突 
                    if (e.next == null)
                    // 将原来的元素 通过 路由运算 (hash & (length-1)) 复制新新的数组
                        newTab[e.hash & (newCap - 1)] = e;
                        // 如果原来的 数组下标 是 红黑树 
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
						// 处理 哈希数组中 已经形成链表的 元素 
						// 低位链表  扩容后的位置 与 当前所在位置一致
                        Node<K,V> loHead = null, loTail = null;
                        // 高位链表 扩容后的位置 为 当前所在位置 + 扩容前的数组长度
                        Node<K,V> hiHead = null, hiTail = null;
                        // 下一个 元素 
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 经行 异位 运算  如果 = 0 说明 是低位数组 反之 是高位数组
                            if ((e.hash & oldCap) == 0) {
                            // 如果低位链表 没有内容了 直接存入 否则存入地位链表的下一个位置
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                    // 是当前的低位链表 保持一致 
                                loTail = e;
                            }
                            // 处理 高位链表内容
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);  //如果当前的元素 == 最后一个元素 并且不是null
                         // 低位元素 不是nul 将低位链表尾部 下一位 设null 将 将当前链表存入新的数组指定下标位置 
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        同上
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
  1. HashMap 获取value get
    核心 getNode
final Node<K,V> getNode(int hash, Object key) {
		// tab 当前 哈希数组  first 桶位中的头元素  e  当前下标元素 n数组长度   k 当前kye
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // & 之前 是确定 我们的 哈希数组内有东西
        if ((tab = table) != null && (n = tab.length) > 0 &&
        // 确定 我们的 头位元素 不是null 是有数据的
            (first = tab[(n - 1) & hash]) != null) {
             //  头元素 就是我要查的 所以返回 头元素的hash值 = 我要查的目标的 hash 值 并且 我的key 也是相同的
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
                // 头元素的下一位不是null 说明是链表 或树化
            if ((e = first.next) != null) {
              //  树化 
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //do while 便利 链表 比对 元素 
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
  1. HashMap 核心 removeNode方法
//matchValue 如果是 true 比对 value  不是 值找key
final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
         // 判断我的 哈希数组 初始化过 并且是有 东西的
        if ((tab = table) != null && (n = tab.length) > 0 &&
        // 查找当前的桶位是 元素是有内容的 
            (p = tab[index = (n - 1) & hash]) != null) {
            //node 是当前查到的元素 e是当前元素的下一个元素 
            Node<K,V> node = null, e; K k; V v;
            //说明我当前要找的 元素 就是当前的元素
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
                // 说明当前的元素是链表或者 树 
            else if ((e = p.next) != null) {
             // 但前是树
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                //遍历链表 找到当前的的key 相同的 元素 
                    do {
                    // 链表第一位就是我们要找的 
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);  // 链表下一位不是null
                }
            }
            // 找到了 要找的元素 并且 桶位不是null的 
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //  查询到的原为 和 当前桶位元素相同
                else if (node == p)
                 // 哈希数组当前下标的元素 从当前元素的下一位替换当前桶位元素
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount; // 表结构 修改次数+ 1 
                --size; // 数组长度 -1 
                afterNodeRemoval(node);
                return node; //返回新的 桶位
            }
        }
        return null;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值