集合

1 篇文章 0 订阅

集合

类集设置的目的:为了方便用户操作各个数据结构,所以引入了类集的概念。

可以把类集称为java对数据结构的实现。

类集中最大的操作接口,Collection、Map、Iterator。

所有类集的接口或类都在java.util包中。

java类集结构图

常用的数据结构:栈、队列、数组、链表和红黑树。
栈 (堆栈) :

是限定在表尾进行插入和删除的线性表。允许插入和删除的一端为栈顶,另一端为栈底。无任何数据元素的栈为空栈。栈又称先进后出的线性表。

特点:1、先进后出。 即先存进取得元素要在后面存进的元素依次取出后,才能取出该元素。

​ 2、栈的入口和出口都是栈的顶端位置。

压栈:存元素。

弹栈:取元素。

队列:

queue,简称队,队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许一端插入,另一端删除的线性表。队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。

特点:1、先进先出。 (如火车过山洞) 2、队列的入口和出口各占一侧。

数组:

是有序的元素序列,数组是在内存中开辟的一段连续的空间,并在里面存放元素。

特点:1、查找元素快。2、增删元素慢

链表

链表有一系列结点组成,结点可以在运行时动态生成。结点包括两个部分,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

特点:1、多个结点之间,通过地址进行连接。

​ 2、查找元素慢

​ 3、增删元素快

红黑树(平衡二叉树)

二叉树:是每个结点不超过2的有序树。顶上的叫根结点,两边是左子树和右子树。

特点:速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍。

Collection集合

集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组的区别:

数组的长度是固定的,集合的长度是可变的。

Collection 接口是整个java类集中保存单值的最大操作父接口,操作时只能保存一个对象的数据。

在开发中不会直接使用Collection接口,而使用其操作的子接口,List(允许重复元素)、Set(不允许重复元素)。

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

Collection接口的子接口包括:Set接口和List接口

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

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

List接口的实现类主要有:ArrayList、LinkedList、Vector等

ArrayList 和 Vector 的区别

两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引来取出某个元素,并且其中的数据是允许重复的,这是与 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。

Vector 是线程安全的,ArrayList 是线程不安全的。

Vector 在数据满时(加载因子1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间。

ArrayList和LinkedList的区别

LinkedList 实现了 List 和 Deque 接口,一般称为双向链表;ArrayList 实现了 List 接口,动态数组;

LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;

LinkedList 比 ArrayList 需要更多的内存;

Array 和 ArrayList 有什么区别

Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。

Array 大小是固定的,ArrayList 的大小是动态变化的

HashSet是如何保证数据不可重复的

HashSet的底层其实就是HashMap,只不过HashSet是实现了Set接口并且把数据作为K值,而V值一直使用一个相同的虚值来保存,源码如下:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
}

由于HashMap的K值本身就不允许重复,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V,那么在HashSet中执行这一句话始终会返回一个false,导致插入失败,这样就保证了数据的不可重复性。

Map :Mapping(映射)

Map集合存储的是一个的 键值对 数据。

Map集合的键(key)不可重复

ap接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap

哈希表概述:

对象数组+链表

哈希桶中的数据量大于8时,从链表转换为红黑二叉树。、

哈希桶中的数据量减少到6时,从红黑二叉树转换为链表。

初始桶数量16

散列因子 0.75

HashMap与HashTable的区别

答:

  1. HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
  2. HashMap允许K/V都为null;后者K/V都不允许为null;
  3. HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;
HashMap的put方法的具体流程
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
    // 1.如果table为空或者长度为0,即没有元素,那么使用resize()方法扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 2.计算插入存储的数组索引i,此处计算方法同 1.7 中的indexFor()方法
    // 如果数组为空,即不存在Hash冲突,则直接插入数组
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 3.插入时,如果发生Hash冲突,则依次往下判断
    else {
        HashMap.Node<K,V> e; K k;
        // a.判断table[i]的元素的key是否与需要插入的key一样,若相同则直接用新的value覆盖掉旧的value
        // 判断原则equals() - 所以需要当key的对象重写该方法
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // b.继续判断:需要插入的数据结构是红黑树还是链表
        // 如果是红黑树,则直接在树中插入 or 更新键值对
        else if (p instanceof HashMap.TreeNode)
            e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 如果是链表,则在链表中插入 or 更新键值对
        else {
            // i .遍历table[i],判断key是否已存在:采用equals对比当前遍历结点的key与需要插入数据的key
            //    如果存在相同的,则直接覆盖
            // ii.遍历完毕后任务发现上述情况,则直接在链表尾部插入数据
            //    插入完成后判断链表长度是否 > 8:若是,则把链表转换成红黑树
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 对于i 情况的后续操作:发现key已存在,直接用新value覆盖旧value&返回旧value
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 插入成功后,判断实际存在的键值对数量size > 最大容量
    // 如果大于则进行扩容
    if (++size > threshold)
        resize();
    // 插入成功时会调用的方法(默认实现为空)
    afterNodeInsertion(evict);
    return null;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u6aDAO79-1589804191177)(D:\桌面\批注 2020-05-18 083911.png)]

HashMap的扩容操作是怎么实现的

HashMap通过resize()方法进行扩容或者初始化的操作

/**
 * 该函数有2中使用情况:1.初始化哈希表;2.当前数组容量过小,需要扩容
 */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;// 扩容前的数组(当前数组)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;// 扩容前的数组容量(数组长度)
    int oldThr = threshold;// 扩容前数组的阈值
    int newCap, newThr = 0;

    if (oldCap > 0) {
        // 针对情况2:若扩容前的数组容量超过最大值,则不再扩容
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 针对情况2:若没有超过最大值,就扩容为原来的2倍(左移1位)
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }

    // 针对情况1:初始化哈希表(采用指定或者使用默认值的方式)
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }

    // 计算新的resize上限
    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 = newTab;
    if (oldTab != null) {
        // 把每一个bucket都移动到新的bucket中去
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    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;
                        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);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
HashMap是怎么解决哈希冲突的
什么是哈希?

Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。

什么是哈希冲突

当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。

HashMap是使用了哪些方法来有效解决哈希冲突的

使用链地址法(使用散列表)来链接拥有相同hash值的数据;
使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标

hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值