常见类集问题

常见类集问题

1. Linkedlist、ArrayList 、Vector的关系与区别

相同:
这三个类都是list接口下的常用子类,其中ArrayList、Vector基于数组实现,Linkedlist基于双向链表实现。
不同:
**** ArrayList采用懒加载策略,在第一次添加元素时初始化内部数组大小为10(无参构造)。ArrayList扩容为原先数组的1.5倍。ArrayList采用异步处理,线程不安全,性能较高。使用率80%。
****Vector在产生对象时,初始化大小为10的内部数组(无参构造)。Vector扩容为原先数组的2倍。Vector采用synchronize修饰常用的增删改查方法,线程安全,性能较低(读读互斥)基本不用。
扩展:Java提供的栈实现是Vector的子类
****在任意位置的插入与删除会考虑使用Linkedlist,Queue接口子类实现Linkedlist。

2.了解jcl fail-fast策略?fail-safe呢?

什么是fail-fast?

优先考虑出现异常的场景,当异常产生时,直接抛出异常,程序终止。
为何会产生fail-fast?
modCount!= expectMOdCount
modCount存在于AbstractList中记录List集合被修改(add、remove)的次数
expectMOdCount存在于内部迭代器实现,存储当前集合修改次数。
fail-fast的意义?
保证多线程场景不产生“脏读”
fail-safe:不抛出ConcurrentModificationException的集合称为fail-safe集合
juc包下线程安全的集合类(CopyWriterArrayList、ConcurrentHashMap)

3.Set接口与Map接口的关系

Set实际上内部就是Map,保存的单个元素存储在Map的key。
Set不允许数据重复
HashSet判断两个对象是否重复:equals与hashCode
TreeSet有序、HashSet无序
元素想要保存在TreeSet中,要么元素本身所在的类实现Comparable,要么通过外部传入一个比较器(外部排序)

4.hashcode与equals的关系

hashCode:取得任意一个对象的哈希码
equals:比较两个对象是否相等【equals比的是值,==比的是地址】
hashCode返回值相等的两个对象,equals是否相等?equals不一定相等(x与f(x))
equals(x)返回值相等的两个对象,hashCode(f(x))是否相等?一定相等

5.Java实现大小比较的方式(内部排序、外部排序)——两个对象

内部排序(自己与别人比):Comparable
实现了Comparable接口的子类(String类),表示本类具有天然的可比较性。
int CompareTo(Obeject o)
大于0 本身大于目标对象
小于0 本身小于目标对象
等于0 本身等于目标对象
外部排序:Comparator(推荐使用)
类本身不具备可比较的特性,专门有一个类比较该类的大小(比较器)
策略模式;

6.Hashmap、TreeMap、Hashtable的关系与区别

(1)这三个类都是Map接口下的常用子类,Hashtable基于哈希表实现,TreeMap基于红黑树实现的,HashMap基于哈希表+红黑树(JDK1.8之后,JDK1.8基于哈希表)
(2)关于null值,HashMap允许key、value为null。
Hashtable:key与value都不能为空
TreeMap:只允许value为空
(3)线程安全性
HashMap、TreeMap:采用异步处理,线程不安全,性能较高
Hashtable:使用synchronized同步方法,线程安全,性能较低(锁的是整个哈希表,读读互斥)
哈希表(k,v):数组
根据相应的哈希算法计算key,返回值即为v存储的数组下标。
哈希算法:f(k)->int即为v需要存储的数组下标
查找、添加元素:O(1)
哈希冲突:哈希算法计算的两个不同对象的哈希值相等的情况。
开放地址法:寻找下一个为null的数组下标,而后将冲突元素存储
再散列法:再次使用不同的哈希算法再次计算一下
连地址法(拉链法):将所有冲突元素按照链表存储【HashMap使用此法解决哈希冲突】

7.HashMap源码解析(负载因子、树化策略、内部hash实现、resize策略…)

内部属性
负载因子:final float loadFactor(默认为0.75f)
实际容量:int TREEIFY_THRESHOLD=8;
树化阈值:int UNIREEIFY_THRESHOLD=6;
HashMap也采用懒加载策略,第一次put时初始化哈希表
树化逻辑:索引下标对应的链表长度达到阈值8并且哈希表(数组)的长度达到64才树化,否则只会调用resize方法进行哈希表扩容。resize():扩容为原先数组的2倍。当负载因子过大会导致哈希冲突明显增加,节省内存。负载因子过小会导致哈希表频繁扩容,内存利用率低。
为何JDK1.8要引入红黑树?
当链表长度过长时,会将哈希表查找的时间复杂度退化为O(n)
树化保证即便在哈希冲突严重时,查找的时间复杂度为O(log(n))
当红黑树结点个数再扩容或删除元素时减少为6以下,在下次resize过程中会将红黑树退化为链表,节省内存。
为何不直接使用Object提供的hashCode?
将哈希码保留一半,将高低位都参与哈希运算,减少内存开销,减少哈希冲突。
((h=key.hashCode())^(h>>>16))
put内部逻辑:
1.哈希表索引下标计算:i=(n-1)&hash【保证求出的索引下标都在哈希表的长度范围之内】
2.n:哈希表长度【n必须是2^n】保证哈希表中的所有索引下标都会被访问到。
若n=15,则以下位置永远不能存储元素
0011、0101、1001、1011、1101、1111
15:0000 1111

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //第一次put值将哈希表初始化
    //resize():1.完成哈希表的初始化2.完成哈希表的扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
        //当目标索引未储存元素时,当前元素储存到目标索引的位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
        //哈希表已经初始化并且算出的数组下标已经有元素了
    else {
        Node<K,V> e; K k;
        //若索引下标对应得元素key值恰好与当前元素key值相等且不为null
        //将value替换为当前元素的value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
            //此时索引对应的链表已经树化了,采用红黑树方式将当前节点添加到树中
        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);
                    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;
            },。
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //此时添加了新结点
    if (++size > threshold)
    //扩容
        resize();
    afterNodeInsertion(evict);
    return null;
}

8.ConcurrentHashMap是如何高效实现线程安全的?

Hashtable锁的是整个哈希表(锁的个数只有一个,粒度很粗,读读互斥)
ConcurrentHashMap锁的个数更多且粒度更细

9.ConcurrentHashMap、JDK1.7与JDK1.8区别

JDK1.7 ConcurrentHashMap Segement+哈希表
Segement初始化为16之后,不再扩容。
扩容发生在Segement对应的小哈希表。
ConcurrentHashMap锁的是Segement,由Hashtable的一把锁增加为16把锁,锁的粒度更细(支持的并发线程1拿到Segement1的锁并不会影响线程2访问Segement2)
Segement是ReentrantLock的子类,使用Lock来保证线程安全
JDK1.8 ConcurrentHashMap 哈希表+红黑树
Segement没有具体作用,只保留结构
ConcurrentHashMap锁的是哈希桶,锁的粒度更加细,锁的个数会随着哈希表的扩容而增加。
CAS+synchronized代码块来保证线程安全性

其他注意问题:

  1. String str1 = new String("abc");//2个对象
  2. 字符串的拼接,String—>StringBuilder.append()
    字符串拼接时,只有纯常量拼接不会转为StringBuilder,直接在常量池寻找是否存在已有值。否则,只要存在一个字符串常量"+",都会转为StringBuilder调用append()。
  3. 异常体系中,若finally代码块中存在return语句,则try,catch语句失效。
    若finally无return语句。try,catch有return语句,则try,catch代码块先暂存代码块中的值,然后执行fianlly代码块,最后返回暂存值。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值