1 Star 0 Fork 2

程闯/觅友突击课程

forked from 大灰/觅友突击课程 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
百道模拟面试题.html 312.60 KB
一键复制 编辑 原始数据 按行查看 历史
大灰 提交于 2021-03-01 16:11 +08:00 . 'dsf'
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
<!doctype html>
<html>
<head>
<meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>
<title>百道模拟面试题</title><link href='https://fonts.loli.net/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext' rel='stylesheet' type='text/css' /><style type='text/css'>html {overflow-x: initial !important;}:root { --bg-color:#ffffff; --text-color:#333333; --select-text-bg-color:#B5D6FC; --select-text-font-color:auto; --monospace:"Lucida Console",Consolas,"Courier",monospace; --title-bar-height:20px; }
.mac-os-11 { --title-bar-height:28px; }
html { font-size: 14px; background-color: var(--bg-color); color: var(--text-color); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; }
body { margin: 0px; padding: 0px; height: auto; bottom: 0px; top: 0px; left: 0px; right: 0px; font-size: 1rem; line-height: 1.42857; overflow-x: hidden; background: inherit; tab-size: 4; }
iframe { margin: auto; }
a.url { word-break: break-all; }
a:active, a:hover { outline: 0px; }
.in-text-selection, ::selection { text-shadow: none; background: var(--select-text-bg-color); color: var(--select-text-font-color); }
#write { margin: 0px auto; height: auto; width: inherit; word-break: normal; overflow-wrap: break-word; position: relative; white-space: normal; overflow-x: visible; padding-top: 36px; }
#write.first-line-indent p { text-indent: 2em; }
#write.first-line-indent li p, #write.first-line-indent p * { text-indent: 0px; }
#write.first-line-indent li { margin-left: 2em; }
.for-image #write { padding-left: 8px; padding-right: 8px; }
body.typora-export { padding-left: 30px; padding-right: 30px; }
.typora-export .footnote-line, .typora-export li, .typora-export p { white-space: pre-wrap; }
.typora-export .task-list-item input { pointer-events: none; }
@media screen and (max-width: 500px) {
body.typora-export { padding-left: 0px; padding-right: 0px; }
#write { padding-left: 20px; padding-right: 20px; }
.CodeMirror-sizer { margin-left: 0px !important; }
.CodeMirror-gutters { display: none !important; }
}
#write li > figure:last-child { margin-bottom: 0.5rem; }
#write ol, #write ul { position: relative; }
img { max-width: 100%; vertical-align: middle; image-orientation: from-image; }
button, input, select, textarea { color: inherit; font: inherit; }
input[type="checkbox"], input[type="radio"] { line-height: normal; padding: 0px; }
*, ::after, ::before { box-sizing: border-box; }
#write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write p, #write pre { width: inherit; }
#write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write p { position: relative; }
p { line-height: inherit; }
h1, h2, h3, h4, h5, h6 { break-after: avoid-page; break-inside: avoid; orphans: 4; }
p { orphans: 4; }
h1 { font-size: 2rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.6rem; }
h4 { font-size: 1.4rem; }
h5 { font-size: 1.2rem; }
h6 { font-size: 1rem; }
.md-math-block, .md-rawblock, h1, h2, h3, h4, h5, h6, p { margin-top: 1rem; margin-bottom: 1rem; }
.hidden { display: none; }
.md-blockmeta { color: rgb(204, 204, 204); font-weight: 700; font-style: italic; }
a { cursor: pointer; }
sup.md-footnote { padding: 2px 4px; background-color: rgba(238, 238, 238, 0.7); color: rgb(85, 85, 85); border-radius: 4px; cursor: pointer; }
sup.md-footnote a, sup.md-footnote a:hover { color: inherit; text-transform: inherit; text-decoration: inherit; }
#write input[type="checkbox"] { cursor: pointer; width: inherit; height: inherit; }
figure { overflow-x: auto; margin: 1.2em 0px; max-width: calc(100% + 16px); padding: 0px; }
figure > table { margin: 0px; }
tr { break-inside: avoid; break-after: auto; }
thead { display: table-header-group; }
table { border-collapse: collapse; border-spacing: 0px; width: 100%; overflow: auto; break-inside: auto; text-align: left; }
table.md-table td { min-width: 32px; }
.CodeMirror-gutters { border-right: 0px; background-color: inherit; }
.CodeMirror-linenumber { user-select: none; }
.CodeMirror { text-align: left; }
.CodeMirror-placeholder { opacity: 0.3; }
.CodeMirror pre { padding: 0px 4px; }
.CodeMirror-lines { padding: 0px; }
div.hr:focus { cursor: none; }
#write pre { white-space: pre-wrap; }
#write.fences-no-line-wrapping pre { white-space: pre; }
#write pre.ty-contain-cm { white-space: normal; }
.CodeMirror-gutters { margin-right: 4px; }
.md-fences { font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; overflow: visible; white-space: pre; background: inherit; position: relative !important; }
.md-diagram-panel { width: 100%; margin-top: 10px; text-align: center; padding-top: 0px; padding-bottom: 8px; overflow-x: auto; }
#write .md-fences.mock-cm { white-space: pre-wrap; }
.md-fences.md-fences-with-lineno { padding-left: 0px; }
#write.fences-no-line-wrapping .md-fences.mock-cm { white-space: pre; overflow-x: auto; }
.md-fences.mock-cm.md-fences-with-lineno { padding-left: 8px; }
.CodeMirror-line, twitterwidget { break-inside: avoid; }
.footnotes { opacity: 0.8; font-size: 0.9rem; margin-top: 1em; margin-bottom: 1em; }
.footnotes + .footnotes { margin-top: 0px; }
.md-reset { margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: top; background: 0px 0px; text-decoration: none; text-shadow: none; float: none; position: static; width: auto; height: auto; white-space: nowrap; cursor: inherit; -webkit-tap-highlight-color: transparent; line-height: normal; font-weight: 400; text-align: left; box-sizing: content-box; direction: ltr; }
li div { padding-top: 0px; }
blockquote { margin: 1rem 0px; }
li .mathjax-block, li p { margin: 0.5rem 0px; }
li blockquote { margin: 1rem 0px; }
li { margin: 0px; position: relative; }
blockquote > :last-child { margin-bottom: 0px; }
blockquote > :first-child, li > :first-child { margin-top: 0px; }
.footnotes-area { color: rgb(136, 136, 136); margin-top: 0.714rem; padding-bottom: 0.143rem; white-space: normal; }
#write .footnote-line { white-space: pre-wrap; }
@media print {
body, html { border: 1px solid transparent; height: 99%; break-after: avoid; break-before: avoid; font-variant-ligatures: no-common-ligatures; }
#write { margin-top: 0px; padding-top: 0px; border-color: transparent !important; }
.typora-export * { -webkit-print-color-adjust: exact; }
.typora-export #write { break-after: avoid; }
.typora-export #write::after { height: 0px; }
.is-mac table { break-inside: avoid; }
}
.footnote-line { margin-top: 0.714em; font-size: 0.7em; }
a img, img a { cursor: pointer; }
pre.md-meta-block { font-size: 0.8rem; min-height: 0.8rem; white-space: pre-wrap; background: rgb(204, 204, 204); display: block; overflow-x: hidden; }
p > .md-image:only-child:not(.md-img-error) img, p > img:only-child { display: block; margin: auto; }
#write.first-line-indent p > .md-image:only-child:not(.md-img-error) img { left: -2em; position: relative; }
p > .md-image:only-child { display: inline-block; width: 100%; }
#write .MathJax_Display { margin: 0.8em 0px 0px; }
.md-math-block { width: 100%; }
.md-math-block:not(:empty)::after { display: none; }
.MathJax_ref { fill: currentcolor; }
[contenteditable="true"]:active, [contenteditable="true"]:focus, [contenteditable="false"]:active, [contenteditable="false"]:focus { outline: 0px; box-shadow: none; }
.md-task-list-item { position: relative; list-style-type: none; }
.task-list-item.md-task-list-item { padding-left: 0px; }
.md-task-list-item > input { position: absolute; top: 0px; left: 0px; margin-left: -1.2em; margin-top: calc(1em - 10px); border: none; }
.math { font-size: 1rem; }
.md-toc { min-height: 3.58rem; position: relative; font-size: 0.9rem; border-radius: 10px; }
.md-toc-content { position: relative; margin-left: 0px; }
.md-toc-content::after, .md-toc::after { display: none; }
.md-toc-item { display: block; color: rgb(65, 131, 196); }
.md-toc-item a { text-decoration: none; }
.md-toc-inner:hover { text-decoration: underline; }
.md-toc-inner { display: inline-block; cursor: pointer; }
.md-toc-h1 .md-toc-inner { margin-left: 0px; font-weight: 700; }
.md-toc-h2 .md-toc-inner { margin-left: 2em; }
.md-toc-h3 .md-toc-inner { margin-left: 4em; }
.md-toc-h4 .md-toc-inner { margin-left: 6em; }
.md-toc-h5 .md-toc-inner { margin-left: 8em; }
.md-toc-h6 .md-toc-inner { margin-left: 10em; }
@media screen and (max-width: 48em) {
.md-toc-h3 .md-toc-inner { margin-left: 3.5em; }
.md-toc-h4 .md-toc-inner { margin-left: 5em; }
.md-toc-h5 .md-toc-inner { margin-left: 6.5em; }
.md-toc-h6 .md-toc-inner { margin-left: 8em; }
}
a.md-toc-inner { font-size: inherit; font-style: inherit; font-weight: inherit; line-height: inherit; }
.footnote-line a:not(.reversefootnote) { color: inherit; }
.md-attr { display: none; }
.md-fn-count::after { content: "."; }
code, pre, samp, tt { font-family: var(--monospace); }
kbd { margin: 0px 0.1em; padding: 0.1em 0.6em; font-size: 0.8em; color: rgb(36, 39, 41); background: rgb(255, 255, 255); border: 1px solid rgb(173, 179, 185); border-radius: 3px; box-shadow: rgba(12, 13, 14, 0.2) 0px 1px 0px, rgb(255, 255, 255) 0px 0px 0px 2px inset; white-space: nowrap; vertical-align: middle; }
.md-comment { color: rgb(162, 127, 3); opacity: 0.8; font-family: var(--monospace); }
code { text-align: left; vertical-align: initial; }
a.md-print-anchor { white-space: pre !important; border-width: initial !important; border-style: none !important; border-color: initial !important; display: inline-block !important; position: absolute !important; width: 1px !important; right: 0px !important; outline: 0px !important; background: 0px 0px !important; text-decoration: initial !important; text-shadow: initial !important; }
.md-inline-math .MathJax_SVG .noError { display: none !important; }
.html-for-mac .inline-math-svg .MathJax_SVG { vertical-align: 0.2px; }
.md-math-block .MathJax_SVG_Display { text-align: center; margin: 0px; position: relative; text-indent: 0px; max-width: none; max-height: none; min-height: 0px; min-width: 100%; width: auto; overflow-y: hidden; display: block !important; }
.MathJax_SVG_Display, .md-inline-math .MathJax_SVG_Display { width: auto; margin: inherit; display: inline-block !important; }
.MathJax_SVG .MJX-monospace { font-family: var(--monospace); }
.MathJax_SVG .MJX-sans-serif { font-family: sans-serif; }
.MathJax_SVG { display: inline; font-style: normal; font-weight: 400; line-height: normal; zoom: 90%; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; overflow-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px; }
.MathJax_SVG * { transition: none 0s ease 0s; }
.MathJax_SVG_Display svg { vertical-align: middle !important; margin-bottom: 0px !important; margin-top: 0px !important; }
.os-windows.monocolor-emoji .md-emoji { font-family: "Segoe UI Symbol", sans-serif; }
.md-diagram-panel > svg { max-width: 100%; }
[lang="flow"] svg, [lang="mermaid"] svg { max-width: 100%; height: auto; }
[lang="mermaid"] .node text { font-size: 1rem; }
table tr th { border-bottom: 0px; }
video { max-width: 100%; display: block; margin: 0px auto; }
iframe { max-width: 100%; width: 100%; border: none; }
.highlight td, .highlight tr { border: 0px; }
mark { background: rgb(255, 255, 0); color: rgb(0, 0, 0); }
.md-html-inline .md-plain, .md-html-inline strong, mark .md-inline-math, mark strong { color: inherit; }
mark .md-meta { color: rgb(0, 0, 0); opacity: 0.3 !important; }
@media print {
.typora-export h1, .typora-export h2, .typora-export h3, .typora-export h4, .typora-export h5, .typora-export h6 { break-inside: avoid; }
}
.md-diagram-panel .messageText { stroke: none !important; }
.md-diagram-panel .start-state { fill: var(--node-fill); }
.md-diagram-panel .edgeLabel rect { opacity: 1 !important; }
.md-require-zoom-fix foreignobject { font-size: var(--mermaid-font-zoom); }
.CodeMirror { height: auto; }
.CodeMirror.cm-s-inner { background: inherit; }
.CodeMirror-scroll { overflow: auto hidden; z-index: 3; }
.CodeMirror-gutter-filler, .CodeMirror-scrollbar-filler { background-color: rgb(255, 255, 255); }
.CodeMirror-gutters { border-right: 1px solid rgb(221, 221, 221); background: inherit; white-space: nowrap; }
.CodeMirror-linenumber { padding: 0px 3px 0px 5px; text-align: right; color: rgb(153, 153, 153); }
.cm-s-inner .cm-keyword { color: rgb(119, 0, 136); }
.cm-s-inner .cm-atom, .cm-s-inner.cm-atom { color: rgb(34, 17, 153); }
.cm-s-inner .cm-number { color: rgb(17, 102, 68); }
.cm-s-inner .cm-def { color: rgb(0, 0, 255); }
.cm-s-inner .cm-variable { color: rgb(0, 0, 0); }
.cm-s-inner .cm-variable-2 { color: rgb(0, 85, 170); }
.cm-s-inner .cm-variable-3 { color: rgb(0, 136, 85); }
.cm-s-inner .cm-string { color: rgb(170, 17, 17); }
.cm-s-inner .cm-property { color: rgb(0, 0, 0); }
.cm-s-inner .cm-operator { color: rgb(152, 26, 26); }
.cm-s-inner .cm-comment, .cm-s-inner.cm-comment { color: rgb(170, 85, 0); }
.cm-s-inner .cm-string-2 { color: rgb(255, 85, 0); }
.cm-s-inner .cm-meta { color: rgb(85, 85, 85); }
.cm-s-inner .cm-qualifier { color: rgb(85, 85, 85); }
.cm-s-inner .cm-builtin { color: rgb(51, 0, 170); }
.cm-s-inner .cm-bracket { color: rgb(153, 153, 119); }
.cm-s-inner .cm-tag { color: rgb(17, 119, 0); }
.cm-s-inner .cm-attribute { color: rgb(0, 0, 204); }
.cm-s-inner .cm-header, .cm-s-inner.cm-header { color: rgb(0, 0, 255); }
.cm-s-inner .cm-quote, .cm-s-inner.cm-quote { color: rgb(0, 153, 0); }
.cm-s-inner .cm-hr, .cm-s-inner.cm-hr { color: rgb(153, 153, 153); }
.cm-s-inner .cm-link, .cm-s-inner.cm-link { color: rgb(0, 0, 204); }
.cm-negative { color: rgb(221, 68, 68); }
.cm-positive { color: rgb(34, 153, 34); }
.cm-header, .cm-strong { font-weight: 700; }
.cm-del { text-decoration: line-through; }
.cm-em { font-style: italic; }
.cm-link { text-decoration: underline; }
.cm-error { color: red; }
.cm-invalidchar { color: red; }
.cm-constant { color: rgb(38, 139, 210); }
.cm-defined { color: rgb(181, 137, 0); }
div.CodeMirror span.CodeMirror-matchingbracket { color: rgb(0, 255, 0); }
div.CodeMirror span.CodeMirror-nonmatchingbracket { color: rgb(255, 34, 34); }
.cm-s-inner .CodeMirror-activeline-background { background: inherit; }
.CodeMirror { position: relative; overflow: hidden; }
.CodeMirror-scroll { height: 100%; outline: 0px; position: relative; box-sizing: content-box; background: inherit; }
.CodeMirror-sizer { position: relative; }
.CodeMirror-gutter-filler, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-vscrollbar { position: absolute; z-index: 6; display: none; }
.CodeMirror-vscrollbar { right: 0px; top: 0px; overflow: hidden; }
.CodeMirror-hscrollbar { bottom: 0px; left: 0px; overflow: hidden; }
.CodeMirror-scrollbar-filler { right: 0px; bottom: 0px; }
.CodeMirror-gutter-filler { left: 0px; bottom: 0px; }
.CodeMirror-gutters { position: absolute; left: 0px; top: 0px; padding-bottom: 30px; z-index: 3; }
.CodeMirror-gutter { white-space: normal; height: 100%; box-sizing: content-box; padding-bottom: 30px; margin-bottom: -32px; display: inline-block; }
.CodeMirror-gutter-wrapper { position: absolute; z-index: 4; background: 0px 0px !important; border: none !important; }
.CodeMirror-gutter-background { position: absolute; top: 0px; bottom: 0px; z-index: 4; }
.CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4; }
.CodeMirror-lines { cursor: text; }
.CodeMirror pre { border-radius: 0px; border-width: 0px; background: 0px 0px; font-family: inherit; font-size: inherit; margin: 0px; white-space: pre; overflow-wrap: normal; color: inherit; z-index: 2; position: relative; overflow: visible; }
.CodeMirror-wrap pre { overflow-wrap: break-word; white-space: pre-wrap; word-break: normal; }
.CodeMirror-code pre { border-right: 30px solid transparent; width: fit-content; }
.CodeMirror-wrap .CodeMirror-code pre { border-right: none; width: auto; }
.CodeMirror-linebackground { position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 0; }
.CodeMirror-linewidget { position: relative; z-index: 2; overflow: auto; }
.CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; }
.CodeMirror-measure { position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden; }
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor { position: absolute; visibility: hidden; border-right: none; width: 0px; }
.CodeMirror div.CodeMirror-cursor { visibility: hidden; }
.CodeMirror-focused div.CodeMirror-cursor { visibility: inherit; }
.cm-searching { background: rgba(255, 255, 0, 0.4); }
@media print {
.CodeMirror div.CodeMirror-cursor { visibility: hidden; }
}
:root {
--side-bar-bg-color: #fafafa;
--control-text-color: #777;
}
@include-when-export url(https://fonts.loli.net/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext);
/* open-sans-regular - latin-ext_latin */
/* open-sans-italic - latin-ext_latin */
/* open-sans-700 - latin-ext_latin */
/* open-sans-700italic - latin-ext_latin */
html {
font-size: 16px;
}
body {
font-family: "Open Sans","Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: rgb(51, 51, 51);
line-height: 1.6;
}
#write {
max-width: 860px;
margin: 0 auto;
padding: 30px;
padding-bottom: 100px;
}
@media only screen and (min-width: 1400px) {
#write {
max-width: 1024px;
}
}
@media only screen and (min-width: 1800px) {
#write {
max-width: 1200px;
}
}
#write > ul:first-child,
#write > ol:first-child{
margin-top: 30px;
}
a {
color: #4183C4;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin-top: 1rem;
margin-bottom: 1rem;
font-weight: bold;
line-height: 1.4;
cursor: text;
}
h1:hover a.anchor,
h2:hover a.anchor,
h3:hover a.anchor,
h4:hover a.anchor,
h5:hover a.anchor,
h6:hover a.anchor {
text-decoration: none;
}
h1 tt,
h1 code {
font-size: inherit;
}
h2 tt,
h2 code {
font-size: inherit;
}
h3 tt,
h3 code {
font-size: inherit;
}
h4 tt,
h4 code {
font-size: inherit;
}
h5 tt,
h5 code {
font-size: inherit;
}
h6 tt,
h6 code {
font-size: inherit;
}
h1 {
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
h2 {
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}
/*@media print {
.typora-export h1,
.typora-export h2 {
border-bottom: none;
padding-bottom: initial;
}
.typora-export h1::after,
.typora-export h2::after {
content: "";
display: block;
height: 100px;
margin-top: -96px;
border-top: 1px solid #eee;
}
}*/
h3 {
font-size: 1.5em;
line-height: 1.43;
}
h4 {
font-size: 1.25em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: 1em;
color: #777;
}
p,
blockquote,
ul,
ol,
dl,
table{
margin: 0.8em 0;
}
li>ol,
li>ul {
margin: 0 0;
}
hr {
height: 2px;
padding: 0;
margin: 16px 0;
background-color: #e7e7e7;
border: 0 none;
overflow: hidden;
box-sizing: content-box;
}
li p.first {
display: inline-block;
}
ul,
ol {
padding-left: 30px;
}
ul:first-child,
ol:first-child {
margin-top: 0;
}
ul:last-child,
ol:last-child {
margin-bottom: 0;
}
blockquote {
border-left: 4px solid #dfe2e5;
padding: 0 15px;
color: #777777;
}
blockquote blockquote {
padding-right: 0;
}
table {
padding: 0;
word-break: initial;
}
table tr {
border-top: 1px solid #dfe2e5;
margin: 0;
padding: 0;
}
table tr:nth-child(2n),
thead {
background-color: #f8f8f8;
}
table th {
font-weight: bold;
border: 1px solid #dfe2e5;
border-bottom: 0;
margin: 0;
padding: 6px 13px;
}
table td {
border: 1px solid #dfe2e5;
margin: 0;
padding: 6px 13px;
}
table th:first-child,
table td:first-child {
margin-top: 0;
}
table th:last-child,
table td:last-child {
margin-bottom: 0;
}
.CodeMirror-lines {
padding-left: 4px;
}
.code-tooltip {
box-shadow: 0 1px 1px 0 rgba(0,28,36,.3);
border-top: 1px solid #eef2f2;
}
.md-fences,
code,
tt {
border: 1px solid #e7eaed;
background-color: #f8f8f8;
border-radius: 3px;
padding: 0;
padding: 2px 4px 0px 4px;
font-size: 0.9em;
}
code {
background-color: #f3f4f4;
padding: 0 2px 0 2px;
}
.md-fences {
margin-bottom: 15px;
margin-top: 15px;
padding-top: 8px;
padding-bottom: 6px;
}
.md-task-list-item > input {
margin-left: -1.3em;
}
@media print {
html {
font-size: 13px;
}
table,
pre {
page-break-inside: avoid;
}
pre {
word-wrap: break-word;
}
}
.md-fences {
background-color: #f8f8f8;
}
#write pre.md-meta-block {
padding: 1rem;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border: 0;
border-radius: 3px;
color: #777777;
margin-top: 0 !important;
}
.mathjax-block>.code-tooltip {
bottom: .375rem;
}
.md-mathjax-midline {
background: #fafafa;
}
#write>h3.md-focus:before{
left: -1.5625rem;
top: .375rem;
}
#write>h4.md-focus:before{
left: -1.5625rem;
top: .285714286rem;
}
#write>h5.md-focus:before{
left: -1.5625rem;
top: .285714286rem;
}
#write>h6.md-focus:before{
left: -1.5625rem;
top: .285714286rem;
}
.md-image>.md-meta {
/*border: 1px solid #ddd;*/
border-radius: 3px;
padding: 2px 0px 0px 4px;
font-size: 0.9em;
color: inherit;
}
.md-tag {
color: #a7a7a7;
opacity: 1;
}
.md-toc {
margin-top:20px;
padding-bottom:20px;
}
.sidebar-tabs {
border-bottom: none;
}
#typora-quick-open {
border: 1px solid #ddd;
background-color: #f8f8f8;
}
#typora-quick-open-item {
background-color: #FAFAFA;
border-color: #FEFEFE #e5e5e5 #e5e5e5 #eee;
border-style: solid;
border-width: 1px;
}
/** focus mode */
.on-focus-mode blockquote {
border-left-color: rgba(85, 85, 85, 0.12);
}
header, .context-menu, .megamenu-content, footer{
font-family: "Segoe UI", "Arial", sans-serif;
}
.file-node-content:hover .file-node-icon,
.file-node-content:hover .file-node-open-state{
visibility: visible;
}
.mac-seamless-mode #typora-sidebar {
background-color: #fafafa;
background-color: var(--side-bar-bg-color);
}
.md-lang {
color: #b4654d;
}
.html-for-mac .context-menu {
--item-hover-bg-color: #E6F0FE;
}
#md-notification .btn {
border: 0;
}
.dropdown-menu .divider {
border-color: #e5e5e5;
}
.ty-preferences .window-content {
background-color: #fafafa;
}
.ty-preferences .nav-group-item.active {
color: white;
background: #999;
}
</style>
</head>
<body class='typora-export os-windows'>
<div id='write' class=''><div class='md-toc' mdtype='toc'><p class="md-toc-content" role="list"><span role="listitem" class="md-toc-item md-toc-h2" data-ref="n1491"><a class="md-toc-inner" href="#1注意">1.注意</a></span><span role="listitem" class="md-toc-item md-toc-h3" data-ref="n1492"><a class="md-toc-inner" href="#自我介绍">自我介绍 </a></span><span role="listitem" class="md-toc-item md-toc-h3" data-ref="n1494"><a class="md-toc-inner" href="#重点核心技术"><strong>重点核心技术</strong></a></span><span role="listitem" class="md-toc-item md-toc-h2" data-ref="n1500"><a class="md-toc-inner" href="#2技术指南">2.技术指南</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1501"><a class="md-toc-inner" href="#1spring是什么">1.Spring是什么?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1503"><a class="md-toc-inner" href="#什么是ioc">什么是Ioc?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1509"><a class="md-toc-inner" href="#什么是aop">什么是AOP?</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1514"><a class="md-toc-inner" href="#springaop的应用场景">SpringAOP的应用场景</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1516"><a class="md-toc-inner" href="#springaop的底层是怎样实现的">SpringAop的底层是怎样实现的?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1529"><a class="md-toc-inner" href="#2spring管理事务的方式有哪几种">2.spring管理事务的方式有哪几种?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1537"><a class="md-toc-inner" href="#3spring事务中的隔离级别有哪几种">3.spring事务中的隔离级别有哪几种?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1544"><a class="md-toc-inner" href="#4spring事务有哪几种传播机制">4.spring事务有哪几种传播机制?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1559"><a class="md-toc-inner" href="#5transactionalrollbackforexceptionclass注解了解吗">5.@Transactional(rollbackFor=Exception.class)注解了解吗?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1564"><a class="md-toc-inner" href="#6是否阅读过spring源码">6.是否阅读过Spring源码?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1572"><a class="md-toc-inner" href="#7spring-cloud-的核心组件有哪些">7.spring cloud 的核心组件有哪些?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1579"><a class="md-toc-inner" href="#8dubbo是什么为什么要用dubbo聊一聊服务注册与发现的流程图">8.Dubbo是什么?为什么要用Dubbo?聊一聊服务注册与发现的流程图</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1582"><a class="md-toc-inner" href="#核心组件节点角色说明">核心组件:节点角色说明</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1602"><a class="md-toc-inner" href="#调用关系说明">调用关系说明</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1616"><a class="md-toc-inner" href="#9dubbo-架构具有以下几个特点分别是连通性健壮性伸缩性以及向未来架构的升级性">9、Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1634"><a class="md-toc-inner" href="#10dubbo-和-spring-cloud-有什么区别">10.Dubbo 和 Spring Cloud 有什么区别?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1637"><a class="md-toc-inner" href="#10-bionioaio-有什么区别">10. BIO,NIO,AIO 有什么区别?</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1638"><a class="md-toc-inner" href="#1阻塞与非阻塞">1、阻塞与非阻塞</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1645"><a class="md-toc-inner" href="#2同步与异步">2、同步与异步</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1673"><a class="md-toc-inner" href="#11在-provider-上可以配置的-consumer-端的属性有哪些">11.在 Provider 上可以配置的 Consumer 端的属性有哪些?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1675"><a class="md-toc-inner" href="#12dubbo推荐使用什么序列化框架你知道的还有哪些">12.Dubbo推荐使用什么序列化框架,你知道的还有哪些?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1677"><a class="md-toc-inner" href="#13dubbo有哪几种负载均衡策略默认是哪种">13.Dubbo有哪几种负载均衡策略,默认是哪种?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1679"><a class="md-toc-inner" href="#14dubbo可以对结果进行缓存吗">14.Dubbo可以对结果进行缓存吗?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1681"><a class="md-toc-inner" href="#17dubbo服务之间的调用是阻塞的吗">17.Dubbo服务之间的调用是阻塞的吗?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1684"><a class="md-toc-inner" href="#18服务提供者能实现失效踢出是什么原理">18.服务提供者能实现失效踢出是什么原理?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1686"><a class="md-toc-inner" href="#19zookeeper宕机与dubbo直连的情况">19.zookeeper宕机与dubbo直连的情况</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1704"><a class="md-toc-inner" href="#20dubbo-支持哪些协议每种协议的应用场景优缺点">20.Dubbo 支持哪些协议,每种协议的应用场景,优缺点?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1706"><a class="md-toc-inner" href="#21dubbo-的注册中心集群挂掉发布者和订阅者之间还能通信么">21.Dubbo 的注册中心集群挂掉,发布者和订阅者之间还能通信么?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1708"><a class="md-toc-inner" href="#22什么是bean实例请解释spring-bean的生命周期">22.什么是Bean实例?请解释Spring Bean的生命周期?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1731"><a class="md-toc-inner" href="#23-解释spring支持的几种bean的作用域">23. 解释Spring支持的几种bean的作用域。</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1739"><a class="md-toc-inner" href="#24spring框架中的单例beans是线程安全的么">24、Spring框架中的单例Beans是线程安全的么?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1741"><a class="md-toc-inner" href="#25spring如何处理线程并发问题">25、Spring如何处理线程并发问题?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1746"><a class="md-toc-inner" href="#26spring基于xml注入bean的几种方式">26、Spring基于xml注入bean的几种方式:</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1751"><a class="md-toc-inner" href="#27spring-框架中都用到了哪些设计模式">27、Spring 框架中都用到了哪些设计模式?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1758"><a class="md-toc-inner" href="#28spring中的常用注解最少写10个">28.Spring中的常用注解(最少写10个)</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1807"><a class="md-toc-inner" href="#12springmvc常用注解最少写5个以上">12.SpringMVC常用注解(最少写5个以上)</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1819"><a class="md-toc-inner" href="#springmvc的流程">SpringMVC的流程?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1821"><a class="md-toc-inner" href="#29解释一下spring-aop里面的几个名词">29、解释一下Spring AOP里面的几个名词?</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1822"><a class="md-toc-inner" href="#aop的概念"><strong>AOP的概念</strong></a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1828"><a class="md-toc-inner" href="#通知advice">通知(Advice)</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1839"><a class="md-toc-inner" href="#切面aspect">切面(Aspect)</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1841"><a class="md-toc-inner" href="#连接点join-point">连接点(Join point)</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1843"><a class="md-toc-inner" href="#切点poincut">切点(Poincut)</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1847"><a class="md-toc-inner" href="#引入introduction">引入(Introduction)</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1850"><a class="md-toc-inner" href="#织入weaving">织入(Weaving)</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1859"><a class="md-toc-inner" href="#30springaop中有哪些不同的通知类型">30.SpringAOP中有哪些不同的通知类型?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1872"><a class="md-toc-inner" href="#31spring-事务处理中几种不生效的情况">31.spring 事务处理中几种不生效的情况?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1884"><a class="md-toc-inner" href="#32什么是回表查询什么是索引覆盖如何实现索引覆盖哪些场景可以利用索引覆盖来优化sql">32.什么是回表查询?什么是索引覆盖?<strong>如何实现索引覆盖?</strong>哪些场景,可以利用索引覆盖来优化SQL?</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n1885"><a class="md-toc-inner" href="#什么是myisam和innodb">什么是MyISAM和InnoDB?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n1987"><a class="md-toc-inner" href="#33策略模式和工厂模式的区别">33.策略模式和工厂模式的区别?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2036"><a class="md-toc-inner" href="#34什么是索引">34.什么是索引?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2039"><a class="md-toc-inner" href="#35索引是如何提高查询速度的">35.索引是如何提高查询速度的?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2041"><a class="md-toc-inner" href="#36索引的优点和缺点">36.索引的优点和缺点?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2060"><a class="md-toc-inner" href="#37索引创建原则">37.索引创建原则?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2078"><a class="md-toc-inner" href="#38使用索引一定能提高查询性能吗">38.使用索引一定能提高查询性能吗?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2080"><a class="md-toc-inner" href="#39mysql索引主要使用的两种数据结构">39.Mysql索引主要使用的两种数据结构</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2081"><a class="md-toc-inner" href="#哈希索引btree索引">哈希索引、BTree索引</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2085"><a class="md-toc-inner" href="#40最左前缀原则联合索引内容补充">40.最左前缀原则、联合索引内容补充</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2087"><a class="md-toc-inner" href="#最左前缀原则联合索引">最左前缀原则、联合索引</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2094"><a class="md-toc-inner" href="#注意避免冗余索引">注意避免冗余索引</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2097"><a class="md-toc-inner" href="#41数据库锁">41.数据库锁</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2123"><a class="md-toc-inner" href="#数据库分表"><strong>数据库分表</strong></a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2127"><a class="md-toc-inner" href="#cap原则"><strong>CAP原则</strong></a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2149"><a class="md-toc-inner" href="#42什么是事务">42.什么是事务?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2152"><a class="md-toc-inner" href="#43事务的特性acid">43.事务的特性(ACID)?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2164"><a class="md-toc-inner" href="#44并发事务带来哪些问题">44.并发事务带来哪些问题?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2175"><a class="md-toc-inner" href="#45不可重复度和幻读区别">45.不可重复度和幻读区别?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2179"><a class="md-toc-inner" href="#46事务隔离级别有哪些">46.事务隔离级别有哪些?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2222"><a class="md-toc-inner" href="#47java-设计模式">47.Java 设计模式</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2234"><a class="md-toc-inner" href="#48项目中用到了哪些设计模式">48、项目中用到了哪些设计模式?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2278"><a class="md-toc-inner" href="#49spring使用工厂模式可以通过--beanfactory-或--applicationcontext-创建-bean-对象的区别">49.Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象的区别?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2295"><a class="md-toc-inner" href="#50哪些可以作为可达性算法的根节点">50.哪些可以作为可达性算法的根节点</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2297"><a class="md-toc-inner" href="#51jvm内部结构及-java文件是如何被运行的">51.jvm内部结构及 Java文件是如何被运行的</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2301"><a class="md-toc-inner" href="#①-类加载器">① 类加载器</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2303"><a class="md-toc-inner" href="#②-方法区">② 方法区</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2306"><a class="md-toc-inner" href="#③-堆">③ 堆</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2309"><a class="md-toc-inner" href="#④-栈">④ 栈</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2316"><a class="md-toc-inner" href="#⑤-程序计数器">⑤ 程序计数器</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2325"><a class="md-toc-inner" href="#小总结">小总结</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2337"><a class="md-toc-inner" href="#52类加载器的介绍">52.类加载器的介绍</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2340"><a class="md-toc-inner" href="#53双亲委派机制">53.双亲委派机制</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2348"><a class="md-toc-inner" href="#54-对象的访问定位">54 对象的访问定位?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2358"><a class="md-toc-inner" href="#55-string-类和常量池">55 String 类和常量池</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2368"><a class="md-toc-inner" href="#56string-s1--new-stringabc这句话创建了几个字符串对象">56.String s1 = new String("abc");这句话创建了几个字符串对象?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2371"><a class="md-toc-inner" href="#57-8-种基本类型的包装类和常量池">57. 8 种基本类型的包装类和常量池</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2373"><a class="md-toc-inner" href="#58揭开-jvm-内存分配与回收的神秘面纱">58.揭开 JVM 内存分配与回收的神秘面纱</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2384"><a class="md-toc-inner" href="#59如何判断对象是否死亡两种方法)">59.如何判断对象是否死亡(两种方法)?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2400"><a class="md-toc-inner" href="#60再谈引用">60.再谈引用</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2416"><a class="md-toc-inner" href="#61不可达的对象并非非死不可">61.不可达的对象并非“非死不可”</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2419"><a class="md-toc-inner" href="#62-如何判断一个常量是废弃常量">62. 如何判断一个常量是废弃常量?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2422"><a class="md-toc-inner" href="#63-如何判断一个类是无用的类">63 如何判断一个类是无用的类</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2434"><a class="md-toc-inner" href="#64垃圾收集有哪些算法各自的特点">64.垃圾收集有哪些算法,各自的特点?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2450"><a class="md-toc-inner" href="#65常见的垃圾回收器有哪些">65.常见的垃圾回收器有哪些?</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2469"><a class="md-toc-inner" href="#serial-收集器">Serial 收集器</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2481"><a class="md-toc-inner" href="#66minor-gc-和-full-gc-有什么不同呢">66.Minor Gc 和 Full GC 有什么不同呢?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2485"><a class="md-toc-inner" href="#67redis跳表">67.Redis跳表</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2488"><a class="md-toc-inner" href="#什么是跳跃表">什么是跳跃表</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2498"><a class="md-toc-inner" href="#redis跳跃表">Redis跳跃表</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2502"><a class="md-toc-inner" href="#67消息队列">67.消息队列</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2514"><a class="md-toc-inner" href="#使用消息队列带来的一些问题">使用消息队列带来的一些问题</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2522"><a class="md-toc-inner" href="#rabbitmq-的高可用性">RabbitMQ 的高可用性</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2536"><a class="md-toc-inner" href="#生产者弄丢了数据">生产者弄丢了数据</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2541"><a class="md-toc-inner" href="#rabbitmq-弄丢了数据">RabbitMQ 弄丢了数据</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2555"><a class="md-toc-inner" href="#消费端弄丢了数据">消费端弄丢了数据</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2560"><a class="md-toc-inner" href="#68常见的消息队列对比">68.常见的消息队列对比</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2591"><a class="md-toc-inner" href="#69分布式锁">69.分布式锁</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2600"><a class="md-toc-inner" href="#redis-最普通的分布式锁重要">Redis 最普通的分布式锁(重要)</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2615"><a class="md-toc-inner" href="#redlock-算法">RedLock 算法</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2630"><a class="md-toc-inner" href="#zk-分布式锁">zk 分布式锁</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2635"><a class="md-toc-inner" href="#70redis缓存雪崩">70.Redis缓存雪崩</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2642"><a class="md-toc-inner" href="#71什么是缓存穿透">71.什么是缓存穿透?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2648"><a class="md-toc-inner" href="#72缓存击穿">72.缓存击穿</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2659"><a class="md-toc-inner" href="#73什么是redis中的bigkey">73.什么是Redis中的BigKey</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2663"><a class="md-toc-inner" href="#74redis的bigkey的危害">74.Redis的BigKey的危害</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2668"><a class="md-toc-inner" href="#75redis的bigkey怎么产生">75.Redis的Bigkey怎么产生?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2674"><a class="md-toc-inner" href="#76redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复">76.Redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2688"><a class="md-toc-inner" href="#77一条sql的执行流程">77.一条sql的执行流程</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2725"><a class="md-toc-inner" href="#更新语句">更新语句</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2738"><a class="md-toc-inner" href="#总结">总结</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2748"><a class="md-toc-inner" href="#78大表优化">78.大表优化</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2750"><a class="md-toc-inner" href="#1-限定数据的范围">1. 限定数据的范围</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2752"><a class="md-toc-inner" href="#2-读写分离">2. 读/写分离</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2754"><a class="md-toc-inner" href="#3-垂直分区">3. 垂直分区</a></span><span role="listitem" class="md-toc-item md-toc-h5" data-ref="n2762"><a class="md-toc-inner" href="#4-水平分区">4. 水平分区</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2774"><a class="md-toc-inner" href="#79解释一下什么是池化设计思想什么是数据库连接池为什么需要数据库连接池">79.解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2781"><a class="md-toc-inner" href="#80分库分表之后id-主键如何处理">80.分库分表之后,id 主键如何处理?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2796"><a class="md-toc-inner" href="#81在mybatis中和的区别是什么">81.在MyBatis中,#{}和${}的区别是什么?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2801"><a class="md-toc-inner" href="#82数据库连接池的原理为什么要使用连接池">82.数据库连接池的原理。为什么要使用连接池。</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2804"><a class="md-toc-inner" href="#83说说preparedstatement和statement的区别">83.说说preparedStatement和Statement的区别</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2807"><a class="md-toc-inner" href="#84列举工作中常用的几个git命令">84.列举工作中常用的几个git命令?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2810"><a class="md-toc-inner" href="#85redis-内存淘汰机制">85.Redis 内存淘汰机制</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2835"><a class="md-toc-inner" href="#86说说springboot自动配置原理">86.说说SpringBoot自动配置原理</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2837"><a class="md-toc-inner" href="#87springboot的核心注解是哪个它主要由哪几个注解组成的">87.SpringBoot的核心注解是哪个?它主要由哪几个注解组成的?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2839"><a class="md-toc-inner" href="#88说说listsetmap三者的区别">88.说说List,Set,Map三者的区别?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2859"><a class="md-toc-inner" href="#89什么是线程死锁">89.什么是线程死锁</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2873"><a class="md-toc-inner" href="#90如何避免线程死锁">90.如何避免线程死锁?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2884"><a class="md-toc-inner" href="#91hashmap和concurrenthashmap的区别">91.HashMap和ConcurrentHashMap的区别</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2888"><a class="md-toc-inner" href="#92现在有线程-t1t2-和-t3你如何确保-t2-线程在-t1-之后执行并且-t3-线程在-t2-之后执行">92.现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2890"><a class="md-toc-inner" href="#93hashmap和hashtable的区别">93.HashMap和Hashtable的区别</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2892"><a class="md-toc-inner" href="#94arraylist的拓容机制">94.ArrayList的拓容机制</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2894"><a class="md-toc-inner" href="#95arraylist-与-linkedlist-区别">95.Arraylist 与 LinkedList 区别?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2899"><a class="md-toc-inner" href="#96说说-sleep-方法和-wait-方法区别和共同点">96.说说 sleep() 方法和 wait() 方法区别和共同点?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2909"><a class="md-toc-inner" href="#97你重写过-hashcode-和-equals-么为什么重写-equals-时必须重写-hashcode-方法">97.你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2911"><a class="md-toc-inner" href="#98-与-equals区别">98.== 与 equals区别</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2919"><a class="md-toc-inner" href="#99java-序列化中如果有些字段不想进行序列化怎么办">99.Java 序列化中如果有些字段不想进行序列化,怎么办?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2922"><a class="md-toc-inner" href="#100谈一下hashmap的底层原理是什么">100.谈一下HashMap的底层原理是什么?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2924"><a class="md-toc-inner" href="#101谈一下hashmap中put是如何实现的">101.谈一下HashMap中put是如何实现的?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2933"><a class="md-toc-inner" href="#102谈一下hashmap中什么时候需要进行扩容扩容resize又是如何实现的">102.谈一下HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2946"><a class="md-toc-inner" href="#103谈一下hashmap中get是如何实现的">103.谈一下HashMap中get是如何实现的?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2948"><a class="md-toc-inner" href="#104为什么不直接将key作为哈希值而是与高16位做异或运算">104.为什么不直接将key作为哈希值而是与高16位做异或运算?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2950"><a class="md-toc-inner" href="#105为什么是16为什么必须是2的幂如果输入值不是2的幂比如10会怎么样">105.为什么是16?为什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2954"><a class="md-toc-inner" href="#106谈一下当两个对象的hashcode相等时会怎么样">106.谈一下当两个对象的hashCode相等时会怎么样?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2956"><a class="md-toc-inner" href="#107请解释一下hashmap的参数loadfactor它的作用是什么">107.请解释一下HashMap的参数loadFactor,它的作用是什么?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2958"><a class="md-toc-inner" href="#108如果hashmap的大小超过了负载因子load-factor定义的容量怎么办">108.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2960"><a class="md-toc-inner" href="#109传统hashmap的缺点为什么引入红黑树">109.传统HashMap的缺点(为什么引入红黑树?):</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2962"><a class="md-toc-inner" href="#110平时在使用hashmap时一般使用什么类型的元素作为key">110.平时在使用HashMap时一般使用什么类型的元素作为Key?</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2964"><a class="md-toc-inner" href="#111volatile关键字">111.volatile关键字</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2970"><a class="md-toc-inner" href="#112threadlocal简介">112.ThreadLocal简介</a></span><span role="listitem" class="md-toc-item md-toc-h4" data-ref="n2973"><a class="md-toc-inner" href="#113threadlocal-内存泄露问题">113.ThreadLocal 内存泄露问题</a></span></p></div><p>&nbsp;</p><h2><a name="1注意" class="md-header-anchor"></a><span>1.注意</span></h2><h3><a name="自我介绍" class="md-header-anchor"></a><span>自我介绍 </span></h3><p><span>主要讲自己会的技术细节,项目经验 在这个项目中你负责了什么、做了什么、担任了什么角色 手机腾讯会议视频</span></p><h3><a name="重点核心技术" class="md-header-anchor"></a><strong><span>重点核心技术</span></strong></h3><p><span>数据库优化,然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题</span></p><p><span>项目场景 再刷一些面试题</span></p><p><span>工作内容:任务排期,组织每日晨会,每周的review代码,需求会及需求反讲,整体任务的把握,核心功能方案的设计及功能开发</span></p><p><span>用了什么技术实现了什么功能 比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。</span></p><p>&nbsp;</p><h2><a name="2技术指南" class="md-header-anchor"></a><span>2.技术指南</span></h2><h4><a name="1spring是什么" class="md-header-anchor"></a><span>1.Spring是什么?</span></h4><p><span> Spring是一个轻量级的IoC和AOP容器框架,是为Java应用程序提供基础性服务的一套框架,简化了应用程序的开发,它使得开发者只需要关心业务需求。</span></p><h4><a name="什么是ioc" class="md-header-anchor"></a><span>什么是Ioc?</span></h4><p><span>inversion of control,即控制反转是spring重要的概念,它不是什么技术,而是一种解耦的设计思想。主要目的是借助于第三方(spring中的ioc容器)实现具有依赖关系的对象之间的解耦(IOC去管理对象,我们只需要去使用即可),从而降低了代码之间的耦合度。Ioc是一种原则,而不是一个模式。</span></p><p><span>SpringIoc就像一个工厂一样,当我们需要一个对象的时候,只要配置好配置文件或者注解即可,完全不用考虑对象是如何被创建出来的。Ioc容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。这样大大增加了项目的可维护性,降低了开发难度。</span></p><p><strong><span>控制反转怎么理解呢</span></strong><span>? </span></p><p><span>举个例子:&quot;对象a 依赖了对象 b,当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中&quot;.</span></p><p><span>DI(Dependecy Inject,依赖注入)是实现控制反转的一种具体实现,依赖注入就是将实例变量传入到一个对象中去。</span></p><h4><a name="什么是aop" class="md-header-anchor"></a><span>什么是AOP?</span></h4><p><span>AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。</span></p><p><span>AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。</span></p><p><span>Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。</span></p><p><span>当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。</span></p><h5><a name="springaop的应用场景" class="md-header-anchor"></a><span>SpringAOP的应用场景</span></h5><p><span>场景一: 记录日志 </span>
<span>场景二: 监控方法运行时间 (监控性能) </span>
<span>场景三: 权限控制 </span>
<span>场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 ) </span>
<span>场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )</span></p><h5><a name="springaop的底层是怎样实现的" class="md-header-anchor"></a><span>SpringAop的底层是怎样实现的?</span></h5><p><span>1、JDK动态代理</span></p><p><span>2、CGLIB代理</span></p><p><span>常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。</span></p><p><strong><span>Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Content。</span></strong></p><p><span>主要由以下几个模块组成:</span></p><p><span>Spring Core:核心类库,提供IOC服务,Sprign的所有功能都是借助IOC实现的;</span></p><p><span>Spring Context:Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入;</span></p><p><span>Spring AOP:AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置;</span></p><p><span>Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;</span></p><p><span>Spring ORM:Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas等框架的支持;</span></p><p><span>Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;</span></p><p><span>Spring MVC:提供面向Web应用的Model-View-Controller实现。</span></p><h4><a name="2spring管理事务的方式有哪几种" class="md-header-anchor"></a><span>2.spring管理事务的方式有哪几种?</span></h4><p><strong><span>Spring事务的种类:</span></strong></p><p><span>spring支持编程式事务管理和声明式事务管理两种方式:</span></p><p><span>①编程式事务管理使用TransactionTemplate。</span></p><p><span>②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。</span></p><p><span>声明式事务又分为两种:一种是基于XML的声明方式,一种是基于注解的声明方式</span></p><p><strong><span>Spring事务的实现方式和实现原理:</span></strong></p><p><span>Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。</span></p><h4><a name="3spring事务中的隔离级别有哪几种" class="md-header-anchor"></a><span>3.spring事务中的隔离级别有哪几种?</span></h4><p><span>TransactionDefinition接口中定义了5个表示隔离级别的常量。</span></p><p><span>TransactionDefinition.ISOLATION_</span><strong><span>DEFAULT</span></strong><span>:使用后台数据库默认的隔离级别,mysql数据库默认隔离级别是REPEATABLE_READ,Oracle默认采用的READ_COMMITED隔离级别。</span></p><p><span>TransactionDefinition.ISOLATION_</span><strong><span>READ_UNCOMMITED</span></strong><span>:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。</span></p><p><span>TransactionDefinition.ISOLATION_</span><strong><span>READ_COMMITTED</span></strong><span>: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。</span></p><p><span>TransactionDefinition.ISOLATION_</span><strong><span>REPEATABLE_READ</span></strong><span>: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。</span></p><p><span>TransactionDefinition.ISOLATION_</span><strong><span>SERIALIZABLE</span></strong><span>: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。</span></p><h4><a name="4spring事务有哪几种传播机制" class="md-header-anchor"></a><span>4.spring事务有哪几种传播机制?</span></h4><p><span>事务传播行为,是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。</span></p><p><span>例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。</span></p><p><span>在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:</span></p><p><span>支持当前事务的情况:</span></p><p><span>TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。</span></p><p><span>TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。</span></p><p><span>TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)</span></p><p><span>不支持当前事务的情况:</span></p><p><span>TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。</span></p><p><span>TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。</span></p><p><span>TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。</span></p><p><span>其他情况:</span></p><p><span>TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于</span></p><p><span>TransactionDefinition.PROPAGATION_REQUIRED。</span></p><h4><a name="5transactionalrollbackforexceptionclass注解了解吗" class="md-header-anchor"></a><span>5.@Transactional(rollbackFor=Exception.class)注解了解吗?</span></h4><p><span>Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于程序应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。</span></p><p><span>当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。</span></p><p><span>在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class ,可以让事物在遇到非运行时异常时也回滚。</span></p><p><span>非运行时异常,如IOException,SQLException,FileNotFoundException,NoSuchFileException,NoSuchMethodException等。</span></p><h4><a name="6是否阅读过spring源码" class="md-header-anchor"></a><span>6.是否阅读过Spring源码?</span></h4><p><span>阅读过spring源码,比如spring中bean是如何创建的。?</span></p><p><span>1.通过getSingleton(beanName)方法去缓存中获取对应的单例bean实例(第一次初始化,缓存中不存在)</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="java"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">private</span> <span class="cm-keyword">final</span> <span class="cm-variable">Map</span><span class="cm-operator">&lt;</span><span class="cm-variable-3">String</span>, <span class="cm-variable-3">Object</span><span class="cm-operator">&gt;</span> <span class="cm-variable">singletonObjects</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">HashMap</span><span class="cm-operator">&lt;&gt;</span>();</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>存储单例Bean的集合(多例或者叫原型Bean不存储到集合)</span></p><p><span>2.如果缓存中没有找到,则需要xml配置文件解析出来的对应信息封装成BeanDifinition对象,map的key为beanname</span></p><p><span>3.根据BeanDifinition的信息去创建对应的bean实例,具体的创建流程,</span><strong><span>doCreateBean</span></strong><span>完成Bean实例的创建(实例化、填充属性、初始化)对象实例化(new),依赖注入(属性填充),对象初始化(调用初始化方法initializeBean())</span></p><p><span>4.将创建出来的bean对象实例放入缓存中。</span></p><h4><a name="7spring-cloud-的核心组件有哪些" class="md-header-anchor"></a><span>7.spring cloud 的核心组件有哪些?</span></h4><p><span>Spring Cloud 是为了解决微服务架构中服务治理而提供的一系列功能的开发框架,并且 Spring Cloud是完全基于 Spring Boot 而开发,Spring Cloud 利用 Spring Boot 特性整合了开源行业中优秀的组件,整体对外提供了一套在微服务架构中服务治理的解决方案。如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。</span></p><p><span>-</span><span> </span><strong><span>Eureka</span></strong><span>:注册中心。服务注册于发现。</span></p><p><span>-</span><span> </span><strong><span>Feign</span></strong><span>:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。</span></p><p><span>-</span><span> </span><strong><span>Ribbon</span></strong><span>:实现负载均衡,从一个服务的多台机器中选择一台。</span></p><p><span>-</span><span> </span><strong><span>Hystrix</span></strong><span>:Netflix开源的一款容错框架,除了一些基本的熔断器,还能实现服务降级、服务限流的功能, 提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。</span></p><p><span>-</span><span> </span><strong><span>Zuul</span></strong><span>:网关管理,由 Zuul 网关转发请求给对应的服务。Zuul使用一系列不同类型的过滤器,提供统一的降级、限流、认证授权、安全过滤等等等功能。</span></p><h4><a name="8dubbo是什么为什么要用dubbo聊一聊服务注册与发现的流程图" class="md-header-anchor"></a><span>8.Dubbo是什么?为什么要用Dubbo?聊一聊服务注册与发现的流程图</span></h4><p><span>Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。</span></p><p><img src="images/architecture.png" referrerpolicy="no-referrer" alt="img"></p><h5><a name="核心组件节点角色说明" class="md-header-anchor"></a><span>核心组件:节点角色说明</span></h5><figure><table><thead><tr><th><span>节点</span></th><th><span>角色说明</span></th></tr></thead><tbody><tr><td><code>Provider</code></td><td><span>暴露服务的服务提供方</span></td></tr><tr><td><code>Consumer</code></td><td><span>调用远程服务的服务消费方</span></td></tr><tr><td><code>Registry</code></td><td><span>服务注册与发现的注册中心</span></td></tr><tr><td><code>Monitor</code></td><td><span>统计服务的调用次数和调用时间的监控中心</span></td></tr><tr><td><code>Container</code></td><td><span>服务运行容器</span></td></tr></tbody></table></figure><h5><a name="调用关系说明" class="md-header-anchor"></a><span>调用关系说明</span></h5><ol start='' ><li><span>服务容器负责启动,加载,运行服务提供者。</span></li><li><span>服务提供者在启动时,向注册中心注册自己提供的服务。</span></li><li><span>服务消费者在启动时,向注册中心订阅自己所需的服务。</span></li><li><span>注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。</span></li><li><span>服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。</span></li><li><span>服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。</span></li></ol><h4><a name="9dubbo-架构具有以下几个特点分别是连通性健壮性伸缩性以及向未来架构的升级性" class="md-header-anchor"></a><span>9、Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。</span></h4><ul><li><span>注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小</span></li><li><span>监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示</span></li><li><span>注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外</span></li><li><span>注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者</span></li><li><span>注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表</span></li><li><span>注册中心和监控中心都是可选的,服务消费者可以直连服务提供者</span></li><li><span>服务提供者无状态,任意一台宕掉后,不影响使用</span></li><li><span>服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复</span></li></ul><h4><a name="10dubbo-和-spring-cloud-有什么区别" class="md-header-anchor"></a><span>10.Dubbo 和 Spring Cloud 有什么区别?</span></h4><p><span>1)SpringCloud是Apache旗下的Spring体系下的微服务解决方案,Dubbo是阿里系的分布式服务治理框架</span>
<span>从技术维度上,其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现了服务治理,而SpringCloud拥有众多的子项目,所以其实很多人都会说Dubbo和SpringCloud是不公平的。但是由于RPC以及注册中心元数据等原因,在技术选型的时候我们只能二者选其一,所以我们常常为用他俩来对比。</span>
<span>2)服务的调用方式,Dubbo使用的是RPC远程调用,而SpringCloud使用的是 Rest API,其实更符合微服务官方的定义;</span>
<span>服务的注册中心来看,Dubbo使用了第三方的ZooKeeper作为其底层的注册中心,实现服务的注册和发现,SpringCloud使用Spring Cloud Netflix Eureka实现注册中心,当然SpringCloud也可以使用ZooKeeper实现,但一般我们不会这样做;</span>
<span>3)服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,而SpringCloud有</span><strong><span>Zuul路由网关</span></strong><span>,作为路由服务器,进行消费者的请求分发,SpringCloud还支持</span><strong><span>断路器</span></strong><span>,与git完美集成分布式配置文件等等一系列的微服务架构要素。</span>
<span>4)最大的区别:</span><strong><span>Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。</span></strong>
<span></span><strong><span>SpringCloud 是基于 Http 协议+Rest 接口调用远程过程的通信</span></strong><span>,相对来说,</span><strong><span>Http 请求会有更大的报文,占的带宽也会更多</span></strong><span>。但是REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖。</span></p><p><span>扩展性的问题,没有好坏,只有适合不适合,不过我好像更倾向于使用 Dubbo, Spring Cloud 版本升级太快,组件更新替换太频繁,配置太繁琐。</span></p><h4><a name="10-bionioaio-有什么区别" class="md-header-anchor"></a><span>10. BIO,NIO,AIO 有什么区别?</span></h4><h5><a name="1阻塞与非阻塞" class="md-header-anchor"></a><span>1、阻塞与非阻塞</span></h5><p><span>阻塞与非阻塞是描述进程在访问某个资源时,数据是否准备就绪的的一种处理方式。当数据没有准备就绪时:</span></p><ul><li><span>阻塞:线程持续等待资源中数据准备完成,直到返回响应结果。</span></li><li><span>非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。</span></li></ul><h5><a name="2同步与异步" class="md-header-anchor"></a><span>2、同步与异步</span></h5><ul><li><span>同步与异步是指访问数据的机制,同步一般指主动请求并等待IO操作完成的方式。</span></li><li><span>异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知。</span></li></ul><p><span>老王烧开水:</span>
<span> 1、普通水壶煮水,站在旁边,主动的看水开了没有?同步的阻塞</span>
<span> 2、普通水壶煮水,去干点别的事,每过一段时间去看看水开了没有,水没开就走人。 同步非阻塞</span>
<span> 3、响水壶煮水,站在旁边,不会每过一段时间主动看水开了没有。如果水开了,水壶自动通知他。 异步阻塞</span>
<span> 4、响水壶煮水,去干点别的事,如果水开了,水壶自动通知他。异步非阻塞</span></p><p><span>Java NIO和IO的主要区别</span></p><figure><table><thead><tr><th><span>IO</span></th><th><span>NIO</span></th></tr></thead><tbody><tr><td><span>面向Stream</span></td><td><span>面向Buffer</span></td></tr><tr><td><span>阻塞IO</span></td><td><span>非阻塞IO</span></td></tr><tr><td><span></span></td><td><span>Selectors</span></td></tr></tbody></table></figure><ul><li><strong><span>BIO (Blocking I/O):</span></strong><span> 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。</span></li><li><strong><span>NIO (Non-blocking/New I/O):</span></strong><span> NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 </span><code>Socket</code><span></span><code>ServerSocket</code><span> 相对应的 </span><code>SocketChannel</code><span></span><code>ServerSocketChannel</code><span> 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发</span></li><li><strong><span>AIO (Asynchronous I/O):</span></strong><span> AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。</span></li></ul><h4><a name="11在-provider-上可以配置的-consumer-端的属性有哪些" class="md-header-anchor"></a><span>11.在 Provider 上可以配置的 Consumer 端的属性有哪些?</span></h4><p><span>1)timeout:方法调用超时</span>
<span>2)retries:失败重试次数,默认重试 2 次</span>
<span>3)loadbalance:负载均衡算法,默认随机</span>
<span>4)actives 消费者端,最大并发调用限制</span></p><h4><a name="12dubbo推荐使用什么序列化框架你知道的还有哪些" class="md-header-anchor"></a><span>12.Dubbo推荐使用什么序列化框架,你知道的还有哪些?</span></h4><p><span>推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化。</span></p><h4><a name="13dubbo有哪几种负载均衡策略默认是哪种" class="md-header-anchor"></a><span>13.Dubbo有哪几种负载均衡策略,默认是哪种?</span></h4><p><span>随机、轮询、最少活跃调用数、一致性Hash,其中默认使用Random LoadBalance(基于权重的随机负载均衡机制)</span></p><h4><a name="14dubbo可以对结果进行缓存吗" class="md-header-anchor"></a><span>14.Dubbo可以对结果进行缓存吗?</span></h4><p><span>可以,Dubbo 提供了声明式缓存,用于加速热门数据的访问速度,以减少用户加缓存的工作量。</span></p><h4><a name="17dubbo服务之间的调用是阻塞的吗" class="md-header-anchor"></a><span>17.Dubbo服务之间的调用是阻塞的吗?</span></h4><p><span>默认是同步等待结果阻塞的,支持异步调用。</span></p><p><span>Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。</span></p><h4><a name="18服务提供者能实现失效踢出是什么原理" class="md-header-anchor"></a><span>18.服务提供者能实现失效踢出是什么原理?</span></h4><p><span>服务失效踢出基于 </span><strong><span>Zookeeper 的临时节点原理</span></strong><span></span></p><h4><a name="19zookeeper宕机与dubbo直连的情况" class="md-header-anchor"></a><span>19.zookeeper宕机与dubbo直连的情况</span></h4><p><span>zookeeper宕机与dubbo直连的情况在面试中可能会被经常问到,所以要引起重视。</span></p><p><span>在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种体现。</span></p><p><strong><span>dubbo的健壮性表现:</span></strong></p><ol start='' ><li><span>监控中心宕掉不影响使用,只是丢失部分采样数据</span></li><li><span>数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务</span></li><li><span>注册中心对等集群,任意一台宕掉后,将自动切换到另一台</span></li><li><span>注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯</span></li><li><span>服务提供者无状态,任意一台宕掉后,不影响使用</span></li><li><span>服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复</span></li></ol><p><span>我们前面提到过:注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。所以,我们可以完全可以绕过注册中心——采用 </span><strong><span>dubbo 直连</span></strong><span> ,即在服务消费方配置服务提供方的位置信息。</span></p><h4><a name="20dubbo-支持哪些协议每种协议的应用场景优缺点" class="md-header-anchor"></a><span>20.Dubbo 支持哪些协议,每种协议的应用场景,优缺点?</span></h4><p><span>默认使用 dubbo 协议,dubbo: 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议 TCP,异步,Hessian 序列化;其他协议:rmi、webservice、http、hessian、memcache: 基于 memcached 实现的 RPC 协议、redis: 基于 redis 实现的 RPC 协议</span></p><h4><a name="21dubbo-的注册中心集群挂掉发布者和订阅者之间还能通信么" class="md-header-anchor"></a><span>21.Dubbo 的注册中心集群挂掉,发布者和订阅者之间还能通信么?</span></h4><p><span>可以的,启动 dubbo 时,消费者会从 zookeeper 拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。</span>
<span>注册中心对等集群,任意一台宕机后,将会切换到另一台;注册中心全部宕机后,服务的提供者和消费者仍能通过本地缓存通讯。服务提供者无状态,任一台 宕机后,不影响使用;服务提供者全部宕机,服务消费者会无法使用,并无限次重连等待服务者恢复;</span>
<span>挂掉是不要紧的,但前提是你没有增加新的服务,如果你要调用新的服务,则是不能办到的。</span></p><h4><a name="22什么是bean实例请解释spring-bean的生命周期" class="md-header-anchor"></a><span>22.什么是Bean实例?请解释Spring Bean的生命周期?</span></h4><p><span>在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。</span></p><p><span> 首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;</span></p><p><span> Spring上下文中的Bean生命周期也类似,如下:</span></p><p><strong><span>(1)实例化Bean:</span></strong></p><p><span>对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。</span></p><p><strong><span>(2)设置对象属性(依赖注入):</span></strong></p><p><span>实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。</span></p><p><strong><span>(3)处理Aware接口:</span></strong></p><p><span>接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:</span></p><p><span>①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;</span></p><p><span>②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。</span></p><p><span>③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;</span></p><p><strong><span>(4)BeanPostProcessor自定义的处理:</span></strong></p><p><span>如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。</span></p><p><strong><span>(5)InitializingBean 与 init-method:</span></strong></p><p><span>如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。</span></p><p><span>(6)如果这个Bean实现了BeanPostProcessor接口,将会</span><strong><span>调用postProcessAfterInitialization</span></strong><span>(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以</span><strong><span>被应用于内存或缓存技术</span></strong><span></span></p><p><span>以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。</span></p><p><span>(7)DisposableBean:</span></p><p><span>当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;</span></p><p><span>(8)destroy-method:</span></p><p><span>最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。</span></p><h4><a name="23-解释spring支持的几种bean的作用域" class="md-header-anchor"></a><span>23. 解释Spring支持的几种bean的作用域。</span></h4><p><span>Spring容器中的bean可以分为5个范围:</span></p><p><span>(1)singleton:默认单例模式,</span><strong><span>在容器中,只实例化一次。</span></strong><span>每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。</span></p><p><span>(2)prototype:原型模式,为每一个bean请求提供一个实例。</span><strong><span>调用多少次bean,就实例化多少次。</span></strong></p><p><span>(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。</span></p><p><span>(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。</span></p><p><span>(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。</span></p><p><span>在AppplicationContext容器中,singleton在applicaitonContext.xml加载时就被预先实例化,而prototype必须在调用时才实例化。</span></p><h4><a name="24spring框架中的单例beans是线程安全的么" class="md-header-anchor"></a><span>24、Spring框架中的单例Beans是线程安全的么?</span></h4><p><span>Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的</span><strong><span>Spring bean并没有可变的状态</span></strong><span>(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。</span><strong><span>如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。</span></strong></p><h4><a name="25spring如何处理线程并发问题" class="md-header-anchor"></a><span>25、Spring如何处理线程并发问题?</span></h4><p><span>在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态</span><strong><span>采用ThreadLocal</span></strong><span>进行处理,解决线程安全问题。</span></p><p><strong><span>ThreadLocal和线程同步机制</span></strong><span>都是为了解决多线程中相同变量的访问冲突问题。</span></p><p><strong><span>同步机制采用了“时间换空间”的方式,仅提供一份变量</span></strong><span>,不同的线程在访问前</span><strong><span>需要获取锁</span></strong><span>,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。</span></p><p><strong><span>ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。</span></strong><span>因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。</span></p><h4><a name="26spring基于xml注入bean的几种方式" class="md-header-anchor"></a><span>26、Spring基于xml注入bean的几种方式:</span></h4><p><span>(1)Set方法注入;</span></p><p><span>(2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;</span></p><p><span>(3)静态工厂注入;</span></p><p><span>(4)实例工厂;</span></p><h4><a name="27spring-框架中都用到了哪些设计模式" class="md-header-anchor"></a><span>27、Spring 框架中都用到了哪些设计模式?</span></h4><p><span>(1)工厂模式:Spring使用工厂模式通过 BeanFactory 、 ApplicationContext 创建 bean 对象。</span></p><p><span>(2)单例模式:Bean默认为单例模式。</span></p><p><span>(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;</span></p><p><span>(4)模板方法:用来解决代码重复的问题。Spring 中 jdbcTemplate 、 hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。</span></p><p><span>(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。</span></p><p><span>(6)适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式,spring MVC 中也是用到了适配器模式适配 Controller 。Spring MVC 中的 Controller 种类众多,如果不利用适配器模式的话DispatcherServlet 直接获取对应类型的 Controller ,需要的自行来判断,大量的if-else判断,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则–对扩展开放,对修改关闭。</span></p><h4><a name="28spring中的常用注解最少写10个" class="md-header-anchor"></a><span>28.Spring中的常用注解(最少写10个)</span></h4><p><strong><span>1、声明bean的注解</span></strong></p><p><span>@Component 组件,没有明确的角色</span></p><p><span>@Service 在业务逻辑层使用(service层)</span></p><p><span>@Repository 在数据访问层使用(dao层)</span></p><p><span>@Controller 在展现层使用,控制器的声明(C)</span></p><p><strong><span>2、注入bean的注解</span></strong></p><p><span>@Autowired:由Spring提供</span></p><p><span>@Inject:由JSR-330提供</span></p><p><span>@Resource:由JSR-250提供</span></p><p><strong><span>3、java配置类相关注解</span></strong></p><p><span>@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(类上)</span></p><p><span>@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)</span></p><p><span>@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)</span></p><p><span>@ComponentScan 用于对Component进行扫描,相当于xml中的(类上)</span></p><p><strong><span>4、切面(AOP)相关注解</span></strong></p><p><span>Spring支持AspectJ的注解式切面编程。</span></p><p><span>@Aspect 声明一个切面(类上)</span>
<span>使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。</span></p><p><span>@After 在方法执行之后执行(方法上)</span>
<span>@Before 在方法执行之前执行(方法上)</span>
<span>@Around 在方法执行之前与之后执行(方法上)</span></p><p><span>@PointCut 声明切点</span>
<span>在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)</span></p><p><strong><span>5、@Bean的属性支持</span></strong></p><p><span>@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean)</span>
<span>其设置类型包括:</span></p><p><span>Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),</span>
<span>Protetype (每次调用新建一个bean),</span>
<span>Request (web项目中,给每个http request新建一个bean),</span>
<span>Session (web项目中,给每个http session新建一个bean),</span>
<span>GlobalSession(给每一个 global http session新建一个Bean实例)</span></p><p><span>@StepScope 在Spring Batch中还有涉及</span></p><p><span>@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod</span></p><p><span>@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod</span></p><p><strong><span>6、@Value注解</span></strong></p><p><span>@Value 为属性注入值</span></p><p><strong><span>7、环境切换</span></strong></p><p><span>@Profile 通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上)</span></p><p><span>@Conditional Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上)</span></p><p><strong><span>8、异步相关</span></strong></p><p><span>@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上)</span></p><p><span>@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)</span></p><p><strong><span>9、定时任务相关</span></strong></p><p><span>@EnableScheduling 在配置类上使用,开启计划任务的支持(类上)</span></p><p><span>@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)</span></p><p><strong><span>10、@Enable</span><span>*</span><span>注解说明</span></strong></p><p><span>这些注解主要用来开启对xxx的支持。</span>
<span>@EnableAspectJAutoProxy 开启对AspectJ自动代理的支持</span></p><p><span>@EnableAsync 开启异步方法的支持</span></p><p><span>@EnableScheduling 开启计划任务的支持</span></p><p><span>@EnableWebMvc 开启Web MVC的配置支持</span></p><p><span>@EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持</span></p><p><span>@EnableJpaRepositories 开启对SpringData JPA Repository的支持</span></p><p><span>@EnableTransactionManagement 开启注解式事务的支持</span></p><p><span>@EnableTransactionManagement 开启注解式事务的支持</span></p><p><span>@EnableCaching 开启注解式的缓存支持</span></p><p><strong><span>11、测试相关注解</span></strong></p><p><span>@RunWith 运行器,Spring中通常用于对JUnit的支持</span></p><h5><a name="12springmvc常用注解最少写5个以上" class="md-header-anchor"></a><span>12.SpringMVC常用注解(最少写5个以上)</span></h5><p><span>@EnableWebMvc 在配置类中开启Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若无此句,重写WebMvcConfigurerAdapter方法(用于对SpringMVC的配置)。</span></p><p><span>@Controller 声明该类为SpringMVC中的Controller</span></p><p><span>@RequestMapping 用于映射Web请求,包括访问路径和参数(类或方法上)</span></p><p><span>@ResponseBody 支持将返回值放在response内,而不是一个页面,通常用户返回json数据(返回值旁或方法上)</span></p><p><span>@RequestBody 允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前)</span></p><p><span>@PathVariable 用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。</span></p><p><span>@RestController 该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。</span></p><p><span>@ControllerAdvice 通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,</span>
<span>这对所有注解了 @RequestMapping的控制器内的方法有效。</span></p><p><span>@ExceptionHandler 用于全局处理控制器里的异常</span></p><p><span>@InitBinder 用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。</span></p><p><span>@ModelAttribute 本来的作用是绑定键值对到Model里,在@ControllerAdvice中是让全局的@RequestMapping都能获得在此处设置的键值对。</span></p><h5><a name="springmvc的流程" class="md-header-anchor"></a><span>SpringMVC的流程?</span></h5><p><span>(1)用户发送请求至前端控制器DispatcherServlet;</span>
<span>(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;</span>
<span>(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;</span>
<span>(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;</span>
<span>(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);</span>
<span>(6)Handler执行完成返回ModelAndView;</span>
<span>(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;</span>
<span>(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;</span>
<span>(9)ViewResolver解析后返回具体View;</span>
<span>(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)</span>
<span>(11)DispatcherServlet响应用户。</span></p><h4><a name="29解释一下spring-aop里面的几个名词" class="md-header-anchor"></a><span>29、解释一下Spring AOP里面的几个名词?</span></h4><h5><a name="aop的概念" class="md-header-anchor"></a><strong><span>AOP的概念</span></strong></h5><p><span>AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。</span></p><p><strong><span>横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。这样做有两个优点:</span></strong></p><p><span>1)每个关注点都集中于一个地方,而不是分散到多处代码中;</span></p><p><span>2)服务模块更简洁,因为它们只包含主要的关注点的代码(核心业务逻辑),</span></p><p><span>而次要关注点的代码(日志,事务,安全等)都被转移到切面中。</span></p><h5><a name="通知advice" class="md-header-anchor"></a><span>通知(Advice)</span></h5><p><strong><span>切面类有自己要完成的工作,切面类的工作就称为通知。通知定义了切面是做什么以及何时使用。</span></strong></p><p><span>&quot;做什么&quot;,即切面类中定义的方法是干什么的;</span></p><p><span>&quot;何时使用&quot;,即5种通知类型,是在目标方法执行前,还是目标方法执行后等等;</span></p><p><span>&quot;何处做&quot;,即通知定义了做什么,何时使用,但是不知道用在何处,而切点定义的就是告诉通知应该用在哪个类的哪个目标方法上,从而完美的完成横切点功能。</span></p><p><strong><span>Spring切面定义了5种类型通知:</span></strong></p><p><span>1)前置通知(Before):在目标方法被调用之前调用通知功能。</span></p><p><span>2)后置通知(After):在目标方法完成之后调用通知,不会关心方法的输出是什么。</span></p><p><span>3)返回通知(After-returning): 在目标方法成功执行之后调用通知。</span></p><p><span>4)异常通知(After-throwing):在目标方法抛出异常后调用通知。</span></p><p><span>5)环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为。</span></p><h5><a name="切面aspect" class="md-header-anchor"></a><span>切面(Aspect)</span></h5><p><strong><span>切面是通知和切点的结合</span></strong><span>,通知和切点共同定义了切面的全部内容。因为通知定义的是切面的&quot;要做什么&quot;&quot;在何时做&quot;,而切点定义的是切面的&quot;在何地做&quot;。将两者结合在一起,就可以完美的展现切面在何时,何地,做什么(功能)。</span></p><h4><a name="连接点join-point" class="md-header-anchor"></a><span>连接点(Join point)</span></h4><p><span>即被通知的类中的方法都可能成为切点,所以这些都是连接点,定义成切点之后,这个连接点就变成了切点,通知的类可能是一个类,也有可能是一个包底下的所有类,所以连接点可以成千上万来记,是一个虚概念,可以把连接点看成是切点的集合。</span></p><h4><a name="切点poincut" class="md-header-anchor"></a><span>切点(Poincut)</span></h4><p><span>在被通知的类上,连接点谈的是一个飘渺的大范围,而切点是一个具体的位置,用于缩小切面所通知的连接点的范围。</span></p><p><span>前面说过,通知定义的是切面的&quot;要做什么&quot;&quot;在何时做&quot;,是不是没有去哪里做,而切点就定义了&quot;去何处做&quot;</span></p><p><span>切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或者是使用正则表达式定义所匹配的类和方法名称来指定切点。说白了,切点就是让通知找到&quot;发泄的地方&quot;</span></p><h4><a name="引入introduction" class="md-header-anchor"></a><span>引入(Introduction)</span></h4><p><span>引入这个概念就比较高大尚,引入允许我们向现有的类添加新方法或属性。</span></p><p><span>主要目的是想在无需修改A的情况下,引入B的行为和状态。</span></p><h4><a name="织入weaving" class="md-header-anchor"></a><span>织入(Weaving)</span></h4><p><span>织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。</span></p><p><span>在目标对象的生命周期里有多个点可以进行织入:</span></p><p><strong><span>编译期:</span></strong><span> </span></p><p><span> 切面在目标类编译时被织入。需要特殊的编译器,是AspectJ的方式,不是spring的菜。</span></p><p><strong><span>类加载期:</span></strong><span> </span></p><p><span> 切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5支持这种方式。</span></p><p><strong><span>运行期:</span></strong><span> </span></p><p><span>切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个代理对象。而这正是Spring AOP的织入切面的方式。</span></p><h4><a name="30springaop中有哪些不同的通知类型" class="md-header-anchor"></a><span>30.SpringAOP中有哪些不同的通知类型?</span></h4><p><span>通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型:</span></p><ol start='' ><li><strong><span>前置通知(Before Advice)</span></strong><span>: 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 </span><code>@Before</code><span> 注解使用这个Advice。</span></li><li><strong><span>返回之后通知(After Retuning Advice)</span></strong><span>: 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 </span><code>@AfterReturning</code><span> 关注使用它。</span></li><li><strong><span>抛出(异常)后执行通知(After Throwing Advice)</span></strong><span>: 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 </span><code>@AfterThrowing</code><span> 注解来使用。</span></li><li><strong><span>后置通知(After Advice)</span></strong><span>: 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 </span><code>@After</code><span> 注解使用。</span></li><li><strong><span>围绕通知(Around Advice)</span></strong><span>: 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 </span><code>@Around</code><span> 注解使用。</span></li></ol><h4><a name="31spring-事务处理中几种不生效的情况" class="md-header-anchor"></a><span>31.spring 事务处理中几种不生效的情况?</span></h4><p><span>第一种情况:</span></p><p><strong><span>spring 事务处理中,同一个类中:A方法(无事务)调B方法(有事务),事务不生效问题</span></strong></p><p><strong><span>在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.</span></strong><span>是因为spring采用动态代理机制来实现事务控制,而</span><strong><span>动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!</span></strong></p><p><span>解决方案:</span></p><p><span>可以把方法B放到另外一个service或者dao,然后把这个server或者dao通过@Autowired注入到方法A的bean里面,这样</span><strong><span>即使方法A没用事务,方法B也可以执行自己的事务了</span></strong><span></span></p><p><span>第二种情况:</span></p><p><strong><span>Spring service本类中方法调用另一个方法事务不生效问题</span></strong></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" style="break-inside: unset;"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="java"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">interface</span> <span class="cm-def">AService</span> { &nbsp;</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-variable">a</span>(); &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-variable">b</span>(); &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">} &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; </span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-meta">@Service</span>() &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">class</span> <span class="cm-def">AServiceImpl1</span> <span class="cm-keyword">implements</span> <span class="cm-variable">AService</span>{ &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-meta">@Transactional</span>(<span class="cm-variable">propagation</span> <span class="cm-operator">=</span> <span class="cm-variable">Propagation</span>.<span class="cm-variable">REQUIRED</span>) &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-variable">a</span>() { &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-variable">b</span>(); &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; } &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-meta">@Transactional</span>(<span class="cm-variable">propagation</span> <span class="cm-operator">=</span> <span class="cm-variable">Propagation</span>.<span class="cm-variable">REQUIRES_NEW</span>) &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-variable-3">void</span> <span class="cm-variable">b</span>() { &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; } &nbsp;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">} &nbsp;</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 345px;"></div><div class="CodeMirror-gutters" style="display: none; height: 345px;"></div></div></div></pre><p><strong><span>目标对象内部的自我调用将无法实施切面中的增强,此处的this指向目标对象</span></strong><span>,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,因此b方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)”将不会实施</span></p><p><span>解决方案: </span><strong><span>((AService) AopContext.currentProxy()).b(); </span></strong><span>AOP上下文中当前代理对象</span></p><p>&nbsp;</p><h4><a name="32什么是回表查询什么是索引覆盖如何实现索引覆盖哪些场景可以利用索引覆盖来优化sql" class="md-header-anchor"></a><span>32.什么是回表查询?什么是索引覆盖?</span><strong><span>如何实现索引覆盖?</span></strong><span>哪些场景,可以利用索引覆盖来优化SQL?</span></h4><h5><a name="什么是myisam和innodb" class="md-header-anchor"></a><span>什么是MyISAM和InnoDB?</span></h5><p><strong><span>1. MyIASM</span></strong></p><p><span>MyIASM,不支持数据库事务、行级锁和外键,因此在 INSERT(插入)或 UPDATE(更新)数据即写操作时需要锁定整个表,效率较低。MyIASM 的缺点是更新数据慢且不支持事务处理,优点是查询速度快。</span></p><p><strong><span>2. InnoDB</span></strong></p><p><span>InnoDB 为 MySQL 提供了事务支持、回滚、崩溃修复能力、多版本并发控制、事务安全的操作。InnoDB 的底层存储结构为 B+ 树,B+ 树的每个节点都对应 InnoDB 的一个 Page,Page 大小是固定的,一般被设为 16KB。其中,非叶子节点只有键值,叶子节点包含完整的数据。</span></p><p><strong><span>一、什么是回表查询?</span></strong></p><p><span>这先要从InnoDB的索引实现说起,InnoDB有两大类索引:</span></p><ul><li><span>聚集索引(clustered index)</span></li><li><span>普通索引(secondary index)</span></li></ul><p><strong><span>InnoDB聚集索引和普通索引有什么差异?</span></strong></p><p><span>InnoDB</span><strong><span>聚集索引</span></strong><span>的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:</span></p><p><span>(1)如果表定义了PK,则PK就是聚集索引;</span></p><p><span>(2)如果表没有定义PK,则第一个not NULL unique列是聚集索引;</span></p><p><span>(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;</span></p><p><em><span>画外音:所以PK查询非常快,直接定位行记录。</span></em></p><p><span>InnoDB</span><strong><span>普通索引</span></strong><span>的叶子节点存储主键值。</span></p><p><em><span>画外音:注意,不是存储行记录头指针,MyISAM的索引叶子节点存储记录指针。</span></em></p><p>&nbsp;</p><p><span>举个栗子,不妨设有表:</span></p><p><em><span>t(id PK, name KEY, sex, flag);</span></em></p><p><em><span>画外音:id是聚集索引,name是普通索引。</span></em></p><p>&nbsp;</p><p><span>表中有四条记录:</span></p><p><span>  </span><em><span>1, shenjian, m, A</span></em></p><p><span>  </span><em><span>3, zhangsan, m, A</span></em></p><p><span>  </span><em><span>5, lisi, m, A</span></em></p><p><span>  </span><em><span>9, wangwu, f, B</span></em></p><p><img src="images/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODg1ODU5LzIwMTkwNy84ODU4NTktMjAxOTA3MjkxODQ4MDgzMDYtNzU4NjYwMjIyLnBuZw" referrerpolicy="no-referrer" alt="img"></p><p><span>两个B+树索引分别如上图:</span></p><p><span>(1)id为PK,聚集索引,叶子节点存储行记录;</span></p><p><span>(2)name为KEY,普通索引,叶子节点存储PK值,即id;</span></p><p><span>既然从普通索引无法直接定位行记录,那</span><strong><span>普通索引的查询过程是怎么样的呢?</span></strong></p><p><span>通常情况下,需要扫码两遍索引树。</span></p><p><img src="images/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODg1ODU5LzIwMTkwNy84ODU4NTktMjAxOTA3MjkxODQ5MTE2OTktNjc2MjU3NDI3LnBuZw" referrerpolicy="no-referrer" alt="img"></p><p><span></span><strong><span>粉红色</span></strong><span>路径,需要扫码两遍索引树:</span></p><p><span>(1)先通过普通索引定位到主键值id=5;</span></p><p><span>(2)在通过聚集索引定位到行记录;</span></p><p><span>这就是所谓的</span><strong><span>回表查询</span></strong><span>,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。</span></p><p>&nbsp;</p><p><strong><span>二、什么是索引覆盖*</span></strong><span>*(Covering index)</span><strong></strong><span>?**</span></p><p><strong><span>覆盖索引</span></strong><span>:SQL只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。</span></p><p><strong><span>三、如何实现索引覆盖?</span></strong></p><p><span>常见的方法是:将被查询的字段,建立到联合索引里去。</span></p><p><span>仍是之前中的例子:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">create</span> <span class="cm-keyword">table</span> user (</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; id <span class="cm-builtin">int</span> primary key,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; name <span class="cm-builtin">varchar</span>(<span class="cm-number">20</span>),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; sex <span class="cm-builtin">varchar</span>(<span class="cm-number">5</span>),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; index(name)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">)engine=innodb;</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 138px;"></div><div class="CodeMirror-gutters" style="display: none; height: 138px;"></div></div></div></pre><p><span>第一个SQL语句:  </span></p><p><img src="images/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODg1ODU5LzIwMTkwNy84ODU4NTktMjAxOTA3MjkxODUwMjg1NTctMTcwMzQyMjQ3OC5wbmc" referrerpolicy="no-referrer" alt="img"></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> id,name <span class="cm-keyword">from</span> user <span class="cm-keyword">where</span> name=<span class="cm-string">'shenjian'</span>; </span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>能够命中name索引,索引叶子节点存储了主键id,通过name的索引树即可获取id和name,无需回表,符合索引覆盖,效率较高。</span></p><p><em><span>画外音,Extra:</span><strong><span>Using index</span></strong><span></span></em></p><p><span>第二个SQL语句: </span></p><p><img src="images/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODg1ODU5LzIwMTkwNy84ODU4NTktMjAxOTA3MjkxODUwNTMwNzAtNzY3MjA4Mjc0LnBuZw" referrerpolicy="no-referrer" alt="img"></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> id,name,sex <span class="cm-keyword">from</span> user <span class="cm-keyword">where</span> name=<span class="cm-string">'shenjian'</span>;</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>能够命中name索引,索引叶子节点存储了主键id,但sex字段必须回表查询才能获取到,不符合索引覆盖,需要再次通过id值扫码聚集索引获取sex字段,效率会降低。</span></p><p><em><span>画外音,Extra:</span><strong><span>Using index condition</span></strong><span></span></em></p><p><span>如果把(name)单列索引升级为联合索引(name, sex)就不同了。</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">create</span> <span class="cm-keyword">table</span> user (</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; id <span class="cm-builtin">int</span> primary key,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; name <span class="cm-builtin">varchar</span>(<span class="cm-number">20</span>),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; sex <span class="cm-builtin">varchar</span>(<span class="cm-number">5</span>),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; index(name, sex)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">)engine=innodb;</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 138px;"></div><div class="CodeMirror-gutters" style="display: none; height: 138px;"></div></div></div></pre><p>&nbsp;</p><p><img src="images/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODg1ODU5LzIwMTkwNy84ODU4NTktMjAxOTA3MjkxODUxNDA4MTEtMjA2MzUzNjIwMS5wbmc" referrerpolicy="no-referrer" alt="img"></p><p><span>可以看到:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> id,name ... <span class="cm-keyword">where</span> name=<span class="cm-string">'shenjian'</span>;</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> id,name,sex ... <span class="cm-keyword">where</span> name=<span class="cm-string">'shenjian'</span>;</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 46px;"></div><div class="CodeMirror-gutters" style="display: none; height: 46px;"></div></div></div></pre><p><span>都能够命中索引覆盖,无需回表。</span></p><p><em><span>画外音,Extra:</span><strong><span>Using index</span></strong><span></span></em></p><p><strong><span>四、哪些场景可以利用索引覆盖来优化SQL?</span></strong></p><p><strong><span>场景1:全表count查询优化</span></strong></p><p><img src="images/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODg1ODU5LzIwMTkwNy84ODU4NTktMjAxOTA3MjkxODUyMDUyNDMtMTc3OTI0OTcyMS5wbmc" referrerpolicy="no-referrer" alt="img"></p><p><span>原表为:</span></p><p><em><span>user(PK id, name, sex);</span></em></p><p><span>直接:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> <span class="cm-keyword">count</span>(name) <span class="cm-keyword">from</span> user;</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>不能利用索引覆盖。</span></p><p><span>添加索引:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">alter</span> <span class="cm-keyword">table</span> user add key(name);</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>就能够利用索引覆盖提效。</span></p><p><strong><span>场景2:列查询回表优化</span></strong></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> id,name,sex ... <span class="cm-keyword">where</span> name=<span class="cm-string">'shenjian'</span>;</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>这个例子不再赘述,将单列索引(name)升级为联合索引(name, sex),即可避免回表。</span></p><p><strong><span>场景3:分页查询</span></strong></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> id,name,sex ... <span class="cm-keyword">order</span> <span class="cm-keyword">by</span> name <span class="cm-keyword">limit</span> <span class="cm-number">500</span>,<span class="cm-number">100</span>;</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>将单列索引(name)升级为联合索引(name, sex),也可以避免回表。</span></p><p><strong><span>type(重要)</span></strong></p><p><span>表示MySQL在表中找到所需行的方式,又称“访问类型”。是分析”查数据过程”的重要依据</span></p><p><span>常用的类型有: </span><strong><span>ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)</span></strong><span>ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行 ,逐行做全表扫描.,运气不好扫描到最后一行. (说明语句写的很失败)</span></p><p><span>index: Full Index Scan,index与ALL区别为index类型只遍历索引树,相当于data_all index 扫描所有的索引节点,相当于index_all</span></p><p><span>range:只检索给定范围的行,使用一个索引来选择行,能根据索引做范围的扫描</span></p><p><span>ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值,通过索引列,可以直接引用到某些数据行</span></p><p><span>eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件</span></p><p><span>const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system</span></p><p><span>NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。</span></p><p><span>const、system、NULL指查询优化到常量级别, 甚至不需要查找时间.</span></p><p><span>举例:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">SELECT</span> uid <span class="cm-keyword">FROM</span> group_user <span class="cm-keyword">WHERE</span> gid = <span class="cm-number">2</span> <span class="cm-keyword">ORDER</span> <span class="cm-keyword">BY</span> create_time <span class="cm-keyword">ASC</span> <span class="cm-keyword">LIMIT</span> <span class="cm-number">10</span>;</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><img src="images/152805_pdZo_2927759.jpg" referrerpolicy="no-referrer" alt="img"></p><p><span>从Explain的结果可以看出,查询已经使用了索引,但为什么还这么慢?</span></p><p><strong><span>分析</span></strong><span>:首先,该语句ORDER BY 使用了Using filesort文件排序,查询效率低;其次,查询字段不在索引上,没有使用覆盖索引,需要通过索引回表查询;也有数据分布的原因。</span></p><p><strong><span>解决方案</span></strong><span>:由于只需查询uid字段,添加一个联合索引便可以避免回表和文件排序,利用覆盖索引提升查询速度,同时利用索引完成排序。</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">ALTER</span> <span class="cm-keyword">TABLE</span> group_user ADD INDEX idx_gid_ctime_uid(gid,create_time,uid);</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><img src="images/152850_EHwB_2927759.jpg" referrerpolicy="no-referrer" alt="img"></p><p>&nbsp;</p><h4><a name="33策略模式和工厂模式的区别" class="md-header-anchor"></a><span>33.策略模式和工厂模式的区别?</span></h4><p><span>工厂模式和策略模式看着很像,经常让人混淆不清; </span></p><p><span>它们的区别在哪里,需要细细体味;</span></p><p><strong><span>相似点</span></strong></p><p><span>在模式结构上,两者很相似;</span></p><p><strong><span>差异</span></strong></p><p><span>用途不一样 </span></p><p><span>工厂是创建型模式,它的作用就是创建对象; </span></p><p><span>策略是行为型模式,它的作用是让一个对象在许多行为中选择一种行为;</span></p><p><span>关注点不一样 </span></p><p><span>一个关注对象创建 </span></p><p><span>一个关注行为的封装</span></p><p><span>解决不同的问题 </span></p><p><span>工厂模式是创建型的设计模式,它接受指令,创建出符合要求的实例;它主要解决的是资源的统一分发,将对象的创建完全独立出来,</span><strong><span>让对象的创建和具体的使用客户无关</span></strong><span>。主要应用在多数据库选择,类库文件加载等。 </span></p><p><span>策略这个词应该怎么理解,打个比方说,我们去逛商场,商场现在正在搞活动,有打折的、有满减的、有返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身只是一种策略,并且这些算法是随时都可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。对if-else、switch-case这类多分支结构的解耦,把每一种情况封装成一种策略,实现控制器代码的简化。</span></p><p><strong><span>策略模式(Strategy)</span></strong><span>,定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。策略模式让策略的变化独立于使用策略的客户。其中,Context是上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用;Strategy是策略类,用于定义所有支持算法的公共接口;ConcreteStrategy是具体策略类,封装了具体的算法或行为,继承于Strategy。</span></p><p><span>工厂相当于黑盒子,策略相当于白盒子;</span></p><p><strong><span>举例说明</span></strong></p><p><span>工厂模式 </span></p><p><span>有一天你决定去吃培根披萨,首先得选择店铺,A店和B店都有培根披萨; </span></p><p><span>你点了A店的培根披萨,过了二十分钟,你的披萨就来了就可以吃到了。但这个披萨是怎么做的,到底面粉放了多少,培根放了多少,佐料放了多少,有多少道工序,你是不需要管的,你需要的是一个美味培根披萨。</span></p><p><span>策略模式 </span></p><p><span>在披萨店,你要一个培根披萨,老板说有标准的pizza,也可以自己去做。原料有培根、面粉、佐料。工序有1、2、3工序,你自己去做吧。然后你就需要自己去做,到底放多少培根,放多少面粉,放多少佐料,这都你自己来决定,工序1、2、3,你是怎么实现的,都你自己决定。最后你得到了披萨。</span></p><p><strong><span>策略模式的应用</span></strong></p><p><strong><span>1. 何时使用</span></strong></p><p><span>一个系统有许多类,而区分它们的只是他们直接的行为时</span></p><p><strong><span>2. 方法</span></strong></p><p><span>将这些算法封装成一个一个的类,任意的替换</span></p><p><strong><span>3. 优点</span></strong></p><p><span>算法可以自由切换</span></p><p><span>避免使用多重条件判断(如果不用策略模式我们可能会使用多重条件语句,不利于维护)</span></p><p><span>扩展性良好,增加一个策略只需实现接口即可</span></p><p><strong><span>4. 缺点</span></strong></p><p><span>策略类数量会增多,每个策略都是一个类,复用的可能性很小</span></p><p><strong><span>所有的策略类都需要对外暴露</span></strong></p><p><strong><span>5. 使用场景</span></strong></p><p><span>多个类只有算法或行为上稍有不同的场景</span></p><p><span>算法需要自由切换的场景</span></p><p><span>需要屏蔽算法规则的场景</span></p><p><strong><span>6. 应用实例</span></strong></p><p><span>出行方式,自行车、汽车等,每一种出行方式都是一个策略</span></p><p><span>商场促销方式,打折、满减等</span></p><p><strong><span>7. 注意事项</span></strong></p><p><span>如果一个系统的策略多于四个,就需要考虑使用</span><strong><span>混合模式</span></strong><span>来解决策略类膨胀的问题</span></p><p><strong><span>策略模式的缺陷,它的具体策略必须暴露出去</span></strong><span>,而且还要由上层模块初始化,这与迪米特法则不符( 迪米特法则又叫作最少认知原则,就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。高层模块对底层模块仅仅在接触层次上,而不应该是耦合关系。正好工厂模式可以帮我们解决这个问题。</span></p><p><strong><span>混合模式: 策略模式+工厂方法模式+门面模式</span></strong></p><p><span>策略模式:负责</span><strong><span>对扣款策略进行封装</span></strong><span>,保证两个策略可以自由切换,而且日后增加扣款策略也非常简单。</span></p><p><span>工厂方法模式:修正策略模式必须对外暴露具体策略的问题,由</span><strong><span>工厂方法模式直接产生具体策略对象</span></strong><span>,其他模块不需依赖具体策略。</span></p><p><span>门面模式:负责</span><strong><span>对复杂的扣款系统进行封装</span></strong><span>,避免高层模块深入子系统内部,同时提供系统的高内聚、低耦合的特性。</span></p><h4><a name="34什么是索引" class="md-header-anchor"></a><span>34.什么是索引?</span></h4><p><strong><span>索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B树, B+树和Hash。</span></strong></p><p><span>索引的作用就相当于字典的目录作用。只需要先去目录里查找字的位置,然后直接翻到那一页就行了。</span></p><h4><a name="35索引是如何提高查询速度的" class="md-header-anchor"></a><span>35.索引是如何提高查询速度的?</span></h4><p><span>将无序的数据变成相对有序的数据</span></p><h4><a name="36索引的优点和缺点" class="md-header-anchor"></a><span>36.索引的优点和缺点?</span></h4><p><span>索引的优点:</span></p><ol start='' ><li><span>通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。</span></li><li><strong><span>可以大大加快数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。</span></strong><span> </span></li><li><span>帮助服务器避免排序和临时表。</span></li><li><span>将随机IO变为顺序IO。</span></li><li><span>可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。</span></li></ol><p><span>索引的缺点:</span></p><ol start='' ><li><strong><span>创建索引和维护索引需要耗费许多时间</span></strong><span>:当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低SQL执行效率。而且这种时间随着数据量的增加而增加。</span></li><li><strong><span>占用物理存储空间</span></strong><span> :索引需要使用物理文件存储,也会耗费一定空间。</span></li></ol><h4><a name="37索引创建原则" class="md-header-anchor"></a><span>37.索引创建原则?</span></h4><p><span>最左前缀原则</span></p><p><span>◎ 选择唯一性索引:唯一性索引一般基于 Hash 算法实现,可以快速、唯一地定位某条数据。</span></p><p><span>◎ 为经常需要排序、分组和联合操作的字段建立索引。</span></p><p><span>◎ 为常作为查询条件的字段建立索引。</span></p><p><span>◎ 限制索引的数量:索引越多,数据更新表越慢,因为在数据更新时会不断计算和添加索引。</span></p><p><span>◎ 尽量使用数据量少的索引:如果索引的值很长,则占用的磁盘变大,查询速度会受到影响。</span></p><p><span>◎ 尽量使用前缀来索引:如果索引字段的值过长,则不但影响索引的大小,而且会降低索引的执行效率,这时需要使用字段的部分前缀来作为索引。</span></p><p><span>◎ 删除不再使用或者很少使用的索引。</span></p><p><span>◎ 尽量选择区分度高的列作为索引:区分度表示字段值不重复的比例。</span></p><p><span>◎ 索引列不能参与计算:带函数的查询不建议参与索引。</span></p><p><span>◎ 尽量扩展现有索引:联合索引的查询效率比多个独立索引高。</span></p><p><span>◎限制每张表上的索引数量,建议单张表索引不超过 5 个</span></p><p><span>◎禁止给表中的每一列都建立单独的索引</span></p><p><span>◎每个 Innodb 表必须有个主键</span></p><p><span>◎对于频繁的查询优先考虑使用覆盖索引</span></p><p><span>◎避免使用子查询,可以把子查询优化为 join 操作</span></p><p><span>◎由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。</span></p><h4><a name="38使用索引一定能提高查询性能吗" class="md-header-anchor"></a><span>38.使用索引一定能提高查询性能吗?</span></h4><p><span>大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。</span></p><h4><a name="39mysql索引主要使用的两种数据结构" class="md-header-anchor"></a><span>39.Mysql索引主要使用的两种数据结构</span></h4><h5><a name="哈希索引btree索引" class="md-header-anchor"></a><span>哈希索引、BTree索引</span></h5><p><span>对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。</span></p><p><span>##### </span></p><p>&nbsp;</p><h4><a name="40最左前缀原则联合索引内容补充" class="md-header-anchor"></a><span>40.最左前缀原则、联合索引内容补充</span></h4><h5><a name="最左前缀原则联合索引" class="md-header-anchor"></a><span>最左前缀原则、联合索引</span></h5><p><strong><span>什么是联合索引?</span></strong></p><p><span>MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),</span></p><p><strong><span>最左前缀原则</span></strong><span>指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: </span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">select * from user where name=xx and city=xx ; //可以命中索引</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">select * from user where name=xx ; // 可以命中索引</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">select * from user where city=xx ; // 无法命中索引 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 69px;"></div><div class="CodeMirror-gutters" style="display: none; height: 69px;"></div></div></div></pre><p><span>这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 </span><code>city= xx and name =xx</code><span>,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。</span></p><p><span>由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDER BY子句也遵循此规则。</span></p><h5><a name="注意避免冗余索引" class="md-header-anchor"></a><span>注意避免冗余索引</span></h5><p><span>重复索引:表示一个列或者顺序相同的几个列上建立的多个索引。 </span></p><p><span>冗余索引:两个索引所覆盖的列重叠。如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的,在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。</span></p><h4><a name="41数据库锁" class="md-header-anchor"></a><span>41.数据库锁</span></h4><p><strong><span>1.行级锁</span></strong></p><p><strong><span>行级锁</span></strong><span>指对某行数据加锁,防止其他事务修改此行。执行以下操作,自动应用行级锁。</span></p><p><span>◎ INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n|NOWAIT]。</span></p><p><span>◎ SELECT … FOR UPDATE 语句允许用户一次针对多条记录执行更新。</span></p><p><span>◎ 使用 COMMIT 或 ROLLBACK 语句释放锁。</span></p><p><strong><span>2.表级锁</span></strong></p><p><strong><span>表级锁</span></strong><span>指对当前操作的整张表加锁。</span></p><p><span>常用的 MyISAM 与 InnoDB 都支持表级锁定。</span></p><p><span>表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。</span></p><p><strong><span>3.页级锁</span></strong></p><p><strong><span>页级锁</span></strong><span>的锁定粒度介于行级锁和表级锁之间。</span></p><p><span>表级锁的加锁速度快,但冲突多。</span></p><p><span>行级冲突少,但加锁速度慢。</span></p><p><span>页级锁在二者之间做了平衡,一次锁定相邻的一组记录。</span></p><p><strong><span>4.基于 Redis 的分布式锁</span></strong></p><p><span>数据库锁是基于单个数据库实现的,在我们的业务跨多个数据库时,就要使用分布式锁来保证数据的一致性。</span></p><p><strong><span>使用 Redis 实现一个分布式锁的流程:</span></strong></p><p><span>Redis 实现的分布式锁以 Redis setnx 命令为中心实现,setnx 是 Redis 的写入操作命令,具体语法为 setnx(key val)。</span></p><p><span>在且仅在 key 不存在时,则插入一个 key 为 val 的字符串,返回 1;</span></p><p><span>若 key 存在,则什么都不做,返回 0。</span></p><p><span>通过 setnx 实现分布式锁的思路如下。</span></p><p><span>◎ 获取锁:在获取锁时调用 setnx,如果返回 0,则该锁正在被别人使用;如果返回 1,则成功获取锁。</span></p><p><span>◎ 释放锁:在释放锁时,判断锁是否存在,如果存在,则执行 Redis 的 delete 操作释放锁。</span></p><p><strong><span>简单的 Redis 实现分布式锁的代码如下:</span></strong></p><p><span>如果</span><strong><span>锁并发比较大,则可以设置一个锁的超时时间</span></strong><span>,在超时时间到后,Redis 会自动释放锁给其他线程使用</span></p><h5><a name="数据库分表" class="md-header-anchor"></a><strong><span>数据库分表</span></strong></h5><p><span>垂直切分和水平切分两种。下面是区别。</span></p><p><span></span><strong><span>垂直切分</span></strong><span>:将表</span><strong><span>按照功能模块</span></strong><span>、关系密切程度划分并部署到不同的库中。例如,我们会创建定义数据库 workDB、商品数据库 payDB、用户数据库 userDB、日志数据库 logDB 等,分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等</span></p><p><span></span><strong><span>水平切分</span></strong><span>:在一个表中的数据量过大时,我们可以把该</span><strong><span>表的数据按照某种规则如 userID 散列进行划分</span></strong><span>,然后将其存储到</span><strong><span>多个结构相同的表和不同的库上</span></strong><span></span></p><h5><a name="cap原则" class="md-header-anchor"></a><strong><span>CAP原则</span></strong></h5><p><strong><span>CAP</span></strong><span>指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)三者不可兼得。</span></p><p><span></span><strong><span>一致性:</span></strong><span>在分布式系统的所有数据备份中,在同一时刻是否有同样的值(等同于所有节点都访问同一份最新的数据副本)。</span></p><p><span></span><strong><span>可用性:</span></strong><span>在集群中一部分节点发生故障后,集群整体能否响应客户端的读写请求(对数据更新具备高可用性)。</span></p><p><span></span><strong><span>分区容错性:</span></strong><span>系统如果不能在时限内达成数据的一致性,就意味着发生了分区,必须就当前操作在 C 和 A 之间做出选择。以实际效果而言,分区相当于对通信的时限要求。</span></p><p><span>CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。</span></p><p><strong><span>柔性事务</span></strong></p><p><span>在分布式数据库领域,基于 CAP 理论及 BASE 理论,</span><strong><span>阿里巴巴</span></strong><span>提出了</span><strong><span>柔性事务概念</span></strong><span></span><strong><span>BASE 理论</span></strong><span></span><strong><span>CAP 理论的延伸</span></strong><span>,包括基本可用(Basically Available)、柔性状态(Soft State)、最终一致性(Eventual Consistency)三个原则,并基于这三个原则设计出了柔性事务。</span></p><p><span>通常所说的</span><strong><span>柔性事务分为</span></strong><span></span></p><ul><li><span>两阶段型</span></li><li><span>补偿型</span></li><li><span>异步确保型</span></li><li><span>最大努力通知型</span></li></ul><p><strong><span>两阶段型事务</span></strong><span>指分布式事务的两阶段提交</span></p><p><strong><span>TCC 型事务</span></strong><span>(Try、Confirm、Cancel)为</span><strong><span>补偿型事务</span></strong></p><p><strong><span>异步确保型事务</span></strong><span>指将一系列同步的事务操作修改为</span><strong><span>基于消息队列异步执行的操作</span></strong></p><p><strong><span>最大努力通知型事务</span></strong><span>也是</span><strong><span>通过消息中间件实现</span></strong><span></span></p><h4><a name="42什么是事务" class="md-header-anchor"></a><span>42.什么是事务?</span></h4><p><span>事务是逻辑上的一组操作,要么都执行,要么都不执行。</span></p><p><span>事务最经典也经常被拿出来说例子就是银行转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。</span></p><h4><a name="43事务的特性acid" class="md-header-anchor"></a><span>43.事务的特性(ACID)?</span></h4><p><img src="images/事务特性.png" referrerpolicy="no-referrer" alt="事务的特性"></p><ol start='' ><li><strong><span>原子性:</span></strong><span> 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;</span></li><li><strong><span>一致性:</span></strong><span> 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;</span></li><li><strong><span>隔离性:</span></strong><span> 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;</span></li><li><strong><span>持久性:</span></strong><span> 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。</span></li></ol><h4><a name="44并发事务带来哪些问题" class="md-header-anchor"></a><span>44.并发事务带来哪些问题?</span></h4><p><span>在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。</span></p><ul><li><strong><span>脏读(Dirty read):</span></strong><span> 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。</span></li><li><strong><span>丢失修改(Lost to modify):</span></strong><span> 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。</span><span> </span><span>例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。</span></li><li><strong><span>不可重复读(Unrepeatableread):</span></strong><span> 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。</span></li><li><strong><span>幻读(Phantom read):</span></strong><span> 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。</span></li></ul><h4><a name="45不可重复度和幻读区别" class="md-header-anchor"></a><span>45.不可重复度和幻读区别?</span></h4><p><span>不可重复读的重点是修改,幻读的重点在于新增或者删除。</span></p><p><span>例1(</span><strong><span>同样的条件, 你读取过的数据, 再次读取出来发现值不一样了</span></strong><span>):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。</span></p><p><span> 例2(</span><strong><span>同样的条件, 第1次和第2次读出来的记录数不一样</span></strong><span> ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。</span></p><h4><a name="46事务隔离级别有哪些" class="md-header-anchor"></a><span>46.事务隔离级别有哪些?</span></h4><p><strong><span>SQL 标准定义了四个隔离级别:</span></strong></p><ul><li><strong><span>READ-UNCOMMITTED(读取未提交):</span></strong><span> 最低的隔离级别,允许读取尚未提交的数据变更,</span><strong><span>可能会导致脏读、幻读或不可重复读</span></strong><span></span></li><li><strong><span>READ-COMMITTED(读取已提交):</span></strong><span> 允许读取并发事务已经提交的数据,</span><strong><span>可以阻止脏读,但是幻读或不可重复读仍有可能发生</span></strong><span></span></li><li><strong><span>REPEATABLE-READ(可重复读):</span></strong><span> 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,</span><strong><span>可以阻止脏读和不可重复读,但幻读仍有可能发生</span></strong><span></span></li><li><strong><span>SERIALIZABLE(可串行化):</span></strong><span> 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,</span><strong><span>该级别可以防止脏读、不可重复读以及幻读</span></strong><span></span></li></ul><hr /><figure><table><thead><tr><th style='text-align:center;' ><span>隔离级别</span></th><th style='text-align:center;' ><span>脏读</span></th><th style='text-align:center;' ><span>不可重复读</span></th><th style='text-align:center;' ><span>幻影读</span></th></tr></thead><tbody><tr><td style='text-align:center;' ><span>READ-UNCOMMITTED</span></td><td style='text-align:center;' ><span></span></td><td style='text-align:center;' ><span></span></td><td style='text-align:center;' ><span></span></td></tr><tr><td style='text-align:center;' ><span>READ-COMMITTED</span></td><td style='text-align:center;' ><span>×</span></td><td style='text-align:center;' ><span></span></td><td style='text-align:center;' ><span></span></td></tr><tr><td style='text-align:center;' ><span>REPEATABLE-READ</span></td><td style='text-align:center;' ><span>×</span></td><td style='text-align:center;' ><span>×</span></td><td style='text-align:center;' ><span></span></td></tr><tr><td style='text-align:center;' ><span>SERIALIZABLE</span></td><td style='text-align:center;' ><span>×</span></td><td style='text-align:center;' ><span>×</span></td><td style='text-align:center;' ><span>×</span></td></tr></tbody></table></figure><p><span>MySQL InnoDB 存储引擎的默认支持的隔离级别是 </span><strong><span>REPEATABLE-READ(可重读)</span></strong><span>。我们可以通过</span><code>SELECT @@tx_isolation;</code><span>命令来查看,MySQL 8.0 该命令改为</span><code>SELECT @@transaction_isolation;</code></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">mysql&gt; <span class="cm-keyword">SELECT</span> @@tx_isolation;</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">+-----------------+</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">| @@tx_isolation |</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">+-----------------+</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">| REPEATABLE-READ |</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">+-----------------+</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 138px;"></div><div class="CodeMirror-gutters" style="display: none; height: 138px;"></div></div></div></pre><p><span>这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 </span><strong><span>REPEATABLE-READ(可重读)</span></strong><span> 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 </span><strong><span>REPEATABLE-READ(可重读)</span></strong><span>,但是可以通过应用加锁读(例如 </span><code>select * from table for update</code><span> 语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 </span><strong><span>SERIALIZABLE(可串行化)</span></strong><span> 隔离级别。</span></p><p><span>因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是</span><strong><span>READ-COMMITTED(读取提交内容):</span></strong><span>,但是你要知道的是InnoDB 存储引擎默认使用 </span><strong><span>REPEATABLE-READ(可重读)</span></strong><span>并不会有任何性能损失。</span></p><p><span>InnoDB 存储引擎在 </span><strong><span>分布式事务</span></strong><span> 的情况下一般会用到</span><strong><span>SERIALIZABLE(可串行化)</span></strong><span>隔离级别。 </span></p><h4><a name="47java-设计模式" class="md-header-anchor"></a><span>47.Java 设计模式</span></h4><p><strong><span>创建型模式详解</span></strong></p><p><span>单例模式:只有一个实例</span></p><p><span>工厂模式: 由对象工厂生成对象</span></p><p><span>建造者模式: 组装复杂的实例</span></p><p><span>原型模式: 通过复制生成实例</span></p><p><strong><span>结构型模式详解</span></strong></p><p><span>适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式</span></p><p><strong><span>行为型模式</span></strong><span>,</span></p><p><span>职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式观察者模式状态模式、策略模式</span></p><p><strong><span>策略模式作为设计原则中开闭原则最典型的体现,也是经常使用的。</span></strong></p><p><span>定义一系列的算法,把每一个算法封装起来,并且使它们可以相互替换。这个模式中使得各个算法可以独立于使用它的客户而变化。</span></p><h4><a name="48项目中用到了哪些设计模式" class="md-header-anchor"></a><span>48、项目中用到了哪些设计模式?</span></h4><p><strong><span>项目中用到了策略模式、工厂模式、单例模式等</span></strong></p><p><span>单例模式的适用场景</span></p><p><span>对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;</span></p><p><span>由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC停顿时间。</span></p><p><span>适用场景: </span></p><p><span>单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如: </span></p><p><span> </span><strong><span>1.需要频繁实例化然后销毁的对象。</span></strong><span> </span></p><p><span> </span><strong><span>2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。</span></strong><span> </span></p><p><span> </span><strong><span>3.有状态的工具类对象。</span></strong><span> </span></p><p><span> </span><strong><span>4.频繁访问数据库或文件的对象。</span></strong><span> </span></p><p><span>以下都是单例模式的</span><strong><span>经典使用场景</span></strong><span></span></p><p><span> 1.</span><strong><span>资源共享的情况下</span></strong><span>,避免由于资源操作时导致的性能或损耗等。如</span><strong><span>日志文件,应用配置</span></strong><span></span></p><p><span> 2.</span><strong><span>控制资源的情况下</span></strong><span>,方便资源之间的互相通信。如</span><strong><span>线程池</span></strong><span>等。 </span></p><p><span>应用场景举例: </span></p><p><span>网站的计数器,一般也是采用单例模式实现,否则难以同步。 </span></p><p><span>日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 </span></p><p><span>Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 </span></p><p><span>数据库连接池的设计一般也是采用单例模式,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 </span></p><p><span>多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。 </span></p><p><span>实现单利模式的原则和过程: </span></p><p><span> 1.单例模式:确保一个类只有一个实例,自行实例化并向系统提供这个实例 </span></p><p><span> 2.单例模式分类:饿单例模式(类加载时实例化一个对象给自己的引用),懒单例模式(调用取得实例的方法如getInstance时才会实例化对象)(java中饿单例模式性能优于懒单例模式,c++中一般使用懒单例模式) </span></p><p><span> 3.单例模式要素: </span></p><p><span> a.私有构造方法 </span></p><p><span> b.私有静态引用指向自己实例 </span></p><p><span> c.以自己实例为返回值的公有静态方法 </span></p><p><strong><span>双重检验锁</span></strong><span> </span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" style="break-inside: unset;"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="java"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><span><span></span>x</span></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">class</span> <span class="cm-def">Singleton</span> {</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text=""></span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">//volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确*</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 处理uniqueInstance变量</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">private</span> <span class="cm-keyword">volatile</span> <span class="cm-keyword">static</span> <span class="cm-variable">Singleton</span> <span class="cm-variable">uniqueInstance</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text=""></span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">private</span> <span class="cm-variable">Singleton</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text=""></span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">public</span> <span class="cm-keyword">static</span> <span class="cm-variable">Singleton</span> <span class="cm-variable">getInstance</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text=""></span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">//检查实例,如果不存在,就进入同步代码块*</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">uniqueInstance</span> <span class="cm-operator">==</span> <span class="cm-atom">null</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">//只有第一次才彻底执行这里的代码</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">synchronized</span>(<span class="cm-variable">Singleton</span>.<span class="cm-keyword">class</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">//进入同步代码块后,再检查一次,如果仍是null,才创建实例*</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">uniqueInstance</span> <span class="cm-operator">==</span> <span class="cm-atom">null</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">uniqueInstance</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Singleton</span>();</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">uniqueInstance</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 552px;"></div><div class="CodeMirror-gutters" style="display: none; height: 552px;"></div></div></div></pre><p>&nbsp;</p><p><span>优点 资源利用率高,不执行getInstance()就不被实例,可以执行该类其他静态方法 </span></p><p><span>缺点 第一次加载时反应不快,由于java内存模型一些原因偶尔失败 </span></p><p><span>上面代码中的 </span><strong><span>instance = new Singleton()这句代码</span></strong><span>.并非一个原子性操作,实际上在JVM里大概做了3件事:</span></p><p><strong><span>1.给instance分配内存</span></strong></p><p><strong><span>2.调用Singleton构造完成初始化</span></strong></p><p><strong><span>3.使instance对象的引用指向分配的内存空间(完成这一步instance就不是null了)</span></strong></p><p><span>但是在 JVM 的即时编译器中</span><strong><span>存在指令重排序的优化</span></strong><span>.也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2.如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错.(</span><strong><span>为什么使用了synchronized同步还会被其他线程抢占?</span></strong><span>)</span></p><p><span>我们只需要将 instance 变量声明成 volatile 就可以了。</span></p><p><span>使用 volatile 的主要原因是其一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。</span></p><p><strong><span>饿汉式:加载类时初始化实例</span></strong></p><p><span>一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。</span></p><p><strong><span>枚举方式:Enum</span></strong><span>用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="java"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">enum</span> <span class="cm-def">EasySingleton</span>{</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-variable">INSTANCE</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">//变量</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">//方法</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 115px;"></div><div class="CodeMirror-gutters" style="display: none; height: 115px;"></div></div></div></pre><p><span>我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。在《Effective Java》中说枚举是实现单例的最佳方式.建议在实际项目中单例以枚举方式实现.</span></p><h4><a name="49spring使用工厂模式可以通过--beanfactory-或--applicationcontext-创建-bean-对象的区别" class="md-header-anchor"></a><span>49.Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象的区别?</span></h4><p><span>Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象。</span></p><p><span>两者对比:</span></p><p><span>BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于 ApplicationContext 来说</span></p><p><span>会占用更少的内存,程序启动速度更快。</span></p><p><span>ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean。</span></p><p><span>BeanFactory 仅提供了最基本的依赖注入支持, ApplicationContext 扩展了 BeanFactory ,</span></p><p><span>除了有 BeanFactory 的功能还有额外更多功能,所以一般开发人员使用 ApplicationContext 会更多。</span></p><p><span>ApplicationContext的三个实现类:</span></p><ol start='' ><li><span>ClassPathXmlApplication :把上下文文件当成类路径资源。</span></li><li><span>FileSystemXmlApplication :从文件系统中的 XML 文件载入上下文定义信息。</span></li><li><span>XmlWebApplicationContext :从Web系统中的XML文件载入上下文定义信息。</span></li></ol><p>&nbsp;</p><h4><a name="50哪些可以作为可达性算法的根节点" class="md-header-anchor"></a><span>50.哪些可以作为可达性算法的根节点</span></h4><p>&nbsp;</p><h4><a name="51jvm内部结构及-java文件是如何被运行的" class="md-header-anchor"></a><span>51.jvm内部结构及 Java文件是如何被运行的</span></h4><p><img src="images/897863ee5ecb4d92b9119d065f468262-new-imagef7287f0b-c9f0-4f22-9eb4-6968bbaa5a82.png" referrerpolicy="no-referrer"></p><p><span>比如我们现在写了一个 HelloWorld.java 好了,那这个 HelloWorld.java 抛开所有东西不谈,那是不是就类似于一个文本文件,只是这个文本文件它写的都是英文,而且有一定的缩进而已。</span></p><p><span>那我们的 </span><strong><span>JVM</span></strong><span> 是不认识文本文件的,所以它需要一个 </span><strong><span>编译</span></strong><span> ,让其成为一个它会读二进制文件的 </span><strong><span>HelloWorld.class</span></strong><span> </span></p><h5><a name="①-类加载器" class="md-header-anchor"></a><span>① 类加载器</span></h5><p><span>如果 </span><strong><span>JVM</span></strong><span> 想要执行这个 </span><strong><span>.class</span></strong><span> 文件,我们需要将其装进一个 </span><strong><span>类加载器</span></strong><span> 中,它就像一个搬运工一样,会把所有的 </span><strong><span>.class</span></strong><span> 文件全部搬进JVM里面来。</span>
<img src="images/81f1813f371c40ffa1c1f6d78bc49ed9-new-image28314ec8-066f-451e-8373-4517917d6bf7.png" referrerpolicy="no-referrer"></p><h5><a name="②-方法区" class="md-header-anchor"></a><span>② 方法区</span></h5><p><strong><span>方法区</span></strong><span> 是用于存放类似于元数据信息方面的数据的,比如类信息,常量,静态变量,编译后代码···等。方法区也被称为永久代。方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现。</span></p><p><span>类加载器将 .class 文件搬过来就是先丢到这一块上</span></p><h5><a name="③-堆" class="md-header-anchor"></a><span>③ 堆</span></h5><p><strong><span></span></strong><span> 主要放了一些存储的数据,比如对象实例,数组···等,它和方法区都同属于 </span><strong><span>线程共享区域</span></strong><span> 。也就是说它们都是 </span><strong><span>线程不安全</span></strong><span> 的。</span></p><p><span>Java 堆是垃圾收集器管理的主要区域,因此也被称作</span><strong><span>GC 堆(Garbage Collected Heap)</span></strong><span>.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。</span><strong><span>进一步划分的目的是更好地回收内存,或者更快地分配内存。</span></strong></p><h5><a name="④-栈" class="md-header-anchor"></a><span>④ 栈</span></h5><p><strong><span></span></strong><span> 这是我们的代码运行空间。我们编写的每一个方法都会放到 </span><strong><span></span></strong><span> 里面运行。</span></p><p><span>我们会听说过 本地方法栈 或者 本地方法接口 这两个名词,不过我们基本不会涉及这两块的内容,它俩底层是使用C来进行工作的,和Java没有太大的关系。</span></p><p><strong><span>Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。</span></strong><span> (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)</span></p><p><strong><span>局部变量表主要存放了编译期可知的各种数据类型</span></strong><span>(boolean、byte、char、short、int、float、long、double)、</span><strong><span>对象引用</span></strong><span>(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。</span></p><p><strong><span>Java 虚拟机栈会出现两种错误:</span><code>StackOverFlowError</code><span></span><code>OutOfMemoryError</code><span></span></strong></p><p><span>Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。</span></p><h5><a name="⑤-程序计数器" class="md-header-anchor"></a><span>⑤ 程序计数器</span></h5><p><span>主要就是完成一个加载工作,类似于一个指针一样的,指向下一行我们需要执行的代码。和栈一样,都是 </span><strong><span>线程独享</span></strong><span> 的,就是说每一个线程都会有自己对应的一块区域而不会存在并发和多线程的问题。</span></p><p><span>程序计数器主要有两个作用:**</span></p><ol start='' ><li><span>字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。</span></li><li><span>在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。</span></li></ol><p><span>**注意:程序计数器是唯一一个不会出现 </span><code>OutOfMemoryError</code><span> 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡</span></p><h5><a name="小总结" class="md-header-anchor"></a><span>小总结</span></h5><ol start='' ><li><p><span>Java文件经过编译后变成 .class 字节码文件</span></p></li><li><p><span>字节码文件通过类加载器被搬运到 JVM 虚拟机中</span></p></li><li><p><span>虚拟机主要的5大块:方法区,堆都为线程共享区域,有线程安全问题,栈和本地方法栈和计数器都是独享区域,不存在线程安全问题,而 JVM 的调优主要就是围绕堆,栈两大块进行、</span></p></li><li><h5><a name="为什么要将永久代-permgen-替换为元空间-metaspace-呢整个永久代有一个-jvm-本身设置固定大小上限无法进行调整而元空间使用的是直接内存受本机可用内存的限制虽然元空间仍旧可能溢出但是比原来出现的几率会更小" class="md-header-anchor"></a><span>为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。</span></h5></li><li><p><strong><span>运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)</span></strong></p></li></ol><h4><a name="52类加载器的介绍" class="md-header-anchor"></a><span>52.类加载器的介绍</span></h4><p><span>之前也提到了它是负责加载.class文件的,它们在文件开头会有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责class文件的加载,而是否能够运行则由 Execution Engine 来决定</span></p><p><span>从类被加载到虚拟机内存中开始,到释放内存总共有7个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中</span><strong><span>验证,准备,解析三个部分统称为连接</span></strong></p><h4><a name="53双亲委派机制" class="md-header-anchor"></a><span>53.双亲委派机制</span></h4><p><span>当一个类收到了加载请求时,它是不会先自己去尝试加载的,而是委派给父类去完成,比如我现在要new一个Person,这个Person是我们自定义的类,如果我们要加载它,就会先委派App ClassLoader,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的Class)时,子类加载器才会自行尝试加载。</span></p><p><span>这样做的好处是,加载位于rt.jar包中的类时不管是哪个加载器加载,最终都会委托到BootStrap ClassLoader进行加载,这样保证了使用不同的类加载器得到的都是同一个结果。</span></p><p><span>其实这个也是一个隔离的作用,避免了我们的代码影响了JDK的代码,比如我现在要来一个</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">public class String(){</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; public static void main(){sout;}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 69px;"></div><div class="CodeMirror-gutters" style="display: none; height: 69px;"></div></div></div></pre><p><span>这种时候,我们的代码肯定会报错,因为在加载的时候其实是找到了rt.jar中的String.class,然后发现这也没有main方法</span></p><p><span>JDK 1.8 之前:**</span></p><p><img src="images/JVM运行时数据区域.png" referrerpolicy="no-referrer"></p><h4><a name="54-对象的访问定位" class="md-header-anchor"></a><span>54 对象的访问定位?</span></h4><p><span>建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有</span><strong><span>①使用句柄</span></strong><span></span><strong><span>②直接指针</span></strong><span>两种:</span></p><ol start='' ><li><strong><span>句柄:</span></strong><span> 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;</span></li><li><strong><span>直接指针:</span></strong><span> 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。</span></li></ol><p><img src="images/对象的访问定位-直接指针.png" referrerpolicy="no-referrer" alt="对象的访问定位-直接指针"></p><p><strong><span>这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。</span></strong></p><p>&nbsp;</p><h4><a name="55-string-类和常量池" class="md-header-anchor"></a><span>55 String 类和常量池</span></h4><p><strong><span>String 对象的两种创建方式:</span></strong></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="java"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable-3">String</span> <span class="cm-variable">str1</span> <span class="cm-operator">=</span> <span class="cm-string">"abcd"</span>;<span class="cm-comment">//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd"";</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable-3">String</span> <span class="cm-variable">str2</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable-3">String</span>(<span class="cm-string">"abcd"</span>);<span class="cm-comment">//堆中创建一个新的对象</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable-3">String</span> <span class="cm-variable">str3</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable-3">String</span>(<span class="cm-string">"abcd"</span>);<span class="cm-comment">//堆中创建一个新的对象</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">System</span>.<span class="cm-variable">out</span>.<span class="cm-variable">println</span>(<span class="cm-variable">str1</span><span class="cm-operator">==</span><span class="cm-variable">str2</span>);<span class="cm-comment">//false</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">System</span>.<span class="cm-variable">out</span>.<span class="cm-variable">println</span>(<span class="cm-variable">str2</span><span class="cm-operator">==</span><span class="cm-variable">str3</span>);<span class="cm-comment">//false</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 138px;"></div><div class="CodeMirror-gutters" style="display: none; height: 138px;"></div></div></div></pre><p><span>这两种不同的创建方法是有差别的。</span></p><ul><li><span>第一种方式是在常量池中拿对象;</span></li><li><span>第二种方式是直接在堆内存空间创建一个新的对象。</span></li></ul><p><span>记住一点:</span><strong><span>只要使用 new 方法,便需要创建新的对象。</span></strong><span>直接使用双引号声明出来的 String 对象会直接存储在常量池中。</span></p><h4><a name="56string-s1--new-stringabc这句话创建了几个字符串对象" class="md-header-anchor"></a><span>56.String s1 = new String(&quot;abc&quot;);这句话创建了几个字符串对象?</span></h4><p><strong><span>将创建 1 或 2 个字符串</span></strong><span>。如果池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。如果池中没有字符串常量“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。</span></p><p>&nbsp;</p><h4><a name="57-8-种基本类型的包装类和常量池" class="md-header-anchor"></a><span>57. 8 种基本类型的包装类和常量池</span></h4><p><span>Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean 直接返回True Or False。如果超出对应范围仍然会去创建新的对象。 </span></p><h4><a name="58揭开-jvm-内存分配与回收的神秘面纱" class="md-header-anchor"></a><span>58.揭开 JVM 内存分配与回收的神秘面纱</span></h4><p><strong><span>1 对象优先在 eden 区分配</span></strong></p><p><span>目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。通过 </span><strong><span>分配担保机制</span></strong><span> 把新生代的对象提前转移到老年代中去。</span></p><p><strong><span>2 大对象直接进入老年代</span></strong></p><p><span>大对象就是需要大量连续内存空间的对象(比如:</span><strong><span>字符串、数组</span></strong><span>)。为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。</span></p><p><strong><span>3 长期存活的对象将进入老年代</span></strong></p><p><span>既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个</span><strong><span>对象年龄(Age)计数器</span></strong><span></span></p><p><span>动态对象年龄判定,如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1,对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。</span></p><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><h4><a name="59如何判断对象是否死亡两种方法)" class="md-header-anchor"></a><span>59.如何判断对象是否死亡(两种方法)?</span></h4><p><strong><span>引用计数法</span></strong><span></span><strong><span>可达性分析算法</span></strong><span></span></p><p><span>引用计数法,给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。</span></p><p><strong><span>这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。</span></strong><span>比如除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。</span></p><p><strong><span>可达性分析算法</span></strong><span>,解决引用算法的弊端,这个算法的基本思想就是通过一系列的称为 </span><strong><span>“GC Roots”</span></strong><span> 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。</span></p><p><strong><span>可作为GC Roots的对象包括下面几种</span></strong><span>:</span></p><ul><li><p><strong><span>虚拟机栈(栈帧中的本地变量表)</span></strong><span>中引用的对象</span></p></li><li><p><strong><span>本地方法栈(Native方法)</span></strong><span>中引用的对象</span></p></li><li><p><strong><span>方法区中类静态属性</span></strong><span>引用的对象</span></p></li><li><p><strong><span>方法区中常量</span></strong><span>引用的对象</span></p><p>&nbsp;</p></li></ul><h4><a name="60再谈引用" class="md-header-anchor"></a><span>60.再谈引用</span></h4><p><span>无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。</span></p><p><span>JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)</span></p><p><strong><span>1.强引用(StrongReference)</span></strong></p><p><span>以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于</span><strong><span>必不可少的生活用品</span></strong><span>,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。</span></p><p><strong><span>2.软引用(SoftReference)</span></strong></p><p><span>如果一个对象只具有软引用,那就类似于</span><strong><span>可有可无的生活用品</span></strong><span>。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。</span></p><p><span>软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。</span></p><p><strong><span>3.弱引用(WeakReference)</span></strong></p><p><span>如果一个对象只具有弱引用,那就类似于</span><strong><span>可有可无的生活用品</span></strong><span>。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 </span></p><p><span>弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。</span></p><p><strong><span>4.虚引用(PhantomReference)</span></strong></p><p><span>&quot;虚引用&quot;顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。</span></p><p><strong><span>虚引用主要用来跟踪对象被垃圾回收的活动</span></strong><span></span></p><p><strong><span>虚引用与软引用和弱引用的一个区别在于:</span></strong><span> 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 </span></p><p><span>特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为</span><strong><span>软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生</span></strong><span></span></p><h4><a name="61不可达的对象并非非死不可" class="md-header-anchor"></a><span>61.不可达的对象并非“非死不可”</span></h4><p><span>即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。</span></p><p><span>被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。</span></p><h4><a name="62-如何判断一个常量是废弃常量" class="md-header-anchor"></a><span>62. 如何判断一个常量是废弃常量?</span></h4><p><span>运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?</span></p><p><span>假如在常量池中存在字符串 &quot;abc&quot;,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 &quot;abc&quot; 就是废弃常量,如果这时发生内存回收的话而且有必要的话,&quot;abc&quot; 就会被系统清理出常量池。</span></p><h4><a name="63-如何判断一个类是无用的类" class="md-header-anchor"></a><span>63 如何判断一个类是无用的类</span></h4><p><span>方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?</span></p><p><span>判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 </span><strong><span>“无用的类”</span></strong><span></span></p><ul><li><span>该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。</span></li><li><span>加载该类的 ClassLoader 已经被回收。</span></li><li><span>该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。</span></li></ul><p><span>虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。</span></p><p>&nbsp;</p><h4><a name="64垃圾收集有哪些算法各自的特点" class="md-header-anchor"></a><span>64.垃圾收集有哪些算法,各自的特点?</span></h4><p><strong><span>标记-清除算法</span></strong></p><p><span>该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:</span></p><ol start='' ><li><strong><span>效率问题</span></strong></li><li><strong><span>空间问题(标记清除后会产生大量不连续的碎片)</span></strong></li></ol><p><strong><span>复制算法</span></strong></p><p><span>为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。</span></p><p><strong><span>标记-整理算法</span></strong></p><p><span>根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。</span></p><p><strong><span>分代收集算法</span></strong></p><p><span>当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。</span></p><p><strong><span>比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。</span></strong></p><p>&nbsp;</p><h4><a name="65常见的垃圾回收器有哪些" class="md-header-anchor"></a><span>65.常见的垃圾回收器有哪些?</span></h4><ul><li><span>Serial 收集器</span></li><li><span>ParNew 收集器</span></li><li><span>Parallel Scavenge 收集器</span></li><li><span>Serial Old 收集器</span></li><li><span>Parallel Old 收集器</span></li><li><span>CMS 收集器</span></li><li><span>G1 收集器</span></li></ul><p><strong><span>如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。</span></strong></p><p><span>因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,</span><strong><span>我们能做的就是根据具体应用场景选择适合自己的垃圾收集器</span></strong><span>。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。</span></p><h5><a name="serial-收集器" class="md-header-anchor"></a><span>Serial 收集器</span></h5><p><span>Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 </span><strong><span>“单线程”</span></strong><span> 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程</span><strong><span>STW</span></strong><span></span><strong><span>&quot;Stop The World&quot;</span></strong><span> ),直到它收集结束。</span></p><p><strong><span>ParNew 收集器</span></strong></p><p><span>其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。</span></p><p><span> </span><strong><span>新生代采用复制算法,老年代采用标记-整理算法。</span></strong></p><p><strong><span>Parallel Scavenge 收集器</span></strong></p><p><span>Parallel Scavenge也是使用复制算法的多线程收集器, 收集器关注点是吞吐量(高效率的利用 CPU),是JDK1.8默认收集器</span></p><p><span>Serial Old 收集器,Serial 收集器的老年代版本</span></p><p><strong><span>CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。</span></strong></p><p><strong><span>CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。</span></strong></p><p><strong><span>G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.*</span></strong><span>*G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)**。</span></p><p>&nbsp;</p><h4><a name="66minor-gc-和-full-gc-有什么不同呢" class="md-header-anchor"></a><span>66.Minor Gc 和 Full GC 有什么不同呢?</span></h4><p><strong><span>新生代GC(Minor GC):</span></strong><span>指发生在新生代的垃圾收集动作,因为Java对象大多都具有朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。</span></p><p><strong><span>老年代GC(Major GC/Full GC):</span></strong><span>指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scanvenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。</span></p><p>&nbsp;</p><h4><a name="67redis跳表" class="md-header-anchor"></a><span>67.Redis跳表</span></h4><p><span>跳跃表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。</span></p><p><span>这么说,我们可能很难理解,我们可以先回忆一下链表。</span></p><h5><a name="什么是跳跃表" class="md-header-anchor"></a><span>什么是跳跃表</span></h5><p><span> 对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是 O(n)。</span></p><p><img src="images/单链表.png" referrerpolicy="no-referrer" alt="单链表"></p><p><span> 如果我们想要提高其查找效率,可以考虑在链表上建索引的方式。每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作索引。</span><img src="images/一层跳跃表.png" referrerpolicy="no-referrer" alt="一层跳跃表"></p><p><span> 这个时候,我们假设要查找节点8,我们可以先在索引层遍历,当遍历到索引层中值为 7 的结点时,发现下一个节点是9,那么要查找的节点8肯定就在这两个节点之间。我们下降到链表层继续遍历就找到了8这个节点。原先我们在单链表中找到8这个节点要遍历8个节点,而现在有了一级索引后只需要遍历五个节点。</span></p><p><span> 从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍的结点个数减少了,也就是说查找效率提高了,同理再加一级索引。</span></p><p><img src="images/二层跳跃表.png" referrerpolicy="no-referrer" alt="二层跳跃表"></p><p><span> 从图中我们可以看出,查找效率又有提升。在例子中我们的数据很少,当有大量的数据时,我们可以增加多级索引,其查找效率可以得到明显提升。</span></p><p><img src="images/跳跃表.png" referrerpolicy="no-referrer" alt="跳跃表"></p><p><span> </span><strong><span>像这种链表加多级索引的结构,就是跳跃表!</span></strong></p><h5><a name="redis跳跃表" class="md-header-anchor"></a><span>Redis跳跃表</span></h5><p><span> Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的</span><strong><span>元素数量比较多</span></strong><span>,又或者有序集合中元素的</span><strong><span>成员是比较长的字符串</span></strong><span>时, Redis就会使用跳跃表来作为有序集合健的底层实现。</span></p><p><span>这里我们需要思考一个问题——为什么元素数量比较多或者成员是比较长的字符串的时候Redis要使用跳跃表来实现?</span></p><p><span> 从上面我们可以知道,跳跃表在链表的基础上增加了多级索引以提升查找的效率,但其是一个空间换时间的方案,必然会带来一个问题——</span><strong><span>索引是占内存的</span></strong><span>。原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值值和几个指针,并不需要存储对象,因此当节点本身比较大或者元素数量比较多的时候,其优势必然会被放大,而缺点则可以忽略。</span></p><h4><a name="67消息队列" class="md-header-anchor"></a><span>67.消息队列</span></h4><p><span>主要有两点好处:1.通过异步处理提高系统性能(削峰、减少响应所需时间);2.降低系统耦合性。</span></p><p><span>(1) 通过异步处理提高系统性能(削峰、减少响应所需时间);</span></p><p><span>(2) 降低系统耦合性。</span></p><p><span>A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃</span></p><p><span>(3)异步</span></p><p><span>再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。</span></p><p><img src="images/mq-3.png" referrerpolicy="no-referrer" alt="mq-3"></p><p><span>一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。</span></p><p><span>如果</span><strong><span>使用 MQ</span></strong><span>,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快!</span></p><p><span>大量流量就来查缓存,你的数据库会不会炸,</span></p><p><span>高的并发量,后来发现也是可以用消息队列来解决,对流量削峰填谷</span></p><h5><a name="使用消息队列带来的一些问题" class="md-header-anchor"></a><span>使用消息队列带来的一些问题</span></h5><ul><li><strong><span>系统可用性降低:</span></strong><span> 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑</span><strong><span>消息丢失或者说MQ挂掉</span></strong><span>等等的情况,但是,引入MQ之后你就需要去考虑了!</span></li><li><strong><span>系统复杂性提高:</span></strong><span> 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!</span></li><li><strong><span>一致性问题:</span></strong><span> 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!</span></li></ul><h5><a name="rabbitmq-的高可用性" class="md-header-anchor"></a><span>RabbitMQ 的高可用性</span></h5><p><span>RabbitMQ 是比较有代表性的,因为是</span><strong><span>基于主从</span></strong><span>(非分布式)做高可用性的,我们就以 RabbitMQ 为例子讲解第一种 MQ 的高可用性怎么实现。集群模式。</span></p><p><span>怎么保证消息队列消费的幂等性?</span></p><p><span>其实还是得结合业务来思考,我这里给几个思路:</span></p><ul><li><span>比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。</span></li><li><span>比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。</span></li><li><span>比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。</span></li><li><span>比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。</span></li></ul><p>&nbsp;</p><h5><a name="生产者弄丢了数据" class="md-header-anchor"></a><span>生产者弄丢了数据</span></h5><p><span>生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。</span></p><p><span>此时可以选择用 RabbitMQ 提供的事务功能,就是生产者</span><strong><span>发送数据之前</span></strong><span>开启 RabbitMQ 事务 </span><code>channel.txSelect</code><span> ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 </span><code>channel.txRollback</code><span> ,然后重试发送消息;如果收到了消息,那么可以提交事务 </span><code>channel.txCommit</code><span></span></p><p><span>所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启 </span><code>confirm</code><span> 模式,在生产者那里设置开启 </span><code>confirm</code><span> 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 </span><code>ack</code><span> 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 </span><code>nack</code><span> 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。</span></p><p><code>confirm</code><span> 机制是</span><strong><span>异步</span></strong><span>的,一般在生产者这块</span><strong><span>避免数据丢失</span></strong><span>,都是用 </span><code>confirm</code><span> 机制的。</span></p><h5><a name="rabbitmq-弄丢了数据" class="md-header-anchor"></a><span>RabbitMQ 弄丢了数据</span></h5><p><span>就是 RabbitMQ 自己弄丢了数据,这个你必须</span><strong><span>开启 RabbitMQ 的持久化</span></strong><span>,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,</span><strong><span>恢复之后会自动读取之前存储的数据</span></strong><span>,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,</span><strong><span>可能导致少量数据丢失</span></strong><span>,但是这个概率较小。</span></p><p><span>设置持久化有</span><strong><span>两个步骤</span></strong><span></span></p><ul><li><span>创建 queue 的时候将其设置为持久化</span><br></li></ul><p><span>这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。</span></p><ul><li><span>第二个是发送消息的时候将消息的 </span><code>deliveryMode</code><span> 设置为 2</span><br></li></ul><p><span>就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。</span></p><p><span>必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。</span></p><p><span>注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。</span></p><p><span>所以,持久化可以跟生产者那边的 </span><code>confirm</code><span> 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 </span><code>ack</code><span> 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 </span><code>ack</code><span> ,你也是可以自己重发的。</span></p><h5><a name="消费端弄丢了数据" class="md-header-anchor"></a><span>消费端弄丢了数据</span></h5><p><span>RabbitMQ 如果丢失了数据,主要是因为你消费的时候,</span><strong><span>刚消费到,还没处理,结果进程挂了</span></strong><span>,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。</span></p><p><span>这个时候得用 RabbitMQ 提供的 </span><code>ack</code><span> 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 </span><code>ack</code><span> ,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 </span><code>ack</code><span> 一把。这样的话,如果你还没处理完,不就没有 </span><code>ack</code><span> 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。</span></p><p><img src="images/image-20200906155641956.png" referrerpolicy="no-referrer" alt="image-20200906155641956"></p><p>&nbsp;</p><h4><a name="68常见的消息队列对比" class="md-header-anchor"></a><span>68.常见的消息队列对比</span></h4><figure><table><thead><tr><th><span>对比方向</span></th><th><span>概要</span></th></tr></thead><tbody><tr><td><span>吞吐量</span></td><td><span>万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。</span></td></tr><tr><td><span>可用性</span></td><td><span>都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用</span></td></tr><tr><td><span>时效性</span></td><td><span>RabbitMQ 基于erlang开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。</span></td></tr><tr><td><span>功能支持</span></td><td><span>除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准</span></td></tr><tr><td><span>消息丢失</span></td><td><span>ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。</span></td></tr></tbody></table></figure><p><strong><span>总结:</span></strong></p><ul><li><span>ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。</span></li><li><span>RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。</span></li><li><span>RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的</span></li><li><span>kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。</span></li></ul><p>&nbsp;</p><h4><a name="69分布式锁" class="md-header-anchor"></a><span>69.分布式锁</span></h4><p><span>分布式锁有 3 个重要的考量点:</span></p><ul><li><span>互斥(只能有一个客户端获取锁)</span></li><li><span>不能死锁</span></li><li><span>容错(只要大部分 Redis 节点创建了这把锁就可以)</span></li></ul><h5><a name="redis-最普通的分布式锁重要" class="md-header-anchor"></a><span>Redis 最普通的分布式锁(重要)</span></h5><p><span>第一个最普通的实现方式,就是在 Redis 里使用 </span><code>SET key value [EX seconds] [PX milliseconds] NX</code><span> 创建一个 key,这样就算加锁。其中:</span></p><ul><li><code>NX</code><span>:表示只有 </span><code>key</code><span> 不存在的时候才会设置成功,如果此时 redis 中存在这个 </span><code>key</code><span>,那么设置失败,返回 </span><code>nil</code><span></span></li><li><code>EX seconds</code><span></span><strong><span>设置 </span><code>key</code><span> 的过期时间</span></strong><span>,精确到秒级。意思是 </span><code>seconds</code><span> 秒后锁自动释放,别人创建的时候如果发现已经有了就不能加锁了。</span></li><li><code>PX milliseconds</code><span></span><strong><span>同样是设置 </span><code>key</code><span> 的过期时间,精确到毫秒级</span></strong><span></span></li></ul><p><span>比如执行以下命令:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="r"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="r"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">SET</span> <span class="cm-variable">resource_name</span> <span class="cm-variable">my_random_value</span> <span class="cm-variable">PX</span> <span class="cm-number">30000</span> <span class="cm-variable">NX</span></span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>释放锁就是删除 key ,但是</span><strong><span>一般可以用 </span><code>lua</code><span> 脚本删除,判断 value 一样才删除:</span></strong></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="lua"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="lua"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">if</span> <span class="cm-variable">redis.call</span>(<span class="cm-string">"get"</span>,<span class="cm-variable">KEYS</span>[<span class="cm-number">1</span>]) == <span class="cm-variable">ARGV</span>[<span class="cm-number">1</span>] <span class="cm-keyword">then</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">redis.call</span>(<span class="cm-string">"del"</span>,<span class="cm-variable">KEYS</span>[<span class="cm-number">1</span>])</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">else</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-number">0</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">end</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 138px;"></div><div class="CodeMirror-gutters" style="display: none; height: 138px;"></div></div></div></pre><p><span>为啥要用 </span><code>random_value</code><span> 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 </span><code>lua</code><span> 脚本来释放锁。</span></p><p><span>但是这样是肯定不行的。因为如果是普通的 Redis 单实例,那就是单点故障。或者是 Redis 普通主从,那 Redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。</span></p><h5><a name="redlock-算法" class="md-header-anchor"></a><span>RedLock 算法</span></h5><p><span>这个场景是假设有一个 Redis cluster,有 5 个 Redis master 实例。然后执行如下步骤获取一把锁:</span></p><ol start='' ><li><span>获取当前时间戳,单位是毫秒;</span></li><li><span>跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒;</span></li><li><span>尝试在</span><strong><span>大多数节点</span></strong><span>上建立一个锁,比如 5 个节点就要求是 3 个节点 </span><code>n / 2 + 1</code><span></span></li><li><span>客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;</span></li><li><span>要是锁建立失败了,那么就依次之前建立过的锁删除;</span></li><li><span>只要别人建立了一把分布式锁,你就得</span><strong><span>不断轮询去尝试获取锁</span></strong><span></span></li></ol><h5><a name="zk-分布式锁" class="md-header-anchor"></a><span>zk 分布式锁</span></h5><p><span>zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能</span><strong><span>注册个监听器</span></strong><span>监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁</span></p><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><h4><a name="70redis缓存雪崩" class="md-header-anchor"></a><span>70.Redis缓存雪崩</span></h4><p><strong><span>什么是缓存雪崩?</span></strong></p><p><span>简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。</span></p><p><strong><span>有哪些解决办法?</span></strong></p><p><span>1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。</span></p><p><span> 2:不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀</span></p><p>&nbsp;</p><h4><a name="71什么是缓存穿透" class="md-header-anchor"></a><span>71.什么是缓存穿透?</span></h4><p><span>缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。</span></p><p><span>1)缓存无效 key</span></p><p><span>如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:</span><code>SET key value EX 10086</code><span>。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求key,会导致 redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。</span></p><p><span>2)布隆过滤器</span></p><p><span>*布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在与海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,我会先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程</span></p><h4><a name="72缓存击穿" class="md-header-anchor"></a><span>72.缓存击穿</span></h4><p><span>缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。</span></p><p><span>不同场景下的解决方式可如下:</span></p><ul><li><span>若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。</span></li><li><span>若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。</span></li><li><span>若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。</span></li></ul><p>&nbsp;</p><h4><a name="73什么是redis中的bigkey" class="md-header-anchor"></a><span>73.什么是Redis中的BigKey</span></h4><p><span>在Redis中,一个字符串最大512MB,一个二级数据结构(例如hash、list、set、zset)可以存储大约40亿个(2^32-1)个元素,但实际上中如果下面两种情况,我就会认为它是bigkey。</span></p><p><span>在Redis开发规范中,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。</span></p><p>&nbsp;</p><h4><a name="74redis的bigkey的危害" class="md-header-anchor"></a><span>74.Redis的BigKey的危害</span></h4><p><strong><span>操作导致超时阻塞</span></strong><span>:由于Redis单线程的特性,操作bigkey的通常比较耗时,也就意味着阻塞Redis可能性越大,这样会造成客户端阻塞或者引起故障切换,它们通常出现在慢查询中。</span></p><p><strong><span>查询导致网络阻塞</span></strong><span>: bigkey也就意味着每次获取要产生的网络流量较大,假设一个bigkey为1MB,客户端每秒访问量为1000,那么每秒产生1000MB的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例造成影响,其后果不堪设想。</span></p><p><strong><span>过期删除导致阻塞</span></strong><span>:有个bigkey,它安分守己(只执行简单的命令,例如hget、lpop、zscore等),但它设置了过期时间,当它过期后,会被删除,如果没有使用Redis 4.0的过期异步删除(lazyfree-lazy-expire yes),就会存在阻塞Redis的可能性,而且这个过期删除不会从主节点的慢查询发现(因为这个删除不是客户端产生的,是内部循环事件,可以从latency命令中获取或者从slave节点慢查询发现)</span></p><p>&nbsp;</p><h4><a name="75redis的bigkey怎么产生" class="md-header-anchor"></a><span>75.Redis的Bigkey怎么产生?</span></h4><p><span>这个问题就可以作为面试的时候,你在使用Redis中出现的问题</span></p><p><span>一般来说,bigkey的产生都是由于程序设计不当,或者对于数据规模预料不清楚造成的,来看几个🌰:</span></p><p><span>(1) 社交类:粉丝列表,如果某些明星或者大v不精心设计下,必是bigkey。</span></p><p><span>(2) 统计类:例如按天存储某项功能或者网站的用户集合,除非没几个人用,否则必是bigkey。</span></p><p><span>(3) 缓存类:将数据从数据库load出来序列化放到Redis里,这个方式非常常用,但有两个地方需要注意,第一,是不是有必要把所有字段都缓存,第二,有没有相关关联的数据。</span></p><h4><a name="76redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复" class="md-header-anchor"></a><span>76.Redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)</span></h4><p><span>Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)</span></p><p><strong><span>快照(snapshotting)持久化(RDB):</span></strong></p><p><span>Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。</span></p><p><strong><span>AOF(append-only file)持久化</span></strong></p><p><span>与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">appendonly yes</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。</span></p><p><span>在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">appendfsync always &nbsp; #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">appendfsync no &nbsp; &nbsp; &nbsp; #让操作系统决定何时进行同步</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 69px;"></div><div class="CodeMirror-gutters" style="display: none; height: 69px;"></div></div></div></pre><p><span>为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。</span></p><p><strong><span>Redis 4.0 对于持久化机制的优化</span></strong></p><p><span>Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 </span><code>aof-use-rdb-preamble</code><span> 开启)。</span></p><p><span>如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。</span></p><h4><a name="77一条sql的执行流程" class="md-header-anchor"></a><span>77.一条sql的执行流程</span></h4><p><span>下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。</span></p><p><span>先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。</span></p><ul><li><strong><span>连接器:</span></strong><span> 身份认证和权限相关(登录 MySQL 的时候)。</span></li><li><strong><span>查询缓存:</span></strong><span> 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。</span></li><li><strong><span>分析器:</span></strong><span> 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。</span></li><li><strong><span>优化器:</span></strong><span> 按照 MySQL 认为最优的方案去执行。</span></li><li><strong><span>执行器:</span></strong><span> 执行语句,然后从存储引擎返回数据。</span></li></ul><p><img src="images/169a8bc60a083849" referrerpolicy="no-referrer"></p><p><span>简单来说 MySQL 主要分为 Server 层和存储引擎层:</span></p><ul><li><strong><span>Server 层</span></strong><span>:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。</span></li><li><strong><span>存储引擎</span></strong><span>: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。</span><strong><span>现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本</span></strong></li><li><strong><span>开始就被当做默认存储引擎了。</span></strong></li></ul><p><span>说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下</span><strong><span>查询语句</span></strong><span>,语句如下:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="sql"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="sql"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">select</span> * <span class="cm-keyword">from</span> tb_student A <span class="cm-keyword">where</span> A<span class="cm-variable-2">.age</span>=<span class="cm-string">'18'</span> <span class="cm-keyword">and</span> A<span class="cm-variable-2">.name</span>=<span class="cm-string">' 张三 '</span>;</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>结合上面的说明,我们分析下这个语句的执行流程:</span></p><ul><li><p><span>先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。</span></p></li><li><p><span>通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id=&#39;1&#39;。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。</span></p></li><li><p><span>接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 46px;"></div><div class="CodeMirror-gutters" style="display: none; height: 46px;"></div></div></div></pre><p><span>那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。</span></p></li><li><p><span>进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。</span></p></li></ul><h5><a name="更新语句" class="md-header-anchor"></a><span>更新语句</span></h5><p><span>以上就是一条查询 sql 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?sql 语句如下:</span></p><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 0px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">update tb_student A set A.age='19' where A.name=' 张三 ';</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 23px;"></div><div class="CodeMirror-gutters" style="display: none; height: 23px;"></div></div></div></pre><p><span>我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 </span><strong><span>binlog(归档日志)</span></strong><span> ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 </span><strong><span>redo log(重做日志)</span></strong><span>,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:</span></p><ul><li><span>先查询到张三这一条数据,如果有缓存,也是会用到缓存。</span></li><li><span>然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。</span></li><li><span>执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。</span></li><li><span>更新完成。</span></li></ul><h5><a name="总结" class="md-header-anchor"></a><span>总结</span></h5><ul><li><span>MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。</span></li><li><span>引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。</span></li><li><span>查询语句的执行流程如下:权限校验(如果命中缓存)---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎</span></li><li><span>更新语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log(prepare 状态---》binlog---》redo log(commit状态)</span></li></ul><h4><a name="78大表优化" class="md-header-anchor"></a><span>78.大表优化</span></h4><p><span>当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:</span></p><h5><a name="1-限定数据的范围" class="md-header-anchor"></a><span>1. 限定数据的范围</span></h5><p><span>务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;</span></p><h5><a name="2-读写分离" class="md-header-anchor"></a><span>2. 读/写分离</span></h5><p><span>经典的数据库拆分方案,主库负责写,从库负责读;</span></p><h5><a name="3-垂直分区" class="md-header-anchor"></a><span>3. 垂直分区</span></h5><p><span> </span><strong><span>根据数据库里面数据表的相关性进行拆分。</span></strong><span> 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。</span></p><p><span> </span><strong><span>简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。</span></strong><span> 如下图所示,这样来说大家应该就更容易理解了。</span>
<span> </span><img src="images/数据库垂直分区.png" referrerpolicy="no-referrer" alt="数据库垂直分区"></p><ul><li><strong><span>垂直拆分的优点:</span></strong><span> 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。</span></li><li><strong><span>垂直拆分的缺点:</span></strong><span> 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;</span></li></ul><h5><a name="4-水平分区" class="md-header-anchor"></a><span>4. 水平分区</span></h5><p><strong><span>保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。</span></strong><span> </span></p><p><span> 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。</span></p><p><img src="images/数据库水平拆分.png" referrerpolicy="no-referrer" alt="数据库水平拆分"></p><p><span>水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 </span><strong><span>水平拆分最好分库</span></strong><span></span></p><p><span>水平拆分能够 </span><strong><span>支持非常大的数据量存储,应用端改造也少</span></strong><span>,但 </span><strong><span>分片事务难以解决</span></strong><span> ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 </span><strong><span>尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度</span></strong><span> ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。</span></p><p><strong><span>下面补充一下数据库分片的两种常见方案:</span></strong></p><ul><li><strong><span>客户端代理:</span></strong><span> </span><strong><span>分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。</span></strong><span> 当当网的 </span><strong><span>Sharding-JDBC</span></strong><span> 、阿里的TDDL是两种比较常用的实现。</span></li><li><strong><span>中间件代理:</span></strong><span> </span><strong><span>在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。</span></strong><span> 我们现在谈的 </span><strong><span>Mycat</span></strong><span> 、360的Atlas、网易的DDB等等都是这种架构的实现。</span></li></ul><h4><a name="79解释一下什么是池化设计思想什么是数据库连接池为什么需要数据库连接池" class="md-header-anchor"></a><span>79.解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?</span></h4><p><span>池化设计应该不是一个新名词。常见的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。</span></p><p><span>这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。</span></p><p><span>就好比你去食堂打饭,打饭的大妈会先把饭盛好几份放那里,你来了就直接拿着饭盒加菜即可,不用再临时又盛饭又打菜,效率就高了。除了初始化资源,池化设计还包括如下这些特征:池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。这篇文章对</span><a href='https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&amp;mid=2247485679&amp;idx=1&amp;sn=57dbca8c9ad49e1f3968ecff04a4f735&amp;chksm=cea24724f9d5ce3212292fac291234a760c99c0960b5430d714269efe33554730b5f71208582&amp;token=1141994790&amp;lang=zh_CN#rd'><span>池化设计思想</span></a><span>介绍的还不错,直接复制过来,避免重复造轮子了。</span></p><p><strong><span>数据库连接本质就是一个 socket 的连接</span></strong><span>。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存。我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。</span></p><p><strong><span>在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中</span></strong><span>。 连接池还减少了用户必须等待建立与数据库的连接的时间。</span></p><p>&nbsp;</p><h4><a name="80分库分表之后id-主键如何处理" class="md-header-anchor"></a><span>80.分库分表之后,id 主键如何处理?</span></h4><p><span>因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要一个全局唯一的 id 来支持。</span></p><p><span>生成全局 id 有下面这几种方式:</span></p><ul><li><strong><span>UUID</span></strong><span>:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。</span></li><li><strong><span>数据库自增 id</span></strong><span> : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。</span></li><li><strong><span>利用 redis 生成 id :</span></strong><span> 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。</span></li><li><strong><span>Twitter的snowflake算法</span></strong><span> </span></li><li><strong><span>美团的</span><a href='https://tech.meituan.com/2017/04/21/mt-leaf.html'><span>Leaf</span></a><span>分布式ID生成系统</span></strong><span> :Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。</span></li></ul><p>&nbsp;</p><h4><a name="81在mybatis中和的区别是什么" class="md-header-anchor"></a><span>81.在MyBatis中,#{}和${}的区别是什么?</span></h4><p><code>#{}</code><span>是预编译处理,</span><code>${}</code><span>是字符串替换。</span></p><p><span>Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;</span></p><p><span>Mybatis在处理</span><code>${}</code><span>时,就是把</span><code>${}</code><span>替换成变量的值。</span></p><p><span>使用</span><code>#{}</code><span>可以有效的防止SQL注入,提高系统安全性。</span></p><h4><a name="82数据库连接池的原理为什么要使用连接池" class="md-header-anchor"></a><span>82.数据库连接池的原理。为什么要使用连接池。</span></h4><p><span>1,数据库连接是一件费时的操作,连接池可以使多个操作共享一个连接。</span>
<span>2,数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发,测试及性能调整提供依据。</span>
<span>3,使用连接池是为了提高对数据库连接资源的管理</span></p><p>&nbsp;</p><h4><a name="83说说preparedstatement和statement的区别" class="md-header-anchor"></a><span>83.说说preparedStatement和Statement的区别</span></h4><p><span>1,效率:预编译会话比普通会话对象,数据库系统不会对相同的sql语句不会再次编译</span>
<span>2,安全性:可以有效的避免sql注入攻击!sql注入攻击就是从客户端输入一些非法的特殊字符,而使服务器端在构造sql语句的时候仍然能够正确构造,从而收集程序和服务器的信息和数据。</span></p><p>&nbsp;</p><h4><a name="84列举工作中常用的几个git命令" class="md-header-anchor"></a><span>84.列举工作中常用的几个git命令?</span></h4><p><span>新增文件的命令:git add file或者git add .</span>
<span>提交文件的命令:git commit –m或者git commit –a</span>
<span>查看工作区状况:git status –s</span>
<span>拉取合并远程分支的操作:git fetch/git merge或者git pull</span>
<span>查看提交记录命令:git reflog</span></p><p>&nbsp;</p><h4><a name="85redis-内存淘汰机制" class="md-header-anchor"></a><span>85.Redis 内存淘汰机制</span></h4><p><span>Redis中通过maxmemory参数来设定内存的使用上限,当Redis使用内存达到设定的最大值的时候,会根据配置文件中的策略选取要删除的key来删除,从而给新的键值留出空间。</span></p><p><strong><span></span><em><span>Redis</span></em><span>当中,有生存期的key被称为</span><em><span>volatile</span></em><span></span></strong></p><ul><li><p><strong><span>redis 提供 6种数据淘汰策略:</span></strong><span>(默认策略no-eviction)</span></p><ol start='' ><li><strong><span>volatile-lru(least recently used)</span></strong><span>:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰</span></li><li><strong><span>volatile-ttl(Time to Live)</span></strong><span>:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰</span></li><li><strong><span>volatile-random</span></strong><span>:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰</span></li><li><strong><span>allkeys-lru</span></strong><span>:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)</span></li><li><strong><span>allkeys-random</span></strong><span>:从数据集(server.db[i].dict)中任意选择数据淘汰</span></li><li><strong><span>no-eviction</span></strong><span>:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。</span></li></ol><p><span>4.0版本后增加以下两种:</span></p><ol start='' ><li><strong><span>volatile-lfu(Least Frequently Used)</span></strong><span>:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰</span></li><li><strong><span>allkeys-lfu(Least Frequently Used)</span></strong><span>:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key</span></li></ol></li></ul><h4><a name="86说说springboot自动配置原理" class="md-header-anchor"></a><span>86.说说SpringBoot自动配置原理</span></h4><p><span>SpringBoot自动配置主要是通过</span><strong><span>@EnableAutoConfiguation,@Conditional,@EnableConfigProperties,@ConfigurationProperties</span></strong><span>等注解实现的。</span>
<span>具体流程如下:当我们写好一个启动类后,我们会在启动类上加一个@SpringBootApplication,我们可以点开这个注解可以看到它内部有一个@EnableAutoConfiguation的注解,我们继续进入这个注解可以看到一个@Import的注解,这个注解引入了一个AutoConfigurationImportSelector的类。我们继续打开这个类可以看到它有一个selectorImport的方法,这个方法又调用了一个getCandidateConfigurations方法,这个方法内部通过SpringFactoriesLoader.loadFactoryNames最终调用loadSpringFactories加载到一个META-INF下的spring.factories文件。打开这个文件可以看到是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔。当我们通过springApplication.run启动的时候内部就会执行selectImports方法从而找到配置类对应的class。然后将所有自动配置类加载到Spring容器中,进而实现自动配置。</span></p><h4><a name="87springboot的核心注解是哪个它主要由哪几个注解组成的" class="md-header-anchor"></a><span>87.SpringBoot的核心注解是哪个?它主要由哪几个注解组成的?</span></h4><p><span>@SpringBootConfiguration:组合了 @Configuration 注解,实现配置类的功能。</span>
<span>@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。</span>
<span>@ComponentScan:Spring组件扫描。</span></p><h4><a name="88说说listsetmap三者的区别" class="md-header-anchor"></a><span>88.说说List,Set,Map三者的区别?</span></h4><ul><li><span>Collection是</span><strong><span>单列集合</span></strong><span>的根接口,主要用于存储一系列符合某种规则的元素,它有两个重要的子接口List和Set。</span></li><li><span>List接口的特点是</span><strong><span>元素有序可重复</span></strong></li><li><span>Set接口的特点是</span><strong><span>元素无序,不可重复</span></strong></li><li><span>ArrayList和LinkedList是List接口的</span><strong><span>实现类</span></strong></li><li><span>HashSet和TreeSet是Set接口的</span><strong><span>实现类</span></strong></li><li><span>Map是</span><strong><span>双列集合</span></strong><span>的根接口,用于存储具有键(Key),值(Value)映射关系的元素,每个元素都包含一个键-值对,在使用该集合时可以通过指定的键找的对应的值。</span></li><li><span>Map不能包含相同的键,每个键只能映射一个值。键还决定了储存对象在映射中的储存位置。</span></li><li><strong><span>Map为独立接口</span></strong></li><li><span>HashMap和TreeMap是Map接口的实现类</span></li></ul><h4><a name="89什么是线程死锁" class="md-header-anchor"></a><span>89.什么是线程死锁</span></h4><p><span>线程死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。</span></p><p><span>如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。</span></p><p><img src="images/1590378860955.png" referrerpolicy="no-referrer" alt="1590378860955"></p><p><span>产生死锁必须具备以下四个条件:</span></p><ol start='' ><li><span>互斥条件:该资源任意一个时刻只由一个线程占用。</span></li><li><span>请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。</span></li><li><span>不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。</span></li><li><span>循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。</span></li></ol><h4><a name="90如何避免线程死锁" class="md-header-anchor"></a><span>90.如何避免线程死锁?</span></h4><p><span>我上面说了产生死锁的四个必要条件,为了避免死锁,我们只要破坏产生死锁的四个条件中的其中一个就可以了。现在我们来挨个分析一下:</span></p><ol start='' ><li><strong><span>破坏互斥条件</span></strong><span> :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。</span></li><li><strong><span>破坏请求与保持条件</span></strong><span> :一次性申请所有的资源。</span></li><li><strong><span>破坏不剥夺条件</span></strong><span> :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。</span></li><li><strong><span>破坏循环等待条件</span></strong><span> :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。</span></li></ol><h4><a name="91hashmap和concurrenthashmap的区别" class="md-header-anchor"></a><span>91.HashMap和ConcurrentHashMap的区别</span></h4><p><span>1、HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。</span></p><p><span>2、ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。</span></p><p><span>3、ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。</span></p><h4><a name="92现在有线程-t1t2-和-t3你如何确保-t2-线程在-t1-之后执行并且-t3-线程在-t2-之后执行" class="md-header-anchor"></a><span>92.现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?</span></h4><p><span>这个线程面试题通常在第一轮面试或电话面试时被问到,这道多线程问题为了测试面试者是否熟悉 join 方法的概念。答案也非常简单——可以用 Thread 类的 join 方法实现这一效果。</span></p><h4><a name="93hashmap和hashtable的区别" class="md-header-anchor"></a><span>93.HashMap和Hashtable的区别</span></h4><p><span>HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable。</span></p><h4><a name="94arraylist的拓容机制" class="md-header-anchor"></a><span>94.ArrayList的拓容机制</span></h4><p><span>ArrayList每次扩容都为原先容量1.5倍</span></p><h4><a name="95arraylist-与-linkedlist-区别" class="md-header-anchor"></a><span>95.Arraylist 与 LinkedList 区别?</span></h4><p><span>1.是否保证线程安全:ArrayList</span><code></code><span>LinkedList` 都是不同步的,也就是不保证线程安全;</span></p><p><span>2.底层数据结构: </span><code>Arraylist</code><span> 底层使用的是 Object 数组;</span><code>LinkedList</code><span> 底层使用的是 </span><strong><span>双向链表</span></strong><span> 数据结构</span></p><p><span>3.是否支持快速随机访问:** </span><code>LinkedList</code><span> 不支持高效的随机元素访问,而 </span><code>ArrayList</code><span> 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于</span><code>get(int index)</code><span>方法)。</span></p><p><span>4.内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。</span></p><h4><a name="96说说-sleep-方法和-wait-方法区别和共同点" class="md-header-anchor"></a><span>96.说说 sleep() 方法和 wait() 方法区别和共同点?</span></h4><ul><li><span>两者最主要的区别在于:</span><strong><span>sleep 方法没有释放锁,而 wait 方法释放了锁</span></strong><span></span></li><li><span>两者都可以暂停线程的执行。</span></li><li><span>Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。</span></li><li><span>wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。</span></li></ul><h4><a name="97你重写过-hashcode-和-equals-么为什么重写-equals-时必须重写-hashcode-方法" class="md-header-anchor"></a><span>97.你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?</span></h4><p><span>是为了提高效率,采取重写hashcode方法,先进行hashcode比较,如果不同,那么就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的</span></p><h4><a name="98-与-equals区别" class="md-header-anchor"></a><span>98.== 与 equals区别</span></h4><p><strong><span>==</span></strong><span> : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。</span></p><p><strong><span>equals()</span></strong><span> : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:</span></p><ul><li><span>情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。</span></li><li><span>情况 2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。</span></li></ul><h4><a name="99java-序列化中如果有些字段不想进行序列化怎么办" class="md-header-anchor"></a><span>99.Java 序列化中如果有些字段不想进行序列化,怎么办?</span></h4><p><span>对于不想进行序列化的变量,使用 transient 关键字修饰。</span></p><p><span>transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。</span></p><h4><a name="100谈一下hashmap的底层原理是什么" class="md-header-anchor"></a><span>100.谈一下HashMap的底层原理是什么?</span></h4><p><span>基于hashing的原理,jdk8后采用数组+链表+红黑树的数据结构。我们通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后在返回值对象。</span></p><h4><a name="101谈一下hashmap中put是如何实现的" class="md-header-anchor"></a><span>101.谈一下HashMap中put是如何实现的?</span></h4><p><span>1.计算关于key的hashcode值(与Key.hashCode的高16位做异或运算)</span></p><p><span>2.如果散列表为空时,调用resize()初始化散列表</span></p><p><span>3.如果没有发生碰撞,直接添加元素到散列表中去</span></p><p><span>4.如果发生了碰撞(hashCode值相同),进行三种判断</span></p><p><span> 4.1:若key地址相同或者equals后内容相同,则替换旧值</span></p><p><span> 4.2:如果是红黑树结构,就调用树的插入方法</span></p><p><span> 4.3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。</span></p><p><span>5.如果桶满了大于阀值,则resize进行扩容</span></p><h4><a name="102谈一下hashmap中什么时候需要进行扩容扩容resize又是如何实现的" class="md-header-anchor"></a><span>102.谈一下HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?</span></h4><p><span>调用场景:</span></p><p><span>1.初始化数组table</span></p><p><span>2.当数组table的size达到阙值时即++size &gt; load factor </span><span>*</span><span> capacity 时,也是在putVal函数中</span></p><p><span>实现过程:(细讲)</span></p><p><span>1.通过判断旧数组的容量是否大于0来判断数组是否初始化过</span></p><p><span>否:进行初始化</span></p><p><span>判断是否调用无参构造器,</span></p><p><span> </span><span>是:使用默认的大小和阙值</span></p><p><span> </span><span>否:使用构造函数中初始化的容量,当然这个容量是经过tableSizefor计算后的2的次幂数</span></p><p><span> 是,进行扩容,扩容成两倍(小于最大值的情况下),之后在进行将元素重新进行与运算复制到新的散列表中</span></p><p><span>概括的讲:扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。</span></p><p><span>PS:可见底层数据结构用到了数组,到最后会因为容量问题都需要进行扩容操作</span></p><h4><a name="103谈一下hashmap中get是如何实现的" class="md-header-anchor"></a><span>103.谈一下HashMap中get是如何实现的?</span></h4><p><span>对key的hashCode进行hashing,与运算计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点。</span></p><h4><a name="104为什么不直接将key作为哈希值而是与高16位做异或运算" class="md-header-anchor"></a><span>104.为什么不直接将key作为哈希值而是与高16位做异或运算?</span></h4><p><span>因为数组位置的确定用的是与运算,仅仅最后四位有效,设计者将key的哈希值与高16为做异或运算使得在做&amp;运算确定数组的插入位置时,此时的低位实际是高位与低位的结合,增加了随机性,减少了哈希碰撞的次数。</span></p><h4><a name="105为什么是16为什么必须是2的幂如果输入值不是2的幂比如10会怎么样" class="md-header-anchor"></a><span>105.为什么是16?为什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?</span></h4><p><span>HashMap默认初始化长度为16,并且每次自动扩展或者是手动初始化容量时,必须是2的幂。</span></p><p><span>1.为了数据的均匀分布,减少哈希碰撞。因为确定数组位置是用的位运算,若数据不是2的次幂则会增加哈希碰撞的次数和浪费数组空间。(PS:其实若不考虑效率,求余也可以就不用位运算了也不用长度必需为2的幂次)</span></p><p><span>2.输入数据若不是2的幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数最近的数字</span></p><h4><a name="106谈一下当两个对象的hashcode相等时会怎么样" class="md-header-anchor"></a><span>106.谈一下当两个对象的hashCode相等时会怎么样?</span></h4><p><span>会产生哈希碰撞,若key值相同则替换旧值,不然链接到链表后面,链表长度超过阙值8就转为红黑树存储</span></p><h4><a name="107请解释一下hashmap的参数loadfactor它的作用是什么" class="md-header-anchor"></a><span>107.请解释一下HashMap的参数loadFactor,它的作用是什么?</span></h4><p><span>loadFactor表示HashMap的拥挤程度,影响hash操作到同一个数组位置的概率。默认loadFactor等于0.75,当HashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,在HashMap的构造器中可以定制loadFactor。</span></p><h4><a name="108如果hashmap的大小超过了负载因子load-factor定义的容量怎么办" class="md-header-anchor"></a><span>108.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?</span></h4><p><span>超过阙值会进行扩容操作,概括的讲就是扩容后的数组大小是原数组的2倍,将原来的元素重新hashing放入到新的散列表中去。</span></p><h4><a name="109传统hashmap的缺点为什么引入红黑树" class="md-header-anchor"></a><span>109.传统HashMap的缺点(为什么引入红黑树?):</span></h4><p><span>JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。</span></p><h4><a name="110平时在使用hashmap时一般使用什么类型的元素作为key" class="md-header-anchor"></a><span>110.平时在使用HashMap时一般使用什么类型的元素作为Key?</span></h4><p><span>择Integer,String这种不可变的类型,像对String的一切操作都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的覆写了hashCode()以及equals()方法。作为不可变类天生是线程安全的。</span></p><h4><a name="111volatile关键字" class="md-header-anchor"></a><span>111.volatile关键字</span></h4><ol start='' ><li><span>保持内存可见性: 所有线程都能看到共享内存的最新状态。</span></li><li><span>防止指令重排</span></li></ol><h4><a name="112threadlocal简介" class="md-header-anchor"></a><span>112.ThreadLocal简介</span></h4><p><span>通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。</span><strong><span>如果想实现每一个线程都有自己的专属本地变量该如何解决呢?</span></strong><span> JDK中提供的</span><code>ThreadLocal</code><span>类正是为了解决这样的问题。 </span><strong><code>ThreadLocal</code><span>类主要解决的就是让每个线程绑定自己的值,可以将</span><code>ThreadLocal</code><span>类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。</span></strong></p><p><strong><span>如果你创建了一个</span><code>ThreadLocal</code><span>变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是</span><code>ThreadLocal</code><span>变量名的由来。他们可以使用 </span><code>get()</code><span></span><code>set()</code><span> 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。</span></strong></p><h4><a name="113threadlocal-内存泄露问题" class="md-header-anchor"></a><span>113.ThreadLocal 内存泄露问题</span></h4><p><code>ThreadLocalMap</code><span> 中使用的 key 为 </span><code>ThreadLocal</code><span> 的弱引用,而 value 是强引用。所以,如果 </span><code>ThreadLocal</code><span> 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,</span><code>ThreadLocalMap</code><span> 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 </span><code>set()</code><span></span><code>get()</code><span></span><code>remove()</code><span> 方法的时候,会清理掉 key 为 null 的记录。使用完 </span><code>ThreadLocal</code><span>方法后 最好手动调用</span><code>remove()</code><span>方法。</span></p><p><span>在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。</span></p></div>
</body>
</html>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/chengchuang/micode_lesson.git
git@gitee.com:chengchuang/micode_lesson.git
chengchuang
micode_lesson
觅友突击课程
master

搜索帮助