最近呢面试了鞋厂;
在面试之前也搜索了一下鞋厂的面经,据说重点是GC后来也的确是考到了;
Ok, 现在开始分析这次面试都考了什么;
1.HashMap 的实现方法
这个面试官似乎是想要得到一个比较深层次的回答
我回答了是<K,V>结构,以及描述了一些插入问题。注意HashMap是非线程安全的。
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
作者:美团点评技术团队
链接:https://zhuanlan.zhihu.com/p/21673805
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Java 8系列之重新认识<em>HashMap</em> - 美团点评技术团队的文章 - 知乎https://zhuanlan.zhihu.com/p/21673805
2.ArrayList 和 LinkList的区别
【深入理解Java集合框架】Java <em>ArrayList</em>源码剖析 - 极乐君的文章 - 知乎https://zhuanlan.zhihu.com/p/24709456
具体可以参考这篇文章,问了一下ArrayList与普通数组的区别,还有就是插入元素的时间开销。
对数据结构的了解还是比较有要求。
3.数据库的脏读
这个比较基础
当一个事务允许读取另外一个事务修改但未提交的数据时,就可能发生脏读。
脏读和不可重复读(non-repeatable reads)类似。事务2没有提交造成事务1的语句1两次执行得到不同的结果集。在未提交读隔离级别唯一禁止的是更新混乱,即早期的更新可能出现在后来更新之前的结果集中。
在我们的例子中,事务2修改了一行,但是没有提交,事务1读了这个没有提交的数据。现在如果事务2回滚了刚才的修改或者做了另外的修改的话,事务1中查到的数据就是不正确的了。
事务 1 | 事务 2 |
---|---|
/* Query 1 */
SELECT age FROM users WHERE id = 1;
/* will read 20 */
| |
/* Query 2 */
UPDATE users SET age = 21 WHERE id = 1;
/* No commit here */
| |
/* Query 1 */
SELECT age FROM users WHERE id = 1;
/* will read 21 */
| |
ROLLBACK; /* lock-based DIRTY READ */
|
在这个例子中,事务2回滚后就没有id是1,age是21的数据行了。
在一次事务中,当一行数据获取两遍得到不同的结果表示发生了“不可重复读”.
在基于锁的并发控制中“不可重复读”现象发生在当执行SELECT 操作时没有获得读锁或者SELECT操作执行完后马上释放了读锁; 多版本并发控制中当没有要求一个提交冲突的事务回滚也会发生“不可重复读”现象。
事务 1 | 事务 2 |
---|---|
/* Query 1 */
SELECT * FROM users WHERE id = 1;
| |
/* Query 2 */
UPDATE users SET age = 21 WHERE id = 1;
COMMIT; /* in multiversion concurrency
control, or lock-based READ COMMITTED */
| |
/* Query 1 */
SELECT * FROM users WHERE id = 1;
COMMIT; /* lock-based REPEATABLE READ */
|
在这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。在可序列化(SERIALIZABLE)和可重复读的隔离级别,数据库在第二次SELECT请求的时候应该返回事务2更新之前的值。在提交读和未提交读,返回的是更新之后的值,这个现象就是不可重复读。
有两种策略可以避免不可重复读。一个是要求事务2延迟到事务1提交或者回滚之后再执行。这种方式实现了T1, T2 的串行化调度。串行化调度可以支持可重复读。
另一种策略是多版本并发控制。为了得到更好的并发性能,允许事务2先提交。但因为事务1在事务2之前开始,事务1必须在其开始执行时间点的数据库的快照上面操作。当事务1最终提交时候,数据库会检查其结果是否等价于T1, T2串行调度。如果等价,则允许事务1提交,如果不等价,事务1需要回滚并抛出个串行化失败的错误。
使用基于锁的并发控制,在可重复读的隔离级别中,ID=1的行会被锁住,在事务1提交或回滚前一直阻塞语句2的执行。在提交读的级别,语句1第二次执行,age已经被修改了。
在多版本并发控制机制下,可序列化(SERIALIZABLE)级别,两次SELECT语句读到的数据都是事务1开始的快照,因此返回同样的数据。但是,如果事务1试图UPDATE这行数据,事务1会被要求回滚并抛出一个串行化失败的错误。
在提交读隔离级别,每个语句读到的是语句执行前的快照,因此读到更新前后不同的值。在这种级别不会有串行化的错误(因为这种级别不要求串行化),事务1也不要求重试。
在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为“幻影读(phantom read)”
当事务没有获取范围锁的情况下执行SELECT ... WHERE操作可能会发生“幻影读”。
“幻影读”是不可重复读的一种特殊场景:当事务1两次执行SELECT ... WHERE检索一定范围内数据的操作中间,事务2在这个表中创建了(如INSERT)了一行新数据,这条新数据正好满足事务1的“WHERE”子句。
事务 1 | 事务 2 |
---|---|
/* Query 1 */
SELECT * FROM users
WHERE age BETWEEN 10 AND 30;
| |
/* Query 2 */
INSERT INTO users VALUES ( 3, 'Bob', 27 );
COMMIT;
| |
/* Query 1 */
SELECT * FROM users
WHERE age BETWEEN 10 AND 30;
|
需要指出的是事务1执行了两遍同样的查询语句。如果设了最高的隔离级别,两次会得到同样的结果集,这也正是数据库在可序列化(SERIALIZABLE)隔离级别上需要满足的。但是在较低的隔离级别上,第二次查询可能会得到不同的结果集。
在可序列化隔离级别,查询语句1在age从10到30的记录上加锁,事务2只能阻塞直至事务1提交。在可重复读级别,这个范围不会被锁定,允许记录插入,因此第二次执行语句1的结果中会包括新插入的行。
4.设计算法
两个已经排好序的数组,要找出他们之间相同的元素