今日八股:LinkedList 插入快有个前提,reactive 也不是包一层就好

今日八股:LinkedList 插入快有个前提,reactive 也不是包一层就好

今天的练习

三轮都答完了。上午 Java 从 final 和常量收尾,接上 ArrayList、LinkedList、HashMap 的用法;下午前端考 ref 和 reactive、深浅拷贝、外边距合并;收工前是索引和事务隔离级别。这轮混掉的地方不多,两处值得写下来:LinkedList 插入快其实有个前提,reactive 整体换值的写法我当时说错了。

上午 Java:final、常量,还有两个 List

① final List<String> list = new ArrayList<>(),list 不能重新赋值了,还能 list.add("a") 吗?为什么?
② 变量写成 public static final,你项目里会拿它放什么?
③ get(99) 取第 100 个元素,ArrayList 和 LinkedList 哪个快?快在哪一步?
④ 往中间插一个元素,教科书上说 LinkedList 占优,占的是哪一步的开销?
⑤ 项目里 LinkedList 用得少,为什么大家默认还是 ArrayList?

第一问答对了:final 锁的是引用不是对象,add 照常能加,所以别把 final 理解成“内容不能变”。常量这次举的是重试次数,比上次那个 Spring 注入靠谱多了。

关于常量有个坑我原来不知道:static final 配字面量的时候是编译期常量,编译器会把值直接内联进用到它的地方。也就是说改了常量不重新编译那个类,跑的还是旧值,这种问题查起来很费劲。

取元素那问对,ArrayList 按下标算偏移一步到位,LinkedList 得从头一个个走。

插中间那问我答的是“改指针那一步开销小”,但前提漏了:LinkedList 插之前得先遍历找到那个位置,这一步是 O(n),而且节点在内存里东一块西一块。真跑起来,中间插入常常还是 ArrayList 快,它找位置是常数时间,挪数据虽然是 O(n),但动的是内存里连续的一段,CPU 拷起来比链表一路跳节点快得多。教科书那句“LinkedList 插入快”,前提是你已经站在要插的那个节点上了。

为什么默认用 ArrayList,我答了取值快、方便、内存开销小,都对。还差一条:LinkedList 每个节点除了值还存前后两个指针,每个元素都要多包一个 Node 对象,Node 里存着前驱、后继和值三个引用,内存和 GC 压力都比 ArrayList 大。它真正的用武之地是当队列或者栈,因为它实现了 Deque。

HashMap 那组基本都答上了。有一个地方值得记:我说它和 ArrayList 的区别是“一个是 list 一个是 map”,等于把题目重复了一遍。具体的差别是 ArrayList 里位置就是下标,第十个元素永远在 index 9;HashMap 里元素放哪由 key 的 hash 决定,你说不出“第几个”,只能按 key 找。另外 put 同一个 key 会把被覆盖的旧值返回给你,第一次 put 返回 null,这个是判断“是不是新增”的常用写法。

下午前端:ref、reactive 和深浅拷贝

① const count = ref(0),在 script 里读它改它怎么写?模板 {{ }} 里要不要加 .value?② reactive(0) 存数字行不行?为什么?③ 用 reactive 定义的对象,写一句 state = { count: 1 } 整体重新赋值,页面会跟着更新吗?想避开这个坑,定义的时候换成什么写法?④ const a = { n: 1 },const b = a,b.n = 2,a.n 是多少?⑤ 想让 b 改了不影响 a,{ ...a } 够用吗?要真正互不影响怎么写?⑥ 两个 div 相邻,上面 margin-bottom: 30px,下面 margin-top: 20px,中间隔多远?⑦ 父元素没 padding 没 border,子元素写 margin-top: 20px,会和父元素隔开吗?这现象叫什么?

ref 和 reactive 前两问答得干净:script 里读写都要 .value,模板里 Vue 自动解包;reactive 只认对象,传数字它会警告一句然后把原值还给你,那个值不是响应式的,数组倒是可以。

第三问翻车了。reactive 劫持的是对象内部的属性读写,你让变量指向一个新对象,代理就断了,页面不会更新,这部分我答对了。但我说“在外面包一层 reactive 就好了”,这句有问题:再套一层 reactive(state) 没有任何效果。正解是换成 ref,ref 的整体替换是能追踪的(state.value = {...}),或者在它外面放个容器、把对象挂在属性上(obj.inner = {...}),动的是属性不是变量本身。

赋值和拷贝那两问全对。引用类型赋的是地址,b 改了 a 跟着变;展开运算符只复制第一层,里面那层还是同一个对象。深拷贝我写的是 JSON.parse(JSON.stringify(a)),能跑,边界是函数、undefined、Symbol 会被丢掉,Date 会变成字符串,有循环引用直接报错。现在浏览器和 Node 17 以上有 structuredClone(a),比这招稳。

外边距那两问也对。上下相邻取大的那个,30px;父子那个叫外边距合并,子元素的 margin 会顶穿父元素,带着父元素一起下移。我给的 overflow: hidden 能解决,是因为父元素变成了 BFC,不过这算其中一种,更标准的是 display: flow-root,或者给父元素加 padding、border。

收工前:索引和隔离级别

① 几百万行的表给某个字段加索引,按它查就快了,为什么?② 加了索引之后 INSERT/UPDATE/DELETE 是变快还是变慢?③ WHERE name LIKE '%张%' 和 LIKE '张%',哪个走索引?④ age 上建了索引,WHERE age + 1 = 20 能用上吗?⑤ A 转账把余额从 100 改成 200 并提交,B 在同一个事务里再读,READ COMMITTED 读到多少?RR 读到多少?为什么不一样?

索引那个比喻我用对了,就是书的目录。想再具体一点:索引是把字段值和主键值按顺序排好的一棵树,按这个字段查的时候从根往下走几层就定位到,再拿主键回去取整行(这一步叫回表);全表扫是一行一行比过去。写操作变慢也对,每次增删改都要顺带维护索引,所以索引不是越多越好;只有更新的是索引列才会动索引,改普通字段不影响。

模糊查询那问也对,'张%' 能利用有序性做前缀匹配,'%张%' 前面是通配符,只能全表扫。业务上真要做包含查询,加索引也没用,得上全文索引或者丢给 ES。

对索引列做运算用不上索引,我答对了。同一类失效的还有套函数、隐式类型转换,比如 phone 字段是 varchar,你写 where phone = 13800138000,索引就白建了。想用上就把条件改回裸列,age + 1 = 20 写成 age = 19。

隔离级别那两问是补上次的坑。RC 每次 SELECT 都重新生成 read view,所以读到 200;RR 只在事务里第一次读的时候生成一次,之后复用那个快照,所以还是 100。这次收住了。顺手记一条可能被追问的:RR 下这个快照只对普通 SELECT 生效,写 select ... for update 属于当前读,会拿到最新数据,这也是有人觉得“RR 也能读到新值”的原因。

← 返回文章列表