Java常见面试题
1、ArrayList和LinkedList有哪些区别
1.底层数据结构不同:ArrayList底层基于数组实现,如果空间不够,需要扩容;LinkedList底层基于链表实现,不需要扩容。
2.适用场景不同:ArrayList更合适随机查找,LinkedList更适合删除和添加。
3.都实现了List接口,但是ListedList还额外实现了Deque接口,属于双端队列,可以在头部和尾部添加或删除元素。
2、ConcurrentHashMap的扩容机制
1.7版本
1. 1.7版本的ConcurerentHashMap是基于Segment分段实现的
2. 每个Segment相当于一个小型的HashMap
3. 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
4. 先生成新的数组,然后转移元素到新数组中
5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8版本
1. 1.8版本的ConcurrentHashMap不再基于Segment实现
2. 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容,那么该线程一起进行扩容
3. 如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
4. 支持多个线程同时扩容
5. 扩容之前也先生成一个新的数组
6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移
3、HashMap的实现原理
1、在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
2、HashMap每个元素都是链表(可能表述不准确)的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,同一各链表上的Hash值是相同的,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
3、HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
4、HashMap的线程是不安全的,多线程环境中推荐是ConcurrentHashMap。
5、HashMap容量默认大小是16,默认的负载因子是0.75,当元素个数超过容量的0.75,容量就会自动扩容为之前的两倍。扩容是一个特别耗性能的操作,在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
6、在1.7中采用表头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题;在1.8中采用尾部插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
7、1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置。
8、1.7中是先扩容后插入新值的,1.8中是先插值再扩容
4、Hashtable、HashMap、TreeMap的区别
1.Hashtable的key、value都不能为null;HashMap的key、value可以为null,不过只能有一个key为null,但可以有多个null的value;TreeMap键、值都不能为null。
2.Hashtable、HashMap具有无序特性。TreeMap是利用红黑树
实现的(树中的每个节点的值都会大于或等于它的左子树中的所有节点的值,并且小于或等于它的右子树中的所有节点的值),实现了SortMap接口,能够对保存的记录根据键进行排序。所以一般需求排序的情况下首选TreeMap,默认按键的升序排序
(深度优先搜索),也可以自定义实现Comparator接口实现排序方式。
3.HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力,HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
4.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
5.哈希值的使用不同,HashTable直接使用对象的hashCode。
6.HashTable使用Enumeration,HashMap使用Iterator。
7.TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
5、redis的持久化机制
1.RDB
RDB: Redis DataBase将某一下时刻的内存快照(Snapshot)以二进制的方式写入磁盘。
手动触发:
- save命令,使Redis处于阻塞状态,直到RDB持久化完成,(因为redis是单线程(主进程)的)才会响应其他客户端发来的命令,所以在生产环境一定要慎用。
- bgsave命令,fork出一个子进程执行持久化,主进程只在fork过程中有短暂的阻塞,子进程创建后(利用操作系统CopyOnWrite写时拷贝的机制保障快照文件的准确性:父进程写的时候copy一个副本来修改数据,从而保证子进程生成的快照一定是某一时刻的),主进程就可以响应客户端请求了。
自动触发:
- save m n : 在m秒内,如果有n个键发生改变,则自动触发持久化,通过bgsave执行,如果设置多个、只要满足其一就会触发,配置文件有默认配置(可以注释掉)
- flushall: 用于清空redis所有的数据库,flushdb清空当前redis所在库数据(默认是0号数据库),会清空RDB文件,同时也会生成dump.rdb,内容为空。
- 主从同步:全量同步时会自动触发bgsave命令(增量同步不会),生成rdb发送给从节点
优点:
1、整个Redis数据库将只包含一个文件dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所有是IO最大化。使用单独子进程来进行持久化,主进程不会进程任何IO操作,保证了redis的高性能。
4、相对于数据集大时,比AOF的启动效率更高。