2021.10.9小米一面

10-9
面试

项目介绍
股票的实时价格如何获取
难点 如何解决
集合类简单介绍 对哪个熟悉一些 介绍一下

Arraylist 扩容时候 resize()方法

当向容器中添加元素的时候,会判断当前容器中的元素个数,如果大于等于当前数组的长度*加载因子的时候,需要自动扩容。

什么时候resize()

/** 
 * HashMap 添加节点 
 * 
 * @param hash        当前key生成的hashcode 
 * @param key         要添加到 HashMap 的key 
 * @param value       要添加到 HashMap 的value 
 * @param bucketIndex 桶,也就是这个要添加 HashMap 里的这个数据对应到数组的位置下标 
 */  
void addEntry(int hash, K key, V value, int bucketIndex) {  
    //size:The number of key-value mappings contained in this map.  
    //threshold:The next size value at which to resize (capacity * load factor)  
    //数组扩容条件:1.已经存在的key-value mappings的个数大于等于阈值  
    //             2.底层数组的bucketIndex坐标处不等于null  
    if ((size >= threshold) && (null != table[bucketIndex])) {  
        resize(2 * table.length);//扩容之后,数组长度变了  
        hash = (null != key) ? hash(key) : 0;//为什么要再次计算一下hash值呢?  
        bucketIndex = indexFor(hash, table.length);//扩容之后,数组长度变了,在数组的下标跟数组长度有关,得重算。  
    }  
    createEntry(hash, key, value, bucketIndex);  
}  
  
/** 
 * 这地方就是链表出现的地方,有2种情况 
 * 1,原来的桶bucketIndex处是没值的,那么就不会有链表出来啦 
 * 2,原来这地方有值,那么根据Entry的构造函数,把新传进来的key-value mapping放在数组上,原来的就挂在这个新来的next属性上了 
 */  
void createEntry(int hash, K key, V value, int bucketIndex) {  
    HashMap.Entry<K, V> e = table[bucketIndex];  
    table[bucketIndex] = new HashMap.Entry<>(hash, key, value, e);  
    size++;  
}

resize():(JDK1.7)

    void resize(int newCapacity) {   //传入新的容量
        Entry[] oldTable = table;    //引用扩容前的Entry数组
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了
            threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
            return;
        }
 
        Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组
        transfer(newTable);                         //!!将数据转移到新的Entry数组里
        table = newTable;                           //HashMap的table属性引用新的Entry数组
        threshold = (int) (newCapacity * loadFactor);//修改阈值
    }

transfer()函数将原来Entry数组的元素拷贝到新的Entry数组中

void transfer(Entry[] newTable) {
	Entry[] src = table; //src引用了旧的Entry数组
	int newCapacity = newTable.length;
	for (int j = 0;j < src.length;j++){ //遍历旧的Entry数组
		Entry<K,V> e = src[j];  //取得旧Entry数组的每个元素
		if (e != null) {
			src[j] = null; //释放旧的Entry数组的对象引用 (for循环后,旧的Entry数组不再引用任何对象)
			do {
				Entry<K,V> next = e.next;
				int i = indexFor(e.hash,newCapacity); //重新计算每个元素在数组中的位置
				e.next = newTable[i];//标记
				newTable[i] = e;//将元素放在数组上
				e = next; //访问下一个Entry链上的元素
			} while (e != null);
		}
	}
}
static int indexFor(int h,int length) {
	return h & (length - 1);
}

ArrayList的put()方法 以及 添加节点时候 新节点放哪里 尾部还是头部

在这里插入图片描述

注意:在添加的时候:新加入的放在链表的头部,最早先加入的放在链尾。

Resize线程不安全

1.put操作时导致线程不安全

两个线程A和B,线程A将一个key-value插入到HashMap中,首先计算记录所要落搭配的桶的索引坐标,然后获取到这个桶中的链表头节点,此时线程A的时间片用完了,线程B调度得以执行,线程B将记录插入到桶中。假设线程A插入的记录算出来的桶索引和线程B插入的记录的桶索引一样,那么当线程B成功插入后,线程A再次被调度运行时,线程A持有过期的链表头,它将记录进行插入,就将线程B插入的记录进行了覆盖,导致线程B插入的记录消失。

2. hashmap的get操作可能因为resize扩容而引起死循环

在这里插入图片描述

我们假设有两个线程同时需要执行resize操作,我们原来的桶数量为2,记录数为3,需要resize桶到4,原来的记录分别为:[3,A],[7,B],[5,C],在原来的map里面,我们发现这三个entry都落到了第二个桶里面。
假设线程thread1执行到了transfer方法的Entry next = e.next这一句,然后时间片用完了,此时的e = [3,A], next = [7,B]。线程thread2被调度执行并且顺利完成了resize操作,需要注意的是,此时的[7,B]的next为[3,A]。此时线程thread1重新被调度运行,此时的thread1持有的引用是已经被thread2 resize之后的结果。线程thread1首先将[3,A]迁移到新的数组上,然后再处理[7,B],而[7,B]被链接到了[3,A]的后面,处理完[7,B]之后,就需要处理[7,B]的next了啊,而通过thread2的resize之后,[7,B]的next变为了[3,A],此时,[3,A]和[7,B]形成了环形链表,在get的时候,如果get的key的桶索引和[3,A]和[7,B]一样,那么就会陷入死循环。
如果在取链表的时候从头开始取(现在是从尾部开始取)的话,则可以保证节点之间的顺序,那样就不存在这样的问题了。

Lock和synchronized 介绍以及区别
Synchronized 演进过程
Java内存模型
Volatile

线程安全的集合
异常 error和exception
网络模型 tcp和udp区别 三次握手 四次挥手 三次挥手可以吗
Hashset底层

堆和栈区别

堆:堆是jvm的一块内存区域。存储的是数组和对象(其实数组就是对象),new的东西都是在堆中,堆不会随时释放空间,但是会有垃圾回收机制。(栈中存放的是单个变量,变量释放后,就没有了)
栈:栈是一块内存区域,存储的是局部变量(在方法中定义的都是局部变量,for循环中定义的也是局部变量)。先加载函数才能对局部变量的定义,因此方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。因此栈内存更新速度很快。

区别:

  • 栈内存存储的是局部变量,而堆内存存储的是实体
  • 栈内存的更新速度快于堆内存,因为局部变量的生命周期很短
  • 栈内存存放的变量在生命周期一结束就会被释放,而堆内存存放的对象会被垃圾回收机制不定时地回收。

队列和栈区别
创建线程方式 thread和runnable区别
线程池参数 解释
synchronized修饰静态方法和修饰非静态方法有什么区别

算法题
单例模式 如何改进
二叉树 层序遍历

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值