一 hashmap的put和get过程
put(K key, V value)
用于将键值对插入到 HashMap
中。如果该键已经存在,则更新对应的值。具体过程为:
计算哈希值:HashMap
根据键对象的 hashCode()
方法计算出键的哈希值。哈希值是一个整数,用于确定键值对在 HashMap
中的存储位置。哈希值通常会经过一次二次哈希,以减少冲突并均匀分布。
确定存储位置(索引):通过哈希值和 HashMap
的容量(即桶数组的长度)计算出该键值对的存储索引。计算公式通常是 index = (n - 1) & hash
,其中 n
是桶数组的长度。
处理哈希冲突:如果计算出的索引位置已经有元素存在,就发生了哈希冲突。HashMap
使用链地址法(即链表或红黑树)来解决冲突。
- 链表:如果桶内的元素数量较少(默认阈值为8),
HashMap
使用链表存储多个键值对。 - 红黑树:如果桶内的元素数量较多(超过阈值8),
HashMap
会将链表转化为红黑树,以提高查找和插入的效率。
插入键值对:如果在计算出的索引位置找不到相同哈希值和相同键的元素,HashMap
会将新键值对插入到对应的位置。如果找到了相同的键,则更新该键对应的值。
扩容检查:每次插入新元素时,HashMap
会检查当前的负载因子(Load Factor
),如果超过了预设值(默认0.75),则会触发扩容操作。扩容时,HashMap
会将容量加倍,并将所有元素重新哈希分布到新的桶数组中。
get(Object key)
用于根据键从 HashMap
中检索对应的值。具体过程为:
计算哈希值:与 put
方法类似,HashMap
会根据传入的键对象计算出哈希值。
确定存储位置(索引):通过哈希值和桶数组的长度计算出该键的索引位置。
查找对应的键值对:HashMap
会在计算出的索引位置开始查找,首先检查桶内第一个节点的哈希值和键是否匹配。如果匹配,则返回该节点对应的值。如果不匹配且存在链表或红黑树,则沿着链表或树结构继续查找,直到找到匹配的键或者查找结束(未找到则返回 null
)。
二 介绍垃圾识别算法
引用计数法: 给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1;当引用计数器为0的时候,就说明这个对象没有被引用,也就是垃圾对象,等待回收; 缺点:无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以一般主流虚拟机都不采用这个方法;
可达性分析法 从一个被称为GC Roots的对象向下搜索,如果一个对象到GC Roots没有任何引用链相连接时,说明此对象不可用,但一个对象满足上述条件的时候,不会马上被回收,还需要进行两次标记;第一次标记:判断当前对象是否有finalize()方法并且该方法没有被执行过,若不存在则标记为垃圾对象,等待回收;若有的话,则进行第二次标记;第二次标记将当前对象放入F-Queue队列,并生成一个finalize线程去执行该方法,虚拟机不保证该方法一定会被执行,这是因为如果线程执行缓慢或进入了死锁,会导致回收系统的崩溃;如果执行了finalize方法之后仍然没有与GC Roots有直接或者间接的引用,则该对象会被回收。
在java中可以作为GC Roots的对象有以下几种:
虚拟机栈中引用的对象
方法区类静态属性引用的变量
方法区常量池引用的对象
本地方法栈JNI引用的对象
三 写一个懒汉式单例
public class Singleton {
// volatile 关键字确保 instance 在多线程环境下的可见性
private static volatile Singleton instance;
// 私有化构造方法,防止外部实例化
private Singleton() {
}
// 提供获取实例的全局访问点
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
四 讲讲java中的锁
ava 中的锁(Lock)是实现并发控制的基础工具,用于确保在多线程环境下共享资源的安全访问。锁的主要目的是在同一时间只允许一个线程访问共享资源,从而避免数据不一致的问题。常见的锁如:
synchronized
:简单易用,适合小规模并发控制,自动管理锁的获取和释放。ReentrantLock
:更灵活的锁机制,提供了公平锁、可重入性、中断响应等特性。ReadWriteLock
:适合读多写少的场景,提高并发性。
五 Reentrantlock是如何实现可重入锁的
ReetrantLock是一个可重入的独占锁,主要有两个特性,一个是支持公平锁和非公平锁,一个是可重入。 ReetrantLock实现依赖于AQS(AbstractQueuedSynchronizer)。
ReetrantLock主要依靠AQS维护一个阻塞队列,多个线程对加锁时,失败则会进入阻塞队列。等待唤醒,重新尝试加锁。
ReentrantLock
的可重入性通过内部计数器和对当前持有锁线程的跟踪来实现。每次同一线程成功获取锁时,计数器增加;每次释放锁时,计数器减少。当计数器为 0 时,锁完全释放,其他线程可以获取锁。通过这种机制,ReentrantLock
可以允许同一线程多次进入受锁保护的代码块,而不导致死锁。
六 如果一张表中存在a,b字段,并且a,b单独建立索引,where a=x and b = y索引命中情况
MySQL 可能会选择使用其中一个单列索引,或者在某些情况下采用索引合并。
七 如果数据表中查询的数据访问是1万到1万零10,如何优化sql只查询10条数据
要优化查询指定范围内的数据,直接使用 LIMIT
和 OFFSET
是最简单的方法,但直接使用 OFFSET
可能会导致性能问题,因为数据库需要扫描和跳过大量数据。我们可以通过使用覆盖索引、避免深度分页、ID 范围查询或者将表进行分区的方法,进一步优化查询性能,减少数据扫描量和 I/O 操作。
八 如果存在一个字段为big_int, 大小为1kb,索引树的深度为3,问最多能存储多少条数据,提供计算思路即可
计算每个节点的容量:确定节点大小和键值对大小,计算每个节点可以容纳的键值对数。
计算各级节点的总数:从根节点到叶子节点,逐级计算各层节点的总数。
计算叶子节点的存储数据量:叶子节点存储实际数据,通过叶子节点总数计算出最大存储数据量。