面试问题-1

本文深入解析了ArrayList和HashMap的数据结构特性,重点讲解了HashMap容量选择的2的幂次规则、扩容策略、负载因子应用,以及多线程环境下如何避免死循环。还探讨了Cookie禁用后的替代方案,以及Thread类中yield和join方法的异同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天是2021-2-24.

一。arraylist

1.arraylist最大容量:Integer.MAX_VALUE

二,hashmap

1.构造hashmap时,我们指定的初始容量A会被扩大到比它大的最近的那个2的n次幂的值B,以方便在扩容的时候,计算数据在 newTable 中的位置。
2.扩容阈值会由B0.75得出
3.如果我们想存数据量大的数据,比如说1000,最好是看比1000大的那个2的n次幂的值
0.75是否比它小–1024*0.75=768<1000,这样想存1000条还要触发一次扩容
4.方便起见,我们想指定初始容量时,传入预期值/0.75,通过负载因子放大,就可以保证存放下预期数据量的数据
为什么hashmap的容量总是满足2的n次幂
假设底层数组长度n

  1. 计算索引的公式为:(n-1)&hash
  2. 如果满足2的n次幂,添加元素时就能保证数组中每个索引都可能被使用,就可以大大减少hash冲突的概率。如果不满足,那么数组中有些索引可能永远都不会被使用,浪费了空间。
  3. 在查找元素时,也是通过(n-1)&hash去得出索引,如果索引上存放的是链表,意味着之前添加元素的时候是发生了hash冲突的,那么就要匹配链表直到找到匹配key,查询的效率就会降低
  4. jdk1.7在扩容时,原数组的元素都要进行rehash(),重新计算每个元素的hash值,然后计算在新数组中的索引位置,如果不满足,那么就会降低使用率,增加hash冲突。jdk1.8在扩容时,不重新计算hash值,直接通过(n-1)&hash得到索引,元素要么还在原位置,要么在原位置+数组原长度处。

头插法
获取当前索引上的元素e,也就是原链表的表头,然后以e作为新entry的next属性的值,也就是说新元素指向了原来的表头,那么这个新元素自然成为了新链表的表头
头插法的死循环

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
      while(null != e) {
        Entry<K,V> next = e.next;
        if (rehash) {
          e.hash = null == e.key ? 0 : hash(e.key);
        }
        //通过key值的hash值和新数组的大小算出在当前数组中的存放位置
        int i = indexFor(e.hash, newCapacity);
        e.next = newTable[i];
        newTable[i] = e;
        e = next;
      }
    }
  }


假设有两个线程t1和t2,链表有元素a,b,a->b->null
从e=a开始

  1. t1执行next=a.next=b后挂起
  2. t2开始工作,向新链表插入a元素,此时e=a.next=b,新链表是a->null
  3. t2新一轮循环,此时e=b,b.next=newTable[i]=a,b->a,将b插入t2新链表,此时新链表就变成了b->a->null
  4. t1重新开始工作e=a,next=b,a.next=newTable[i]=null,e=next=b,t1新链表是a->null
  5. t1新一轮循环,e=b,next=b.next=a,b.next=newTable[i]=a,b->a,将b插入新链表,则新链表为b->a->null,e=next=a
  6. t1继续循环,此时e=a,next=a.next=null,a.next=newTable[i]=b,a->b,将a插入新链表,则新链表为a->b->a,e=next=null结束循环
  7. 结束循环后,的新链表已经是a->b->a的环链表了,如果尝试获取一个key为c的键值对,那么就会因为获取不到而在a、b之间不断循环判断下一个元素是否匹配,从而死循环

三。cookie禁用了怎么实现登录效果

前端使用LocalStorage替代,转
限制cookie滥用,可以用localStorage替代大部分cookie功能

四。Thread类

1.实现了Runnable接口
2.yield()方法:与sleep()类似,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
3.join()方法:

join()
join(long millis)     //参数为毫秒
join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间
3.1 实际上调用join方法是调用了Object的wait方法
3.2interrupt()是给线程设置中断标志;interrupted()是检测中断并清除中断状态;isInterrupted()只检测中断。还有重要的一点就是interrupted()作用于当前线程,interrupt()和isInterrupted()作用于此线程,即代码中调用此方法的实例所代表的线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值