mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 13:16:41 +08:00
1541 lines
36 KiB
HTML
1541 lines
36 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>答疑篇:代码篇思考题集锦(二).md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/00 开篇词 业务代码真的会有这么多坑?.md.html">00 开篇词 业务代码真的会有这么多坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/01 使用了并发工具类库,线程安全就高枕无忧了吗?.md.html">01 使用了并发工具类库,线程安全就高枕无忧了吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/02 代码加锁:不要让“锁”事成为烦心事.md.html">02 代码加锁:不要让“锁”事成为烦心事.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/03 线程池:业务代码最常用也最容易犯错的组件.md.html">03 线程池:业务代码最常用也最容易犯错的组件.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/04 连接池:别让连接池帮了倒忙.md.html">04 连接池:别让连接池帮了倒忙.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/05 HTTP调用:你考虑到超时、重试、并发了吗?.md.html">05 HTTP调用:你考虑到超时、重试、并发了吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/06 2成的业务代码的Spring声明式事务,可能都没处理正确.md.html">06 2成的业务代码的Spring声明式事务,可能都没处理正确.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/07 数据库索引:索引并不是万能药.md.html">07 数据库索引:索引并不是万能药.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/08 判等问题:程序里如何确定你就是你?.md.html">08 判等问题:程序里如何确定你就是你?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/09 数值计算:注意精度、舍入和溢出问题.md.html">09 数值计算:注意精度、舍入和溢出问题.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/10 集合类:坑满地的List列表操作.md.html">10 集合类:坑满地的List列表操作.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/11 空值处理:分不清楚的null和恼人的空指针.md.html">11 空值处理:分不清楚的null和恼人的空指针.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/12 异常处理:别让自己在出问题的时候变为瞎子.md.html">12 异常处理:别让自己在出问题的时候变为瞎子.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/13 日志:日志记录真没你想象的那么简单.md.html">13 日志:日志记录真没你想象的那么简单.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/14 文件IO:实现高效正确的文件读写并非易事.md.html">14 文件IO:实现高效正确的文件读写并非易事.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/15 序列化:一来一回你还是原来的你吗?.md.html">15 序列化:一来一回你还是原来的你吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/16 用好Java 8的日期时间类,少踩一些“老三样”的坑.md.html">16 用好Java 8的日期时间类,少踩一些“老三样”的坑.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/17 别以为“自动挡”就不可能出现OOM.md.html">17 别以为“自动挡”就不可能出现OOM.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/18 当反射、注解和泛型遇到OOP时,会有哪些坑?.md.html">18 当反射、注解和泛型遇到OOP时,会有哪些坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/19 Spring框架:IoC和AOP是扩展的核心.md.html">19 Spring框架:IoC和AOP是扩展的核心.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/20 Spring框架:框架帮我们做了很多工作也带来了复杂度.md.html">20 Spring框架:框架帮我们做了很多工作也带来了复杂度.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/21 代码重复:搞定代码重复的三个绝招.md.html">21 代码重复:搞定代码重复的三个绝招.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/22 接口设计:系统间对话的语言,一定要统一.md.html">22 接口设计:系统间对话的语言,一定要统一.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/23 缓存设计:缓存可以锦上添花也可以落井下石.md.html">23 缓存设计:缓存可以锦上添花也可以落井下石.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/24 业务代码写完,就意味着生产就绪了?.md.html">24 业务代码写完,就意味着生产就绪了?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/25 异步处理好用,但非常容易用错.md.html">25 异步处理好用,但非常容易用错.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/26 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?.md.html">26 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/27 数据源头:任何客户端的东西都不可信任.md.html">27 数据源头:任何客户端的东西都不可信任.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/28 安全兜底:涉及钱时,必须考虑防刷、限量和防重.md.html">28 安全兜底:涉及钱时,必须考虑防刷、限量和防重.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/29 数据和代码:数据就是数据,代码就是代码.md.html">29 数据和代码:数据就是数据,代码就是代码.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/30 如何正确保存和传输敏感数据?.md.html">30 如何正确保存和传输敏感数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/31 加餐1:带你吃透课程中Java 8的那些重要知识点(一).md.html">31 加餐1:带你吃透课程中Java 8的那些重要知识点(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/32 加餐2:带你吃透课程中Java 8的那些重要知识点(二).md.html">32 加餐2:带你吃透课程中Java 8的那些重要知识点(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/33 加餐3:定位应用问题,排错套路很重要.md.html">33 加餐3:定位应用问题,排错套路很重要.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/34 加餐4:分析定位Java问题,一定要用好这些工具(一).md.html">34 加餐4:分析定位Java问题,一定要用好这些工具(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/35 加餐5:分析定位Java问题,一定要用好这些工具(二).md.html">35 加餐5:分析定位Java问题,一定要用好这些工具(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/36 加餐6:这15年来,我是如何在工作中学习技术和英语的?.md.html">36 加餐6:这15年来,我是如何在工作中学习技术和英语的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/37 加餐7:程序员成长28计.md.html">37 加餐7:程序员成长28计.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/38 加餐8:Java程序从虚拟机迁移到Kubernetes的一些坑.md.html">38 加餐8:Java程序从虚拟机迁移到Kubernetes的一些坑.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(一).md.html">答疑篇:代码篇思考题集锦(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(三).md.html">答疑篇:代码篇思考题集锦(三).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(二).md.html">答疑篇:代码篇思考题集锦(二).md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:加餐篇思考题答案合集.md.html">答疑篇:加餐篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:安全篇思考题答案合集.md.html">答疑篇:安全篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:设计篇思考题答案合集.md.html">答疑篇:设计篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/结束语 写代码时,如何才能尽量避免踩坑?.md.html">结束语 写代码时,如何才能尽量避免踩坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>答疑篇:代码篇思考题集锦(二)</h1>
|
||
|
||
<p>你好,我是朱晔。</p>
|
||
|
||
<p>今天,我们继续一起分析这门课第 7~12 讲的课后思考题。这些题目涉及了数据库索引、判等问题、数值计算、集合类、空值处理和异常处理的 12 道问题。</p>
|
||
|
||
<p>接下来,我们就一一具体分析吧。</p>
|
||
|
||
<h3>07 | 数据库索引:索引并不是万能药</h3>
|
||
|
||
<p>问题 1:在介绍二级索引代价时,我们通过 EXPLAIN 命令看到了索引覆盖和回表的两种情况。你能用 optimizer trace 来分析一下这两种情况的成本差异吗?</p>
|
||
|
||
<p>答:如下代码所示,打开 optimizer_trace 后,再执行 SQL 就可以查询 information_schema.OPTIMIZER_TRACE 表查看执行计划了,最后可以关闭 optimizer_trace 功能:</p>
|
||
|
||
<pre><code>SET optimizer_trace="enabled=on";
|
||
|
||
|
||
|
||
SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00:00';
|
||
|
||
|
||
|
||
SELECT * FROM information_schema.OPTIMIZER_TRACE;
|
||
|
||
|
||
|
||
SET optimizer_trace="enabled=off";
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>假设我们为表 person 的 NAME 和 SCORE 列建了联合索引,那么下面第二条语句应该可以走索引覆盖,而第一条语句需要回表:</p>
|
||
|
||
<pre><code>explain select * from person where NAME='name1';
|
||
|
||
|
||
|
||
explain select NAME,SCORE from person where NAME='name1';
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>通过观察 OPTIMIZER_TRACE 的输出可以看到,索引覆盖(index_only=true)的成本是 1.21 而回表查询(index_only=false)的是 2.21,也就是索引覆盖节省了回表的成本 1。</p>
|
||
|
||
<p>索引覆盖:</p>
|
||
|
||
<pre><code>analyzing_range_alternatives": {
|
||
|
||
|
||
|
||
"range_scan_alternatives": [
|
||
|
||
|
||
|
||
{
|
||
|
||
|
||
|
||
"index": "name_score",
|
||
|
||
|
||
|
||
"ranges": [
|
||
|
||
|
||
|
||
"name1 <= name <= name1"
|
||
|
||
|
||
|
||
] /* ranges */,
|
||
|
||
|
||
|
||
"index_dives_for_eq_ranges": true,
|
||
|
||
|
||
|
||
"rowid_ordered": false,
|
||
|
||
|
||
|
||
"using_mrr": false,
|
||
|
||
|
||
|
||
"index_only": true,
|
||
|
||
|
||
|
||
"rows": 1,
|
||
|
||
|
||
|
||
"cost": 1.21,
|
||
|
||
|
||
|
||
"chosen": true
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>回表:</p>
|
||
|
||
<pre><code>"range_scan_alternatives": [
|
||
|
||
|
||
|
||
{
|
||
|
||
|
||
|
||
"index": "name_score",
|
||
|
||
|
||
|
||
"ranges": [
|
||
|
||
|
||
|
||
"name1 <= name <= name1"
|
||
|
||
|
||
|
||
] /* ranges */,
|
||
|
||
|
||
|
||
"index_dives_for_eq_ranges": true,
|
||
|
||
|
||
|
||
"rowid_ordered": false,
|
||
|
||
|
||
|
||
"using_mrr": false,
|
||
|
||
|
||
|
||
"index_only": false,
|
||
|
||
|
||
|
||
"rows": 1,
|
||
|
||
|
||
|
||
"cost": 2.21,
|
||
|
||
|
||
|
||
"chosen": true
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>问题 2:索引除了可以用于加速搜索外,还可以在排序时发挥作用,你能通过 EXPLAIN 来证明吗?你知道,针对排序在什么情况下,索引会失效吗?</p>
|
||
|
||
<p>答:排序使用到索引,在执行计划中的体现就是 key 这一列。如果没有用到索引,会在 Extra 中看到 Using filesort,代表使用了内存或磁盘进行排序。而具体走内存还是磁盘,是由 sort_buffer_size 和排序数据大小决定的。</p>
|
||
|
||
<p>排序无法使用到索引的情况有:</p>
|
||
|
||
<p>对于使用联合索引进行排序的场景,多个字段排序 ASC 和 DESC 混用;</p>
|
||
|
||
<p>a+b 作为联合索引,按照 a 范围查询后按照 b 排序;</p>
|
||
|
||
<p>排序列涉及到的多个字段不属于同一个联合索引;</p>
|
||
|
||
<p>排序列使用了表达式。</p>
|
||
|
||
<p>其实,这些原因都和索引的结构有关。你可以再有针对性地复习下第 07 讲的聚簇索引和二级索引部分。</p>
|
||
|
||
<h3>08 | 判等问题:程序里如何确定你就是你?</h3>
|
||
|
||
<p>问题 1:在实现 equals 时,我是先通过 getClass 方法判断两个对象的类型,你可能会想到还可以使用 instanceof 来判断。你能说说这两种实现方式的区别吗?</p>
|
||
|
||
<p>答:事实上,使用 getClass 和 instanceof 这两种方案都是可以判断对象类型的。它们的区别就是,getClass 限制了这两个对象只能属于同一个类,而 instanceof 却允许两个对象是同一个类或其子类。</p>
|
||
|
||
<p>正是因为这种区别,不同的人对这两种方案有不同的喜好,争论也很多。在我看来,你只需要根据自己的要求去选择。补充说明一下,Lombok 使用的是 instanceof 的方案。</p>
|
||
|
||
<p>问题 2:在“hashCode 和 equals 要配对实现”这一节的例子中,我演示了可以通过 HashSet 的 contains 方法判断元素是否在 HashSet 中。那同样是 Set 的 TreeSet,其 contains 方法和 HashSet 的 contains 方法有什么区别吗?</p>
|
||
|
||
<p>答:HashSet 基于 HashMap,数据结构是哈希表。所以,HashSet 的 contains 方法,其实就是根据 hashcode 和 equals 去判断相等的。</p>
|
||
|
||
<p>TreeSet 基于 TreeMap,数据结构是红黑树。所以,TreeSet 的 contains 方法,其实就是根据 compareTo 去判断相等的。</p>
|
||
|
||
<h3>09 | 数值计算:注意精度、舍入和溢出问题</h3>
|
||
|
||
<p>问题 1:BigDecimal提供了 8 种舍入模式,你能通过一些例子说说它们的区别吗?</p>
|
||
|
||
<p>答:@Darren 同学的留言非常全面,梳理得也非常清楚了。这里,我对他的留言稍加修改,就是这个问题的答案了。</p>
|
||
|
||
<p>第一种,ROUND_UP,舍入远离零的舍入模式,在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加 1)。 需要注意的是,此舍入模式始终不会减少原始值。</p>
|
||
|
||
<p>第二种,ROUND_DOWN,接近零的舍入模式,在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加 1,即截断)。 需要注意的是,此舍入模式始终不会增加原始值。</p>
|
||
|
||
<p>第三种,ROUND_CEILING,接近正无穷大的舍入模式。 如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同; 如果为负,则舍入行为与 ROUND_DOWN 相同。 需要注意的是,此舍入模式始终不会减少原始值。</p>
|
||
|
||
<p>第四种,ROUND_FLOOR,接近负无穷大的舍入模式。 如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同; 如果为负,则舍入行为与 ROUND_UP 相同。 需要注意的是,此舍入模式始终不会增加原始值。</p>
|
||
|
||
<p>第五种,ROUND_HALF_UP,向“最接近的”数字舍入。如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则,舍入行为与 ROUND_DOWN 相同。 需要注意的是,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。</p>
|
||
|
||
<p>第六种,ROUND_HALF_DOWN,向“最接近的”数字舍入。如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则,舍入行为与 ROUND_DOWN 相同(五舍六入)。</p>
|
||
|
||
<p>第七种,ROUND_HALF_EVEN,向“最接近的”数字舍入。这种算法叫做银行家算法,具体规则是,四舍六入,五则看前一位,如果是偶数舍入,如果是奇数进位,比如 5.5 -> 6,2.5 -> 2。</p>
|
||
|
||
<p>第八种,ROUND_UNNECESSARY,假设请求的操作具有精确的结果,也就是不需要进行舍入。如果计算结果产生不精确的结果,则抛出 ArithmeticException。</p>
|
||
|
||
<p>问题 2:数据库(比如 MySQL)中的浮点数和整型数字,你知道应该怎样定义吗?又如何实现浮点数的准确计算呢?</p>
|
||
|
||
<p>答:MySQL 中的整数根据能表示的范围有 TINYINT、SMALLINT、MEDIUMINT、INTEGER、BIGINT 等类型,浮点数包括单精度浮点数 FLOAT 和双精度浮点数 DOUBLE 和 Java 中的 float/double 一样,同样有精度问题。</p>
|
||
|
||
<p>要解决精度问题,主要有两个办法:</p>
|
||
|
||
<p>第一,使用 DECIMAL 类型(和那些 INT 类型一样,都属于严格数值数据类型),比如 DECIMAL(13, 2) 或 DECIMAL(13, 4)。</p>
|
||
|
||
<p>第二,使用整数保存到分,这种方式容易出错,万一读的时候忘记 /100 或者是存的时候忘记 *100,可能会引起重大问题。当然了,我们也可以考虑将整数和小数分开保存到两个整数字段。</p>
|
||
|
||
<h3>10 | 集合类:坑满地的 List 列表操作</h3>
|
||
|
||
<p>问题 1:调用类型是 Integer 的 ArrayList 的 remove 方法删除元素,传入一个 Integer 包装类的数字和传入一个 int 基本类型的数字,结果一样吗?</p>
|
||
|
||
<p>答:传 int 基本类型的 remove 方法是按索引值移除,返回移除的值;传 Integer 包装类的 remove 方法是按值移除,返回列表移除项目之前是否包含这个值(是否移除成功)。</p>
|
||
|
||
<p>为了验证两个 remove 方法重载的区别,我们写一段测试代码比较一下:</p>
|
||
|
||
<pre><code>private static void removeByIndex(int index) {
|
||
|
||
|
||
|
||
List<Integer> list =
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toCollection(ArrayList::new));
|
||
|
||
|
||
|
||
System.out.println(list.remove(index));
|
||
|
||
|
||
|
||
System.out.println(list);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
private static void removeByValue(Integer index) {
|
||
|
||
|
||
|
||
List<Integer> list =
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toCollection(ArrayList::new));
|
||
|
||
|
||
|
||
System.out.println(list.remove(index));
|
||
|
||
|
||
|
||
System.out.println(list);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>测试一下 removeByIndex(4),通过输出可以看到第五项被移除了,返回 5:</p>
|
||
|
||
<pre><code>5
|
||
|
||
|
||
|
||
[1, 2, 3, 4, 6, 7, 8, 9, 10]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>而调用 removeByValue(Integer.valueOf(4)),通过输出可以看到值 4 被移除了,返回 true:</p>
|
||
|
||
<pre><code>true
|
||
|
||
|
||
|
||
[1, 2, 3, 5, 6, 7, 8, 9, 10]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>问题 2:循环遍历 List,调用 remove 方法删除元素,往往会遇到 ConcurrentModificationException,原因是什么,修复方式又是什么呢?</p>
|
||
|
||
<p>答:原因是,remove 的时候会改变 modCount,通过迭代器遍历就会触发 ConcurrentModificationException。我们看下 ArrayList 类内部迭代器的相关源码:</p>
|
||
|
||
<pre><code>public E next() {
|
||
|
||
|
||
|
||
checkForComodification();
|
||
|
||
|
||
|
||
int i = cursor;
|
||
|
||
|
||
|
||
if (i >= size)
|
||
|
||
|
||
|
||
throw new NoSuchElementException();
|
||
|
||
|
||
|
||
Object[] elementData = ArrayList.this.elementData;
|
||
|
||
|
||
|
||
if (i >= elementData.length)
|
||
|
||
|
||
|
||
throw new ConcurrentModificationException();
|
||
|
||
|
||
|
||
cursor = i + 1;
|
||
|
||
|
||
|
||
return (E) elementData[lastRet = i];
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
final void checkForComodification() {
|
||
|
||
|
||
|
||
if (modCount != expectedModCount)
|
||
|
||
|
||
|
||
throw new ConcurrentModificationException();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>要修复这个问题,有以下两种解决方案。</p>
|
||
|
||
<p>第一种,通过 ArrayList 的迭代器 remove。迭代器的 remove 方法会维护一个 expectedModCount,使其与 ArrayList 的 modCount 保持一致:</p>
|
||
|
||
<pre><code>List<String> list =
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10).mapToObj(String::valueOf).collect(Collectors.toCollection(ArrayList::new));
|
||
|
||
|
||
|
||
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
|
||
|
||
|
||
|
||
String next = iterator.next();
|
||
|
||
|
||
|
||
if ("2".equals(next)) {
|
||
|
||
|
||
|
||
iterator.remove();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
System.out.println(list);
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>第二种,直接使用 removeIf 方法,其内部使用了迭代器的 remove 方法:</p>
|
||
|
||
<pre><code>List<String> list =
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10).mapToObj(String::valueOf).collect(Collectors.toCollection(ArrayList::new));
|
||
|
||
|
||
|
||
list.removeIf(item -> item.equals("2"));
|
||
|
||
|
||
|
||
System.out.println(list);
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h3>11 | 空值处理:分不清楚的 null 和恼人的空指针</h3>
|
||
|
||
<p>问题 1:ConcurrentHashMap 的 Key 和 Value 都不能为 null,而 HashMap 却可以,你知道这么设计的原因是什么吗?TreeMap、Hashtable 等 Map 的 Key 和 Value 是否支持 null 呢?</p>
|
||
|
||
<p>答:原因正如 ConcurrentHashMap 的作者所说:</p>
|
||
|
||
<p>The main reason that nulls aren’t allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can’t be accommodated. The main one is that if map.get(key) returns null, you can’t detect whether the key explicitly maps to null vs the key isn’t mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.</p>
|
||
|
||
<p>如果 Value 为 null 会增加二义性,也就是说多线程情况下 map.get(key) 返回 null,我们无法区分 Value 原本就是 null 还是 Key 没有映射,Key 也是类似的原因。此外,我也更同意他的观点,就是普通的 Map 允许 null 是否是一个正确的做法,也值得商榷,因为这会增加犯错的可能性。</p>
|
||
|
||
<p>Hashtable 也是线程安全的,所以 Key 和 Value 不可以是 null。</p>
|
||
|
||
<p>TreeMap 是线程不安全的,但是因为需要排序,需要进行 key 的 compareTo 方法,所以 Key 不能是 null,而 Value 可以是 null。</p>
|
||
|
||
<p>问题 2:对于 Hibernate 框架,我们可以使用 @DynamicUpdate 注解实现字段的动态更新。那么,对于 MyBatis 框架来说,要如何实现类似的动态 SQL 功能,实现插入和修改 SQL 只包含 POJO 中的非空字段呢?</p>
|
||
|
||
<p>答:MyBatis 可以通过动态 SQL 实现:</p>
|
||
|
||
<pre><code><select id="findUser" resultType="User">
|
||
|
||
|
||
|
||
SELECT * FROM USER
|
||
|
||
|
||
|
||
WHERE 1=1
|
||
|
||
|
||
|
||
<if test="name != null">
|
||
|
||
|
||
|
||
AND name like #{name}
|
||
|
||
|
||
|
||
</if>
|
||
|
||
|
||
|
||
<if test="email != null">
|
||
|
||
|
||
|
||
AND email = #{email}
|
||
|
||
|
||
|
||
</if>
|
||
|
||
|
||
|
||
</select>
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>如果使用 MyBatisPlus 的话,实现类似的动态 SQL 功能会更方便。我们可以直接在字段上加 @TableField 注解来实现,可以设置 insertStrategy、updateStrategy、whereStrategy 属性。关于这三个属性的使用方式,你可以参考如下源码,或是这里的官方文档:</p>
|
||
|
||
<pre><code>/**
|
||
|
||
|
||
|
||
\* 字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的策略
|
||
|
||
|
||
|
||
\* IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty});
|
||
|
||
|
||
|
||
\* NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
|
||
|
||
|
||
|
||
\* NOT_EMPTY: insert into table_a(<if test="columnProperty != null and columnProperty!=''">column</if>) values (<if test="columnProperty != null and columnProperty!=''">#{columnProperty}</if>)
|
||
|
||
|
||
|
||
*
|
||
|
||
|
||
|
||
\* @since 3.1.2
|
||
|
||
|
||
|
||
*/
|
||
|
||
|
||
|
||
FieldStrategy insertStrategy() default FieldStrategy.DEFAULT;
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/**
|
||
|
||
|
||
|
||
\* 字段验证策略之 update: 当更新操作时,该字段拼接set语句时的策略
|
||
|
||
|
||
|
||
\* IGNORED: 直接拼接 update table_a set column=#{columnProperty}, 属性为null/空string都会被set进去
|
||
|
||
|
||
|
||
\* NOT_NULL: update table_a set <if test="columnProperty != null">column=#{columnProperty}</if>
|
||
|
||
|
||
|
||
\* NOT_EMPTY: update table_a set <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
|
||
|
||
|
||
|
||
*
|
||
|
||
|
||
|
||
\* @since 3.1.2
|
||
|
||
|
||
|
||
*/
|
||
|
||
|
||
|
||
FieldStrategy updateStrategy() default FieldStrategy.DEFAULT;
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/**
|
||
|
||
|
||
|
||
\* 字段验证策略之 where: 表示该字段在拼接where条件时的策略
|
||
|
||
|
||
|
||
\* IGNORED: 直接拼接 column=#{columnProperty}
|
||
|
||
|
||
|
||
\* NOT_NULL: <if test="columnProperty != null">column=#{columnProperty}</if>
|
||
|
||
|
||
|
||
\* NOT_EMPTY: <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
|
||
|
||
|
||
|
||
*
|
||
|
||
|
||
|
||
\* @since 3.1.2
|
||
|
||
|
||
|
||
*/
|
||
|
||
|
||
|
||
FieldStrategy whereStrategy() default FieldStrategy.DEFAULT;
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h3>12 | 异常处理:别让自己在出问题的时候变为瞎子</h3>
|
||
|
||
<p>问题 1:关于在 finally 代码块中抛出异常的坑,如果在 finally 代码块中返回值,你觉得程序会以 try 或 catch 中的返回值为准,还是以 finally 中的返回值为准呢?</p>
|
||
|
||
<p>答:以 finally 中的返回值为准。</p>
|
||
|
||
<p>从语义上来说,finally 是做方法收尾资源释放处理的,我们不建议在 finally 中有 return,这样逻辑会很混乱。这是因为,实现上 finally 中的代码块会被复制多份,分别放到 try 和 catch 调用 return 和 throw 异常之前,所以 finally 中如果有返回值,会覆盖 try 中的返回值。</p>
|
||
|
||
<p>问题 2:对于手动抛出的异常,不建议直接使用 Exception 或 RuntimeException,通常建议复用 JDK 中的一些标准异常,比如IllegalArgumentException、IllegalStateException、UnsupportedOperationException。你能说说它们的适用场景,并列出更多常见的可重用标准异常吗?</p>
|
||
|
||
<p>答:我们先分别看看 IllegalArgumentException、IllegalStateException、UnsupportedOperationException 这三种异常的适用场景。</p>
|
||
|
||
<p>IllegalArgumentException:参数不合法异常,适用于传入的参数不符合方法要求的场景。</p>
|
||
|
||
<p>IllegalStateException:状态不合法异常,适用于状态机的状态的无效转换,当前逻辑的执行状态不适合进行相应操作等场景。</p>
|
||
|
||
<p>UnsupportedOperationException:操作不支持异常,适用于某个操作在实现或环境下不支持的场景。</p>
|
||
|
||
<p>还可以重用的异常有 IndexOutOfBoundsException、NullPointerException、ConcurrentModificationException 等。</p>
|
||
|
||
<p>以上,就是咱们这门课第 7~12 讲的思考题答案了。</p>
|
||
|
||
<p>关于这些题目,以及背后涉及的知识点,如果你还有哪里感觉不清楚的,欢迎在评论区与我留言,也欢迎你把今天的内容分享给你的朋友或同事,一起交流。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(三).md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:加餐篇思考题答案合集.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709970709bdb3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|