<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Sangs Blog</title>
  
  
  <link href="https://sangs3112.github.io/atom.xml" rel="self"/>
  
  <link href="https://sangs3112.github.io/"/>
  <updated>2025-12-30T05:59:16.711Z</updated>
  <id>https://sangs3112.github.io/</id>
  
  <author>
    <name>Sangs</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>MySQL笔记_6</title>
    <link href="https://sangs3112.github.io/posts/ce24a37f.html"/>
    <id>https://sangs3112.github.io/posts/ce24a37f.html</id>
    <published>2025-12-30T05:59:16.711Z</published>
    <updated>2025-12-30T05:59:16.711Z</updated>
    
    <content type="html"><![CDATA[<h1>语句耗时</h1><ol><li>如果<code>select</code>长时间不返回，使用<code>show processlist</code>查看当前语句处于什么状态<ul><li>比如<code>MDL</code>写锁阻塞了别的<code>session</code>的读</li><li>可以查询是什么<code>session</code>阻塞，但是在<code>MySQL</code>启动时需要设置<code>performance_schema = ON</code>，这样会有<code>10%</code>的性能损失</li></ul></li></ol><ul><li><p><code>sys.schema_table_lock_waits</code>，查询这张表可以找到导致阻塞的<code>processid</code>，<code>kill</code>掉即可</p></li><li><p>还有可能被<code>flush</code>阻塞了，一般<code>flush</code>的两个语句是<code>flush table t with read lock</code>锁表<code>t</code>，<code>flush tables with read lock</code>锁所有的表，本身刷脏页的速度很快，但是可能<code>flush</code>命令被别的<code>session</code>阻塞了</p></li><li><p>或者别的线程占用写锁，那么申请<code>lock in share mode</code>的读锁就会被阻塞。通过查找<code>sys.innodb_lock_waits</code>表即可</p></li><li><p>如果在<code>RR</code>场景下，或者在一个事务中，使用<code>select</code>，如果此时别的<code>session</code>有一个<code>update</code>语句，<code>update</code>了一百万次，那么自己的<code>select</code>语句就会特别慢，因为是一致性读，会存在非常大的<code>undolog</code>需要不断查找，而如果使用<code>select in lock share mode</code>，就是当前读，速度就会很快。</p></li></ul><h1>幻读</h1><ol><li><code>RR</code>下，普通查询是快照读，幻读只有在当前读的环境下才会出现，因为快照读不会看到别人提交的数据。并且，如果别人修改了一行，在当前读的两次查询中，第二次查询才读到，那不是幻读。幻读只有是读到别人新插入的行</li></ol><ul><li><p>幻读会破坏加锁的语义，因为先对d=5的行加锁，在别的<code>session</code>中<code>insert</code>了一条d=5，a=1的行，并修改a=1的行将b改成2，这样的话，也就是修改了d=5的行，破坏了最初对d=5的行加锁的语义</p></li><li><p>幻读 + statement格式的<code>binlog</code>会导致数据不一致性，因为<code>binlog</code>是在<code>commit</code>的时候才会记录，</p></li><li><p>即便给所有的行都加上锁，也无法解决幻读的问题，因为原本不存在的行，无法加锁，所以即便给所有的行都加锁，也没有办法阻止插入新的行</p></li><li><p>所以需要加上间隙锁，在值和值的中间加锁，避免插入新的值</p></li><li><p>解决幻读，需要加行锁和间隙锁(<code>gap lock</code>，开区间)</p></li><li><p>行锁 + 间隙锁 = <code>next-key lock</code> (临键锁，左开右闭)</p></li></ul><h2 id="死锁">死锁</h2><ol><li>如果对不存在的一行加锁，比如id=9，不存在<br><code>select * from t where id = 9 for update</code><ul><li>那么本来应该加行锁+间隙锁，但是因为id=9不存在，所以只会加间隙锁</li></ul></li><li>本来<code>select for update</code>是两个排他锁，但是因为不存在这一行，所以两个<code>session</code>都可以对<code>id=9</code>这一行使用<code>select for update</code>，因为都是间隙锁，所以不会冲突</li><li>此时<code>session B</code>判断不存在这一行，尝试添加<code>id=9</code>的记录，但是因为<code>session A</code>中有间隙锁，所以被阻塞</li><li><code>session A</code>也判断不存在这一行，尝试添加<code>id=9</code>的记录，但是因为<code>session B</code>中存在间隙锁，所以被阻塞，导致死锁</li></ol><ul><li>所以<code>RR</code>会导致死锁，因为只有<code>RR</code>才有间隙锁，一般会设置为<code>RC + row</code>格式的<code>binlog</code>，这样可以解决死锁，并且不会出现数据日志不一致问题</li><li>一般在金融等，或者表备份的时候，才是<code>RR</code></li></ul><h1>加锁</h1><ol><li>加锁的基本单位是<code>next-key lock</code>，前开后闭</li><li>查找中访问的对象才会加锁</li><li>索引上等值查询，唯一索引的时候<code>next-key lock</code> 退化为行锁</li><li>等值查询，最右边的值不满足等值查询的时候，退化为间隙锁</li><li>唯一索引上范围查询会找到不满足条件的第一个值</li></ol><p>现在一张表，字段是<code>id，c, d</code>，<code>c</code>上有普通索引，<code>id</code>是主键</p><ul><li>内含数据<code>(0, 0, 0)(5, 5, 5)(10, 10, 10)(15, 15, 15)(20, 20, 20)(25, 25, 25)</code></li></ul><h2 id="举例1-等值查询间隙锁">举例1 - 等值查询间隙锁</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th><th style="text-align:center">session C</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">update t set d = d + 1 where id = 7;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">insert into t values(8, 8, 8);</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where id = 10;</td></tr></tbody></table><ul><li>对于<code>session A</code>表中没有<code>id=7</code>的行，加锁单位是临键锁，前开后闭，所以范围是<code>(5, 10]</code></li><li>id=7是等值查询，找到最后边的值10，不满足等值条件，所以退化为间隙锁，加锁范围就是<code>(5, 10)</code></li><li><code>session B</code>插入id=8就会被阻塞，但是<code>session C</code>插入id=10不会被锁</li></ul><h2 id="举例2-非唯一索引等值查询">举例2 - 非唯一索引等值查询</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th><th style="text-align:center">session C</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">select id from t where c = 5 lock in share mode;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where id = 5;</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">insert into t values(7, 7, 7);</td></tr></tbody></table><ul><li>加临键锁，对<code>(0, 5]</code></li><li>c是普通索引，所以会访问到右边第一个不满足的值为止，也就是查询到10， 所以<code>(5, 10]</code>加临键锁</li><li>c=5是等值查询，最右边一个值10不满足等值条件，退化为间隙锁<code>(5, 10)</code></li><li>访问到的对象才会加锁，覆盖索引不需要访问主键，所以主键索引不需要加锁，因此<code>session B</code>可以完成，<code>session C</code>插入<code>(7, 7, 7)</code>会被阻塞</li><li><code>lock in share mode</code>只会锁覆盖索引，但是<code>for update</code>会判断你要更新，就会给主键索引上满足条件加行锁</li></ul><h2 id="举例3-主键索引范围查询">举例3 - 主键索引范围查询</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th><th style="text-align:center">session C</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">select * from t where id &gt;= 10 and id &lt; 11 for update;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">insert into t values(8, 8, 8);</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">insert into t values(13, 13, 13);</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where id = 15;</td></tr></tbody></table><ul><li>先找到<code>id = 10</code>，<code>next-key lock (5, 10]</code>，但是唯一索引会退化为行锁</li><li>但是范围查找，找到id=15停止，所以会有临键锁<code>(10, 15]</code></li><li>所以B插入<code>(8, 8, 8)</code>可以通过，插入<code>(13, 13, 13)</code>被阻塞；C更新15也被阻塞</li></ul><h2 id="举例4-非唯一索引范围查询">举例4 - 非唯一索引范围查询</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th><th style="text-align:center">session C</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">select * from t where c &gt;= 10 and c &lt; 11 for update;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">insert into t values(8, 8, 8);</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where c = 15;</td></tr></tbody></table><ul><li>查找c = 10的时候，临键锁<code>(5, 10]</code>，不是唯一索引，不会退化为行锁</li><li><code>session A</code>加上了两个临键锁<code>(5, 10]</code>和<code>(10, 15]</code></li><li>B插入<code>(8, 8, 8)</code>阻塞</li></ul><h2 id="举例5-唯一索引范围查询bug">举例5 - 唯一索引范围查询bug</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th><th style="text-align:center">session C</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">select * from t where id &gt; 10 and id &lt;= 15 for update;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where id = 20;</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">insert into t values(16, 16, 16);</td></tr></tbody></table><ul><li>A是范围查询，索引id加到<code>(10, 15]</code>临键锁，id是唯一索引，所以判断到id=15结束</li><li>但是会访问到第一个不满足条件的值为止，所以会找到id=20，范围查询，所以会加上<code>(15, 20]</code>的临键锁</li><li>因此B更新20会阻塞，C插入16也会阻塞</li></ul><h2 id="举例6-非唯一索引存在等值">举例6 - 非唯一索引存在等值</h2><p>比如同时有两行c=10，分别是<code>(10, 10, 10)</code>，和<code>(30, 10, 30)</code></p><ul><li>非唯一索引上包含主键值，所以不会重复</li></ul><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th><th style="text-align:center">session C</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">delete from t where c = 10;</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">insert into t values(12, 12, 12);</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where c = 15;</td></tr></tbody></table><ul><li>A遍历找到第一个c=10的记录，所以临键锁加在<code>(5 5, 10 10]</code>上</li><li>A向右查找，碰到第一个不满足条件的行，也就是<code>15 15</code>停止，等值查询，退化为<code>10 10</code>到<code>15 15</code>的行锁</li><li>所以最后是<code>(5 5, 10 10]</code>临键锁，<code>(10 10, 15 15)</code>行锁，因此B插入12会被阻止，C修改15会通过</li></ul><h2 id="举例7-limit加锁">举例7 - limit加锁</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td></tr><tr><td style="text-align:center">delete from t where c = 10 limit 2;</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">insert into t values(12, 12, 12);</td></tr></tbody></table><ul><li>因为有<code>limit 2</code>，所以遍历到<code>10 30</code>后，就已经满足两条了，循环结束</li><li>A加锁范围就是<code>(5 5, 10 10]</code>临键锁<code>(10 10, 10 30]</code>临键锁</li><li>B插入12通过</li><li>所以删除的时候可以加<code>limit</code>，限制删除的条数，操作更安全。<ul><li>但是唯一索引上删除不需要，因为唯一索引本身就只加行锁</li><li>可能更适合普通索引，但是数据是唯一的情况</li></ul></li></ul><h2 id="举例8-死锁">举例8 - 死锁</h2><table><thead><tr><th style="text-align:center">sessionA</th><th style="text-align:center">session B</th></tr></thead><tbody><tr><td style="text-align:center">begin;</td><td style="text-align:center"></td></tr><tr><td style="text-align:center">select id from t where c = 10 lock in share mode;</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"></td><td style="text-align:center">update t set d = d + 1 where c = 10;</td></tr><tr><td style="text-align:center">insert into t values (8, 8, 8)</td><td style="text-align:center"></td></tr></tbody></table><ul><li>A在c上加了<code>(5, 10]</code>临键锁和<code>(10, 15)</code>间隙锁</li><li>B要在c上加<code>(5, 10]</code>临键锁，所以等待A释放</li><li>A 想插入8，需要加锁，等待B释放，死锁</li><li>最后B回滚</li><li>B的临键锁没有申请成功，但是B加锁<code>(5, 10]</code>先加间隙锁，加锁成功，再加c=10的行锁，阻塞，两段执行</li></ul><h2 id="总结">总结</h2><ul><li>RR才有间隙锁，并且两阶段提交，事务<code>commit</code>了，锁释放</li><li>RC没有间隙锁，并且语句执行完就会释放不满足条件的行的行锁</li><li>RC在外键也会有间隙锁</li></ul><h1>优化</h1><ul><li><code>show variables like '%connections%;查看max_connections</code>，也就是最大连接数，超过最大连接数的就会被阻止，最大连接数不是越大越好。</li></ul><ol><li>处理掉占用连接但是没有工作的线程<ul><li><code>show variables like 'wait_timeout';</code>查看超时时间，超过时间以后就会断开连接</li><li>可以使用<code>kill connection + id</code>，但是这样客户端不会马上知道，只有在发起下一个请求以后才会报错，就变成了<code>MySQL</code>一直都没有恢复，但是再次 调用接口就可以访问了</li></ul></li><li>减少连接消耗<ul><li>用<code>–skip-grant-tables</code>跳过权限验证，但是<strong>风险极高，不建议使用</strong>，找回密码可以用</li><li><strong>8.0版本</strong>如果使用跳过权限验证，会同时<code>--skip-networking</code>打开这个，保证数据库只有本地访问</li></ul></li><li>性能问题导致短链接增加<ul><li>慢查询<ul><li>索引没设计好<ul><li><strong>5.6版本后</strong>可以使用<code>Online DDL</code>创建索引，<code>alert table</code>即可</li><li>如果一主一备，在从库处理，<code>set sql_log_bin=off</code>，不写<code>binlog</code>，加索引</li><li>主备切换</li><li>再在从库<code>set sql_log_bin=off</code>，加索引</li></ul></li><li>语句有问题<ul><li>使用存储过程<code>query_rewrite</code>重写某一个语句</li></ul></li><li>选错索引<ul><li>使用<code>query_rewrite</code>加上索引即可，或者语句加索引</li></ul></li><li>可以在上线前处理好，上线前将<code>slow log</code>打开，<code>long_query_time=0</code>记录查询语句</li><li>测试表模拟线上数据，回归测试</li><li>留意<code>row_examined</code>数据</li><li>全量回归测试可以使用开源工具<code>pt-query-digest</code></li></ul></li><li><code>QPS</code>暴增<ul><li>如果是全新业务有<code>bug</code>，下掉业务，数据库端去除白名单</li><li>新功能是单独数据库用户，删除用户，断开现有连接</li><li>如果和现有业务部署在一起，查询重写功能重写语句，压力最大的<code>SQL</code>直接<code>select 1</code>返回<ul><li>但是会有误伤，如果别的业务也用了这个SQL，或者这个语句还有别的用处，都会导致其他业务失败</li></ul></li></ul></li><li>更新语句</li></ul></li></ol><h1>数据更新</h1><ul><li><p><code>binlog</code>在事务执行时写入<code>binlog cache</code>，事务提交时写入<code>binlog</code>，清空<code>cache</code></p></li><li><p><code>binlog_cache_size</code>控制<code>cache</code>大小，超过大小的暂时写入磁盘</p></li><li><p>多个线程有自己的<code>binlog cache</code>，但是共用一个<code>binlog</code></p></li><li><p><code>write</code>是写入<code>cache</code>，速度快，<code>fsync</code>是写<code>binlog</code>文件，<code>sync_binlog</code>参数控制时机</p><ul><li><code>sync_binlog = 0</code>，每次提交只<code>write</code>，不<code>fsync</code></li><li><code>sync_binlog = 1</code>每次提交都会<code>fsync</code></li><li><code>sync_binlog = N</code>(<code>N &gt; 1</code>)，每次都<code>write</code>，累计<code>N</code>个事务再<code>fsync</code></li><li>IO非常大的话，可以适当调大这个值，<code>100 - 1000</code>，但是如果重启了，就会丢失N条<code>binlog</code>，如果容错率很低，不能设置为1</li></ul></li><li><p><code>redo log</code>三种状态，分别是<code>redo log buffer</code>，<code>write</code>到<code>page cache</code>，没有持久化和<code>fsync</code>到磁盘，<code>innodb_flush_log_at_trx_commit</code>控制</p><ul><li><code>= 0</code> 表示每次都只保存再<code>buffer</code>中</li><li><code>= 1</code> 表示每次提交都持久化到磁盘</li><li><code>= 2</code> 表示每次提交都<code>write</code>到<code>page cache</code></li><li><code>InnoDB</code>后台线程每隔1s都会将<code>redo log buffer</code>中的内容<code>write</code>到<code>pagecache</code>再<code>fsync</code>到磁盘，所有的线程共用一个<code>redo log buffer</code>，与<code>binlog</code>不同，所以没有提交的事务的<code>redo log</code>，也有可能被持久化到磁盘</li><li>同样，如果<code>redo log buffer</code>占用<code>innodb_log_buffer_size</code>即将到达一半，就会触发主动写盘，但是只<code>write</code>，没有<code>fysnc</code></li><li>如果并行事务，并且<code>innodb_flush_log_at_trx_commit=1</code>，那么别的事务提交的时候，会把自己没有提交的<code>redo log</code> 持久化到磁盘</li><li>如果=1，那么<code>prepare</code>阶段就会持久化一次，所以只需要每隔1s的刷盘，崩溃恢复的逻辑，只需要<code>binlog</code>写入磁盘，<code>redo log prepare write</code>了，就不需要<code>fsync</code>了</li><li>如果<code>sync_binlog=1， innodb_flush_log_at_trx_commit=1</code>，那么事务提交前，现需要等两次刷盘，一次是<code>redo log</code>的<code>prepare</code>，一次是<code>binlog</code></li><li>也就是如果<code>MySQL``TPS</code>两万，就会有四万次写磁盘，所以使用了组提交的方法减少写盘。</li></ul></li><li><p>日志逻辑序列号是<code>LSN</code>，单调递增，对应<code>redo log</code>的写入点，写入长度为<code>len</code>，那么<code>LSN + len</code>，可以避免多次重复提交<code>redo log</code></p><ul><li>比如三个事务同时在<code>redo log buffer</code>中，事务1写盘的时候，会带上事务23写盘，这样<code>LSN</code>之前的事务都完成了写盘，只需要看<code>LSN</code>后的事务即可</li><li><code>binlog</code>也是有组提交，但是效果不如<code>redo log</code>，因为<code>redo log</code>时间较短，所以<code>binlog</code>等不到那么多</li></ul></li></ul><ol><li>设置<code>binlog_group_commit_sync_delay</code>和<code>binlog_group_commit_sync_no_delay_count</code>，满足一个即可，减少<code>binlog</code>的写盘次数，故意等待时间，跟<code>sync_binlog</code>不同，<code>sync</code>是没有写盘就<code>commit</code>事务，但是这两个参数是直到写盘了，才<code>commit</code>事务，会增加响应时间，但是不会像<code>sync</code>一样丢数据<ul><li><code>innodb_flush_log_at_trx_commit</code>不建议为0，这样<code>redo log</code>只会在内存中，宕机了会丢数据</li><li><code>innodb_flush_log_at_trx_commit</code>设置成2，性能差不多，只有在主机关机才会丢数据，如果服务宕机，重启以后数据还在</li></ul></li></ol><ul><li><p>binlog不能中断，必须连续写，所以每个线程一个<code>binlog cache</code>，不然主从不一致；<code>redo log</code>甚至可以跟着别的线程一块写，所以都是一个<code>redo log buffer</code></p></li><li><p>所以可以<code>sync_binlog=1,innodb_flush_log_at_trx_commit=2</code>,这样<code>redo log</code>如果丢失了，就通过<code>binlog</code>恢复就可以</p></li><li><p>一般设置非双一，就是<code>innodb_flush_log_at_trx_commit=2</code>、<code>sync_binlog=1000</code></p><ul><li>业务高峰期，会有预案主库改写为非双1</li><li>从库延迟，为了让从库赶上主库，减少<code>binlog</code>生成频率，赶上了可以改回来</li><li>用备份恢复主库的副本，也就是使用<code>binlog</code>的时候</li><li>批量导入数据也可以修改，不然<code>binlog=row</code>产生内容太多</li></ul></li><li><p>主从首先保证了最终一致性，只要主库正确提交，就有<code>binlog</code>，那么从库正常就会重放<code>binlog</code></p></li><li><p>主从延迟就是从库执行完的时间点-主库执行完的时间点</p></li><li><p>从库上执行<code>show slave status</code>，可以看到<code>seconds_behind_master</code>，表示延迟了多少</p></li><li><p>主从延迟最主要是从库消费<code>relay log</code>的速度比主库生成<code>binlog</code>的速度更慢</p></li><li><p>主从延迟来源</p><ul><li>从库性能比主库机器性能差(但是现在一般不会)</li><li>从库压力大，CPU资源占用较多，导致重放<code>relay log</code>速度变慢<ul><li>一主多从，让多个从库分担压力</li><li><code>binlog</code>输出到外部，比如<code>Hadoop</code>，让外部提供查询能力</li></ul></li><li>大事务，比如一个事务再主库上执行了十分钟，就延迟了十分钟</li><li>大表上<code>DDL</code></li></ul></li></ul><h2 id="主从复制">主从复制</h2><ol><li>5.6版本之前，支持单线程，但是会导致应用日志较慢，造成延迟</li><li>多线程就是拆<code>sql thread</code>为多个线程<br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/MySQL/45_sqlthread.png" alt="45_sqlthread"><ul><li>比如<code>coordinator</code>就是原来的<code>sql thread</code>，但是不更新了，只是负责读中转日志，并分发日志，<code>work</code>线程才更新日志，<code>worker</code>线程个数根据<code>slave_parallel_workers</code>决定，一般32核设置8-16即可，</li><li><code>coordinator</code>分发规则：<ul><li>不能更新覆盖，同一行的两个事务必须在同一个<code>worker</code>中</li><li>不能拆分事务，同一个事务必须在同一个<code>worker</code>中</li></ul></li></ul></li><li>按表分发策略<ul><li>如果两个事务更新不同的表，就可以分发到不同的<code>worker</code>中</li><li>如果事务跨表，需要将两张表一起考虑</li><li>每个<code>worker</code>中对应一张<code>hash</code>表，保存当前<code>worker</code>中正在执行的事务里面涉及的表，值是多少个事务在更新这个表</li><li>如果<code>coordinator</code>收到事务T，判断其中修改的表<ul><li>没有<code>worker</code>中包含这些表，那就分给最空闲的<code>worker</code></li><li>如果跟多于一个<code>worker</code>冲突，就等待</li><li>如果只跟一个<code>worker</code>冲突，那就分给这个<code>worker</code></li></ul></li><li>所以如果热点表，就变成了单线程，如果负载均衡，就很好解决问题</li></ul></li><li>按行分发策略<ul><li>需要<code>binlog = row</code></li><li>必须有主键，不能有外键</li><li>但是对于大事务，耗费内存，耗费CPU资源更大</li></ul></li><li>5.6版本，按库分发并行</li><li>5.7版本<code>slave-parallel-type</code>控制并行策略，配置为<code>DATABASE</code>就是按库并行，配置<code>LOGICAL_CLOCK</code><ul><li>同处于<code>prepare</code>的事务，备库可以并行</li><li>处于<code>prepare</code>和<code>commit</code>的事务可以并行</li></ul></li></ol><p>一主多从基本架构<br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/MySQL/45_ms.png" alt="45_ms"></p><ol><li>主库负责写入和一部分读，从库负责读<br>切换主库的时候需要找同步位点，5.6版本使用<code>GTID</code>，全局事务ID，在事务提交时生成<br>两个部分，格式为<code>server_uuid:gno</code><ul><li><code>server_uuid</code>是实例启动产生的，全局唯一</li><li><code>gno</code>是整数，初始值为1，每次提交事务的时候分配给这个事务，就加1</li><li>启动<code>GTID</code>在启动时加上参数<code>gtid_mode=on</code> 和 <code>enforce_gtid_consistency=on</code>即可<br>使用基于<code>GTID</code>的主备切换，就不需要指定位点，因为位点是不精确的。所以建议使用基于<code>GTID</code>的主从复制</li></ul></li></ol><h3 id="主从延迟怎么解决">主从延迟怎么解决</h3><ol><li><p>强制走主库方案；</p><ul><li>比如交易平台，卖家发布商品以后，马上返回主页面，看商品是否发布成功，这个就强制走主库，因为需要拿到最新的结果</li><li>如果是买家，晚几秒看到商品也可以接受，就走从库</li></ul></li><li><p><code>sleep</code>方案</p><ul><li><code>sleep(1)</code>大部分同步1s内能搞定，但是不太稳妥</li></ul></li><li><p>判断主备无延迟方案</p><ol><li><code>show slave status中的seconds_behind_master</code>是否等于0<ul><li>单位是秒，如果精度不够，可以使用位点和<code>GTID</code></li></ul></li><li><code>Master_Log_File</code>和<code>Read_Master_Log_Pos</code>表示读到主库的最新位点<br><code>Relay_Master_Log_File</code>和<code>Exec_Master_Log_Pos</code>表示备库执行的最新位点<br>如果<code>Master_Log_File=Relay_Master_Log_File&amp;&amp;Read_Master_Log_Pos=Exec_Master_Log_Pos</code>表示日志同步完成</li><li><code>GTID</code>中<br><code>Auto_Position=1</code>表示主备使用了<code>GTID</code>协议<br><code>Retrieved_Gtid_Set</code>是备库收到的所有日志<code>GTID</code>集合<br><code>Executed_Gtid_Set</code>是备库已经执行完的<code>GTID</code>集合<br>如果两个集合相同，则日志同步完成</li><li>但是上面的方法是判断备库收到的<code>binlog</code>已经执行完了，但是实际上可能主库有<code>binlog</code>，但是备库还没有收到，这时需要使用半同步复制</li><li>业务高峰期，主库位点和<code>GTID</code>更新很快，位点判断就会一直不成立，那么从库就一直都无法响应</li></ol></li><li><p>配合<code>semi-sync</code>方案</p><ul><li>牺牲一定可用性，保持一致性</li><li>但是因为半同步只要一个从库同步就返回<code>ack</code>，实际上请求可能打到别的没有同步的从库上</li><li>所以半同步配合位点方案，会有问题<ul><li>一主多从，会有过期读的问题</li><li>如果一直有延迟，就一直无法<code>select</code>，但是实际上不需要等待到完全没有延迟，而是只要一个事务完成以后，<code>select</code>到哪个事务即可，不需要等待其他事务也同步完</li></ul></li></ul></li><li><p>等主库位点方案</p><ul><li><code>select master_pos_wait(file, pos[, timeout]);</code><ul><li><code>file pos</code>是主库上的文件名和位置</li><li>从库上执行</li><li><code>timeout</code>可选，表示超时</li><li>返回一个正整数<code>M</code>，表示命令开始执行，到应用完<code>file</code>和<code>pos</code>表示的<code>binlog</code>位置，一共执行了多少个事务</li><li>如果备库挂了，那就返回<code>NULL</code></li><li>如果超时，返回<code>-1</code></li><li>如果执行时，已经执行过这个位置了，返回0</li></ul></li><li>具体流程<ul><li>一个事务执行完，马上在主库上执行<code>show master status</code>得到主库执行到的<code>File</code>和<code>Pos</code>(不需要完全精确)</li><li>选一个从库查询</li><li>从库上执行<code>select master_pos_wait(File, Pos, 1);</code></li><li>如果返回&gt;=0，则再这个从库上执行查询语句</li><li>否则就去主库查询</li></ul></li><li>所以如果不允许过期读的情况出现，那么就超时放弃，或者转到主库查询，并做限流</li></ul></li><li><p>等 <code>GTID</code> 方案</p><ul><li><code>WAIT_FOR_EXECUTED_GTID_SET(gtid_set[, timeout])</code>，超时时间默认为0，如果为0，表示一直等待<ul><li>语句作用：等待，直到这个库执行的事务包含传入的<code>gtid_set</code>，返回0</li><li>超时返回1</li></ul></li><li>5.7版本后，不需要主动执行<code>show master status</code>，而是更新以后返回事务的<code>GTID</code>，减少一次查询<ul><li><code>session_track_gtids = OWN_GTID</code></li><li>通过API从<code>mysql_session_track_get_first</code>解析<code>GTID</code>即可</li></ul></li><li>具体流程<ul><li>事务完成，接受到事务的<code>GTID，gtid1</code></li><li>选一个从库查询，执行<code>WAIT_FOR_EXECUTED_GTID_SET(gtid1, 1)</code></li><li>返回0就在这个从库查询</li><li>否则去主库查询</li></ul></li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;语句耗时&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;如果&lt;code&gt;select&lt;/code&gt;长时间不返回，使用&lt;code&gt;show processlist&lt;/code&gt;查看当前语句处于什么状态
&lt;ul&gt;
&lt;li&gt;比如&lt;code&gt;MDL&lt;/code&gt;写锁阻塞了别的&lt;code&gt;sessi</summary>
      
    
    
    
    <category term="MySQL" scheme="https://sangs3112.github.io/categories/MySQL/"/>
    
    
    <category term="MySQL" scheme="https://sangs3112.github.io/tags/MySQL/"/>
    
    <category term="主从延迟解决" scheme="https://sangs3112.github.io/tags/%E4%B8%BB%E4%BB%8E%E5%BB%B6%E8%BF%9F%E8%A7%A3%E5%86%B3/"/>
    
    <category term="幻读" scheme="https://sangs3112.github.io/tags/%E5%B9%BB%E8%AF%BB/"/>
    
    <category term="死锁" scheme="https://sangs3112.github.io/tags/%E6%AD%BB%E9%94%81/"/>
    
    <category term="加锁判断" scheme="https://sangs3112.github.io/tags/%E5%8A%A0%E9%94%81%E5%88%A4%E6%96%AD/"/>
    
  </entry>
  
  <entry>
    <title>MySQL笔记_7</title>
    <link href="https://sangs3112.github.io/posts/b92393e9.html"/>
    <id>https://sangs3112.github.io/posts/b92393e9.html</id>
    <published>2025-12-30T05:59:16.711Z</published>
    <updated>2025-12-30T05:59:16.711Z</updated>
    
    <content type="html"><![CDATA[<h1>主备</h1><ol><li>主备切换流程<br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/MySQL/45_ms.png" alt="45_ms"><br>状态1，客户端读写都只访问节点A，B是从库，同步A的更新，保证AB数据相同<br>需要切换的时候，就变成状态2，客户端只访问节点B</li></ol><ul><li>状态1不会操作B，但是建议将B设置为<code>read only</code><ul><li>防止他人误操作B</li><li>防止切换逻辑出现双写的问题</li><li>判断访问节点的角色</li><li><code>read only</code>对超级管理员无效，所以可以同步更新的线程有超级权限</li></ul></li></ul><ol start="2"><li><p><code>binlog statement</code></p><ul><li><code>statement</code>就是记录<code>SQL</code>语言，甚至会把注释也都记录进去</li><li><code>statement</code>的格式下，<code>delete</code>带有<code>limit</code>，就是<code>unsafe</code>的，会导致主从不一致</li><li>删除条件的两个都有索引，如果主从版本不一，可能选择的索引不一致，所以顺序不同，导致<code>delete</code>的东西不同，本质上就是主从库可能<code>delete</code>的时候用到的索引不同，因为有<code>limit</code>存在，比如只删一条数据，就会导致数据不一致。</li></ul></li><li><p><code>binlog row</code></p><ul><li><code>row</code>里面多了<code>Table_map</code>和<code>Delete_rows</code></li><li><code>Table_map</code>用于说明接下来操作哪个库哪个表</li><li><code>Delete_rows</code>用于定义删除行为</li><li>实际上，<code>row</code>虽然会占用空间，但是有助于恢复数据，比如误删，可以根据<code>binlog</code>看到，直接插入即可；误增误改都可以这么处理</li></ul></li><li><p><code>binlog mixed</code></p><ul><li>如果有大量的删，那么<code>row</code>就会记录大量的内容，导致磁盘IO变多，占用空间变多</li><li><code>statement</code>就不会，只会有一条语句，但是在某些情况下，会导致不一致</li><li>所以<code>mixed</code>判断，如果不会导致不一致就用<code>statement</code>，如果可能会不一致，就切换到<code>row</code></li><li>这个格式用的不多</li></ul></li></ol><h2 id="双M">双M</h2><p><img src="https://gitee.com/sang3112/blog_imgs/raw/master/MySQL/45_2m.png" alt="45_2m"></p><ul><li>建议设置<code>log_slave_updates=ON</code>，从库执行完<code>relay log</code>以后也生成<code>binlog</code></li><li>A更新事务，<code>binlog</code>记录A的<code>server id</code></li><li>B接收到<code>binlog</code>，更新，并记录<code>server id</code>也就是A的<code>server id</code></li><li>再传回A，A判断<code>server id</code>等于自身，所以就不会处理日志</li></ul><h1>并发连接和并发查询</h1><ul><li>线程等待锁的时候，并发线程计数减一</li><li>因为等待锁的时候不吃cpu资源</li><li><code>innodb_thread_concurrency</code>设置最大并发线程数</li></ul><h1>检测MySQL</h1><ul><li><p>再<code>mysql</code>库中创建一个<code>health_check</code>表，只放一行数据，定期执行<br><code>mysql&gt; select * from mysql.health_check; </code></p></li><li><p>可以检测因为并发线程过多导致的数据库不可用的问题</p><ul><li><code>select 1</code>不行，因为不涉及<code>InnoDB</code>引擎，就算超出限制也会返回</li><li>但是这样的话，如果<code>binlog</code>磁盘占满了，也会阻塞<code>commit</code>事务，但是不会阻塞返回，所以这种方法无法判断是否存在磁盘满的情况</li></ul></li><li><p>更新判断：放一个<code>timestamp</code>字段，表示最后一次执行检测的时间：<code>mysql&gt; update mysql.health_check set t_modified=now();</code></p><ul><li>对主从库都需要执行更新检测</li><li>为了主备更新不冲突，再<code>mysql.health_check</code>上存入多行数据，用两个库的serverid作为主键</li><li>但是可能IO已经100%了，此时正好执行到<code>update</code>语句，那么就无法判断这种情况，不能及时切换</li><li>可以通过检测<code>performance_schema</code>里面<code>redolog</code>和<code>binlog</code>的写入时间判断是否存在问题，但是性能损耗10%</li></ul></li></ul><h1>误删数据</h1><ul><li><code>delete</code>误删某一行，那么就用<code>binlog</code>恢复即可，需要<code>binlog=row</code>，<code>binlog_row_image=FULL</code></li><li>可以将<code>sql_safe_updates=ON</code>预防，如果<code>delete</code>或者<code>update</code>没有写<code>where</code>条件，或者<code>where</code>条件没有索引，就会报错</li><li>如果需要删全表，则使用<code>truncate table</code> 或者 <code>drop table</code></li><li>但是如果<code>drop</code>或者<code>truncate</code>，无法使用<code>binlog</code>恢复，只能全量备份+增量恢复</li><li>如果对于核心库，可以使用延迟备份，减慢<code>binlog</code>到从库执行的时间，避免被删</li></ul><h1>kill</h1><ul><li>两个<code>kill</code>命令，<code>kill query + id</code> 和 <code>kill connection + id</code></li><li>发送<code>kill</code>命令以后，<code>session</code>运行状态改为<code>THD::KILL_QUERY</code>，并给<code>session</code>发送一个信号</li><li><code>kill</code>无效<ul><li>线程没有执行到判断线程状态的逻辑</li><li>终止逻辑耗时较长<ol><li>大事务被<code>kill</code>，需要回滚</li><li>大事务回滚包含较大的临时文件，压力过大，耗时长</li><li><code>DDL</code>最后阶段</li></ol></li><li><code>ctrl+c</code>命令是服务器开启一个新的线程发送<code>kill query</code>命令</li></ul></li></ul><h1>全表扫描</h1><ol><li>如果扫描一张大表，就获取一行数据，存储到<code>net_buffer</code>中，内存大小由<code>net_buffer_length</code>控制，默认<code>16K</code></li><li>重复获取行，直到<code>net_buffer</code>写满，发送</li><li>发送成功清空<code>net_buffer</code>，重复</li><li>发送返回<code>EAGAIN</code>或者<code>WSAEWOULDBLOCK</code>，表示本地网络栈写满，进入等待，直到网络栈重新可写，再继续等待</li></ol><ul><li>所以如果客户端接收的慢，事务执行时间就会变长<ul><li>可以使用<code>mysql_store_result</code>，将收到的内容存入客户端内存，加快接收速度</li><li>但是如果是大查询，就会占用大量内存，就需要使用<code>mysql_use_result</code></li></ul></li></ul><p><code>innodb_buffer_pool_size</code>一般设置为可用物理内存的60%-80%，线上服务的内存命中率需要达到99%，可以通过<code>show engine innodb status</code>查看<code>Buffer pool hit rate</code></p><ul><li><code>InnoDB</code>淘汰数据页用的<code>LRU</code>的改进<ul><li>因为如果对一个不常用的表使用全表扫描，这时候就会将别的业务正在使用的内存给顶掉，导致别的业务缓存失效</li><li>所以<code>InnoDB</code>按照<code>5:3</code>的比例将<code>LRU</code>分为了<code>young</code>和<code>old</code>区，<br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/MySQL/45_youngold.png" alt="45_youndold"></li><li>如图，状态1，访问P3，P3在<code>young</code>区，按照<code>LRU</code>，移到链表头部，变成状态2</li><li>此时，要插入新的数据页，淘汰<code>Pm</code>，但是新的数据页Px插入<code>LRU_old</code>处</li><li>处于<code>old</code>区的数据页，每次被访问都要做下面的判断：<ul><li>如果数据页存在超过1s，就移动到链表头</li><li>少于1s，位置不变，<code>innodb_old_blocks_time=1000</code>，表示1s</li><li>这样扫描大表也用到了<code>buffer pool</code>，但是对于<code>young</code>区没有影响</li></ul></li></ul></li></ul><h1>join</h1><ul><li><code>join</code>应该将小表作为驱动表，大表作为被驱动表，因为<code>InnoDB</code>只会用被驱动表上的索引，对于驱动表，也就是<code>join</code>前面的表，用的是全表扫描。如果驱动表是行数N，被驱动表M被驱动表上索引查询，所以如果回表，就需要在普通索引和主键索引上都查询一次，那么时间复杂度为<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mi>l</mi><mi>o</mi><msub><mi>g</mi><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">2log_2(M)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mclose">)</span></span></span></span>,驱动表上查询<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span>行，所以总共是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>+</mo><mn>2</mn><mi>l</mi><mi>o</mi><msub><mi>g</mi><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">N+2log_2(M)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mclose">)</span></span></span></span>，这就是<code>NLJ</code></li><li>这是仅限于可以使用被驱动表的索引，如果被驱动表没有索引，就会使用<code>BNL</code><ul><li>将驱动表的数据取出放到<code>join_buffer</code>中，</li><li>将被驱动表的每一行跟<code>join_buffer</code>中的数据对比，满足条件放到结果集中</li><li>总时间复杂度就是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>M</mi></mrow><annotation encoding="application/x-tex">NM</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">NM</span></span></span></span></li><li>这样的话，时间复杂度就是两个表的全表扫描<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>+</mo><mi>M</mi><mo>+</mo><mi>N</mi><mi>M</mi></mrow><annotation encoding="application/x-tex">N + M + NM</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">NM</span></span></span></span></li><li>如果驱动表较大，无法放到<code>join_buffer</code>中，就会分段放，<code>join_buffer_size</code>默认<code>256k</code><ul><li>就会将驱动表放一部分行，直到<code>join_buffer</code>放满为止，然后将被驱动表取出比对，再清空<code>join_buffer</code>，放驱动表的下一部分数据，直到比对结束</li><li>假设N行驱动表分<code>K</code>段，<code>N</code>越大，<code>K</code>越大，所以<code>K</code>表示为<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi><mo>×</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">\lambda \times N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">λ</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span>，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi><mo>∈</mo><mo stretchy="false">(</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\lambda \in (0, 1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7335em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">λ</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span></span></li><li>扫描行数就是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>+</mo><mi>λ</mi><mo>×</mo><mi>N</mi><mo>×</mo><mi>M</mi></mrow><annotation encoding="application/x-tex">N + \lambda \times N \times M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">λ</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span></li><li>判断<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>∗</mo><mi>M</mi></mrow><annotation encoding="application/x-tex">N*M</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span></span></span></span>次</li><li>所以应该让小表当驱动表</li></ul></li></ul></li><li>如果<code>join</code>很慢，就把<code>join_buffer_size</code>改大一些</li><li>尽量让<code>join</code>走<code>NLJ</code>，并且警惕<code>explain</code>检查字段是否出现<code>Block Nested Loop</code></li><li>在决定哪个表做驱动表的时候，应该是两个表按照各自的条件过滤，过滤完成之后，计算参与<code>join</code>的各个字段的总数据量，数据量小的那个表，就是“小表”，应该作为驱动表。</li><li><code>join</code>比强行拆分单表查询性能更好</li></ul><h2 id="Multi-Range-Read-MRR">Multi-Range Read(MRR)</h2><ul><li>如果一张表有主键id索引，普通索引a，通过a索引扫描，有时候需要回表查询主键</li><li>此时a虽然是在索引上顺序查询的，但是因为需要回表，所以在主键id索引上就是乱序查的。但是大都数数据都是通过主键自增顺序插入得到的，所以可以认为按照主键递增顺序查询的话对磁盘接近顺序读，性能会更好</li><li>所以MRR就是先根据索引a找到数据，放到<code>read_rnd_buffer</code>中，(随机读缓冲区)，将缓冲区中的数据按照id递增排序，然后依次在主键中查找。大小由<code>read_rnd_buffer_size</code>控制，如果<code>buffer</code>放满了就会先执行排序，然后查找id返回结果，清空<code>buffer</code>再进行下一步</li><li>如果想要使用<code>MRR</code>，<code>set optimizer_switch=&quot;mrr_cost_based=off&quot;</code>，因为优化器不会判断时使用<code>MRR</code>，所以设置为<code>off</code>，固定使用<code>MRR</code></li></ul><h2 id="Batched-Key-Access-BKA">Batched Key Access(BKA)</h2><ul><li><p>当使用<code>join</code>的时候，驱动表一行一行到被驱动表比对，用不上<code>MRR</code></p></li><li><p>现在将驱动表的内容拿出来，放到<code>join_buffer</code>中，原本<code>join_buffer</code>在<code>BNL</code>里才有暂存驱动表的数据，在<code>NLJ</code>没有用，现在<code>NLJ</code>也会用到</p></li><li><p>然后通过<code>join_buffer</code>跟被驱动表比对</p></li><li><p><code>set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';</code>通过这条语句开启<code>MRR</code>和<code>BKA</code>，因为<code>BKA</code>基于<code>MRR</code></p></li><li><p>如果<code>join</code>使用<code>BNL</code>，多次扫描一个冷表，语句执行超过1s，再次扫描冷表就会将数据页放到LRU头部</p></li><li><p>对应的情况就是冷表的数据量小于<code>buffer pool</code>的<code>3/8</code>，能够完全放到<code>old</code>区中。</p></li><li><p>如果冷表很大，业务正常访问的数据页，就没有机会进入<code>young</code>区，这是因为正常访问的数据页要进入<code>young</code>区的话，需要隔1s再被访问到，但是冷表很大，<code>old</code>区在1s内就淘汰了正常数据页，就会导致<code>buffer pool</code>没有这个数据</p></li><li><p>所以大表<code>JOIN</code>不仅会影响IO，IO在<code>join</code>结束也就恢复了，还会影响<code>buffer</code>，需要后面慢慢恢复</p></li></ul><h2 id="BNL对系统的影响">BNL对系统的影响</h2><ol><li>多次扫描被驱动表，影响IO</li><li><code>join</code>条件需要执行<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi><mo>×</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">M \times N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span>次对比，大表占用非常多资源</li><li>导致<code>buffer pool</code>热数据被淘汰，影响内存利用率</li></ol><ul><li><p>可以尝试给被驱动表添加索引</p></li><li><p>如果不适合加索引，比如次数比较少，添加索引不划算。可以建临时表，给临时表加索引，再<code>join</code>即可，优化很好</p></li><li><p>或者自己在业务端实现<code>hash join</code>，会比临时表优化的还好，<code>select * from t1</code>驱动表t1所有数据存入<code>hash</code>结构，比如set之类，<code>select * from t2 where b&gt;=1 and b&lt;=2000</code>获取被驱动表的<code>2000</code>条数据，然后一条一条在业务端跟<code>hash</code>表内容比对即可。临时表需要比较<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi>k</mi><mo>×</mo><mn>2</mn><mi>k</mi></mrow><annotation encoding="application/x-tex">1k \times 2k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord">1</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span>次，但是<code>hash join</code>只需要比较<code>2k</code>次</p></li><li><p>临时表可以用各种引擎，但是内存表只用<code>memory</code>引擎，一般由mysql自动创建销毁</p></li><li><p>临时表建表语法时<code>create temporary table ...</code></p></li><li><p>一个临时表只能被创建它的<code>session</code>访问，其他<code>session</code>不可见</p></li><li><p>临时表可以与普通表重名</p></li><li><p>如果有同名的，那么<code>show create</code>和增删改查访问临时表</p></li><li><p><code>show tables</code>不显示临时表</p></li><li><p>所以临时表不需要担心重名问题，可以用来给<code>join</code>使用，而且不需要担心数据删除问题</p></li><li><p>分库分表跨库查询就会使用临时表</p></li><li><p>创建临时表的时候，<code>InnoDB</code>创建一个<code>frm</code>文件保存表结构定义，前缀是<code>#sql&#123;进程 id&#125;_&#123;线程 id&#125;_ 序列号</code><br>5.6版本之前，会在临时文件目录下创建一个相同前缀.ibd存放数据文件，5.7版本后有专门的临时表空间存放临时数据，因为前缀不同，所以可以创建同名临时表</p></li><li><p>但是普通表是库名+表名得到的，同一个库创建两个同名表就会报错</p></li><li><p>当<code>session</code>结束的时候，就会执行<code>drop temporary table + 表名</code></p></li><li><p>如果<code>binlog</code>不是<code>row</code>，就会记录临时表的操作，不然会报错，提示临时表不存在。但是<code>row</code>格式只会记录数据，所以不会有对临时表的操作，不会发现临时表不存在这种问题，就不会记录临时表</p></li></ul><h2 id="内存临时表">内存临时表</h2><ol><li><code>union</code>的<code>explain</code>会显示<code>using temporary</code>，表示使用临时表，将<code>union</code>前的结果存在临时表中，然后利用主键唯一性剔除第二个查询中的相同的结果</li></ol><ul><li>如果是<code>union all</code>，就没有去重了，所以也不需要临时表了</li></ul><ol start="2"><li><code>group by</code></li></ol><ul><li><p>创建内存临时表，表上两个字段m和c，主键是m</p></li><li><p>扫描t1上的索引a，取id，并%10记x</p><ul><li>临时表没有x的行，那么插入x,1</li><li>如果有x的行，那么对应的c+1</li><li><code>group by</code>处理完以后默认会对字段m按照自然顺序排序，如果不想排序提高效率，就可以直接加上<code>order by null</code><br><code>tmp_table_size</code>是内存临时表的大小设置参数，默认<code>16M</code>，超过大小会转为磁盘临时表</li></ul></li><li><p>不管是内存临时表还是磁盘临时表，<code>group by</code>都需要构建一个带唯一索引的表，执行代价高</p></li><li><p>5.7版本支持<code>generated column</code>机制，用来实现数据关联更新<code>alter table t1 add column z int generated always as(id % 100), add index(z);</code></p></li><li><p>这样索引z上的数据就是<code>group by</code>有序的数据了<code>group by id %100</code>就可以改写为<code>select z, count(*) as c from t1 group by z;</code>此时不再需要临时表，也不需要排序，直接返回即可</p></li><li><p><code>group by</code>语句使用<code>SQL_BIG_RESULT</code>提示，可以告诉<code>sql</code>数据量很大，直接使用磁盘临时表，然后优化器还会将原本的B+树直接改为数组存储 ，两次优化<br><code>select SQL_BIG_RESULT id%100 as m, count(*) as c from t1 group by m;</code></p></li><li><p>初始化<code>sort_buffer</code>，放入整型字段m</p></li><li><p>扫描t1.a，取id%100放入<code>sort_buffer</code></p></li><li><p>对<code>sort_buffer</code>中字段m排序</p></li><li><p>排完序就是有序数组</p></li><li><p>这样就不会使用临时表</p></li></ul><p><code>InnoDB</code>把数据放在主键索引上，其他索引保存主键id就是索引组织表<code>IOT</code></p><ul><li><p>总是有序</p></li><li><p>有空洞时，插入新数据只能放在固定位置，从而保证有序</p></li><li><p>数据位置改变，只需要修改主键索引</p></li><li><p>主键索引查一次，普通索引查两次</p></li><li><p>变长数据<br><code>Memory</code>数据单独存放，索引上保存数据位置，就是堆组织表<code>HOT</code></p></li><li><p>按照写入顺序</p></li><li><p>有空洞时找到空位就可以插入新数据</p></li><li><p>数据位置改变，需要修改所有索引</p></li><li><p>所有索引地位相同</p></li><li><p>不支持变长，定义<code>varchar</code>，也是 按照<code>char(N)</code></p></li><li><p>内存表锁表，并且丢失会有风险，一般不会用</p><ul><li>但是内存临时表不一样，因为不会被其他<code>session</code>访问，所以没有并发性问题</li><li>本来重启就是要删除的，所以丢失没关系</li><li>备库临时表也不会影响主库</li><li>支持<code>hash</code>索引</li></ul></li><li><p><code>MyISAM</code>主键自增值在数据文件中</p></li><li><p><code>InnoDB</code>主键自增值在内存中，8.0版本实现持久化</p><ul><li>5.7版本前没有持久化，保存在内存，重启后就找表的最大值，最大值+1作为自增值</li><li>如果id=10，则<code>AUTO_INCREMENT=11</code>，删除id=10，还是11，如果删除以后重启，AUTO=10</li><li>8.0版本变更记录在<code>redo log</code>中，重启依靠<code>redo log</code>恢复之前的值</li></ul></li><li><p>插入一行数据，指定为自增，判断id=0，null，或者没有指定，就把表的自增值赋值给他</p></li><li><p>如果id指定了值，就使用指定值；如果指定值小于自增值，则自增值不变；</p><ul><li>如果指定值大于等于自增值，就修改自增值，就是<code>auto_increment_offset</code>开始，<code>auto_increment_increment</code>为步长叠加，找到第一个大于指定值作为新的自增值。两个参数默认都是1</li></ul></li><li><p>如果插入数据，自增主键，但是插入数据失败，数据重复了，那么数据无法插入成功，并且主键自增值也没有改回去（唯一键冲突会导致主键自增不连续）</p></li><li><p>事务回滚也会导致自增主键不连续，为了性能，因为申请id很快，但是回滚id可能会导致主键冲突，没有必要</p></li><li><p>批量<code>insert ... select， replace ...select, load data</code>的场景下设置<code>innodb_autoinc_lock_mode=2</code>可以提升并发性，并且不会出现数据一致性问题</p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;主备&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;主备切换流程&lt;br&gt;
&lt;img src=&quot;https://gitee.com/sang3112/blog_imgs/raw/master/MySQL/45_ms.png&quot; alt=&quot;45_ms&quot;&gt;&lt;br&gt;
状态1，客户端读写都只访问节点A</summary>
      
    
    
    
    <category term="MySQL" scheme="https://sangs3112.github.io/categories/MySQL/"/>
    
    
    <category term="MySQL" scheme="https://sangs3112.github.io/tags/MySQL/"/>
    
    <category term="主备切换" scheme="https://sangs3112.github.io/tags/%E4%B8%BB%E5%A4%87%E5%88%87%E6%8D%A2/"/>
    
    <category term="InnoDB和Memory" scheme="https://sangs3112.github.io/tags/InnoDB%E5%92%8CMemory/"/>
    
    <category term="join优化" scheme="https://sangs3112.github.io/tags/join%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Go笔记_0</title>
    <link href="https://sangs3112.github.io/posts/f86b96e2.html"/>
    <id>https://sangs3112.github.io/posts/f86b96e2.html</id>
    <published>2025-12-30T05:59:16.708Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1><code>GOROOT</code> VS <code>GOPATH</code> VS <code>GOBIN</code> VS <code>GOPROXY</code></h1><ul><li><code>GOROOT</code>: Go语言安装路径</li><li><code>GOPATH</code>: 若干工作区目录的路径。是我们自己定义的工作空间  <div class="note info flat"><ul><li><code>GO1.8</code>版本之后，开发包安装完成后会自动设置一个<code>GOPATH</code>目录，</li><li><code>GO1.14</code>版本之后，推荐使用<code>Go Module</code>模式，不一定非要将代码写在<code>GOPATH</code>目录下，也不需要自己配置<code>GOPATH</code></li></ul></div></li><li><code>GOBIN</code>: GO程序生成的可执行文件的路径</li><li><code>GOPROXY</code>: 默认为<code>GOPROXY=https://proxy.golang.org,direct</code>，修改为<code>GOPROXY=https://goproxy.cn,direct</code></li></ul><h1>跨平台编译</h1><h2 id="Windows-编译-LINUX或者OSX">Windows 编译 LINUX或者OSX</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">SET CGO_ENABLED=0   // 禁用CGO</span><br><span class="line">SET GOOS=linux      // 目标平台, [windows, linux, darwin]</span><br><span class="line">SET GOARCH=amd64    // 目标处理器架构</span><br><span class="line">go build</span><br></pre></td></tr></table></figure><h2 id="LINUX-或者-OSX-编译其他环境">LINUX 或者 OSX 编译其他环境</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CGO_ENABLED=0 GOOS=linux|darwin|windows GOARCH=amd64 go build</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li>只有Windows 需要在cmd窗口中运行<code>SET</code>命令</li></ul></div><h1>编写测试</h1><div class="note info flat"><ol><li>有一个程序名为<code>xxx.go</code>，则其测试程序应该名为<code>xxx_test.go</code></li><li>测试函数的命名以<code>Test</code>开始，例如<code>Testxxx()</code></li><li>测试函数只能有一个参数<code>t *testing.T</code>，参数<code>t</code>是测试的<code>hook</code>，测试失败时可以执行<code>t.Fail()</code>等操作</li></ol></div><h2 id="举例">举例</h2><ul><li>有一个<code>main</code>函数，打印<code>Hello world</code></li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// hello.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Hello</span><span class="params">()</span></span> <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="string">&quot;Hello world&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>  &#123;</span><br><span class="line">fmt.Println(Hello())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>则对应的测试代码应该为</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// hello_test.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHello</span><span class="params">(t *testing.T)</span></span>&#123;</span><br><span class="line">got := Hello()</span><br><span class="line">want := <span class="string">&quot;Hello world&quot;</span></span><br><span class="line"><span class="keyword">if</span> got != want&#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;got &#x27;%q&#x27; want &#x27;%q&#x27;&quot;</span>, got, want)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在终端直接执行<code>go test</code>命令即可进行测试，如果文件名错误，会报错:<code>?       gogo    [no test files]</code></li></ul><h2 id="重构-t-Helper">重构 <code>t.Helper()</code></h2><p>现在有这两个文件<code>hello.go, hellotest.go</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// hello.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> helloPrefix = <span class="string">&quot;Hello &quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Hello</span><span class="params">(name <span class="type">string</span>)</span></span> <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> helloPrefix + name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>  &#123;</span><br><span class="line">fmt.Println(Hello(<span class="string">&quot;world&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestHello</span><span class="params">(t *testing.T)</span></span>&#123;</span><br><span class="line">assertCorrectMessage := <span class="function"><span class="keyword">func</span><span class="params">(t *testing.T, got, want <span class="type">string</span>)</span></span>&#123;</span><br><span class="line">t.Helper()</span><br><span class="line"><span class="keyword">if</span> got != want&#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;got &#x27;%q&#x27; want &#x27;%q&#x27;&quot;</span>, got, want)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">// 成功</span></span><br><span class="line">t.Run(<span class="string">&quot;saying hello to people&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(t *testing.T)</span></span>&#123;</span><br><span class="line">got := Hello(<span class="string">&quot;chris&quot;</span>)</span><br><span class="line">want := <span class="string">&quot;Hello chris&quot;</span></span><br><span class="line">assertCorrectMessage(t, got, want)</span><br><span class="line">&#125;)</span><br><span class="line">    <span class="comment">// 失败</span></span><br><span class="line">t.Run(<span class="string">&quot;say hello world when an empty string is supplied&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(t *testing.T)</span></span>&#123;</span><br><span class="line">got := Hello(<span class="string">&quot;&quot;</span>)</span><br><span class="line">want := <span class="string">&quot;Hello world&quot;</span></span><br><span class="line">assertCorrectMessage(t, got, want)</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>将断言单独作为一个函数，由两个子测试进行调用。</li><li>第一个子测试能通过，第二个子测试失败</li><li><code>t.Helper()</code>告诉测试套件这个函数是一个辅助函数，这样测试失败时报告的行号将在函数调用中，而不是在辅助函数内部。</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">不添加t.Helper()的输出</span></span><br><span class="line">--- FAIL: TestHello (0.00s)</span><br><span class="line">    --- FAIL: TestHello/say_hello_world_when_an_empty_string_is_supplied (0.00s)</span><br><span class="line">        hello_test.go:15: got &#x27;&quot;Hello &quot;&#x27; want &#x27;&quot;Hello world&quot;&#x27; # 断言函数处的行号，但是具体不知道是哪个测试用例出错</span><br><span class="line">FAIL</span><br><span class="line">exit status 1</span><br><span class="line">FAIL    gogo    0.001s</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">添加t.Helper()的输出</span></span><br><span class="line">--- FAIL: TestHello (0.00s)</span><br><span class="line">    --- FAIL: TestHello/say_hello_world_when_an_empty_string_is_supplied (0.00s)</span><br><span class="line">        hello_test.go:28: got &#x27;&quot;Hello &quot;&#x27; want &#x27;&quot;Hello world&quot;&#x27; # 具体测试用例内部的行号</span><br><span class="line">FAIL</span><br><span class="line">exit status 1</span><br><span class="line">FAIL    gogo    0.001s</span><br></pre></td></tr></table></figure><h2 id="命名返回值">命名返回值</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Hello</span><span class="params">(name <span class="type">string</span>, language <span class="type">string</span>)</span></span> <span class="type">string</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> name == <span class="string">&quot;&quot;</span>&#123;</span><br><span class="line">        name = <span class="string">&quot;world&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> greetingPrefix(language) + name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">greetingPrefix</span><span class="params">(language <span class="type">string</span>)</span></span> (prefix <span class="type">string</span>)&#123;</span><br><span class="line">    <span class="keyword">switch</span> language &#123;</span><br><span class="line">        <span class="keyword">case</span> french:</span><br><span class="line">            prefix = frenchHelloPrefix</span><br><span class="line">        <span class="keyword">case</span> spanish:</span><br><span class="line">            prefix = spanishHelloPrefix</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            prefix = englishHelloPrefix</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在函数签名中，使用了命名返回值<code>prefix string</code><ul><li>会自动创建一个名为<code>prefix</code>的变量，并且分配零值，即如果是<code>int</code>，则<code>prefix=0</code>，如果是<code>string</code>，则<code>prefix=&quot;&quot;</code></li><li>会在<code>Go Doc</code>中显示，代码更加清晰</li><li>只需要直接写<code>return</code>即可，不需要<code>return prefix</code></li></ul></li><li>函数如果是小写字母开头，则是私有函数；如果是大写字母开头，则是公共函数</li></ul><h2 id="示例">示例</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// integer.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Add takes two integers and returns the sum of them</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Add</span><span class="params">(a, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li><code>Add</code>函数有两个相同类型的参数，所以可以直接写成<code>x, y int</code></li><li>添加的函数注释<code>Add takes two integers and returns the sum of them</code>会放在Go Doc中</li></ul></div><ul><li>添加示例函数，示例函数同样会更新在Go Doc中，可以反映出代码的实际功能</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// integer_test.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ExampleAdd</span><span class="params">()</span></span>&#123;</span><br><span class="line">    sum := Add(<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line">    fmt.Println(sum)</span><br><span class="line">    <span class="comment">// Output: 3</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li><code>// Output: 3</code>实际上是<code>ExampleAdd()</code>这个函数的期望输出，如果写5，表明希望1 + 2 = 5，会返回测试失败</li><li>这个语法不能在别的测试中使用，只有<code>Examplexxx()</code>中可以使用</li><li>使用<code>go test -v</code>可以输出每个测试用例的通过情况，以及整体的通过情况，输出结果如下</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestAdd</span><br><span class="line">--- PASS: TestAdd (0.00s)</span><br><span class="line">=== RUN   ExampleAdd</span><br><span class="line">--- PASS: ExampleAdd (0.00s)</span><br><span class="line">PASS</span><br><span class="line">ok      integer 0.001s</span><br></pre></td></tr></table></figure></div><h2 id="基准测试">基准测试</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// iter.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Repeat</span><span class="params">(a <span class="type">string</span>)</span></span> <span class="type">string</span>&#123;</span><br><span class="line">    <span class="keyword">var</span> repeat <span class="type">string</span></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++&#123;</span><br><span class="line">        repeat += a</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> repeat</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>编写基准测试</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// iter_test.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkRepeat</span><span class="params">(b *testing.B)</span></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++&#123;</span><br><span class="line">        Repeat(<span class="string">&quot;a&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li><code>testing.B</code>可以访问隐性命名<code>b.N</code>，表示这个代码的运行次数，并记录时间</li><li>测试框架会选择<code>b.N</code></li><li>使用<code>go test -bench=.</code>来运行基准测试，如果在<code>Windows</code>中则使用<code>go test -bench=&quot;.&quot;</code></li><li>如果直接使用<code>go test [-v]</code>不会运行基准测试</li><li>使用<code>go test -cover</code>查看覆盖率</li></ul></div><h1>数组 VS 切片</h1><ul><li><code>numbers := [5]int&#123;1,2,3,4,5&#125;</code></li><li><code>numbers := [...]int&#123;1,2,3,4,5&#125;</code></li><li>遍历数组的两个方式</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i ++&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, number := <span class="keyword">range</span> numbers&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// range返回索引和值，使用空白标志符来忽略索引</span></span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li>数组传参，比如需要传入<code>numbers</code>，参数类型应该为<code>numbers [5]int</code></li><li>此时如果传入一个<code>[4]int</code>传入函数，不能通过编译，因为会判定为不同的类型</li><li>所以导致数组没什么用，一般用切片<code>slice</code>，尺寸不固定</li><li>切片就是在声明的时候不指定长度，也就是<code>mySlice := []int{1,2,3}</code>，而不是<code>mySlice := [3]int{1,2,3}</code></li><li>参数是可变数量的切片时，应该写<code>numbers ... []int</code></li><li>不能对切片使用等于号，简单的方法是使用<code>reflect.DeepEqual</code>，用于判断两个变量是否相等</li><li>但是<code>reflect.DeepEqual</code>不是类型安全的，甚至可以比较<code>slice</code>和<code>string</code>。</li><li>使用<code>make</code>创建切片可以指定容量和长度，创建的新切片中所有元素均为<strong>0</strong></li></ul></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;&lt;code&gt;GOROOT&lt;/code&gt; VS &lt;code&gt;GOPATH&lt;/code&gt; VS &lt;code&gt;GOBIN&lt;/code&gt; VS &lt;code&gt;GOPROXY&lt;/code&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GOROOT&lt;/code&gt;: Go语言安装路径&lt;/li</summary>
      
    
    
    
    <category term="Go" scheme="https://sangs3112.github.io/categories/Go/"/>
    
    
    <category term="Go" scheme="https://sangs3112.github.io/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Go笔记_1</title>
    <link href="https://sangs3112.github.io/posts/8f6ca674.html"/>
    <id>https://sangs3112.github.io/posts/8f6ca674.html</id>
    <published>2025-12-30T05:59:16.708Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>方法</h1><div class="note info flat"><ul><li>Go语言中只有不同的<code>package</code>中可以有相同函数名的函数，尽管参数不同，但是如果函数名相同就不能出现在相同的包中(函数重载Go语言没有)</li><li>方法需要通过一个特定的实例调用，比如<code>t.Errorf()</code>，这里的<code>Errorf</code>就是一个方法，通过实例<code>t</code>调用</li><li>函数可以随便调用，没有限制</li></ul></div><h1>接口</h1><ul><li>接口让函数接受不同类型的参数并创造类型安全并且高解耦的代码</li><li>Go语言中 interface resolution 是隐式的。如果传入的类型匹配接口需要的，则编译正确。</li><li>函数实现因此不需要关心参数是什么类型的，只需要声明一个接口，辅助函数就可以从具体类型解耦而只关心本身需要做的工作</li></ul><h1>表格驱动测试</h1><ul><li>如果需要测试一个接口的不同实现，或者传入的数据有很多不同的测试需求，则可以使用表格驱动测试</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestArea</span><span class="params">(t *testing.T)</span></span>&#123;</span><br><span class="line">areaTests := []<span class="keyword">struct</span>&#123;</span><br><span class="line">name <span class="type">string</span></span><br><span class="line">shape Shape</span><br><span class="line">want <span class="type">float64</span></span><br><span class="line">&#125;&#123;</span><br><span class="line">&#123;name: <span class="string">&quot;Rec&quot;</span>, shape: Rec&#123;Width: <span class="number">12</span>, Height: <span class="number">6</span>&#125;, want: <span class="number">72.0</span>&#125;,</span><br><span class="line">&#123;name: <span class="string">&quot;Circle&quot;</span>, shape: Circle&#123;<span class="number">10</span>&#125;, want: <span class="number">314.15926</span>&#125;,</span><br><span class="line">&#123;name: <span class="string">&quot;Tri&quot;</span>, shape: Tri&#123;<span class="number">12</span>, <span class="number">6</span>&#125;, want: <span class="number">361.0</span>&#125;, </span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> _, tt := <span class="keyword">range</span> areaTests&#123;</span><br><span class="line">got := tt.shape.Area()</span><br><span class="line">want := tt.want</span><br><span class="line"><span class="keyword">if</span> got != want&#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;%#v got &#x27;%.2f&#x27;, want &#x27;%.2f&#x27;&quot;</span>, tt.shape, got, want)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li>创建了一个匿名结构体，里面有shape和want，放在一个[]struct切片中</li><li>然后使用两个测试用例填充这个切片</li><li>Go中调用一个函数或者方法的时候，参数会被复制</li><li>使用指针解决这个问题，指向某个值，然后修改</li></ul></div><h1>map</h1><ul><li>Map是引用类型的，拥有对底层数据结构的引用</li><li>因此，Map可以是nil指，如果使用一个nil的map，那么会得到一个nil指针异常，导致程序终止</li><li>永远不要初始化一个空的map变量，比如:<code>var m map[string]string</code></li><li>可以用如下两种方式初始化空的map:</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dict = <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>&#123;&#125;</span><br><span class="line">dict = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span><br></pre></td></tr></table></figure><ul><li>上述两种方法绝对不会出现nil指针异常</li></ul><h1>依赖注入</h1><ul><li><code>fmt.Fprintf</code>接受一个<code>Writer</code>参数，将字符串传递过去。</li><li><code>fmt.Printf</code>是标准的默认输出</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// di.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;io&quot;</span></span><br><span class="line"><span class="string">&quot;os&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Greet</span><span class="params">(writer io.Writer, name <span class="type">string</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">// fmt.Printf(&quot;Hello, %s&quot;, name)</span></span><br><span class="line">fmt.Fprintf(writer, <span class="string">&quot;Hello, %s&quot;</span>, name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">Greet(os.Stdout, <span class="string">&quot;Elodie&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// di_test.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;bytes&quot;</span></span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestGreet</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">buffer := bytes.Buffer&#123;&#125; <span class="comment">// 注入依赖</span></span><br><span class="line">Greet(&amp;buffer, <span class="string">&quot;Chris&quot;</span>)</span><br><span class="line">got := buffer.String()</span><br><span class="line">want := <span class="string">&quot;Hello, Chris&quot;</span></span><br><span class="line"><span class="keyword">if</span> got != want &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;got %s want %s&quot;</span>, got, want)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li>Go中可以使用反引号创建字符串，允许将字符串中的东西放在新的一行，比如<br>`<br>3<br>1<br>2<br>`</li></ul></div><ul><li><p>Go中不会阻塞的操作在成为goroutine的单独进程中运行。使用go关键字声明</p></li><li><p><code>go test -race</code>可以发现<code>goroutine</code>中的竞争条件，比如可能多个进程同时写一个<code>map</code>，但是一次执行并不会触发这种现象。</p></li><li><p>可以通过<code>channels</code>协调<code>goroutine</code>解决数据竞争问题。</p></li><li><p>比如原本需要将多个进程的数据写入<code>map</code>中，现在可以使用<code>channel &lt;- data</code>，将数据发送到<code>channel</code>中，然后再使用<code>for</code>循环，将数据保存保存在新的map中，这样不会产生数据竞争的问题。<code>result := &lt;- channel</code></p></li><li><p>在函数调用之前加上<code>defer</code>前缀会在包含他的函数结束时调用它。</p><ul><li>有时候需要清理资源，比如在函数结束时关闭一个文件，或者关闭一个服务器，但是要把它放在创建服务器语句附近，以便函数内后面的代码仍然可以使用这个服务器，就可以使用<code>defer</code>，等到函数执行完再调用</li></ul></li></ul><h1>进程同步</h1><ul><li><code>select</code>可以轻易实现进程同步</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Racer</span> <span class="params">(a, b <span class="type">string</span>)</span></span> (winner <span class="type">string</span>, err <span class="type">error</span>)&#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;- ping(a):</span><br><span class="line"><span class="keyword">return</span> a, <span class="literal">nil</span></span><br><span class="line"><span class="keyword">case</span> &lt;- ping(b):</span><br><span class="line"><span class="keyword">return</span> b, <span class="literal">nil</span></span><br><span class="line"><span class="keyword">case</span> M- time.After(<span class="number">10</span> *time.Second):</span><br><span class="line"><span class="keyword">return</span> <span class="string">&quot;&quot;</span>, fmt.Errorf(<span class="string">&quot;timed out waiting for %s and %s&quot;</span>, a, b)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ping</span><span class="params">(url <span class="type">string</span>)</span></span> <span class="keyword">chan</span> <span class="type">bool</span>&#123;</span><br><span class="line">ch := <span class="built_in">make</span> (<span class="keyword">chan</span> <span class="type">bool</span>)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>&#123;</span><br><span class="line">http.Get(url)</span><br><span class="line">ch &lt;- <span class="literal">true</span></span><br><span class="line">&#125;()</span><br><span class="line"><span class="keyword">return</span> ch</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>如果是<code>v := &lt;- ch</code>等待值发送给<code>channel</code>，则这是一个阻塞调用，因为需要等待值返回</li><li><code>select</code>允许多个<code>channel</code>等待，第一个发送值的<code>channel</code>胜出。</li><li>使用<code>select</code>时，<code>time.After</code>是一个很好用的函数，因为<code>channel</code>可能永远不会返回一个值，那就有可能不会返回，因此使用<code>time.After</code>设置超时时间</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">t.Run(<span class="string">&quot;returns an error if a server doesn&#x27;t respond within 10s&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    serverA := makeDelayedServer(<span class="number">11</span> * time.Second)</span><br><span class="line">    serverB := makeDelayedServer(<span class="number">12</span> * time.Second)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">defer</span> serverA.Close()</span><br><span class="line">    <span class="keyword">defer</span> serverB.Close()</span><br><span class="line"></span><br><span class="line">    _, err := Racer(serverA.URL, serverB.URL)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line">        t.Error(<span class="string">&quot;expected an error but didn&#x27;t get one&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h1>反射</h1><blockquote><p>编写函数 <code>walk(x interface&#123;&#125;, fn func(string))</code>，参数为结构体x，并对 x 中的所有字符串字段调用 fn 函数</p></blockquote><ul><li>反射提供了程序检查自身结构体的能力。</li><li>允许使用类型<code>interface&#123;&#125;</code>，代表任意类型。但是这样市区了对类型安全的检查，编译器不会再检查类型</li><li>除非真的需要，否则不要使用反射</li><li>如果想要实现多态，可以考虑围绕接口实现。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;方法&lt;/h1&gt;
&lt;div class=&quot;note info flat&quot;&gt;&lt;ul&gt;
&lt;li&gt;Go语言中只有不同的&lt;code&gt;package&lt;/code&gt;中可以有相同函数名的函数，尽管参数不同，但是如果函数名相同就不能出现在相同的包中(函数重载Go语言没有)&lt;/li&gt;
&lt;li</summary>
      
    
    
    
    <category term="Go" scheme="https://sangs3112.github.io/categories/Go/"/>
    
    
    <category term="Go" scheme="https://sangs3112.github.io/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>SpringBoot笔记_0</title>
    <link href="https://sangs3112.github.io/posts/53c118f0.html"/>
    <id>https://sangs3112.github.io/posts/53c118f0.html</id>
    <published>2025-04-22T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.711Z</updated>
    
    <content type="html"><![CDATA[<h1>技术体系架构</h1><h2 id="单一架构">单一架构</h2><blockquote><p>一个项目，一个工程，导出一个<code>war</code>包，在一个<code>Tomcat</code>上运行</p></blockquote><ul><li>应用框架：<code>Spring</code>，<code>SpringMVC</code>，<code>Mybatis</code></li><li><code>SpringMVC</code> 在表述层简化跟前端的交互</li><li><code>Spring</code> 每一层都有</li><li><code>Mybatis</code>对<code>JDBC</code>的封装，在持久化层简化操作<br><img src="" alt="single"></li></ul><h2 id="分布式架构">分布式架构</h2><blockquote><p>一个项目，多个模块，每个模块是一个<code>module</code>，每个工程都运行在自己的<code>Tomcat</code>上，模块之间可以相互调用，每个模块可以看作是一个单一架构</p></blockquote><ul><li><code>SpringBoot</code>是对<code>SSM</code>的封装和简化，节省创建过程</li><li><code>SpringCloud</code>维护服务之间的互相调用<br><img src="" alt="distributed"></li></ul><div class="note info flat"><ul><li>框架 = <code>jar</code>包 + 配置文件 = 反射 + 注解 + 设计模式</li></ul></div><h1><code>SpringFramework</code>主要功能模块</h1><table><thead><tr><th style="text-align:center">功能模块</th><th style="text-align:center">介绍</th></tr></thead><tbody><tr><td style="text-align:center"><code>Core Container</code></td><td style="text-align:center">核心容器，<code>Spring</code>环境下所有的功能都必须基于<code>IoC</code>容器，实现对象管理</td></tr><tr><td style="text-align:center"><code>AOP &amp; Aspects</code></td><td style="text-align:center">面向切面编程，对面向对象编程的完善</td></tr><tr><td style="text-align:center"><code>TX</code></td><td style="text-align:center">声明式事务管理</td></tr><tr><td style="text-align:center"><code>Spring MVC</code></td><td style="text-align:center">提供面向<code>Web</code>应用程序的集成功能</td></tr></tbody></table><h2 id="IoC核心容器"><code>IoC</code>核心容器</h2><blockquote><p><code>Spring</code>帮助完成组件管理<code>IoC</code>，包括组件对象实例化，组件属性赋值，组件对象引用，组件对象存活周期管理…</p></blockquote><ul><li>组件一定是对象，对象未必是组件，组件是映射到应用程序中所有<strong>可重用组件</strong>的<code>Java</code>对象</li></ul><div class="note info flat"><p><code>IoC</code>概念(`Inversion of Control) 控制反转</p><ul><li>主要针对对象的创建和调用控制而言，当应用程序需要使用一个对象的时候，不再是应用程序直接创建该对象，而是由<code>IoC</code>容器来创建和管理</li><li>控制权由应用程序转移到<code>IoC</code>容器，实现了反转。</li><li>基本通过依赖查找方式实现，即<code>IoC</code>容器维护着构成应用程序的对象，负责创建这些对象</li></ul><p><code>DI</code>概念(<code>Dependency Injection</code>) 依赖注入</p><ul><li>组件之间传递依赖关系的过程中，将依赖关系在容器内部进行处理，不必在应用程序代码中硬编码对象之间的以来关系，实现对象之间的耦合</li><li><code>Spring</code>中<code>DI</code>是通过<code>XML</code>配置文件或者注解方式实现的，提供三种形式的依赖注入：<code>Setter</code>方法注入，构造函数注入，接口注入</li></ul><p>优点</p><ol><li>解耦合，通过依赖注入减少依赖关系</li><li>提高可复用性</li><li>通过<code>XML</code>文件或者注解进行配置管理</li><li>通过<code>Spring</code>管理的对象可以使用框架其他功能(<code>AOP</code>等)</li></ol></div><h2 id="IoC容器具体接口和实现类"><code>IoC</code>容器具体接口和实现类</h2><ul><li><code>BeanFactory</code>接口提供了高级配置机制，可以管理任何类型对象，是<code>Spring IoC</code>容器的标准化超接口</li><li><code>ApplicationContext</code>是<code>BeanFactory</code>的子接口，扩展了以下功能<ul><li>更容易与<code>Spring AOP</code>功能集成</li><li>消息资源处理(国际化)</li><li>特定于应用程序给予接口实现，比如<code>Web</code>应用程序的<code>WebApplicationContext</code></li></ul></li><li><code>BeanFactory</code>提供了配置框架和基本功能，<code>ApplicationContext</code>添加更多特定功能，后者是前者的完整超集</li></ul><table><thead><tr><th style="text-align:center">类型名</th><th style="text-align:center">介绍</th></tr></thead><tbody><tr><td style="text-align:center"><code>ClassPathXmlApplicationContext</code></td><td style="text-align:center">通过读取类路径下的<code>XML</code>格式配置文件创建<code>IoC</code>容器对象，类路径下的<code>resources</code></td></tr><tr><td style="text-align:center"><code>FileSystemXmlApplicationContext</code></td><td style="text-align:center">通过文件系统路径读取<code>XML</code>格式配置文件创建<code>IoC</code>容器对象</td></tr><tr><td style="text-align:center"><code>AnnotationConfigApplicationContext</code></td><td style="text-align:center">通过读取<code>Java</code>配置类创建<code>IoC</code>容器对象</td></tr><tr><td style="text-align:center"><code>WebApplicationContext</code></td><td style="text-align:center">专门为<code>Web</code>应用准备，基于<code>Web</code>环境创建<code>IoC</code>容器对象，将对象引入存入<code>ServletContext</code>域</td></tr></tbody></table><ul><li><code>Spring</code>框架提供了多种配置方式<ul><li><code>XML</code>配置方式，最早期就有，配置和解析比较复杂，逐渐不使用</li><li>注解方式，2.5版本开始存在，在<code>Bean</code>上使用注解替代<code>XML</code>配置信息，将<code>Bean</code>注册到<code>Spring IoC</code>容器中</li><li><code>Java</code>配置类方式，3.0版本开始，使用<code>Java</code>编写配置信息，通过<code>@Configuration, @Bean</code>等注解实现<code>Bean</code>和依赖关系的配置</li></ul></li></ul><h2 id="IoC-DI实现步骤"><code>IoC/DI</code>实现步骤</h2><ol><li>配置元数据</li></ol><ul><li>基于<code>XML</code>的配置元数据基本结构</li><li><code>id</code>属性标识单个<code>Bean</code>定义的字符串，随便取，只需要不重复即可</li><li><code>class</code>属性定义<code>Bean</code>类型并使用全限定类名</li><li>如果一个组件类，声明两个组件信息，默认是单例模式，会实例化两个组件对象</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 此处要添加一些约束，配置文件的标签并不是随意命名 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://www.springframework.org/schema/beans&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="string"><span class="tag">    https://www.springframework.org/schema/beans/spring-beans.xsd&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;...&quot;</span> [<span class="attr">1</span>] <span class="attr">class</span>=<span class="string">&quot;...&quot;</span> [<span class="attr">2</span>]&gt;</span>  </span><br><span class="line">    <span class="comment">&lt;!-- collaborators and configuration for this bean go here --&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;...&quot;</span> <span class="attr">class</span>=<span class="string">&quot;...&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- collaborators and configuration for this bean go here --&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- more bean definitions go here --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><ol start="2"><li>实例化<code>IoC</code>容器</li></ol><ul><li>提供给<code>ApplicationContext</code>构造函数的位置路径是资源字符串地址，允许容器从各种外部资源加载配置元数据</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//实例化ioc容器,读取外部配置文件,最终会在容器内进行IoC和DI动作</span></span><br><span class="line"><span class="type">ApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> </span><br><span class="line">           <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;services.xml&quot;</span>, <span class="string">&quot;daos.xml&quot;</span>);</span><br></pre></td></tr></table></figure><ol start="3"><li>获取<code>Bean</code>(组件)</li></ol><ul><li><code>ApplicationContext</code>是一个高级工厂接口，能够维护不同的<code>Bean</code>以及其依赖项的注册表</li><li>使用<code>T getBean(String name, Class&lt;T&gt; requiredType)</code>可以检索<code>Bean</code>实例</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建IoC容器对象，指定配置文件，IoC也开始实例组件对象</span></span><br><span class="line"><span class="type">ApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;services.xml&quot;</span>, <span class="string">&quot;daos.xml&quot;</span>);</span><br><span class="line"><span class="comment">//获取IoC容器的组件对象</span></span><br><span class="line"><span class="type">PetStoreService</span> <span class="variable">service</span> <span class="operator">=</span> context.getBean(<span class="string">&quot;petStore&quot;</span>, PetStoreService.class);</span><br><span class="line"><span class="comment">//使用组件对象</span></span><br><span class="line">List&lt;String&gt; userList = service.getUsernameList();</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li>无参构造函数实例化配置只需要<code>&lt;bean id class /&gt;</code></li><li>静态工厂类<code>IoC</code>配置 <code>&lt;bean id class factory-method=&quot;静态工厂方法&quot;/&gt;</code></li><li>非静态工厂<code>IoC</code>配置<ul><li><code>&lt;bean id=&quot;A&quot; class/&gt;</code></li><li><code>&lt;bean id=&quot;B&quot; factory-bean=&quot;A&quot; factory-method=&quot;静态工厂方法&quot;/&gt;</code></li></ul></li><li><code>FactoryBean</code>不需要再指定<code>factory-method</code><ul><li><code>FactoryBean&lt;T&gt;</code>提供了三种方法</li><li><code>T getObject()</code> 返回工厂的对象实例，返回值存储到<code>IoC</code>容器中，在内部编写复杂的实例化过程</li><li><code>boolean isSingleton()</code> 如果<code>FactoryBean</code>返回单例，则返回<code>true</code>，默认实现返回<code>true</code>，否则返回<code>false</code></li><li><code>Class&lt;?&gt; getObjectType()</code> 返回<code>getObject()</code>方法返回的对象类型，不知道对象类型则返回<code>null</code></li><li>用于代理类场景，第三方框架整合，复杂对象实例化</li><li>工厂<code>bean</code>的标识就是<code>&amp;id</code></li></ul></li></ul><p><code>FactoryBean</code> VS <code>BeanFactory</code></p><ul><li><code>FactoryBean</code>是<code>Spring</code>中特殊的<code>Bean</code>，可以在<code>getObject()</code>工厂方法自定义逻辑创建<code>Bean</code>，能够生产其他<code>Bean</code>的<code>Bean</code></li><li>容器启动时创建，实际使用通过调用<code>getObject()</code>得到生产的<code>Bean</code>，可以自定义任何初始化逻辑</li><li><code>BeanFactory</code>是<code>Spring</code>框架基础，顶级接口定义了容器的基本行为，例如管理<code>bean</code>的生命周期，配置文件的加载和解析，<code>bean</code>的装配和依赖注入</li><li>提供了访问<code>bean</code>的方式，比如<code>getBean()</code>指定获取<code>bean</code>的实例，从不同来源获取<code>bean</code>定义，将其转换为<code>bean</code>实例</li><li>包含很多子类，比如<code>ApplicationContext</code>接口</li><li>前者创建<code>Bean</code>的接口，提供灵活初始化功能，后者管理<code>bean</code>的框架基础接口，提供容器基本功能和<code>bean</code>生命周期管理</li></ul></div><h2 id="Bean依赖注入配置DI"><code>Bean</code>依赖注入配置<code>DI</code></h2><ol><li>配置<code>IoC</code></li><li>配置<code>DI</code> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 无参构造函数声明IoC --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;A&quot;</span> <span class="attr">class</span> /&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 单构造参数注入 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;B&quot;</span> <span class="attr">class</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- value表示值，此处用不上 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- ref 表示对另一个IoC对象的引用 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">ref</span>=<span class="string">&quot;A&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 多构造参数注入 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;B1&quot;</span> <span class="attr">class</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 按照顺序赋值 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">ref</span>=<span class="string">&quot;A&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;B2&quot;</span> <span class="attr">class</span>&gt;</span> </span><br><span class="line">    <span class="comment">&lt;!-- 按照参数名 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">&quot;&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;A&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;B2&quot;</span> <span class="attr">class</span>&gt;</span> </span><br><span class="line">    <span class="comment">&lt;!-- 按照参数下标 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">&quot;1&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">&quot;0&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">&quot;2&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;A&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- setter方法注入 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span> <span class="attr">class</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- name 是setter方法去掉set和，并且首字母小写的值 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- value ref 同样二选一 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span> <span class="attr">ref</span> <span class="attr">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></table></figure></li></ol><div class="note info flat"><ul><li><code>Spring IoC</code>是一个高级容器，会先创建对象(<code>IoC</code>)，在进行赋值(<code>DI</code>)</li><li>因此先后顺序没有关系</li></ul></div><h2 id="读取-IoC组件信息">读取`IoC组件信息</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方式1 创建时指定xml文件即可，可以指定多个，或者一个，也可以无参</span></span><br><span class="line"><span class="type">ApplicationContext</span> <span class="variable">aC</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;location1&quot;</span>,<span class="string">&quot;location2&quot;</span>,...);</span><br><span class="line"><span class="comment">// 方式2 一般是源码的配置过程</span></span><br><span class="line"><span class="type">ClassPathXmlApplicationContext</span> <span class="variable">aC1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>();</span><br><span class="line">aC1.setConfigLocations(<span class="string">&quot;locations&quot;</span>);</span><br><span class="line">aC1.refresh();<span class="comment">//调用IoC和DI的流程</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式1 直接根据bean id获取，返回类型是Object 需要强制类型转换，不推荐</span></span><br><span class="line"><span class="type">Object</span> <span class="variable">h</span> <span class="operator">=</span> aC1.getBean(<span class="string">&quot;id&quot;</span>);</span><br><span class="line"><span class="comment">// 方式2 根据bean id获取，同时指定类型</span></span><br><span class="line"><span class="type">H</span> <span class="variable">h1</span> <span class="operator">=</span> aC1.getBean(<span class="string">&quot;id&quot;</span>, H.class);</span><br><span class="line"><span class="comment">// 方式3 根据类型获取，但是同一个类型在IoC容器中只能存在一个，否则报错NoUniqueBeanDefinitionException</span></span><br><span class="line"><span class="comment">// IoC的配置一定是实现类，不能是接口，但是可以根据接口获取值，因为使用了instance of 获取容器类型，接口和实现类的instance of 是相同的</span></span><br><span class="line"><span class="type">H</span> <span class="variable">h2</span> <span class="operator">=</span> aC1.getBean(H.class);</span><br><span class="line"></span><br><span class="line">h2.doWork();</span><br></pre></td></tr></table></figure><h2 id="Bean组件的作用域和周期方法配置"><code>Bean</code>组件的作用域和周期方法配置</h2><ul><li>组件类中定义方法，当<code>IoC</code>容器实例化和小会组件对象的时候调用</li><li>组件声明<code>&lt;bean&gt;</code>只是将<code>Bean</code>信息配置给<code>IoC</code>容器，在容器中，<code>bean</code>标签对应的信息转成<code>Spring</code>内部的<code>BeanDefinition</code>对象</li><li>包含定义的信息<code>id,class,属性</code>，创建多少个<code>Bean</code>实例对象，由<code>Bean</code>的<code>Scope</code>属性指定</li></ul><table><thead><tr><th style="text-align:center">作用域</th><th style="text-align:center">含义</th><th style="text-align:center">创建对象时机</th><th style="text-align:center">默认</th></tr></thead><tbody><tr><td style="text-align:center"><code>singleton</code></td><td style="text-align:center"><code>IoC</code>容器中，这个<code>bean</code>对象始终为单例</td><td style="text-align:center"><code>IoC</code>容器初始化时</td><td style="text-align:center">是</td></tr><tr><td style="text-align:center"><code>propotype</code></td><td style="text-align:center"><code>IoC</code>容器中，这个<code>bean</code>对象有多个实例</td><td style="text-align:center">获取<code>bean</code>时</td><td style="text-align:center">否</td></tr><tr><td style="text-align:center"><code>request</code></td><td style="text-align:center">请求范围内有效的实例</td><td style="text-align:center">每次请求</td><td style="text-align:center">否</td></tr><tr><td style="text-align:center"><code>propotype</code></td><td style="text-align:center">会话范围内有效的实例</td><td style="text-align:center">每次会话</td><td style="text-align:center">否</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;技术体系架构&lt;/h1&gt;
&lt;h2 id=&quot;单一架构&quot;&gt;单一架构&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;一个项目，一个工程，导出一个&lt;code&gt;war&lt;/code&gt;包，在一个&lt;code&gt;Tomcat&lt;/code&gt;上运行&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li</summary>
      
    
    
    
    <category term="SpringBoot" scheme="https://sangs3112.github.io/categories/SpringBoot/"/>
    
    
    <category term="Spring" scheme="https://sangs3112.github.io/tags/Spring/"/>
    
  </entry>
  
  <entry>
    <title>Maven笔记_1</title>
    <link href="https://sangs3112.github.io/posts/6080062c.html"/>
    <id>https://sangs3112.github.io/posts/6080062c.html</id>
    <published>2025-03-09T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.710Z</updated>
    
    <content type="html"><![CDATA[<h1><code>build</code>设置</h1><h2 id="自定义打包文件">自定义打包文件</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 自定义打包文件, 需要指定后缀 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">fileName</span>&gt;</span>xxx.war<span class="tag">&lt;/<span class="name">fileName</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="包含特定的文件">包含特定的文件</h2><blockquote><p>如果一些配置文件没有放在<code>resources/</code>下</p><p>有时候<code>Mybatis</code>中会将编写<code>SQL</code>语句的映射文件和<code>Mapper</code>接口都写在<code>src/main/java</code>的某个包中</p><p>此时映射文件不会被打包, 就需要单独设置</p></blockquote><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 设置资源所在目录 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">directory</span>&gt;</span>src/main/java<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">includes</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 设置包含的类型资源 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">include</span>&gt;</span>**/*.xml<span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">includes</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="设置自定义插件">设置自定义插件</h2><blockquote><p>可以设置自定义插件, 比如修改<code>jdk</code>版本, <code>tomcat</code>插件, <code>mybatis</code>分页插件, <code>mybatis</code>逆向工程插件</p></blockquote><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- java编译插件, 配置jdk编译版本 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">source</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">source</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">target</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">target</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">encoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">encoding</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- tomcat插件 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.tomcat.maven<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>tomcat7-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">port</span>&gt;</span>8090<span class="tag">&lt;/<span class="name">port</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">path</span>&gt;</span>/<span class="tag">&lt;/<span class="name">path</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 解决tomcat7使用GET请求获取请求参数的时候会乱码的问题 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">uriEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">uriEncoding</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">server</span>&gt;</span>tomcat7<span class="tag">&lt;/<span class="name">server</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">builid</span>&gt;</span></span><br></pre></td></tr></table></figure><h1><code>Maven</code>依赖的传递特性</h1><ul><li>如果<code>Maven</code>项目B依赖A, C依赖B, 则项目C也依赖于项目A, 执行项目C的时候会下载AB项目的<code>jar</code>包</li><li>简化依赖的导入过程, 确保依赖的版本正确</li><li>只有是<code>compile</code>范围的依赖才能够传递, 其他都不能传递, 或者如果设置了<code>&lt;optional&gt;true&lt;/optinoal&gt;</code>同样可以终止传递</li></ul><h2 id="依赖冲突">依赖冲突</h2><ul><li>比如A依赖B, B依赖了X-1.0.jar, A又依赖了C, C依赖了X-2.0.jar, 此时<code>Maven</code>会自动解决冲突</li></ul><ol><li>短路优先原则(第一原则)</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; X (version 1.0)</span><br><span class="line">A --&gt; F --&gt; X (version 2.0)</span><br><span class="line">此时A依赖2.0版本的X</span><br></pre></td></tr></table></figure><ol start="2"><li>路径长度相同, 则先声明的优先</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">A --&gt; E --&gt; X (version 1.0)</span><br><span class="line">A --&gt; F --&gt; X (version 2.0)</span><br><span class="line">在&lt;dependencies&gt;中, 先声明的路径相同会先选择</span><br></pre></td></tr></table></figure><ol start="3"><li>手动排除</li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 依赖排除 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 需要排除的依赖 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span><span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span><span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h1>继承和聚合</h1><h2 id="继承关系">继承关系</h2><ul><li><code>Maven</code>项目中, 一个项目从另一个项目继承配置信息, 共享配置信息, 简化管理和维护</li><li>可以在父工程中同意管理项目中依赖, 对一个大型的项目进行模块拆分, 一个<code>project</code>下面, 创造很多<code>module</code>, 每个<code>module</code>都有自己的配置信息</li><li>每个<code>module</code>自己配置自己的依赖信息, 难以统一管理</li><li>在同一个框架中不同的<code>jar</code>包, 应该是同一个版本, 所以整个项目中使用的框架版本需要统一</li><li>使用框架需要的<code>jar</code>包组合, 需要反复调试, 最后确定一个可用的组合, 不用在新的项目中重新摸索</li><li>所以在父工程中为整个项目维护依赖信息组合保证了项目规范, 也可以沉淀技术</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 父工程不需要参与打包, 也不需要任何代码, 所以可以直接把src/删掉 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">packaging</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">packaging</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 然后选中父工程, 右键新建工程, 可以创建子工程 --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 子工程groupId和父工程的保持一致 --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 子工程会设置父工程坐标 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">pareent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span><span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span><span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span><span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 子工程只需要artifactId, 因为其他都与父工程保持一致 --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 父工程依赖无条件继承到子工程,  但是一般不是所有的依赖每个子工程都需要, 所以会使用DependenceManagement --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 然后子工程中选择需要的依赖, 只需要groupId和artifactId即可, version什么都不需要 --&gt;</span></span><br></pre></td></tr></table></figure><h2 id="聚合">聚合</h2><ul><li>多个项目组织到一个父项目中, 一起构建和管理, 更好管理一组相关的子项目, 简化部署过程</li><li>一个命令构建和发布一组相关项目</li><li>优化构建顺序, 避免依赖混乱构建失败的情况</li><li>统一管理依赖项, 避免重复定义</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.example<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>parent-project<span class="tag">&lt;<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">packaging</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">packaging</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modules</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">module</span>&gt;</span>child-project1(路径, 不是工程名)<span class="tag">&lt;/<span class="name">module</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">module</span>&gt;</span>child-project2<span class="tag">&lt;/<span class="name">module</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">modules</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h1>私服</h1><ul><li>局域网内的仓库服务, 代理位于外部的远程仓库, 局域网内某个用户需要某个依赖的时候<ul><li>请求本地仓库, 如果没有</li><li>请求私服, 将所需依赖下载到本地仓库, 如果私服不存在</li><li>请求外部远程仓库, 下载到私服, 如果外部远程仓库没有, 则直接报错</li></ul></li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第一次运行nexus时间较长</span></span><br><span class="line">./nexus /run </span><br></pre></td></tr></table></figure><ul><li>端口号默认为8081</li><li>如果<strong>访问网站卡住</strong>就直接命令行ctrl + c</li><li><code>Type</code>是<code>proxy</code>, 则这个仓库是远程仓库的代理, 帮助从中央仓库中下载依赖, 配置<code>Remote storage</code>即可</li><li><code>Type</code>是<code>group</code>, 这个仓库是从远程仓库下载到本地存放的仓库(<code>public</code>)</li><li><code>Type</code>是<code>hosted</code>, 有两个仓库, 分别是正式版本<code>releases</code>和测试版本的<code>snapshots</code></li><li>需要修改本地的<code>settings.xml</code></li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 设置新的本地仓库地址 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">localRepository</span>&gt;</span>D:\\new-MvnRepo<span class="tag">&lt;/<span class="name">localRepository</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 设置新的远程mirror --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mirror</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>nexus-mine<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mirrorOf</span>&gt;</span>central<span class="tag">&lt;/<span class="name">mirrorOf</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>Nexus mine<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://ip:port/repository/maven-public/<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mirror</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 如果禁止了匿名访问, 还需要继续设置 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">server</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>nexus-mine<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">username</span>&gt;</span>admin<span class="tag">&lt;/<span class="name">username</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">password</span>&gt;</span>*****<span class="tag">&lt;/<span class="name">password</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">server</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>修改<code>IDEA</code>的配置</li></ul><h2 id="将jar部署到Nexus">将<code>jar</code>部署到<code>Nexus</code></h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">distributionManagement</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">snapshotRepository</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>nexus-mine<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">name</span>&gt;</span>Nexus Snapshot<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">url</span>&gt;</span>....../repository/maven-snapshots/<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">snapshotRepository</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">distributionManagement</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 此处id需要和settings.xml中mirror保持一致 --&gt;</span></span><br></pre></td></tr></table></figure><ul><li>然后执行<code>mvn deploy</code></li></ul><h2 id="引用别人部署的jar">引用别人部署的<code>jar</code></h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">repositories</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">repository</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>nexus-mine<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">name</span>&gt;</span>nexus-mine<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">url</span>&gt;</span>.../repositoryu/maven-snapshots/<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">snapshots</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">enabled</span>&gt;</span>true<span class="tag">&lt;/<span class="name">enabled</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">snapshots</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">releases</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">enabled</span>&gt;</span>true<span class="tag">&lt;/<span class="name">enabled</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">releases</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">repository</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">repositories</span>&gt;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;&lt;code&gt;build&lt;/code&gt;设置&lt;/h1&gt;
&lt;h2 id=&quot;自定义打包文件&quot;&gt;自定义打包文件&lt;/h2&gt;
&lt;figure class=&quot;highlight xml&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;li</summary>
      
    
    
    
    <category term="Maven" scheme="https://sangs3112.github.io/categories/Maven/"/>
    
    
    <category term="Maven" scheme="https://sangs3112.github.io/tags/Maven/"/>
    
    <category term="组合 &amp; 聚合" scheme="https://sangs3112.github.io/tags/%E7%BB%84%E5%90%88-%E8%81%9A%E5%90%88/"/>
    
    <category term="私服Nexus" scheme="https://sangs3112.github.io/tags/%E7%A7%81%E6%9C%8DNexus/"/>
    
  </entry>
  
  <entry>
    <title>Elasticsearch笔记_3</title>
    <link href="https://sangs3112.github.io/posts/cbe58153.html"/>
    <id>https://sangs3112.github.io/posts/cbe58153.html</id>
    <published>2025-03-08T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>自动补全</h1><blockquote><p>与<code>IK</code>分词器一样, 拼音分词器作为插件安装在<code>ES</code>中即可</p></blockquote><ul><li><a href="https://github.com/medcl/elasticsearch-analysis-pinyin">pinyin</a></li><li>解压, 移动到<code>plugin/</code>, 重启<code>elasticsearch</code></li></ul><h2 id="自定义分词器">自定义分词器</h2><div class="note info flat"><p><code>ES</code>中分词器包含三个部分</p><ol><li><code>character filters</code>: 在<code>tokenizer</code>之前对文本进行处理, 比如删除字符, 替换字符</li><li><code>tokenizer</code>: 将文本按照一定规则切割成词条<code>term</code>, 比如<code>keyword</code>就是不分词, 还有<code>ik_smart</code></li><li><code>tokenizer filter</code>: 将<code>tokenizer</code>输出的词条进一步进行处理, 比如大小写转换, 同义词处理, 拼音处理</li></ol></div><ul><li>创建索引库的时候, 使用<code>settings</code>配置自定义的<code>analyzer</code>分词器</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">PUT /test</span><br><span class="line">&#123;</span><br><span class="line">    &quot;settings&quot;: &#123;</span><br><span class="line">        &quot;analysis&quot;: &#123;</span><br><span class="line">            &quot;analyzer&quot;: &#123; // 自定义分词器</span><br><span class="line">                &quot;my_analyzer&quot;: &#123; // 分词器名称</span><br><span class="line">                    &quot;tokenizer&quot;: &quot;ik_max_word&quot;,</span><br><span class="line">                    &quot;filter&quot;: &quot;py&quot;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &quot;filter&quot;: &#123; // 自定义tokenizer filter</span><br><span class="line">            &quot;py&quot;: &#123; // 过滤器名</span><br><span class="line">                &quot;type&quot;: &quot;pinyin&quot;, // 过滤器类型</span><br><span class="line">                &quot;keep_full_pinyin&quot;: false, // 每个中文单独拼音</span><br><span class="line">                &quot;keep_joined_full_pinyin&quot;: true, // 使用多个中文的全拼</span><br><span class="line">                &quot;keep_original&quot;: true, // 是否保留中文</span><br><span class="line">                &quot;limit_first_letter_length&quot;: 16,</span><br><span class="line">                &quot;remove_duplicated_term&quot;: true,</span><br><span class="line">                &quot;none_chinese_pinyin_tokenize&quot;: false</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>拼音分词器只适合在创建倒排索引的时候使用, 不能在搜索的时候使用, 不然当用户输入中文的时候, 会根据其拼音搜索到其他同音词</p><p>通过指定<code>search_analyzer</code>, 可以指定搜索使用不同的分词器</p></blockquote><h2 id="completion-suggester查询"><code>completion suggester</code>查询</h2><blockquote><p>这个查询匹配以用户输入内容开头的词条并返回, 为了提高补全查询的效率, 对于文档字段类型有一些约束</p><p>参与补全查询的字段必须是<code>completion</code>类型</p><p>字段的内容一般是用来补全的多个词条形成的数组</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">GET /test/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;suggest&quot;: &#123;</span><br><span class="line">        &quot;title_suggest&quot;: &#123; // 这边随便起</span><br><span class="line">            &quot;text&quot;: &quot;s&quot;, // 关键字</span><br><span class="line">            &quot;completion&quot;: &#123;</span><br><span class="line">                &quot;field&quot;: &quot;title&quot;, // 补全查询的字段</span><br><span class="line">                &quot;skip_duplicaties&quot;: true, // 跳过重复的</span><br><span class="line">                &quot;size&quot;: 10 // 获取前十条结果</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>数据同步</h1><h2 id="方案1-同步调用">方案1: 同步调用</h2><p><img src="https://gitee.com/sang3112/blog_imgs/raw/e01fa18e70bb8f2ff1d85a5d89f2723af1d8e52a/ES/tongbu.png" alt="tongbu"></p><ul><li>实现简单, 依次执行, 业务耦合, 影响性能</li></ul><h2 id="方案2-异步通知">方案2: 异步通知</h2><p><img src="https://gitee.com/sang3112/blog_imgs/raw/e01fa18e70bb8f2ff1d85a5d89f2723af1d8e52a/ES/yibu.png" alt="yibu"></p><ul><li>依赖<code>MQ</code>, 解除耦合</li></ul><h2 id="方案3-监听binlog">方案3: 监听<code>binlog</code></h2><p><img src="https://gitee.com/sang3112/blog_imgs/raw/e01fa18e70bb8f2ff1d85a5d89f2723af1d8e52a/ES/jianting.png" alt="jianting"></p><ul><li>开启<code>binlog</code>增加数据库负担, 实现起来复杂一些</li></ul><h1>集群</h1><h2 id="搭建">搭建</h2><blockquote><p>单机会有海量数据, 单点故障问题</p><p>海量数据存储: 将索引库逻辑上拆分成<code>N</code>片(<code>shard</code>), 存储到多个节点(<code>node</code>)中</p><p>单点故障问题: 分片数据存储在不同的节点备份(<code>replica</code>)</p><p>所以一个分片的主分片和副本分片不会在同一个节点上<br><img src="https://gitee.com/sang3112/blog_imgs/raw/d45a7b6d52c0178c63ca5afac6a21dcab46ad67b/ES/shard.png" alt="shard"></p></blockquote><h3 id="编写docker-compose">编写<code>docker-compose</code></h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;2.2&#x27;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">es01:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">elasticsearch:7.12.1</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">es01</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">node.name=es01</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.name=es-docker-cluster</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">discovery.seed_hosts=es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.initial_master_nodes=es01,es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data01:/usr/share/elasticsearch/data</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9200</span><span class="string">:9200</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">elastic</span></span><br><span class="line">  <span class="attr">es02:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">elasticsearch:7.12.1</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">es02</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">node.name=es02</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.name=es-docker-cluster</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">discovery.seed_hosts=es01,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.initial_master_nodes=es01,es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data02:/usr/share/elasticsearch/data</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9201</span><span class="string">:9200</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">elastic</span></span><br><span class="line">  <span class="attr">es03:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">elasticsearch:7.12.1</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">es03</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">node.name=es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.name=es-docker-cluster</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">discovery.seed_hosts=es01,es02</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">cluster.initial_master_nodes=es01,es02,es03</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data03:/usr/share/elasticsearch/data</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">9202</span><span class="string">:9200</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">elastic</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data01:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line">  <span class="attr">data02:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line">  <span class="attr">data03:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">local</span></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">elastic:</span></span><br><span class="line">    <span class="attr">driver:</span> <span class="string">bridge</span></span><br></pre></td></tr></table></figure><h3 id="需要修改权限">需要修改权限</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/sysctl.conf</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">添加下面内容</span></span><br><span class="line">vm.max_map_count=262144</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">执行命令使其生效</span></span><br><span class="line">sysctl -p</span><br></pre></td></tr></table></figure><h3 id="监控集群状态">监控集群状态</h3><ul><li><code>kibana</code>可以监控, 但是一般只能监控一个节点, 如果要监控整个集群, 需要安装<code>es-x-pack</code>插件, 比较复杂</li><li>所以使用<code>cerebro</code>监控集群状态, <a href="https://github.com/lmenezes/cerebro">cerebro</a></li><li>解压运行即可</li></ul><h3 id="指定分片信息">指定分片信息</h3><ul><li>可以在创建索引库的时候指定分片信息</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">PUT /idxName</span><br><span class="line">&#123;</span><br><span class="line">    &quot;settings&quot;: &#123;</span><br><span class="line">        &quot;number_of_shards&quot;: 3, // 分片数量</span><br><span class="line">        &quot;number_of_replicas&quot;: 1 // 副本数量</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;mappings&quot;: &#123;</span><br><span class="line">        &quot;properties&quot;: &#123;</span><br><span class="line">            // mapping 映射定义</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="节点角色">节点角色</h2><table><thead><tr><th style="text-align:center">节点类型</th><th style="text-align:center">配置参数</th><th style="text-align:center">默认值</th><th style="text-align:center">职责</th></tr></thead><tbody><tr><td style="text-align:center"><code>master eligible</code></td><td style="text-align:center"><code>node.master</code></td><td style="text-align:center"><code>true</code></td><td style="text-align:center">备选主节点, 主节点可以管理和记录集群状态, 决定分片在哪个节点上, 处理创建和删除索引库的请求</td></tr><tr><td style="text-align:center"><code>data</code></td><td style="text-align:center"><code>node.data</code></td><td style="text-align:center"><code>true</code></td><td style="text-align:center">数据节点, 存储数据, 搜索, 聚合, <code>CRUD</code></td></tr><tr><td style="text-align:center"><code>ingest</code></td><td style="text-align:center"><code>node.ingest</code></td><td style="text-align:center"><code>true</code></td><td style="text-align:center">数据存储之前的预处理</td></tr><tr><td style="text-align:center"><code>coordinating</code></td><td style="text-align:center">上面三个参数均为<code>false</code>则为<code>coordinating</code></td><td style="text-align:center">无</td><td style="text-align:center">路由请求到其他节点, 合并其他节点的结果, 返回</td></tr></tbody></table><h2 id="脑裂问题">脑裂问题</h2><ul><li>默认情况下, 每个节点都是<code>master eligible</code>节点, 一旦<code>master</code>节点宕机, 其他候选节点会选举成为一个主节点, 主节点与其他节点网络故障时, 可能发生脑裂问题</li><li>为了避免脑裂问题(一个集群中出现两个主节点), 要求选票超过(<code>eligible</code>节点数量 + 1) / 2才能当选主节点<ul><li>因此<code>eligible</code>节点个数最好为奇数, 对应配置项是<code>discovery.zen.minimum_master_nodes</code></li><li><code>es 7.0</code>以后, 已经成为默认配置, 所以一般不会发生脑裂问题</li></ul></li></ul><h2 id="数据分片规则">数据分片规则</h2><ul><li><code>coordinating node</code>根据<code>hash</code>算法计算文档应该在哪个分片中</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>h</mi><mi>a</mi><mi>r</mi><mi>d</mi><mo>=</mo><mi>h</mi><mi>a</mi><mi>s</mi><mi>h</mi><mo stretchy="false">(</mo><mi mathvariant="normal">_</mi><mi>r</mi><mi>o</mi><mi>u</mi><mi>t</mi><mi>i</mi><mi>n</mi><mi>g</mi><mo stretchy="false">)</mo><mtext> </mtext><mi>M</mi><mi>O</mi><mi>D</mi><mtext> </mtext><mi>n</mi><mi>u</mi><mi>m</mi><mi>b</mi><mi>e</mi><mi>r</mi><mi mathvariant="normal">_</mi><mi>o</mi><mi>f</mi><mi mathvariant="normal">_</mi><mi>s</mi><mi>h</mi><mi>a</mi><mi>r</mi><mi>d</mi><mi>s</mi></mrow><annotation encoding="application/x-tex">shard = hash(\_routing) \ MOD \ number\_of\_shards</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">ha</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.06em;vertical-align:-0.31em;"></span><span class="mord mathnormal">ha</span><span class="mord mathnormal">s</span><span class="mord mathnormal">h</span><span class="mopen">(</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathnormal">ro</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mord mathnormal">in</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mclose">)</span><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.02778em;">MO</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mspace"> </span><span class="mord mathnormal">n</span><span class="mord mathnormal">u</span><span class="mord mathnormal">mb</span><span class="mord mathnormal" style="margin-right:0.02778em;">er</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathnormal">s</span><span class="mord mathnormal">ha</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">d</span><span class="mord mathnormal">s</span></span></span></span><ul><li><code>_routing</code>默认是文档<code>ID</code></li><li>算法与分片数量有关, 索引库一旦创建, 分片数量不能修改</li></ul></li></ul><p><img src="https://gitee.com/sang3112/blog_imgs/raw/d45a7b6d52c0178c63ca5afac6a21dcab46ad67b/ES/coordinating.png" alt="coordinating"></p><div class="note info flat"><p><code>ES</code>查询分两个阶段:</p><ol><li><code>scatter phase</code>: 分散阶段, <code>coordinating node</code>将请求发到<strong>每一个分片</strong>上</li><li><code>gather phase</code>: 聚集阶段, <code>coordinating node</code>汇总<code>data node</code>的搜索结果, 处理为最终结果集返回给用户<br><img src="https://gitee.com/sang3112/blog_imgs/raw/d45a7b6d52c0178c63ca5afac6a21dcab46ad67b/ES/search.png" alt="search"></li></ol></div><h2 id="故障转移">故障转移</h2><blockquote><p><code>master</code>节点监控集群中的节点状态, 假设一个节点宕机, 会立即将宕机节点的分片数据转移到其他节点, 确保数据安全</p><p>如果是主节点宕机了, <code>Eligible Master</code>节点会重新选举出主节点</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;自动补全&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;与&lt;code&gt;IK&lt;/code&gt;分词器一样, 拼音分词器作为插件安装在&lt;code&gt;ES&lt;/code&gt;中即可&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/m</summary>
      
    
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/categories/Elasticsearch/"/>
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/tags/Elasticsearch/"/>
    
  </entry>
  
  <entry>
    <title>Maven笔记_0</title>
    <link href="https://sangs3112.github.io/posts/178736ba.html"/>
    <id>https://sangs3112.github.io/posts/178736ba.html</id>
    <published>2025-03-08T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.710Z</updated>
    
    <content type="html"><![CDATA[<h1><code>Maven</code>软件原理</h1><p><img src="https://gitee.com/sang3112/blog_imgs/raw/master/Maven/maven.png" alt="Maven"></p><ul><li><code>pom.xml</code>是<code>Maven</code>的核心配置文件, <code>pom</code>指<code>Project Object Model</code>, 项目对象模型, 将当前项目抽象为文档对象, 再操作整个项目</li><li><code>Dependency</code>, 依赖管理模型, 主要负责<code>Maven</code>的依赖管理功能<ul><li>在<code>pom.xml</code>中配置了一个坐标, <code>Dependency</code>就会在本地仓库(<code>local</code>)中找到对应的<code>jar</code>包</li><li>如果<code>local</code>没有, 就会通过其他方式(<code>b2b</code>, <code>central</code>中央仓库)下载</li></ul></li><li><code>Maven</code>本地仓库中一共存在三种<code>jar</code>包, 自己工程安装到本地仓库的<code>jar</code>包, 从网上下载的第三方<code>jar</code>包, 支持<code>Maven</code>工作的过程中需要用到的<code>jar</code>包</li></ul><h1><code>Maven</code>结构</h1><blockquote><p><code>Maven</code>安装需要有<code>JAVA_HOME</code>环境变量</p></blockquote><ul><li><code>bin</code>: 包含有<code>Maven</code>的运行脚本, 比如<code>mvn.cmd</code></li><li><code>boot</code>: 包含有<code>plexus-classworlds</code>类加载器框架</li><li><code>conf</code>: 包含核心配置文件, 主要是<code>settings.xml</code></li><li><code>lib</code>: 包含运行时需要的<code>Java</code>类库</li></ul><h1><code>Maven</code>配置</h1><ol><li><p>修改本地仓库位置</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">localRepository</span>&gt;</span>D:\MvnRepo<span class="tag">&lt;/<span class="name">localRepository</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li><p><code>maven</code>下载镜像</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">mirror</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">id</span>&gt;</span>alimaven<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">name</span>&gt;</span>aliyun maven<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://maven.aliyun.com/nexus/content/groups/public/<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">mirrorOf</span>&gt;</span>central<span class="tag">&lt;/<span class="name">mirrorOf</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mirror</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li><p><code>maven</code>选用编译项目的<code>jdk</code>版本</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">id</span>&gt;</span>jdk-17<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">activation</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">activeByDefault</span>&gt;</span>true<span class="tag">&lt;/<span class="name">activeByDefault</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">jdk</span>&gt;</span>17<span class="tag">&lt;/<span class="name">jdk</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">activation</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.source</span>&gt;</span>17<span class="tag">&lt;/<span class="name">maven.compiler.source</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.target</span>&gt;</span>17<span class="tag">&lt;/<span class="name">maven.compiler.target</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.compilerVersion</span>&gt;</span>17<span class="tag">&lt;/<span class="name">maven.compiler.compilerVersion</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li><p><code>IDEA</code>配置本地<code>Maven</code><br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/Maven/setting.png" alt="setting"><br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/Maven/start.png" alt="start"><br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/Maven/projectstructure.png" alt="projectstructure"></p></li></ol><h1><code>Maven</code>工程的<code>GAVP</code></h1><blockquote><p><code>GroupId</code>, <code>ArtifactId</code>, <code>Version</code>, <code>Packaging</code>, 最后一个是可选项, 如果是<code>Java</code>工程默认就是打<code>jar</code>包</p></blockquote><ol><li><code>GroupId</code>: <code>com.&#123;公司/BU&#125;.业务线.[子业务线]</code>, 最多四级, <code>com.taobao.tddl</code>, <code>com.alibaba.sourcing.multilang</code></li><li><code>ArtifactId</code>: <code>产品线名-模块名</code>, 语义不重复不遗漏, <code>tc-client</code>, <code>uic-api</code></li><li><code>Version</code>: <code>主版本号.次版本号.修订号</code>, 不兼容<code>api</code>修改应该更新主版本号, 向下兼容功能新增修改次版本号, 修复<code>bug</code>修改修订号</li><li><code>Packaging</code>: 指示项目打包为什么类型文件, <code>idea</code>根据<code>packing</code>值, 识别<code>maven</code>项目类型<ul><li><code>jar</code>表示普通<code>Java</code>工程</li><li><code>war</code>表示<code>web</code>工程</li><li><code>pom</code>表示不会打包, 用来做继承的父工程, 只负责依赖管理和聚合, 所以不用参与打包</li></ul></li></ol><h2 id="pom-xml"><code>pom.xml</code></h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 这里是指POM版本为4.0.0 --&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">groupId</span>&gt;</span>edu.njupt.maven<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven_java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 这个是Maven工程坐标 --&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 中间放Maven工程属性 --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 依赖 --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h1><code>Maven</code>项目结构</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">├── pom.xml/                            # Maven 项目管理文件</span><br><span class="line">└── src/                  </span><br><span class="line">    ├── main/                           # 项目主要代码</span><br><span class="line">    |   ├── java/                       # Java源代码目录</span><br><span class="line">    |   |   └── com/example/myapp/   </span><br><span class="line">    |   |       ├── controller/     </span><br><span class="line">    |   |       ├── service/</span><br><span class="line">    |   |       ├── dao/</span><br><span class="line">    |   |       └── model/</span><br><span class="line">    |   ├── resources/</span><br><span class="line">    |   |   ├── log4j.properties        # 日志配置文件</span><br><span class="line">    |   |   ├── spring-mybatis.xml      # Spring Mybatis配置文件</span><br><span class="line">    |   |   └── static/                 # 静态资源目录</span><br><span class="line">    |   |       ├── css/</span><br><span class="line">    |   |       ├── js/</span><br><span class="line">    |   |       └── images/</span><br><span class="line">    |   └── webapp/                     # 存放Web相关配置和资源</span><br><span class="line">    |       ├── WEB-INF/                # 存放Web应用配置文件</span><br><span class="line">    |       |   ├── web.xml             # Web应用的部署描述文件</span><br><span class="line">    |       |   ├── lib/                # 第三方jar包</span><br><span class="line">    |       |   └── classes/            # 存放编译后的classes文件</span><br><span class="line">    |       └── index.html              # Web应用入口页面</span><br><span class="line">    └── test/                           # 项目测试代码, 比如Junit单元测试</span><br><span class="line">        ├── java/                       # 单元测试目录</span><br><span class="line">        └── resources/                  # 测试资源目录</span><br></pre></td></tr></table></figure><blockquote><p><code>WEB-INF</code>目录下的资源是受保护的资源, 不可通过浏览器直接访问</p></blockquote><h1><code>Maven</code>命令构建</h1><blockquote><p>在<code>pom.xml</code>同级目录下运行下面的代码</p><p><code>Maven</code>中测试类的测试方法名需要以<code>testXXX</code>命名, 测试类需要以<code>XXXXTest</code> 或 <code>TestXXXX</code>命名, 否则不会进行测试</p><p>如果需要一次性执行多条命令, 可以在<code>mvn</code>后面跟上多条指令, 比如<code>mvn clean test</code></p></blockquote><table><thead><tr><th style="text-align:center">命令</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>mvn compile</code></td><td style="text-align:center">编译项目, 生成<code>target</code>文件, 只会编译核心代码</td></tr><tr><td style="text-align:center"><code>mvn test-compile</code></td><td style="text-align:center">编译测试代码</td></tr><tr><td style="text-align:center"><code>mvn test</code></td><td style="text-align:center">自动执行测试方法</td></tr><tr><td style="text-align:center"><code>mvn package</code></td><td style="text-align:center">打包项目, 生成<code>jar, war</code>文件, 会执行测试代码, 将核心程序和测试程序都进行编译, 但是打包里面不会包含测试程序</td></tr><tr><td style="text-align:center"><code>mvn clean</code></td><td style="text-align:center">清理编译或打包以后的项目结构</td></tr><tr><td style="text-align:center"><code>mvn install</code></td><td style="text-align:center">打包后上传到<code>Maven</code>本地仓库, 如果一个项目需要自己写的另一个项目的<code>jar</code>, 就需要先安装到本地仓库中</td></tr><tr><td style="text-align:center"><code>mvn deploy</code></td><td style="text-align:center">只打包, 上传到<code>Maven</code>私服仓库</td></tr><tr><td style="text-align:center"><code>mvn site</code></td><td style="text-align:center">生成站点</td></tr><tr><td style="text-align:center"><code>mvn test</code></td><td style="text-align:center">执行测试代码</td></tr></tbody></table><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- war包打包插件和jdk版本不匹配, 打包war会报错 --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- pom.xml添加如下内容即可 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- jdk17和war包插件不匹配 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-war-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.2.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="生命周期">生命周期</h2><blockquote><p>构建的生命周期可以看作是一组固定构建命令的有序集合, 触发周期后的命令会自动触发周期前的命令</p></blockquote><ul><li>构建周期的作用可以简化构建流程</li><li>一个周期包含若干命令, 包含若干插件</li></ul><h1>依赖管理</h1><h2 id="依赖属性">依赖属性</h2><ul><li><code>properties</code>中可以指定属性  <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">junit.version</span>&gt;</span>5.9.2<span class="tag">&lt;/<span class="name">junit.version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span><span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span><span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;junit.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="依赖范围-scope">依赖范围(<code>scope</code>)</h2><blockquote><p>对应<code>jar</code>包的作用范围为: 编译环境, 测试环境, 运行环境</p></blockquote><table><thead><tr><th style="text-align:center">范围</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center"><code>compile</code></td><td style="text-align:center">编译依赖范围, 默认值, 对于三种<code>classpath</code>均有效, <code>Maven</code>在上述三种<code>classpath</code>中均会被引入, 比如<code>log4j</code>在编译, 测试, 运行过程中都是必须的</td></tr><tr><td style="text-align:center"><code>test</code></td><td style="text-align:center">测试依赖范围, 只对测试<code>classpath</code>有效, 比如<code>junit</code></td></tr><tr><td style="text-align:center"><code>provided</code></td><td style="text-align:center">已提供依赖范围, 只对编译和测试<code>classpath</code>有效, 比如<code>servlet-api</code>在运行阶段由于外部环境已经提供了, 所以不需要</td></tr><tr><td style="text-align:center"><code>runtime</code></td><td style="text-align:center">运行时依赖范围, 只对测试, 运行<code>classpath</code>有效, 比如<code>JDBC</code>驱动实现依赖, 在编译时只需要<code>JDK</code>提供的<code>JDBC</code>接口即可, 只有测试和运行阶段才需要实现了<code>JDBC</code>接口的驱动</td></tr><tr><td style="text-align:center"><code>system</code></td><td style="text-align:center">系统依赖范围, 与<code>provided</code>一致, 用于添加非<code>Maven</code>仓库的本地依赖, 通过依赖元素<code>dependency</code>中的<code>systemPath</code>元素指定本地依赖的路径, 使用会导致项目的可移植性降低, 所以一般不使用</td></tr><tr><td style="text-align:center"><code>import</code></td><td style="text-align:center">导入依赖范围, 只能与<code>dependencyManagement</code>元素配合使用, 将目标<code>pom.xml</code>中的<code>dependencyManagement</code>的配置导入合并到当前的<code>pom.xml</code>的<code>dependencyManagement</code>中</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;&lt;code&gt;Maven&lt;/code&gt;软件原理&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://gitee.com/sang3112/blog_imgs/raw/master/Maven/maven.png&quot; alt=&quot;Maven&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;cod</summary>
      
    
    
    
    <category term="Maven" scheme="https://sangs3112.github.io/categories/Maven/"/>
    
    
    <category term="Maven" scheme="https://sangs3112.github.io/tags/Maven/"/>
    
    <category term="原理" scheme="https://sangs3112.github.io/tags/%E5%8E%9F%E7%90%86/"/>
    
    <category term="配置" scheme="https://sangs3112.github.io/tags/%E9%85%8D%E7%BD%AE/"/>
    
    <category term="结构" scheme="https://sangs3112.github.io/tags/%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>Elasticsearch笔记_2</title>
    <link href="https://sangs3112.github.io/posts/bce2b1c5.html"/>
    <id>https://sangs3112.github.io/posts/bce2b1c5.html</id>
    <published>2025-03-07T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>搜索结果处理</h1><h2 id="排序">排序</h2><blockquote><p>默认根据相关度分数排序, 可以排序的类型分为<code>keyword</code>, 数值类型, 地理坐标, 日期</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;match_all&quot;: &#123;&#125;,</span><br><span class="line">        &quot;sort&quot;: [</span><br><span class="line">            &#123;</span><br><span class="line">                &quot;FIELD&quot;: &quot;desc&quot;</span><br><span class="line">            &#125;,</span><br><span class="line">            &#123;</span><br><span class="line">                &quot;_geo_distance&quot;: &#123;</span><br><span class="line">                    &quot;FIELD&quot;: &quot;&lt;lat, lon&gt;&quot;,</span><br><span class="line">                    &quot;order&quot;: &quot;asc&quot;,</span><br><span class="line">                    &quot;unit&quot;: &quot;km&quot;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="分页">分页</h2><blockquote><p>默认只会返回10条数据</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;match_all&quot;: &#123;&#125;</span><br><span class="line">    &#125;, </span><br><span class="line">    &quot;from&quot;: 990,</span><br><span class="line">    &quot;size&quot;: 10,</span><br><span class="line">    &quot;sort&quot;: [</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;price&quot;: &quot;asc&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br><span class="line">// from 文档开始位置</span><br><span class="line">// size 文档总数</span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li>由于<code>ES</code>是倒排索引, 因此分页非常困难, 本身分页是逻辑分页</li><li>比如查询990开始的10条数据, 实际上是<code>ES</code>查询了0-1000的所有数据, 然后截取了其中的990-1000的部分</li><li>单点<code>ES</code>没有问题, 但是如果是集群, <code>ES</code>中会将数据分片</li><li>因此需要将每个分片上的前1000, 然后再合并汇总, 重新排序得到前1000, 最后截取990-1000的部分</li><li>所以如果搜索页数过多, 或者结果集过深(<code>from + size</code>)过大, 资源消耗过多, 所以限制了<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mi>r</mi><mi>o</mi><mi>m</mi><mo>+</mo><mi>s</mi><mi>i</mi><mi>z</mi><mi>e</mi><mo>&lt;</mo><mn>10000</mn></mrow><annotation encoding="application/x-tex">from + size &lt; 10000</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">ro</span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6986em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">i</span><span class="mord mathnormal">ze</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10000</span></span></span></span></li><li>官方给出两个解决办法:<ul><li><code>search after</code> 分页时需要排序, 从上一次排序值开始, 查询下一页数据, 但是只能往后查询, 不能往前查询</li><li><code>scroll</code>: 将排序数据形成快照, 保存在内存中, 官方现在不推荐用, 对内存消耗较大, 并且快照更新无法实时</li></ul></li></ul></div><h2 id="高亮">高亮</h2><blockquote><p>搜索结果突出显示, 提前用标签标记, 然后对标签添加css样式</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;match&quot;: &#123;</span><br><span class="line">            &quot;FIELD&quot;: &quot;TEXT&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;highlight&quot;: &#123;</span><br><span class="line">        &quot;fields&quot;: &#123;</span><br><span class="line">            &quot;FIELD&quot;: &#123;</span><br><span class="line">                &quot;require_field_match&quot;: &quot;false&quot;,</span><br><span class="line">                &quot;pre_tags&quot;: &quot;&lt;em&gt;&quot;,</span><br><span class="line">                &quot;posts_tags&quot;: &quot;&lt;/em&gt;&quot;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">// FIELD 表示需要高亮的字段</span><br><span class="line">// pre_tags表示关键字之前的标签</span><br><span class="line">// 不能使用match_all, 因为需要关键字了</span><br><span class="line">// 默认情况下, 搜索字段必须要和高亮字段一致, 也就是两个FIELD需要相同</span><br><span class="line">// 使用require_field_match = false可以解决这个问题</span><br></pre></td></tr></table></figure><h1><code>RestClient</code>查询文档</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">MatchAll</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 准备Request</span></span><br><span class="line">    <span class="type">SearchRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchRequest</span>(<span class="string">&quot;xxx&quot;</span>);</span><br><span class="line">    <span class="comment">// 2. 准备DSL</span></span><br><span class="line">    req.source().query(QueryBuilders.matchAllQuery());</span><br><span class="line">    <span class="comment">// 2.1 排序</span></span><br><span class="line">    req.source().sort(<span class="string">&quot;xxx&quot;</span>, SortOrder.ASC);</span><br><span class="line">    <span class="comment">// 2.2 分页</span></span><br><span class="line">    req.source().from(<span class="number">0</span>).size(<span class="number">5</span>);</span><br><span class="line">    <span class="comment">// 3. 返回值</span></span><br><span class="line">    <span class="type">SearchResponse</span> <span class="variable">resp</span> <span class="operator">=</span> client.search(req, RequestOptions.DEFAULT);</span><br><span class="line">    <span class="comment">// 4. 解析结果</span></span><br><span class="line">    <span class="type">SearchHits</span> <span class="variable">hits</span> <span class="operator">=</span> resp.getHits();</span><br><span class="line">    <span class="comment">// 4.1 获得总数</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">total</span> <span class="operator">=</span> hits.getTotalHits().value;</span><br><span class="line">    <span class="comment">// 4.2 查询结果数组</span></span><br><span class="line">    SearchHit[] h = hits.getHits();</span><br><span class="line">    <span class="keyword">for</span> (SearchHit hh : h) &#123;</span><br><span class="line">        <span class="comment">// 4.3 得到source</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> hh.getSourceAsString();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">Highlight</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">SearchRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchRequest</span>(<span class="string">&quot;xxx&quot;</span>);</span><br><span class="line">    req.source().query(QueryBuilders.matchQuery(<span class="string">&quot;all&quot;</span>, <span class="string">&quot;xxxx&quot;</span>));</span><br><span class="line">    req.source().highlighter(<span class="keyword">new</span> <span class="title class_">HighlightBuilder</span>().field(<span class="string">&quot;xxx&quot;</span>).requireFieldMatch(<span class="literal">false</span>));</span><br><span class="line">    <span class="type">SearchResponse</span> <span class="variable">resp</span> <span class="operator">=</span> client.search(req, RequestOptions.DEFAULT);</span><br><span class="line">    <span class="comment">// ... 处理得到hit</span></span><br><span class="line">    <span class="comment">// 反序列化获取source</span></span><br><span class="line">    <span class="type">HotelDoc</span> <span class="variable">hD</span> <span class="operator">=</span> JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);</span><br><span class="line">    <span class="comment">// 处理高亮</span></span><br><span class="line">    Map&lt;String, HighlightField&gt; hF = hit.getHighlightFields();</span><br><span class="line">    <span class="keyword">if</span> (!CollectionUtils.isEmpty(hF)) &#123;</span><br><span class="line">        <span class="comment">// 获取高亮字段结果</span></span><br><span class="line">        <span class="type">HighlightField</span> <span class="variable">hlF</span> <span class="operator">=</span> hF.get(<span class="string">&quot;xxx&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (hlF != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 去除高亮结果数组中的第一个, 就是需要的东西</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> hlF.getFragments()[<span class="number">0</span>].string();</span><br><span class="line">            <span class="comment">// 覆盖非高亮结果</span></span><br><span class="line">            hD.setName(name);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>数据聚合</h1><blockquote><p>实现对文档数据的统计, 分析, 运算, 不能对<code>Text</code>进行聚合</p></blockquote><h2 id="种类">种类</h2><h3 id="桶聚合-Bucket">桶聚合(<code>Bucket</code>)</h3><blockquote><p>对文档分组, 默认是对所有文档进行搜索, 限定聚合范围可以使用<code>query</code>条件</p></blockquote><ol><li><code>TermAggregation</code>: 按照文档字段值分组</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;range&quot;: &#123;</span><br><span class="line">            &quot;price&quot;: &#123;</span><br><span class="line">                &quot;lte&quot;: 200 // 限定聚合范围</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;size&quot;: 0, // 设置size = 0, 结果不包含文档, 只包含聚合结果</span><br><span class="line">    &quot;aggs&quot;: &#123; // 定义聚合</span><br><span class="line">        &quot;brandAgg&quot;: &#123; // 给聚合起名字</span><br><span class="line">            &quot;terms&quot;: &#123; // 聚合的类型, 按照品牌值进行聚合, 所以选择term</span><br><span class="line">                &quot;field&quot;: &quot;brand&quot;, // 参与聚合的字段</span><br><span class="line">                &quot;size&quot;: 20 // 希望获取的聚合结果数量</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><code>Date Histogram</code>: 按照日期阶梯分组, 比如一周一组</li></ol><h3 id="度量聚合-Metric">度量聚合(<code>Metric</code>)</h3><ol><li><code>Avg</code>: 平均</li><li><code>Max</code>: 最大</li><li><code>Min</code>: 最小</li><li><code>Stats</code>: 同时求<code>min</code>, <code>max</code>, <code>avg</code>, <code>sum</code>等</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;range&quot;: &#123;</span><br><span class="line">            &quot;price&quot;: &#123;</span><br><span class="line">                &quot;lte&quot;: 200 // 限定聚合范围</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;size&quot;: 0, // 设置size = 0, 结果不包含文档, 只包含聚合结果</span><br><span class="line">    &quot;aggs&quot;: &#123; // 定义聚合</span><br><span class="line">        &quot;brandAgg&quot;: &#123; // 给聚合起名字</span><br><span class="line">            &quot;terms&quot;: &#123; // 聚合的类型, 按照品牌值进行聚合, 所以选择term</span><br><span class="line">                &quot;field&quot;: &quot;brand&quot;, // 参与聚合的字段</span><br><span class="line">                &quot;size&quot;: 20 // 希望获取的聚合结果数量</span><br><span class="line">                &quot;order&quot;: &#123;</span><br><span class="line">                    &quot;scoreAgg.avg&quot;: &quot;desc&quot;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;, </span><br><span class="line">            &quot;aggs&quot;: &#123; // 对桶聚合的结果进行统计</span><br><span class="line">                &quot;scoreAgg&quot;: &#123;</span><br><span class="line">                    &quot;stats&quot;: &#123;</span><br><span class="line">                        &quot;field&quot;: &quot;score&quot;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="管道聚合-pipeline">管道聚合(<code>pipeline</code>)</h3><blockquote><p>其他聚合的结果为基础进行聚合</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;搜索结果处理&lt;/h1&gt;
&lt;h2 id=&quot;排序&quot;&gt;排序&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;默认根据相关度分数排序, 可以排序的类型分为&lt;code&gt;keyword&lt;/code&gt;, 数值类型, 地理坐标, 日期&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure cla</summary>
      
    
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/categories/Elasticsearch/"/>
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/tags/Elasticsearch/"/>
    
    <category term="搜索结果处理" scheme="https://sangs3112.github.io/tags/%E6%90%9C%E7%B4%A2%E7%BB%93%E6%9E%9C%E5%A4%84%E7%90%86/"/>
    
    <category term="RestClient" scheme="https://sangs3112.github.io/tags/RestClient/"/>
    
    <category term="数据聚合" scheme="https://sangs3112.github.io/tags/%E6%95%B0%E6%8D%AE%E8%81%9A%E5%90%88/"/>
    
  </entry>
  
  <entry>
    <title>Elasticsearch笔记_1</title>
    <link href="https://sangs3112.github.io/posts/25ebe07f.html"/>
    <id>https://sangs3112.github.io/posts/25ebe07f.html</id>
    <published>2025-03-06T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<div class="note info flat"><ol><li><code>ES</code>支持的两种地理坐标存储形式</li></ol><ul><li><code>geo_point</code>: 纬度和经度确定的一个点</li><li><code>geo_shape</code>: 多个<code>geo_point</code>组成的一个复杂图形, 比如直线, 一般是一个区域</li></ul><ol start="2"><li>如果<code>ES</code>中同时需要根据名称, 位置, 商圈, 城市等信息进行多个字段的搜索, 又希望性能高一些, 可以使用<code>copy_to</code></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&quot;all&quot;: &#123;</span><br><span class="line">    &quot;type&quot;: &quot;text&quot;,</span><br><span class="line">    &quot;anaylzer&quot;: &quot;ik_max_word&quot;</span><br><span class="line">&#125;,</span><br><span class="line">&quot;brand&quot;: &#123;</span><br><span class="line">    &quot;type&quot;: &quot;keyword&quot;,</span><br><span class="line">    &quot;copy_to&quot;: &quot;all&quot;</span><br><span class="line">&#125;</span><br><span class="line">// 这样就可以在一个字段`all`中查询到所有字段中</span><br></pre></td></tr></table></figure></div><h1><code>JavaRestClient</code></h1><h2 id="初始化">初始化</h2><ol><li>引入<code>ES</code>的<code>HighLevelRestClient</code></li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.elasticsearch.client<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>elasticsearch-rest-high-level-client<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><ol start="2"><li>覆盖<code>SpringBoot</code>默认的7.6.2版本设置</li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">elasticsearch-version</span>&gt;</span>7.12.1<span class="tag">&lt;/<span class="name">elasticsearch-version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br></pre></td></tr></table></figure><ol start="3"><li>初始化<code>RestHighLevelClient</code></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RestHighLevelClient</span> <span class="variable">client</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RestHighLevelClient</span>(RestClient.builder(</span><br><span class="line">    HttpHost.create(<span class="string">&quot;http://&lt;ipaddress&gt;:9200&quot;</span>)</span><br><span class="line">));</span><br></pre></td></tr></table></figure><h2 id="创建索引库">创建索引库</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">testCreateIndex</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 创建Request对象</span></span><br><span class="line">    <span class="type">CreateIndexRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreateIndexRequest</span>(<span class="string">&quot;hotel&quot;</span>);</span><br><span class="line">    <span class="comment">// 2. 请求参数</span></span><br><span class="line">    req.source(MAPPING_TEMPLATE, XContentType.JSON);</span><br><span class="line">    <span class="comment">// 3. 发起请求</span></span><br><span class="line">    client.indices().create(req, RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// indices()返回的对象中包含了索引库操作中的所有方法</span></span><br></pre></td></tr></table></figure><h2 id="添加数据到索引库">添加数据到索引库</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">IndexDocument</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 创建Request对象</span></span><br><span class="line">    <span class="type">IndexRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IndexRequest</span>(<span class="string">&quot;idxName&quot;</span>).id(<span class="string">&quot;1&quot;</span>);</span><br><span class="line">    <span class="comment">// 2. 准备JSON文档</span></span><br><span class="line">    req.source(MAPPING_TEMPLATE, XContentType.JSON);</span><br><span class="line">    <span class="comment">// 3. 发送请求</span></span><br><span class="line">    client.index(req, RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="查询文档">查询文档</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">GetDocumentById</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 创建Request对象</span></span><br><span class="line">    <span class="type">GetRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GetRequest</span>(<span class="string">&quot;idxName&quot;</span>, <span class="string">&quot;1&quot;</span>);</span><br><span class="line">    <span class="comment">// 2. 发送请求</span></span><br><span class="line">    <span class="type">GetResponse</span> <span class="variable">resp</span> <span class="operator">=</span> client.get(req, RequestOptions.DEFAULT);</span><br><span class="line">    <span class="comment">// 3. 解析结果</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> resp.getSourceAsString();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="根据ID修改数据">根据ID修改数据</h2><ol><li>全量更新, 直接删除再重新插入数据</li><li>局部更新</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">UpdateDocumentById</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 创建Request对象</span></span><br><span class="line">    <span class="type">UpdateRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UpdateRequest</span>(<span class="string">&quot;idxName&quot;</span>, <span class="string">&quot;1&quot;</span>);</span><br><span class="line">    <span class="comment">// 2. 准备参数, 每两个参数为一对k-v</span></span><br><span class="line">    req.doc(</span><br><span class="line">        <span class="string">&quot;age&quot;</span>, <span class="number">18</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span>, <span class="string">&quot;rose&quot;</span></span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 3. 更新文档</span></span><br><span class="line">    client.update(req, RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="批量导入数据">批量导入数据</h2><ol><li>利用<code>mybatis-plus</code>查询数据</li><li>将查询到的数据转换为文档数据</li><li>利用<code>JavaRestClient</code>中的<code>Bulk</code>批处理, 实现批量新增文档</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">testBulk</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 创建Bulk请求</span></span><br><span class="line">    <span class="type">BulkRequest</span> <span class="variable">req</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BulkRequest</span>();</span><br><span class="line">    <span class="comment">// 2. 添加需要批量提交的请求, 比如添加两个新增文档的操作</span></span><br><span class="line">    req.add(<span class="keyword">new</span> <span class="title class_">IndexRequest</span>(<span class="string">&quot;hotel&quot;</span>).id(<span class="string">&quot;101&quot;</span>).source(<span class="string">&quot;json source1&quot;</span>, XContentType.JSON));</span><br><span class="line">    req.add(<span class="keyword">new</span> <span class="title class_">IndexRequest</span>(<span class="string">&quot;hotel&quot;</span>).id(<span class="string">&quot;102&quot;</span>).source(<span class="string">&quot;json source2&quot;</span>, XContentType.JSON));</span><br><span class="line">    <span class="comment">// 3. 发起bulk请求</span></span><br><span class="line">    client.bulk(req, RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>DSL查询语法</h1><h2 id="分类">分类</h2><ol><li>查询所有: 有分页, 一次20条, <code>match_all</code></li><li>全文检索: 利用分词器对内容分词后使用倒排索引匹配, <code>match_query</code>, <code>multi_match_query</code></li><li>精确查询: 根据精确词条查询, 一般是<code>keyword</code>, 数值, 日期, <code>boolean</code>, 不分词, <code>ids</code>, <code>range</code>, <code>term</code></li><li>地理查询: 根据经纬度查询, <code>geo_distance, geo_bounding_box</code></li><li>复合查询: 将上述查询组合查询, <code>bool</code>, <code>function_score</code></li></ol><h2 id="语法">语法</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &lt;查询类型&gt;: &#123;</span><br><span class="line">            &lt;查询条件&gt;: &lt;查询值&gt;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="全文检索">全文检索</h2><blockquote><p>对用户输入内容分词以后用于搜索框检索</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;match&quot;: &#123;</span><br><span class="line">            &quot;all&quot;: &quot;xxxx&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;multi_match&quot;:&#123;</span><br><span class="line">            &quot;query&quot;: &quot;xxx&quot;,</span><br><span class="line">            &quot;fields&quot;: [&quot;1&quot;, &quot;2&quot;]</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">// 搜索字段越多, 搜索效率越低, 建议使用`copy_to`将多个字段拷贝到一个字段中</span><br></pre></td></tr></table></figure><h2 id="精确查询">精确查询</h2><blockquote><p>对不可分词的条件查询</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;term&quot;: &#123;</span><br><span class="line">            &quot;city&quot;: &#123;</span><br><span class="line">                &quot;value&quot;: &quot;xxx&quot;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;range&quot;: &#123;</span><br><span class="line">            &quot;price&quot;: &#123;</span><br><span class="line">                &quot;gte&quot;: 100,</span><br><span class="line">                &quot;lte&quot;: 300</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="地理查询">地理查询</h2><blockquote><p>查询附近的酒店, 打车, 附近的人</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">// 查询某个geo_point落在矩形框中的文档</span><br><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;geo_bounding_box&quot;: &#123;</span><br><span class="line">            &quot;FIELD&quot;: &#123;</span><br><span class="line">                &quot;top_left&quot;: &#123;</span><br><span class="line">                    &quot;lat&quot;: 33.1,</span><br><span class="line">                    &quot;lon&quot;: 121.5</span><br><span class="line">                &#125;,</span><br><span class="line">                &quot;bottom_right&quot;: &#123;</span><br><span class="line">                    &quot;lat&quot;: 30.9,</span><br><span class="line">                    &quot;lon&quot;: 121.7</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">// 到指定中心点小于某个距离的文档</span><br><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;geo_distance&quot;: &#123;</span><br><span class="line">            &quot;distance&quot;: &quot;10km&quot;,</span><br><span class="line">            &quot;FIELD&quot;: &quot;31.21, 121.5&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复合查询">复合查询</h2><div class="note info flat"><ul><li>在简单查询基础上进行功能叠加, <code>function_score</code>算分函数查询, 给相关词条打分, 返回值按照倒序排列</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mi>F</mi><mo stretchy="false">(</mo><mtext>词条频率</mtext><mo stretchy="false">)</mo><mo>=</mo><mfrac><mtext>词条出现频率</mtext><mtext>文档中词条总数</mtext></mfrac></mrow><annotation encoding="application/x-tex">TF(\text{词条频率}) = \frac{\text{词条出现频率}}{\text{文档中词条总数}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">TF</span><span class="mopen">(</span><span class="mord text"><span class="mord cjk_fallback">词条频率</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2173em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8723em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord cjk_fallback mtight">文档中词条总数</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord cjk_fallback mtight">词条出现频率</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></li><li>如果只使用<code>TF</code>算法, 某一个词条可能会在所有文档中都出现, 那计算这个词条的数量就没有意义, 所以产生<code>TF-IDF</code>算法</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi><mi>D</mi><mi>F</mi><mo stretchy="false">(</mo><mtext>逆文档频率</mtext><mo stretchy="false">)</mo><mo>=</mo><mi>l</mi><mi>o</mi><mi>g</mi><mo stretchy="false">(</mo><mfrac><mtext>文档总数</mtext><mtext>包含词条的文档数</mtext></mfrac><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">IDF(\text{逆文档频率}) = log(\frac{\text{文档总数}}{\text{包含词条的文档数}})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mopen">(</span><span class="mord text"><span class="mord cjk_fallback">逆文档频率</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2173em;vertical-align:-0.345em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mopen">(</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8723em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord cjk_fallback mtight">包含词条的文档数</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord cjk_fallback mtight">文档总数</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span></span></span></span></li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo>=</mo><msubsup><mo>∑</mo><mi>i</mi><mi>n</mi></msubsup><mo stretchy="false">(</mo><mi>T</mi><mi>F</mi><mo stretchy="false">(</mo><mtext>词条频率</mtext><mo stretchy="false">)</mo><mo>∗</mo><mi>I</mi><mi>D</mi><mi>F</mi><mo stretchy="false">(</mo><mtext>逆文档频率</mtext><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">score = \sum_i^n(TF(\text{词条频率}) * IDF(\text{逆文档频率}))</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">score</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.104em;vertical-align:-0.2997em;"></span><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:0em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8043em;"><span style="top:-2.4003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span><span style="top:-3.2029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2997em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.13889em;">TF</span><span class="mopen">(</span><span class="mord text"><span class="mord cjk_fallback">词条频率</span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mopen">(</span><span class="mord text"><span class="mord cjk_fallback">逆文档频率</span></span><span class="mclose">))</span></span></span></span></li><li>但是从<code>ES5.1</code>开始就没有使用这种<code>TF-IDF</code>算法了, 使用<code>BM25</code>算法</li><li><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>Q</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∑</mo><mi>i</mi><mi>n</mi></msubsup><mi>l</mi><mi>o</mi><mi>g</mi><mo stretchy="false">(</mo><mn>1</mn><mo>+</mo><mfrac><mrow><mi>N</mi><mo>−</mo><mi>n</mi><mo>+</mo><mn>0.5</mn></mrow><mrow><mi>n</mi><mo>+</mo><mn>0.5</mn></mrow></mfrac><mo stretchy="false">)</mo><mo>⋅</mo><mfrac><msub><mi>f</mi><mi>i</mi></msub><mrow><msub><mi>f</mi><mi>i</mi></msub><mo>+</mo><msub><mi>k</mi><mn>1</mn></msub><mo>⋅</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>b</mi><mo>+</mo><mi>b</mi><mo>⋅</mo><mfrac><msub><mi>d</mi><mi>l</mi></msub><mrow><mi>a</mi><mi>v</mi><mi>g</mi><mo stretchy="false">(</mo><msub><mi>d</mi><mi>l</mi></msub><mo stretchy="false">)</mo></mrow></mfrac><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">Score(Q, d) = \sum_i^nlog(1 + \frac{N - n + 0.5}{n + 0.5}) \cdot \frac{f_i}{f_i + k_1 \cdot (1 - b + b \cdot \frac{d_l}{avg(d_l)})}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord mathnormal">core</span><span class="mopen">(</span><span class="mord mathnormal">Q</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.104em;vertical-align:-0.2997em;"></span><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:0em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8043em;"><span style="top:-2.4003em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span><span style="top:-3.2029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2997em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mopen">(</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.2757em;vertical-align:-0.4033em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8723em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">+</span><span class="mord mtight">0.5</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">n</span><span class="mbin mtight">+</span><span class="mord mtight">0.5</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4033em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.917em;vertical-align:-0.9848em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9322em;"><span style="top:-2.4415em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3281em;"><span style="top:-2.357em;margin-left:-0.1076em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">+</span><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03148em;">k</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3173em;"><span style="top:-2.357em;margin-left:-0.0315em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span><span class="mbin mtight">⋅</span><span class="mopen mtight">(</span><span class="mord mtight">1</span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">b</span><span class="mbin mtight">+</span><span class="mord mathnormal mtight">b</span><span class="mbin mtight">⋅</span><span class="mord mtight"><span class="mopen nulldelimiter sizing reset-size3 size6"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.0693em;"><span style="top:-2.6408em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">vg</span><span class="mopen mtight">(</span><span class="mord mtight"><span class="mord mathnormal mtight">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.3448em;margin-left:0em;margin-right:0.1em;"><span class="pstrut" style="height:2.6944em;"></span><span class="mord mathnormal mtight" style="margin-right:0.01968em;">l</span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3496em;"><span></span></span></span></span></span></span><span class="mclose mtight">)</span></span></span></span><span style="top:-3.2255em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line mtight" style="border-bottom-width:0.049em;"></span></span><span style="top:-3.5732em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">d</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.3448em;margin-left:0em;margin-right:0.1em;"><span class="pstrut" style="height:2.6944em;"></span><span class="mord mathnormal mtight" style="margin-right:0.01968em;">l</span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3496em;"><span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.609em;"><span></span></span></span></span></span><span class="mclose nulldelimiter sizing reset-size3 size6"></span></span><span class="mclose mtight">)</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.4461em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10764em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3281em;"><span style="top:-2.357em;margin-left:-0.1076em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9848em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></li><li>因为传统算法词频越高, <code>TF-IDF</code>算法得分越高, <code>BM25</code>算法最终会趋于水平<br><img src="https://gitee.com/sang3112/blog_imgs/raw/master/ES/TF-BM25.png" alt="TF-BM25"></li></ul></div><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;function_score&quot;: &#123;</span><br><span class="line">            &quot;query&quot;: &#123;</span><br><span class="line">                &quot;mathch&quot;: &#123;</span><br><span class="line">                    &quot;all&quot;: &quot;xxx&quot;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            &quot;functions&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;filter&quot;: &#123;</span><br><span class="line">                        &quot;term&quot;: &#123;</span><br><span class="line">                            &quot;id&quot;: &quot;1&quot;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;, </span><br><span class="line">                    &quot;weight&quot;: 10</span><br><span class="line">                &#125;</span><br><span class="line">            ],</span><br><span class="line">            &quot;boost_mode&quot;: &quot;multiply&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">// query 原始查询条件, 根据相关性打分得到 query score</span><br><span class="line">// filter 是过滤条件, 只有复合条件的文档才会被重新算分</span><br><span class="line">// weight 算分函数, 结果称为function score, 将来与query score运算, 得到新的算分</span><br><span class="line">//  weight: 给一个常量作为函数结果</span><br><span class="line">//  field_value_factor: 用文档中某个字段值作为函数结果</span><br><span class="line">//  random_score: 使用随机值所谓函数结果</span><br><span class="line">//  script_score: 自定义计算公式, 作为函数结果</span><br><span class="line">// boost_mode 加权模式, 定义function score与 query score 的运算方式</span><br><span class="line">//  multiply: 二者相乘</span><br><span class="line">//  replace: 使用function score替换query score</span><br><span class="line">//  sum, avg, max, min</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">// 布尔查询是一个或多个子句组合</span><br><span class="line">// must: 必须匹配, 与</span><br><span class="line">// should: 选择性匹配, 或</span><br><span class="line">// must_not: 必须不匹配, 非</span><br><span class="line">// filter: 必须匹配, 不参与算分</span><br><span class="line">// 如果子查询比较多, 每个都算分, filter会放到缓存中, 也不会算分, 所以性能更好</span><br><span class="line">GET /idxName/_search</span><br><span class="line">&#123;</span><br><span class="line">    &quot;query&quot;: &#123;</span><br><span class="line">        &quot;bool&quot;: &#123;</span><br><span class="line">            &quot;must&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;term&quot;: &#123;</span><br><span class="line">                        &quot;city&quot;: &quot;上海&quot;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            ],</span><br><span class="line">            &quot;should&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;term&quot;: &#123;</span><br><span class="line">                        &quot;brand&quot;: &quot;皇冠假日&quot;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;,</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;term&quot;: &#123;</span><br><span class="line">                        &quot;brand&quot;: &quot;华美达&quot;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            ],</span><br><span class="line">            &quot;must_not&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;range&quot;: &#123;</span><br><span class="line">                        &quot;price&quot;: &#123;</span><br><span class="line">                            &quot;lte&quot;: 500</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            ],</span><br><span class="line">            &quot;filter&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;range&quot;: &#123;</span><br><span class="line">                        &quot;score&quot;: &#123;</span><br><span class="line">                            &quot;gte&quot;: 45</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            ]</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;note info flat&quot;&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ES&lt;/code&gt;支持的两种地理坐标存储形式&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;geo_point&lt;/code&gt;: 纬度和经度确定的一个点&lt;/li&gt;
&lt;li&gt;&lt;code&gt;geo</summary>
      
    
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/categories/Elasticsearch/"/>
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/tags/Elasticsearch/"/>
    
    <category term="JavaRestClient" scheme="https://sangs3112.github.io/tags/JavaRestClient/"/>
    
    <category term="查询语法" scheme="https://sangs3112.github.io/tags/%E6%9F%A5%E8%AF%A2%E8%AF%AD%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>Elasticsearch笔记_0</title>
    <link href="https://sangs3112.github.io/posts/52ecd0e9.html"/>
    <id>https://sangs3112.github.io/posts/52ecd0e9.html</id>
    <published>2025-03-05T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.707Z</updated>
    
    <content type="html"><![CDATA[<h1>使用<code>Docker</code>安装<code>ES</code></h1><ol><li>设置<code>Docker</code>网络</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker network create es-net</span><br></pre></td></tr></table></figure><ol start="2"><li>拉取<code>ES</code>镜像</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull elasticsearch:7.12.1</span><br><span class="line">docker pull kibana:7.12.1</span><br></pre></td></tr></table></figure><blockquote><p>如果有打包好的<code>tar</code>包, 可以使用<code>docker load -i xxxx.tar</code>完成加载</p></blockquote><ol start="3"><li>运行<code>ES</code>镜像</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name es \</span><br><span class="line">    -e &quot;ES_JAVA_OPTS=-Xms1024m -Xmx1024m&quot; \</span><br><span class="line">    -e &quot;discovery.type=single-node&quot; \</span><br><span class="line">    -v es-data:/usr/share/elasticsearch/data \</span><br><span class="line">    -v es-plugins:/usr/share/elasticsearch/plugins \</span><br><span class="line">    --privileged \</span><br><span class="line">    --network=es-net \</span><br><span class="line">    -p 9200:9200 \</span><br><span class="line">    -p 9300:9300 \</span><br><span class="line">    elasticsearch:7.12.1</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-d 表示后台运行</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">--name 表示容器名为es</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-e 设置环境变量, JAVA堆内存为1G, 设置为单点模式运行</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-v 设置数据保存目录和插件目录</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">--network 加入Docker网络es-net</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-p 暴露端口映射, 9200是HTTP端口, 9300是集群各个节点互联端口</span></span><br></pre></td></tr></table></figure><ul><li>进入浏览器<code>ip:9200</code>, 出现一个<code>JSON</code>表明运行成功</li></ul><ol><li>运行<code>Kibana</code></li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name kibana \</span><br><span class="line">    -e ELASTICSEARCH_HOSTS=https://es:9200 \</span><br><span class="line">    --network=es-net \</span><br><span class="line">    -p 5601:5601 \</span><br><span class="line">    kibana:7.12.1</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">因为在同一个es-net中, 所以可以使用es名称加端口号互联</span></span><br></pre></td></tr></table></figure><ul><li>进入<code>ip:5601</code>即可正常使用</li><li><code>Dev Tools</code>可以写<code>DSL</code>语句, 并将语句发送给<code>ES</code>, 本质就是发送一个<code>Restful</code>的请求到网络中</li></ul><h2 id="安装分词器">安装分词器</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">POST /_analyze</span><br><span class="line">&#123;</span><br><span class="line">    &quot;text&quot;: 你好，我在南邮学习Java,</span><br><span class="line">    &quot;analyzer&quot;: &quot;english&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>无论使用英文分词器(<code>english</code>), 中文分词器(<code>chinese</code>), 还是标准分词器(<code>standard</code>), 都没有办法区分中文</p><p>所以一般使用<code>IK</code>分词器</p></blockquote><ol><li>在线安装</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">进入容器内部</span></span><br><span class="line">docker exec -it elasticsearch /bin/bash</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">在线下载安装(版本需要严格对应, 不然会报错)</span></span><br><span class="line">bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/7.12.1</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">退出重启</span></span><br><span class="line">exit</span><br><span class="line">docker restart elasticsearch</span><br></pre></td></tr></table></figure><ol start="2"><li>离线安装</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看Docker plugins目录位置</span></span><br><span class="line">docker inspect es-plugins</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将IK分词器的文件夹放到plugins目录中即可</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">重启es容器</span></span><br><span class="line">docker restart es</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看日志</span></span><br><span class="line">docker logs -f es</span><br></pre></td></tr></table></figure><ul><li><code>IK</code>分词器包含最少切分(<code>ik_smart</code>)和最细切分(<code>ik_max_word</code>)</li></ul><div class="note info flat"><p><code>IK</code>分词器支持扩展, 只需要修改<code>config</code>目录中的<code>IkAnalyzer.cfg.xml</code>文件</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">comment</span>&gt;</span>Ik Analyzer 扩展配置<span class="tag">&lt;/<span class="name">comment</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--配置扩展词典, 这个其实是一个文件名, 在同一目录下新建--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">&quot;ext_dict&quot;</span>&gt;</span>ext.dic<span class="tag">&lt;/<span class="name">entry</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--配置停用词典--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">&quot;ext_stopwords&quot;</span>&gt;</span>stopword.dic<span class="tag">&lt;/<span class="name">entry</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br></pre></td></tr></table></figure><p>配置完成后重启容器生效</p></div><h1>使用<code>ES</code></h1><h2 id="mapping属性"><code>mapping</code>属性</h2><blockquote><p><code>mapping</code>是对索引库中文档的约束</p></blockquote><table><thead><tr><th style="text-align:center">属性</th><th style="text-align:center">含义</th></tr></thead><tbody><tr><td style="text-align:center"><code>type</code></td><td style="text-align:center">字段数据类型, 字符串分为<code>text</code>(可分词文本), <code>keyword</code>(精确值, 比如国家, 品牌, 姓名); 数值类型分为: long integer short byte double float; boolean类型; 日期(<code>date</code>); 对象(<code>object</code>) ; 没有数组, 但是允许一个字段有多个值</td></tr><tr><td style="text-align:center"><code>index</code></td><td style="text-align:center">字段是否创建倒排索引, 默认为<code>true</code></td></tr><tr><td style="text-align:center"><code>analyzer</code></td><td style="text-align:center">使用哪种分词器, 只有<code>text</code>类型需要分词</td></tr><tr><td style="text-align:center"><code>properties</code></td><td style="text-align:center">该字段的子字段</td></tr></tbody></table><h2 id="创建索引库">创建索引库</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">PUT /索引库名</span><br><span class="line">&#123;</span><br><span class="line">    &quot;mappings&quot;: &#123;</span><br><span class="line">        &quot;properties&quot;: &#123;</span><br><span class="line">            &quot;字段1&quot;: &#123;</span><br><span class="line">                &quot;type&quot;: &quot;text&quot;,</span><br><span class="line">                &quot;analyzer&quot;: &quot;ik_smart&quot;</span><br><span class="line">            &#125;, </span><br><span class="line">            &quot;字段2&quot;: &#123;</span><br><span class="line">                &quot;type&quot;: &quot;keyword&quot;,</span><br><span class="line">                &quot;index&quot;: false</span><br><span class="line">            &#125;,</span><br><span class="line">            &quot;字段3&quot;: &#123;</span><br><span class="line">                &quot;properties&quot;: &#123;</span><br><span class="line">                    &quot;子字段&quot;: &#123;</span><br><span class="line">                        &quot;type&quot;: &quot;keyword&quot;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            // ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="查看索引库">查看索引库</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /索引库名</span><br></pre></td></tr></table></figure><h2 id="删除索引库">删除索引库</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE /索引库名</span><br></pre></td></tr></table></figure><h2 id="修改索引库">修改索引库</h2><blockquote><p><code>ES</code>禁止修改索引库原有字段, 但是可以添加新的字段</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">PUT /索引库名/_mapping </span><br><span class="line">&#123;</span><br><span class="line">    &quot;properties&quot;: &#123;</span><br><span class="line">        &quot;新字段名&quot;: &#123;</span><br><span class="line">            &quot;type&quot;: &quot;integer&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">// 新字段名不能与原有字段名重复</span><br><span class="line">// 如果修改字段会报400</span><br><span class="line">// 如果查看不存在的索引库会报404</span><br></pre></td></tr></table></figure><h2 id="新增文档">新增文档</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">POST /索引库名/_doc/文档id</span><br><span class="line">&#123;</span><br><span class="line">    &quot;字段1&quot;: &quot;值1&quot;,</span><br><span class="line">    &quot;字段2&quot;: &#123;</span><br><span class="line">        &quot;子字段1&quot;: &quot;值2&quot;,</span><br><span class="line">        // ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="查询文档">查询文档</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /索引库名/_doc/文档id</span><br></pre></td></tr></table></figure><h2 id="删除文档">删除文档</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE /索引库名/_doc/文档id</span><br></pre></td></tr></table></figure><h2 id="修改文档">修改文档</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">// 全量修改, 先删除旧文档, 添加新文档</span><br><span class="line">PUT /索引库名/_doc/文档id</span><br><span class="line">&#123;</span><br><span class="line">    &quot;字段1&quot;: &quot;值1&quot;,</span><br><span class="line">    &quot;字段2&quot;: &#123;</span><br><span class="line">        &quot;子字段1&quot;: &quot;值2&quot;,</span><br><span class="line">        // ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">// 增量修改</span><br><span class="line">POST /索引库名/_update/文档id</span><br><span class="line">&#123;</span><br><span class="line">    &quot;doc&quot;: &#123;</span><br><span class="line">        &quot;字段名&quot;: &quot;新值&quot;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;使用&lt;code&gt;Docker&lt;/code&gt;安装&lt;code&gt;ES&lt;/code&gt;&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;设置&lt;code&gt;Docker&lt;/code&gt;网络&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td c</summary>
      
    
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/categories/Elasticsearch/"/>
    
    
    <category term="Elasticsearch" scheme="https://sangs3112.github.io/tags/Elasticsearch/"/>
    
    <category term="安装" scheme="https://sangs3112.github.io/tags/%E5%AE%89%E8%A3%85/"/>
    
  </entry>
  
  <entry>
    <title>Linux笔记_1</title>
    <link href="https://sangs3112.github.io/posts/575b36ca.html"/>
    <id>https://sangs3112.github.io/posts/575b36ca.html</id>
    <published>2025-03-04T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.710Z</updated>
    
    <content type="html"><![CDATA[<h1>用户</h1><ul><li><p>可以查看<code>/etc/passwd</code>文件, 得到系统用户信息</p><ul><li>这个目录最早用来存储用户密码的哈希, 但是所有用户都可以访问</li><li>现在用户密码的哈希存储在<code>/etc/shadow</code>中, 只有<code>root</code>可以访问</li></ul></li><li><p>如果执行<code>apt update</code>发现权限不足, 可以使用<code>sudo !!</code>进行补救, 因为<code>!!</code>表示上一条命令</p></li></ul><h2 id="用户组">用户组</h2><ul><li>一组用户的集合, 使用<code>groups</code>命令查看当前用户组</li><li>一般用户在创建的时候, 都会创建一个与用户名相同的用户组</li></ul><h2 id="添加用户">添加用户</h2><ul><li><code>Debian</code>系列使用<code>adduser</code>可以实现添加用户, 添加组, 将用户添加到组<ul><li>添加用户: <code>sudo adduser 用户名</code></li><li>添加组: <code>sudo adduser --group 组名</code></li><li>将用户添加指定用户组: <code>sudo adduser 用户名 组名</code></li><li>一般创建的用户没有<code>sudo</code>权限, 可以将用户添加到<code>sudo</code>用户组中</li></ul></li></ul><h1>文件</h1><h2 id="文件权限">文件权限</h2><ul><li><code>ls -l</code>可以查看文件的详细信息<ul><li>第一位是文件类型: <code>-</code>表示普通文件, <code>d</code>表示目录</li><li>接下来三位是所属用户的权限</li><li>再三位是所属用户组的权限</li><li>最后三位是其他人的权限</li></ul></li></ul><h2 id="文件层次">文件层次</h2><ul><li><code>Linux</code>下所有的文件从<code>/</code>开始, 以树结构的形式, 其他的分区以挂载的形式挂在了树上</li></ul><table><thead><tr><th style="text-align:center">目录</th><th style="text-align:center">内容</th></tr></thead><tbody><tr><td style="text-align:center"><code>/bin</code></td><td style="text-align:center">存放程序文件, 所有用户均可用</td></tr><tr><td style="text-align:center"><code>/boot</code></td><td style="text-align:center">存放启动系统的文件</td></tr><tr><td style="text-align:center"><code>/dev</code></td><td style="text-align:center">存放设备文件</td></tr><tr><td style="text-align:center"><code>/etc</code></td><td style="text-align:center">存放系统和配置文件</td></tr><tr><td style="text-align:center"><code>/home</code></td><td style="text-align:center">存放用户的信息</td></tr><tr><td style="text-align:center"><code>/lib</code></td><td style="text-align:center">存放程序库文件</td></tr><tr><td style="text-align:center"><code>/media, /mnt</code></td><td style="text-align:center">都用于挂载其他文件系统, <code>/media</code>用于挂载可移除文件系统, <code>/mnt</code>挂载临时使用的文件系统</td></tr><tr><td style="text-align:center"><code>/opt</code></td><td style="text-align:center">存放额外的程序包, 一些大型 商用程序</td></tr><tr><td style="text-align:center"><code>/root</code></td><td style="text-align:center"><code>root</code>用户的家目录</td></tr><tr><td style="text-align:center"><code>/srv</code></td><td style="text-align:center">存放网络服务数据</td></tr><tr><td style="text-align:center"><code>/usr</code></td><td style="text-align:center">大多数软件存放于此</td></tr><tr><td style="text-align:center"><code>/tmp</code></td><td style="text-align:center">临时目录, 所有用户均可使用</td></tr><tr><td style="text-align:center"><code>/var</code></td><td style="text-align:center">存放会发生变化的程序的相关文件</td></tr></tbody></table><h2 id="特殊权限位">特殊权限位</h2><ul><li><code>/etc/shadow</code>存储密码, 只有<code>root</code>用户可以查看修改, 但是普通用户可以通过<code>passwd</code>查看和修改自己的密码<ul><li>这是因为有三个特殊权限位</li></ul></li></ul><table><thead><tr><th style="text-align:center">权限</th><th style="text-align:center">解释</th></tr></thead><tbody><tr><td style="text-align:center"><code>setuid</code></td><td style="text-align:center">以文件所属的用户身份执行此程序</td></tr><tr><td style="text-align:center"><code>setgid</code></td><td style="text-align:center">对文件来说, 以文件所属用户组的身份执行此程序, 对目录来说, 在这个目录下创建的文件的用户组都与目录的用户组一致, 而不是文件本身的用户组</td></tr><tr><td style="text-align:center"><code>sticky</code></td><td style="text-align:center">目录中的所有文件只能由除了<code>root</code>以外的所有者删除或者移动</td></tr></tbody></table><blockquote><p>比如<code>/tmp</code>下, 可以删除修改自己的文件, 无法删除修改别人的文件<br><code>ls -l /usr/bin/passwd</code>查看文件, 可看到权限为<code>-rwsr-xr-x</code>, <code>s</code>表示有特殊权限位<code>setuid</code></p></blockquote><h2 id="实际用户与有效用户">实际用户与有效用户</h2><ul><li>使用<code>sudo</code>命令修改<code>passwd</code>, 文件却知道具体用户是你, 而不是<code>root</code></li><li>这是因为有两个获取当前进程<code>UID</code>的函数, <code>getuid(), geteuid</code></li><li>前者对应实际用户, 后者对应有效用户, 使用<code>sudo</code>命令, 前者是自己, 后者是<code>root</code></li></ul><h1><code>I/O</code>重定向</h1><ul><li><code>echo &quot;hello world&quot; &gt; file</code> 可以将<code>hello world</code> 输入到<code>file</code>中, 如果不存在则会创建, 如果已存在则覆盖</li><li><code>echo &quot;new&quot; &gt;&gt; file</code> 可以将<code>new</code>追加到<code>file</code>中, 如果不存在则创建, 如果已存在则追加</li></ul><h2 id="wget"><code>wget</code></h2><ul><li>使用<code>HTTP, FTP</code>协议从网上下载</li><li>可以在用户注销以后的后台工作, 支持断点续传</li></ul><h2 id="curl"><code>curl</code></h2><ul><li>可以下载文件, 也可以模拟<code>web</code>请求</li></ul><h2 id="文本处理">文本处理</h2><ul><li><code>wc file</code>可以统计文本行数, 单词数, 字节数<ul><li>如果包含中文, 可以使用<code>wc -m file</code>统计宽字符</li></ul></li><li><code>diff file1 file2</code>比较两个不同的文件, 列出差异<ul><li><code>-b</code>忽略空白字符的数量变化, <code>-w</code>忽略空白字符</li></ul></li><li><code>head, tail</code>查看文本开头末尾<ul><li><code>head -n 24 f</code> 查看文本开头24行</li><li><code>head -24 f</code> 查看文本开头24行</li><li><code>tail -c 10 f</code> 查看文本结尾10个字节</li><li><code>tail -f file</code> <code>tail</code>命令独有, 持续输出文本末尾新增加的内容</li></ul></li><li><code>grep</code>查找文本内容<ul><li><code>grep -i &quot;System&quot; f</code> 查找文本中包含<code>System</code>的行</li><li><code>grep -R &quot;hello&quot;</code> 递归查找当前目录下包含<code>hello</code>的文件</li></ul></li><li><code>sed</code>替换文本字符串<ul><li><code>sed 's/hello/world/g' f</code>使用<code>world</code>全局替换文本中的<code>hello</code>输出</li><li><code>sed 's/hello/world' f</code> 使用<code>world</code>替换文本第一个<code>hello</code>输出</li><li><code>sed -i 's/hello/world/g' f</code> 使用<code>-i</code>表示直接写入文件</li><li><code>sed -i.bak 's/hello/world/g' f</code> 使用<code>-i.bak</code>可以备份到<code>f.bak</code>中再写入<code>f</code></li></ul></li></ul><h2 id="shell"><code>shell</code></h2><table><thead><tr><th style="text-align:center">位置变量</th><th style="text-align:center">含义</th></tr></thead><tbody><tr><td style="text-align:center"><code>$#</code></td><td style="text-align:center">命令行参数个数, 不包括<code>$0</code></td></tr><tr><td style="text-align:center"><code>$?</code></td><td style="text-align:center">最后命令退出代码, 0表示成功, 其他表示失败</td></tr><tr><td style="text-align:center"><code>$$</code></td><td style="text-align:center">当前进程的<code>PID</code></td></tr><tr><td style="text-align:center"><code>$!</code></td><td style="text-align:center">最近一个后台运行进程的进程号</td></tr><tr><td style="text-align:center"><code>$*</code></td><td style="text-align:center">所有参数构成一个字符串</td></tr><tr><td style="text-align:center"><code>$@</code></td><td style="text-align:center">使用双引号扩起所有参数构成字符串</td></tr></tbody></table><ul><li><p><code>read</code>读取用户输入, 使用<code>-p</code>添加提示, 使用<code>-r</code>避免转移字符</p></li><li><p><code>echo</code>输出, <code>-e</code>解析转义字符, <code>-n</code>结尾不换行</p></li><li><p><code>sort</code></p></li></ul><table><thead><tr><th style="text-align:center">选项</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>-r</code></td><td style="text-align:center">倒序</td></tr><tr><td style="text-align:center"><code>-o</code></td><td style="text-align:center">输出指定文件</td></tr><tr><td style="text-align:center"><code>-u</code></td><td style="text-align:center">去除重复</td></tr><tr><td style="text-align:center"><code>-n</code></td><td style="text-align:center">数值排序, 否则是字典序</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;用户&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可以查看&lt;code&gt;/etc/passwd&lt;/code&gt;文件, 得到系统用户信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个目录最早用来存储用户密码的哈希, 但是所有用户都可以访问&lt;/li&gt;
&lt;li&gt;现在用户密码的哈希存储在&lt;code&gt;/et</summary>
      
    
    
    
    <category term="Linux" scheme="https://sangs3112.github.io/categories/Linux/"/>
    
    
    <category term="Linux" scheme="https://sangs3112.github.io/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Linux笔记_0</title>
    <link href="https://sangs3112.github.io/posts/205c065c.html"/>
    <id>https://sangs3112.github.io/posts/205c065c.html</id>
    <published>2025-03-02T06:37:35.000Z</published>
    <updated>2025-12-30T05:59:16.710Z</updated>
    
    <content type="html"><![CDATA[<h1>软件安装</h1><ul><li><p><code>apt</code>软件源列表在<code>/etc/apt/sources.list</code>下</p></li><li><p>一般情况下, <code>Ubuntu</code>的官方地址为<code>https://archive.ubuntu.com/</code>, 可以替换源</p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo sed -i &#x27;s|//.*archive.ubuntu.com|//mirrors.ustc.edu.cn|g&#x27; /etc/apt/sources.list</span><br></pre></td></tr></table></figure></li><li><p>如果需要一个命令可以在任意地方执行, 可以将这个目录中的内容添加到<code>/usr/local</code>目录下, 即<code>sudo cp -R * /usr/local/</code></p><ul><li>因为<code>usr/local/bin/</code>是在<code>PATH</code>的环境变量下</li></ul></li></ul><h1>基本命令</h1><h2 id="查看文件内容">查看文件内容</h2><ul><li><code>cat</code>是<code>concatenate</code>的缩写, 连接多个文件并输出文件内容, 如果只有一个文件, 则直接输出这个文件的内容</li><li><code>less</code>一次只显示一页，支持前后滚动搜索的功能</li></ul><table><thead><tr><th style="text-align:center">按键</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>d/u</code></td><td style="text-align:center">向下/向上半页</td></tr><tr><td style="text-align:center"><code>f/b</code></td><td style="text-align:center">向下/向上一页</td></tr><tr><td style="text-align:center"><code>g/G</code></td><td style="text-align:center">文件开头/结尾</td></tr><tr><td style="text-align:center"><code>j/k</code></td><td style="text-align:center">向下/向上一行</td></tr><tr><td style="text-align:center"><code>/PATTERN</code></td><td style="text-align:center">搜索<code>PATTERN</code></td></tr><tr><td style="text-align:center"><code>n/N</code></td><td style="text-align:center">跳转上一个/下一个找到的<code>PATTERN</code></td></tr><tr><td style="text-align:center"><code>q</code></td><td style="text-align:center">退出</td></tr></tbody></table><blockquote><p><code>10 j</code> 就是向下移动10行</p></blockquote><h2 id="复制文件目录">复制文件目录</h2><ul><li><code>cp [OPTION] SOURCE... DEST</code></li></ul><table><thead><tr><th style="text-align:center">选项</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>-r</code></td><td style="text-align:center">递归复制, 一般用于复制目录</td></tr><tr><td style="text-align:center"><code>-f</code></td><td style="text-align:center">覆盖<code>DEST</code>位置的同名文件</td></tr><tr><td style="text-align:center"><code>-u</code></td><td style="text-align:center">源文件比目标文件新才进行复制</td></tr><tr><td style="text-align:center"><code>-l</code></td><td style="text-align:center">创建硬链接</td></tr><tr><td style="text-align:center"><code>-s</code></td><td style="text-align:center">创建软链接</td></tr></tbody></table><h2 id="创建目录">创建目录</h2><ul><li><code>mkdir -p DIR_NAME</code>, 表示如果路径中有中间目录, 则先创建中间目录, 如果要创建的目录已经存在，则不报错</li></ul><h2 id="创建文件">创建文件</h2><ul><li><code>touch FILE_NAME</code>, 不叫<code>create</code>是因为本质上是修改文件的<strong>访问时间</strong>和<strong>修改时间</strong><ul><li>相当于是摸了一下文件, 使得他的访问时间和修改时间发生改变了, 如果文件不存在，则会创建一个新文件。</li><li>如果对已有文件进行<code>touch</code>, 则其<code>Access time</code>, <code>Modify time</code>会发生改变</li></ul></li></ul><h2 id="搜索文件和目录">搜索文件和目录</h2><ul><li><code>find [OPTION] PATH [EXPRESSION]</code></li></ul><table><thead><tr><th style="text-align:center">选项</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>-name '*.ext'</code></td><td style="text-align:center">文件后缀名为<code>ext</code>的所有文件</td></tr><tr><td style="text-align:center"><code>-type d</code></td><td style="text-align:center">文件类型为目录的文件, <code>f</code>表示普通文件</td></tr><tr><td style="text-align:center"><code>-size +1M</code></td><td style="text-align:center">文件大小大于<code>1M</code>的文件, <code>-</code>表示小于</td></tr><tr><td style="text-align:center"><code>-or</code></td><td style="text-align:center">表示前后两个选项满足一个即可</td></tr></tbody></table><h2 id="模式匹配">模式匹配</h2><ul><li><code>bash</code>的模式匹配称为<code>glob</code></li><li><code>?</code>表示一个字符, <code>*</code>表示任意字符, <code>*.[ch]</code>表示以<code>.c</code>或者是<code>.h</code>结尾的文件</li></ul><h2 id="tar存档压缩"><code>tar</code>存档压缩</h2><table><thead><tr><th style="text-align:center">选项</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>-A</code></td><td style="text-align:center">将一个存档文件追加到另一个存档文件</td></tr><tr><td style="text-align:center"><code>-r</code></td><td style="text-align:center">将一些存档文件追加到另一个存档文件</td></tr><tr><td style="text-align:center"><code>-c</code></td><td style="text-align:center">从一些文件创建存档文件</td></tr><tr><td style="text-align:center"><code>-t</code></td><td style="text-align:center">列出一个存档文件内容</td></tr><tr><td style="text-align:center"><code>-x</code></td><td style="text-align:center">从存档文件提取出文件</td></tr><tr><td style="text-align:center"><code>-f</code></td><td style="text-align:center">使用指定的存档文件</td></tr><tr><td style="text-align:center"><code>-C</code></td><td style="text-align:center">指定输出的目录</td></tr><tr><td style="text-align:center"><code>-z</code></td><td style="text-align:center">使用<code>gzip</code>算法处理存档文件</td></tr><tr><td style="text-align:center"><code>-j</code></td><td style="text-align:center">使用<code>bzip2</code>算法处理存档文件</td></tr><tr><td style="text-align:center"><code>-J</code></td><td style="text-align:center">使用<code>xz</code>算法处理存档文件</td></tr></tbody></table><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将f1, f2, f3打包为t.tar</span></span><br><span class="line">tar -cf t.tar f1 f2 f3</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将t.tar中的文件提取到t/</span></span><br><span class="line">tar -xf t.tar -C t/</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将f1, f2, f3打包并使用gzip压缩，得到t.tar.gz</span></span><br><span class="line">tar -czf t.tar.gz f1 f2 f3</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将t.tar.gz 解压缩到t/</span></span><br><span class="line">tar -xzf t.tar.gz -C t/</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将a1.tar, a2.tar, a3.tar追加到a.tar中</span></span><br><span class="line">tar -Af a.tar a1.tar a2.tar a3.tar</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出a.tar中的文件内容</span></span><br><span class="line">tar -tf a.tar</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">打印文件详细信息</span></span><br><span class="line">tar -tvf a.tar</span><br></pre></td></tr></table></figure><h2 id="查看帮助">查看帮助</h2><ul><li><code>man tar</code> 可以查看<code>tar</code>命令的帮助目录</li><li><code>tldr 命令</code>可以直接学习相关命令</li></ul><h1>进程</h1><blockquote><p><code>htop</code>可以查看进程信息</p></blockquote><h2 id="ps"><code>ps</code></h2><blockquote><p><code>ps</code>(<code>process status</code>) 进程状态工具</p></blockquote><ul><li>直接使用<code>ps</code>会返回本终端中运行的进程</li><li>使用<code>ps aux</code>会显示所有进程</li></ul><h2 id="PID进程标识符"><code>PID</code>进程标识符</h2><ul><li><code>Linux</code>系统内核从引导程序接手控制权以后, 执行内核初始化, 变为<code>init_stack</code>, 随后创建出1号程序, 一般为<code>init, systemd</code></li><li><code>systemd</code>衍生出用户空间所有进程, 2号进程<code>kthreadd</code>衍生出所有内核线程, 0号进程成为<code>idle</code>进程</li><li><code>kthreadd</code>进程在内核空间中, 所以需要<code>shift + K(大写)</code>才能看见, <code>htop</code>中也看不见0号进程, 只能看见1号, 2号进程的<code>PPID</code>为0</li></ul><h2 id="优先级">优先级</h2><ul><li><code>htop</code>中有两个值<code>PRI(proiority)</code>和<code>NI(nice)</code></li><li><code>nice</code>值是用户层使用的, 越高表示对其他进程越友好, 优先级越低, 通常最高为19, 最低为-20, 一般创建的时候默认为0</li><li>可以使用<code>nice</code>命令在创建进程的时候指定优先级, <code>renice</code>可以重新指定</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">以10的NI启动vim</span></span><br><span class="line">nice -n 10 vim</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将PID=12345的进程NI调整为10</span></span><br><span class="line">renice -n 10 -p 12345</span><br></pre></td></tr></table></figure><ul><li><code>PRI = NI + 20</code>, 普通进程的<code>PRI</code>会被映射到一个非负整数</li></ul><h2 id="进程状态">进程状态</h2><ol><li>正在运行: 运行态</li><li>可以运行, 但是还在排队等待: 就绪态</li><li>正在等待其他资源, 无法立即开始执行: 阻塞态</li></ol><ul><li><code>htop</code>的状态有<code>R: running, S: sleeping, T: traced/stopped, Z: zombie, D: disk sleep</code><ul><li><code>R</code>对应运行态和就绪态</li><li><code>S, D</code>对应阻塞态, <code>S</code>表示可以被中断, <code>D</code>表示不可被中断</li><li><code>Z</code>是僵尸进程, 上下文结束, 但是仍然占用一个<code>PID</code>, 保存一个返回值</li><li><code>T</code>是上下文使用<code>ctrl + Z</code>导致挂起(<code>T</code>), 或者使用<code>gdb</code>这种调试工具跟踪状态时(<code>t</code>)</li></ul></li></ul><h2 id="用户控制进程">用户控制进程</h2><blockquote><p>进程之间不共享内存, 无法直接发送消息, 所以如果需要控制进程, 就需要操作系统帮忙, 所以产生了信号机制<br>发送信号的命令就是<code>kill</code>, 因为早期信号的作用就是杀死进程<br>使用<code>man 7 signal</code>可以查看信号</p></blockquote><table><thead><tr><th style="text-align:center">信号</th><th style="text-align:center">意义</th><th style="text-align:center">行为</th><th style="text-align:center">产生方式</th></tr></thead><tbody><tr><td style="text-align:center"><code>SIGINT (interrupt)</code></td><td style="text-align:center">朋友, 别干了</td><td style="text-align:center">终止进程</td><td style="text-align:center"><code>ctrl + c</code></td></tr><tr><td style="text-align:center"><code>SIGTERM (termiate)</code></td><td style="text-align:center">优雅死去</td><td style="text-align:center">终止进程</td><td style="text-align:center"><code>kill &lt;PID&gt; or pkill 进程名</code></td></tr><tr><td style="text-align:center"><code>SIGKILL (kill)</code></td><td style="text-align:center">立即去世</td><td style="text-align:center">终止进程</td><td style="text-align:center"><code>kill -9 &lt;PID&gt; or pklii -9 进程名</code></td></tr><tr><td style="text-align:center"><code>SIGSEGV (segment violation)</code></td><td style="text-align:center">你想知道的太多了</td><td style="text-align:center">核心转储</td><td style="text-align:center">什么都不用做, 代码写的烂导致的</td></tr><tr><td style="text-align:center"><code>SIGSTOP (stop) or SIGTSTP (stop)</code></td><td style="text-align:center">让某个进程变成植物人</td><td style="text-align:center">停止进程</td><td style="text-align:center"><code>ctrl + z</code></td></tr><tr><td style="text-align:center"><code>SIGCONT (continue)</code></td><td style="text-align:center">让植物人苏醒</td><td style="text-align:center">继续进程</td><td style="text-align:center"><code>fg or bg</code></td></tr></tbody></table><ul><li><p>如果需要一个进程在后台运行, 不阻止运行时在同一个<code>shell</code>界面操作其他内容, 可以在执行时后面加上<code>&amp;</code></p></li><li><p>如果要将一个前台正在执行的程序切换到后台执行, 可以使用<code>ctrl + z</code>挂起进程, 控制权还给<code>shell</code></p><ul><li>接着使用<code>jobs</code>命令查看当前<code>shell</code>下所有挂起的进程, 比如为<code>[2]</code>, 可以使用<code>bg %2</code>将进程放到后台执行</li></ul></li><li><p>如果使用<code>SSH</code>连接终端, 如果终端关闭会断开连接, 这是因为终端关闭以后会向每个进程都发送<code>SIGHUP</code>, 默认动作就是退出程序运行</p></li><li><p>可以使用<code>nohup</code>, 使程序不被<code>SIGHUP</code>命令影响, <code>nohup ping xxx.xxx.xxx.xxx &amp;</code></p><ul><li>此时输出将会被重定向到<code>nohup.out</code>中</li></ul></li></ul><h2 id="tmux"><code>tmux</code></h2><blockquote><p>先<code>ctrl + b</code>再输入快捷键</p></blockquote><table><thead><tr><th style="text-align:center">快捷键</th><th style="text-align:center">功能</th></tr></thead><tbody><tr><td style="text-align:center"><code>%</code></td><td style="text-align:center">左右分屏</td></tr><tr><td style="text-align:center"><code>&quot;</code></td><td style="text-align:center">上下分屏</td></tr><tr><td style="text-align:center">方向键</td><td style="text-align:center">切换分屏选中</td></tr><tr><td style="text-align:center"><code>d</code></td><td style="text-align:center">从命令行脱离, 回到终端界面</td></tr><tr><td style="text-align:center"><code>z</code></td><td style="text-align:center">将<code>panel</code>暂时全屏, 再按一次恢复原状</td></tr><tr><td style="text-align:center"><code>c</code></td><td style="text-align:center">新建窗口</td></tr><tr><td style="text-align:center"><code>,</code></td><td style="text-align:center">为窗口命名</td></tr><tr><td style="text-align:center"><code>s</code></td><td style="text-align:center">列出所有<code>session</code></td></tr></tbody></table><ul><li>如果掉线了, 会话依然保存在后台, 使用<code>tmux attach [-t 名字]</code>可以恢复</li></ul><h2 id="fork"><code>fork</code></h2><blockquote><p>创建进程的基本方法</p></blockquote><ul><li>调用<code>fork</code>的是父进程, 被<code>fork</code>的是子进程<ul><li>父进程先退出, 子进程就变成孤儿进程, 被操作系统回收, 交给<code>init</code>领养</li><li>子进程先退出, 父进程没有回应, 则子进程变成僵尸进程, 释放大部分资源, 占用<code>PID</code>, 大量僵尸进程会导致无法创建新进程</li></ul></li></ul><h1>服务</h1><h2 id="自定义服务">自定义服务</h2><blockquote><p>将一个基于<code>Web</code>的应用作为局域网内的服务, 方便其他设备访问, 可以编写<code>.service</code></p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=xxx # 服务简要描述</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">PIDFile=/run/xxx.pid # 用来存放PID文件</span><br><span class="line">ExecStat=/usr/local/bin/xxx --allow-port # 使用绝对路径标明命令以及参数</span><br><span class="line"></span><br><span class="line">WorkingDirectory=/root # 服务启动的工作目录</span><br><span class="line">Restart=always # 无论因何原因退出都重启</span><br><span class="line">RestartSec=10 # 退出10s重启</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target # 依赖目标, 指进入多用户模式后再启动该服务</span><br></pre></td></tr></table></figure><ul><li>配置好的文件保存为<code>/etc/systemd/system/xxx.service</code>, 运行<code>systemctl daemon-reload</code>, 就可以使用<code>systemctl</code>管理这个服务</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;软件安装&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;apt&lt;/code&gt;软件源列表在&lt;code&gt;/etc/apt/sources.list&lt;/code&gt;下&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一般情况下, &lt;code&gt;Ubuntu&lt;/code&gt;的官方地址为&lt;code</summary>
      
    
    
    
    <category term="Linux" scheme="https://sangs3112.github.io/categories/Linux/"/>
    
    
    <category term="Linux" scheme="https://sangs3112.github.io/tags/Linux/"/>
    
    <category term="软件源" scheme="https://sangs3112.github.io/tags/%E8%BD%AF%E4%BB%B6%E6%BA%90/"/>
    
  </entry>
  
  <entry>
    <title>安卓逆向笔记_0</title>
    <link href="https://sangs3112.github.io/posts/4dd06a82.html"/>
    <id>https://sangs3112.github.io/posts/4dd06a82.html</id>
    <published>2025-01-15T08:08:00.000Z</published>
    <updated>2025-12-30T05:59:16.711Z</updated>
    
    <content type="html"><![CDATA[<h1>环境配置</h1><ol><li><p>雷电模拟器9(<code>debug</code>版)</p><ul><li>开启<code>Root</code>权限</li></ul></li><li><p>安装面具(<code>app-debug.apk</code>)</p><ul><li>进入主页后安装<code>Magisk</code>, 会发现只勾选了<code>安装到Recovery</code>, 不要点击其他内容</li><li>直接控制台杀死进程</li><li>重新打开面具, 再次点击安装, 发现勾选了两个选项, 点击下一步</li><li>方式选择<code>安装至系统分区</code>, 点击开始</li><li>如果出现了<code>Installation failed</code>, 需要在雷电模拟器的<code>设置</code>中选择<code>磁盘</code> -&gt; <code>系统磁盘</code>, 勾选<code>可写入</code></li><li>出现<code>All done</code>表示安装完成</li><li>重启模拟器</li><li>再次启动面具, 如果有弹窗<code>异常状态: 检测到不属于Magisk的su文件</code>直接<code>确定</code>不需要管</li><li>在面具的设置中打开<code>Zygisk</code></li></ul></li></ol><div class="note info flat"><ul><li>关于<code>异常状态</code>的提示, 可以进入模拟器的文件管理器</li><li><code>System</code> -&gt; <code>xbin</code>删除其中的<code>su</code>文件, 第一次删除失败就再删一次, 就可以消除这个提示</li></ul></div><ol start="2"><li><p><code>LSPosed</code>模块</p><ul><li>模拟器右侧<code>更多</code> -&gt; <code>共享文件夹</code> -&gt; <code>打开电脑文件夹</code> -&gt; 将<code>LSPosed.zip</code>拖到文件夹中</li><li>面具下面的<code>模块</code> -&gt; <code>从本地安装</code> -&gt; 右边选择<code>文件管理器</code> -&gt; 找到刚刚的共享文件夹 -&gt; 直接点击压缩包即可开始安装</li><li>出现<code>Done</code>表示安装完成, 重启模拟器</li><li>再次进入面具, 点击<code>模块</code>, 如果出现了<code>Zygisk-LSPosed</code>表示安装完成</li></ul></li><li><p>核心破解</p><ul><li>直接安装<code>核心破解.apk</code></li><li>提示<code>已安装, 但尚未激活</code>, 点击提示</li><li><code>启用模块</code> -&gt; 勾选<code>系统框架</code>, 重启模拟器</li></ul></li><li><p>算法助手</p><ul><li>安装<code>算法助手.apk</code></li><li>点击提示, 勾选需要<code>Hook</code>的软件即可, 然后重启<code>算法助手</code>即可</li><li>如果出现模块未启动, 重启模拟器即可</li><li>开启需要<code>Hook</code>对象的应用开关, 勾选<code>弹窗定位</code>, 点击右上角的启动</li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;环境配置&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;雷电模拟器9(&lt;code&gt;debug&lt;/code&gt;版)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启&lt;code&gt;Root&lt;/code&gt;权限&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安装面具(&lt;code&gt;app-debug.apk&lt;</summary>
      
    
    
    
    <category term="Reverse" scheme="https://sangs3112.github.io/categories/Reverse/"/>
    
    
    <category term="Android" scheme="https://sangs3112.github.io/tags/Android/"/>
    
    <category term="Reverse" scheme="https://sangs3112.github.io/tags/Reverse/"/>
    
    <category term="Environment" scheme="https://sangs3112.github.io/tags/Environment/"/>
    
  </entry>
  
  <entry>
    <title>Java笔记_13</title>
    <link href="https://sangs3112.github.io/posts/9dced3f3.html"/>
    <id>https://sangs3112.github.io/posts/9dced3f3.html</id>
    <published>2024-12-16T01:18:00.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>并发</h1><h2 id="创建线程">创建线程</h2><ol><li>将线程任务放在一个类的<code>run()</code>中, 这个类需要实现<code>Runnable</code>接口</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>Runnable</code>是个函数式接口, 所以可以使用<code>lambda</code>表达式创建实例</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Runnable</span> <span class="variable">r</span> <span class="operator">=</span> () -&gt; &#123;</span><br><span class="line">    task;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>从这个<code>Runnable</code>构造一个<code>Thread</code>对象 <code>Thread t = new Thread(r);</code></li><li>启动线程 <code>t.start();</code></li></ol><div class="note info flat"><ul><li>还可以通过建立<code>Thread</code>类的子类定义线程</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span> <span class="params">()</span> &#123;</span><br><span class="line">        task;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>然后构造这个子类的对象并调用<code>start()</code>, 但是现在一般不用这个方法, 如果有多个任务, 每个任务都要创建一个线程的开销的太大, 一般使用线程池</li><li>不要调用<code>Thread</code>类或者<code>Runnable</code>对象的<code>run()</code>, 直接调用<code>run()</code>会在同一个线程中执行这个任务, 而不会启动新的线程</li><li>应该调用<code>Thread.start()</code>创建一个新的线程执行<code>run()</code></li></ul></div><h2 id="线程的状态">线程的状态</h2><blockquote><p>使用<code>getState()</code>可以确定当前的状态</p></blockquote><ol><li><code>New</code> 新建</li><li><code>Runnable</code> 可运行</li><li><code>Blocked</code> 阻塞</li><li><code>Waiting</code> 等待</li><li><code>Time waiting</code> 计时等待</li><li><code>Terminated</code> 终止</li></ol><h3 id="New"><code>New</code></h3><ul><li>使用<code>new</code>新建一个线程, 比如<code>new Thread(r)</code>, 线程还没有开始运行</li></ul><h3 id="Runnable"><code>Runnable</code></h3><ul><li>调用<code>start()</code>后, 线程就是<code>Runnable</code>状态, 可以是正在运行, 也可以没有在运行</li></ul><h3 id="Blocked-Waiting-Time-waiting"><code>Blocked</code> &amp; <code>Waiting</code> &amp; <code>Time waiting</code></h3><ul><li>处于阻塞或等待状态时, 线程不活动, 不执行任何代码, 消耗最少的资源<ul><li>当一个线程试图获取一个内部的对象锁, 不是<code>java.util.concurrent.Lock</code>, 而这个锁目前被其他线程占有, 该线程就会阻塞; 当其他线程都释放了这个锁, 并且调度器允许该线程持有锁时, 该线程转换为非阻塞状态</li><li>当线程等待另一个线程通知调度器出现某个条件时, 线程进入等待状态, 阻塞状态和等待状态没有太大的区别. 调用<code>Object.wait(), Thread.join()</code>, 或者等待<code>java.util.concurrent</code>中的<code>Lock, Condition</code>时会进入等待状态</li><li>有几个方法有超时参数, 调用这些方法会让线程进入计时等待状态, 这个状态将会一直保持到计时器满, 或者接收到适当通知. 带有超时参数的方法有<code>Thread.sleep()</code>和计时版的<code>Object.wait(), Thread.join(), Lock.tryLock(), Condition.await()</code></li></ul></li></ul><h3 id="Terminated"><code>Terminated</code></h3><ul><li>由于<code>run()</code>正常退出, 线程自然终止</li><li>因为一个没有捕获的异常终止<code>run()</code>, 线程意外终止</li><li><code>stop()</code>会抛出一个<code>ThreadDeath</code>错误对象, 终止线程, 但是现在已经废弃不用</li></ul><h2 id="线程的属性">线程的属性</h2><h3 id="中断线程">中断线程</h3><ul><li>当线程执行了最后一条语句, 或者抛出了一个没有捕获的异常, 线程就会终止</li><li><code>stop()</code>可以强制停止, 但是已经废弃, 现在没有方法可以强制停止一个线程</li><li>可以使用<code>interrupt()</code>请求终止一个线程, 对一个线程调用<code>interrupt()</code>, 设置线程为中断状态, 每个线程都会不时检测这个<code>boolean</code>标志, 判断线程是否被中断了<ul><li>使用<code>Thread.currentThread().isInterrupted()</code>判断当前线程是否处于中断状态</li><li>如果线程被阻塞, 就无法查看中断状态, 需要引入<code>InterruptedException</code></li><li>在一个被<code>sleep(), wait()</code>阻塞的线程上调用<code>interrupt()</code>, 那个阻塞调用将被<code>InterruptedException</code>中断</li><li>如果在线程循环中调用了<code>sleep()</code>, 就没有必要使用<code>isInterrupted()</code>检测了, 因为如果设置了中断状态, <code>sleep()</code>也只会清除中断装填并直接抛出<code>InterruptedException</code>, 所以还要循环调用了<code>sleep()</code>, 可以直接<code>try-catch``InterruptedException</code></li></ul></li></ul><div class="note info flat"><ul><li><code>interrupted()</code>是一个静态方法, 检查当前线程是否被中断, 调用该方法会清除该线程中断状态</li><li><code>isInterrupted()</code>是一个实例方法, 检查是否有线程被中断, 不会清除线程中断状态</li></ul></div><ul><li>如果<code>catch(InterruptedException e)</code>没有什么需要做的, 可以<code>Thread.currentThread().interrupt()</code>设置中断状态, 或者直接不<code>try-catch</code>, 而是<code>throws InterruptedException</code></li></ul><h3 id="守护线程">守护线程</h3><ul><li><code>t.setDaemon(true)</code>将一个线程转换为守护线程, 唯一的作用是为其他线程提供服务</li><li>比如计时器线程, 或者清空过时缓存项的线程, 如果只剩下守护线程, <code>JVM</code>就会退出, 因为只有守护线程就没有必要运行程序了</li></ul><h3 id="线程名">线程名</h3><ul><li><code>t.setName(&quot;Name&quot;);</code>可以为任何线程设置一个名字</li></ul><h3 id="未捕获异常的处理器">未捕获异常的处理器</h3><ul><li>线程的<code>run()</code>不能抛出任何检查型异常, 如果有非检查型异常则会导致线程终止, 最终线程死亡</li><li>对于可以传播的异常, 也没有<code>catch</code>子句, 因为在线程死亡之前, 异常会传递到一个用于处理未捕获异常的处理器</li><li>这个处理器必须属于实现了<code>Thread.UncaughtExceptionHelper</code>接口的类, 接口只有一个方法<code>void uncaughtException(Thread t, Throwable e);</code></li><li>可以调用<code>setUncaughtExceptionHandler()</code>为任何线程设置处理器, 也可以使用静态方法<code>Thread.setDefaultUncaughtExceptionHandler()</code>为所有线程安装一个默认处理器</li><li>如果没有安装默认处理器, 则为<code>null</code>, 如果没有为单个线程安装处理器, 则处理器就是该线程的<code>ThreadGroup</code>对象</li><li>建议不要在自己的程序中使用线程组</li><li><code>ThreadGroup</code>类实现了<code>Thread.UncaughtExceptionhandler</code>接口, <code>uncaughtException()</code>执行以下操作<ul><li>如果该线程组有父线程组, 调用父线程组的<code>uncaughtExcpeiton()</code></li><li>否则, 如果<code>Thread.getDefaultUncaughtExceptionHandler()</code>返回一个非<code>null</code>的处理器, 则调用该处理器</li><li>否则, 如果<code>Throwable</code>是<code>ThreadDeath</code>的一个实例, 则什么都不做</li><li>否则, 将线程的名字以及<code>Throwable</code>的栈轨迹输出到<code>System.err</code></li></ul></li></ul><h3 id="线程优先级">线程优先级</h3><ul><li>每个线程都有一个优先级, 默认一个线程会继承构造他的线程的优先级</li><li>可以使用<code>setPriority()</code>设置优先级, <code>MIN_PRIORITY = 1</code>, <code>MAX_PRIORITY = 10</code>, <code>NORM_PRIORITY = 5</code></li><li>调度器选择新的线程时优先选择优先级高的线程, 优先级高度依赖于系统</li><li>早期优先级可能很有用, 现在不要使用线程优先级</li></ul><h2 id="同步">同步</h2><ul><li><code>javap -c -p xxx</code>可以反编译<code>xxx.class</code>, 查看虚拟机字节码</li><li>两种机制可以防止并发访问一个代码块, <code>synchronized</code>关键字和<code>Java 5</code>引入的<code>ReentrantLock</code>类</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">myLock.lock();</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    myLock.unLock();</span><br><span class="line">    <span class="comment">// 一定需要将unLock()放在finally中, 不然抛出异常退出将永远阻塞</span></span><br><span class="line">    <span class="comment">// 使用锁时不能使用try-with-resources</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>如果两个线程尝试访问同一个对象, 可以保证串行化访问; 如果两个线程访问不同的对象, 每个线程都会得到不同的锁对象, 两个线程都不会阻塞</p><ul><li>这个锁称为重入锁, 因为线程可以反复获得已经拥有的锁, 锁持有一个计数器跟踪对<code>lock</code>方法的嵌套调用</li><li><code>ReentrantLock(boolean fail)</code>可以构造一个采用公平策略的锁, 但是公平锁比常规锁慢的多, 而且就算使用公平锁, 也不能保证就可以公平处理</li></ul></li><li><p>线程进入临界区以后发现需要满足某个条件才能继续执行, 可以使用条件对象(条件变量)管理那些已经获得锁但是不能有效工作的线程</p></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(<span class="type">int</span> from, <span class="type">int</span> to, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">    bankLock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (accounts[from] &lt; amout) &#123;</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        bankLock.unLock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果from账户中没有钱, 则while会一直循环, 一直持有锁, 别的线程无法充钱</span></span><br><span class="line"><span class="comment">// 可以使用newCondition()获得一个条件对象</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Bank</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Condition sufficientFunds;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Bank</span><span class="params">()</span> &#123;</span><br><span class="line">        sufficientFunds = bankLock.newCondition();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果transfer()发现没有钱, 会调用sufficientFunds.await(), 当前线程暂停, 放弃锁, 其他线程可以执行</span></span><br><span class="line"><span class="comment">// 一旦一个线程调用了await(), 就进入了这个条件的等待集</span></span><br><span class="line"><span class="comment">// 锁可用时, 这个线程也不会变成可运行状态, 而是仍然保持非活动状态, 直到另一个线程在同一个条件上调用signalAll()</span></span><br></pre></td></tr></table></figure><ul><li><code>signalAll()</code>会重新激活所有满足条件的线程, 从等待集中移出, 再次变为可运行状态</li><li><code>await()</code>一般放在一个循环中<code>while(!(OK is proceed)) condition.await()</code></li><li>最终都需要有一个其他线程调用<code>signalAll()</code>, 因为当一个线程调用<code>await()</code>时, 没有办法自行激活<ul><li>如果没有别的线程调用<code>signalAll()</code>, 将永远阻塞, 导致死锁</li><li>只要一个对象状态有变化, 并且可能有利于正在等待的线程, 就可以调用<code>signalAll()</code></li><li><code>signal()</code>可以随机选择等待集的一个线程, 解除其阻塞状态, 比解除所有线程阻塞状态更加高效</li><li>但是如果随机解除阻塞状态的线程发现自己仍然无法运行, 就会再次阻塞, 此时没有其他线程调用<code>signal()</code>以后就会导致死锁</li></ul></li></ul><h3 id="synchronized"><code>synchronized</code></h3><ul><li><p>锁用来保护代码段, 一次只允许一个线程执行被保护的代码段</p></li><li><p>锁可以用来管理试图进入被保护的代码段的线程</p></li><li><p>一个锁可以有一个或者多个关联的条件对象</p></li><li><p>每个条件对象管理那些已经进入被保护代码段, 但是还不能执行的线程</p></li><li><p>如果一个方法声明时有<code>synchronized</code>关键字, 则对象的锁将会保护整个方法, 所以如果需要调用这个方法, 则线程必须获得内部对象锁</p></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    method body</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.intrinsicLock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        method body</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.intrinsicLock.unLock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>内部对象锁只有一个关联条件, <code>wait()</code>将一个线程添加到等待集, <code>notifyAll(), notify()</code>可以解除等待集线程阻塞</p></li><li><p>内部锁和条件存在一些限制</p><ul><li>不能中断一个正在尝试获得锁的线程</li><li>不能指定尝试获得锁的线程超时时间</li><li>没有锁只有一个条件对象, 比较低效</li></ul></li></ul><h3 id="同步块">同步块</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(obj) &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 就获得了obj的锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Bank</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span>[] accounts;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(<span class="type">int</span> from, <span class="type">int</span> to, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span>(lock) &#123;</span><br><span class="line">            accounts[from] -= amount;</span><br><span class="line">            accounts[to] += amount;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 但是如果是</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="string">&quot;lock&quot;</span>;</span><br><span class="line"><span class="keyword">synchronized</span> (lock) &#123;...&#125;</span><br><span class="line"><span class="comment">// 这里如果两个线程使用将会锁同一个对象, 因为字符串字面量会共享, 最终导致死锁</span></span><br><span class="line"><span class="comment">// 同时需要避免基本包装类型作为锁</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">lock</span> <span class="operator">=</span> Integer.valueOf(<span class="number">42</span>);</span><br><span class="line"><span class="comment">// 如果同一个数使用两次, 将会导致共享锁</span></span><br><span class="line"><span class="comment">// 如果需要修改一个静态字段, 会从特定的类上获得锁, 而不是从getClass()返回值上获得</span></span><br><span class="line"><span class="keyword">synchronized</span>(MyClass.class) &#123;...&#125; <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">synchronized</span>(getClass()) &#123;...&#125; <span class="comment">// Error</span></span><br><span class="line"><span class="comment">// 如果从一个子类调用这个方法, getClass()会返回一个不同的class对象, 不能保证互斥了</span></span><br></pre></td></tr></table></figure><h3 id="监视器">监视器</h3><ul><li>监视器是指包含私有字段的类</li><li>监视器类的每个对象都有一个关联的锁</li><li>所有方法由这个锁锁定, 如果客户端调用<code>obj.method()</code>, 调用开始时就会自动获得<code>obj</code>对象的锁, 并且在返回时自动释放这个锁<ul><li>因为所有的字段都是私有的, 这样就可以保证一个线程处理字段时, 其他线程都无法访问</li></ul></li><li>锁可以有任意多个关联的条件</li></ul><h3 id="volatile"><code>volatile</code></h3><ul><li>如果写一个对象, 这个对象接下来可能被另一个线程读取; 或者读一个对象, 这个对象可能已经被另一个线程写入, 就必须使用同步</li><li><code>volatile</code>关键字为实例字段的同步访问提供了一种免锁机制</li><li>如果声明一个字段时<code>volatile</code>, 那编译器或虚拟机就会考虑这个字段可能被另一个线程并发更新</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> done;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="type">boolean</span> <span class="title function_">isDone</span><span class="params">()</span> &#123;<span class="keyword">return</span> done;&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">setDone</span><span class="params">()</span> &#123;done = <span class="literal">true</span>;&#125;</span><br><span class="line"><span class="comment">// 如果另一个线程已经对该对象加锁, isDone()和setDone()就会被阻塞</span></span><br><span class="line"><span class="comment">// 可以声明为volatile</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> done;</span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isDone</span><span class="params">()</span> &#123;<span class="keyword">return</span> done;&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDone</span><span class="params">()</span> &#123;done = <span class="literal">true</span>;&#125;</span><br><span class="line"><span class="comment">// 但是volatile不能保证原子性</span></span><br></pre></td></tr></table></figure><h3 id="final变量"><code>final</code>变量</h3><ul><li><code>final var accounts = new HashMap&lt;String, Double&gt;()</code>声明为<code>final</code>以后, 其他线程会在构造器完成构造以后才能看到这个<code>accounts</code></li><li>如果不使用<code>final</code>, 不能保证其他线程看到的是<code>accounts</code>更新以后的值, 可能都只是<code>null</code>, 而不是信构造的<code>HashMap</code></li></ul><h3 id="原子性">原子性</h3><ul><li>假设对共享变量除了赋值以外不做其他操作, 可以使用<code>volatile</code></li><li><code>java.util.concurrent.atomic</code>包中包含很多原子操作, 使用了机器指令, 没使用锁<ul><li><code>AtomicInteger</code>类中包含<code>incrementAndGet, decrementAndGet</code>以原子方式对一个整数完成自增自减</li></ul></li><li>如果有大量的线程要访问相同的原子值, 性能会大幅度下降, 因为乐观更新需要叫多次重试<ul><li><code>LongAdder</code>和<code>LongAccumulator</code>类解决了这个问题</li></ul></li></ul><h3 id="stop-和-suspend"><code>stop()</code> 和 <code>suspend()</code></h3><ul><li><code>stop()</code>方法就不安全, 会终止所有未完成的方法, 包括<code>run()</code><ul><li>一个线程终止时, 会立即释放被它锁定的对象的锁, 导致对象处于不一致状态</li></ul></li><li><code>suspend()</code>不会破坏对象, 但是如果用来挂起一个持有锁的线程, 在这个线程恢复运行之前这个锁不可用<ul><li>如果调用<code>suspend()</code>的线程试图获得同一个锁, 那么就会导致死锁问题</li></ul></li></ul><h3 id="按需初始化">按需初始化</h3><ul><li><code>JVM</code>会在第一次使用类时初始化一个静态初始化器, 并且只会执行一次</li><li><code>JVM</code>利用一个锁来确保这一点, 但是需要确保构造器不会抛出任何异常</li></ul><h3 id="线程局部变量">线程局部变量</h3><ul><li><p>如果一个类中设置静态变量</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span>  <span class="keyword">final</span> <span class="type">SimpleDateFormat</span> <span class="variable">dataFormat</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-mm-dd&quot;</span>);</span><br><span class="line"><span class="comment">// 如果此时两个线程都执行如下操作</span></span><br><span class="line"><span class="type">String</span> <span class="variable">dataStamp</span> <span class="operator">=</span> dataFormat.format(<span class="keyword">new</span> <span class="title class_">Date</span>());</span><br><span class="line"><span class="comment">// 则dataFormat的内部结构可能会被破坏</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;SimpleDateFormat&gt; dataFormat = ThreadLocal.withInitial() -&gt; <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-mm-dd&quot;</span>);</span><br><span class="line"><span class="comment">// 如果需要格式化方法, 可以调用</span></span><br><span class="line"><span class="type">String</span> <span class="variable">dataStamp</span> <span class="operator">=</span> dataFormat.get().format(<span class="keyword">new</span> <span class="title class_">Date</span>());</span><br></pre></td></tr></table></figure></li><li><p><code>java.util.Random</code>是线程安全的, 但是如果多个线程需要等待一个共享随机数生成器, 就很低效</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">random</span> <span class="operator">=</span> ThreadLocalRandom.current().nextInt(upperBound);</span><br><span class="line"><span class="comment">// ThreadLocalRandom.current()会返回当前线程的一个随机数实例</span></span><br></pre></td></tr></table></figure></li><li><p>如果需要共享一个数据库连接</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Connection&gt; connection = ThreadLocal.withInitial(() -&gt; <span class="literal">null</span>);</span><br><span class="line"><span class="comment">// 任务开始时可以初始化这个连接</span></span><br><span class="line">connection.set(connect(url, username, password));</span><br><span class="line"><span class="comment">// 任务调用某些方法, 所有方法都在一个线程, 其中一个方法需要这个连接</span></span><br><span class="line"><span class="type">var</span> <span class="variable">result</span> <span class="operator">=</span> connection.get().executeQuery(query);</span><br></pre></td></tr></table></figure></li><li><p>上述都要求只有一个任务使用线程, 如果是一个线程池执行任务, 可能不想共享相同线程的其他任务提供数据库连接, 就不能使用上述方法</p></li></ul><h2 id="线程安全的集合">线程安全的集合</h2><ul><li>有的并发散列表映射较大, 使用<code>size()</code>返回<code>int</code>类型, 如果超过了20亿则无法正常返回, 可以使用<code>mappingCount()</code>方法返回<code>long</code>类型数据</li><li>集合返回弱一致性迭代器, 表示迭代器不一定能够反映出构造之后所做的全部更改, 但是他们不会将同一个值返回两次, 也不会抛出<code>ConcurrentModificationException</code></li><li><code>java.util</code>包中的集合, 如果集合在迭代器构造之后发生改变, 将会抛出一个``ConcurrentModificationException`</li><li><code>ConcurrentHashMap</code>不允许有<code>null</code>, 如果传入<code>compute, merge</code>的函数返回<code>null</code>, 就会从映射中删除现有的条目</li></ul><h3 id="并发散列映射的批操作">并发散列映射的批操作</h3><ul><li><p>批操作会遍历映射映射, 处理遍历过程中找到的元素, 不会冻结映射的当前快照</p><ul><li>搜索<code>search</code>: 为每个键或值应用一个函数, 直到函数生成一个非<code>null</code>的结果, 然后函数终止, 返回这个结果</li><li>规约<code>reduce</code>: 组合所有的键值, 这里要使用所提供的一个累加函数</li><li><code>forEach</code>为所有键值应用一个函数</li></ul></li><li><p>所有操作都需要指定一个参数化阈值, 如果希望批操作在一个线程中运行, 可以使用<code>Long.MAX_VALUE</code>, 如果希望用尽可能多的线程运行批操作, 可以使用阈值1</p></li><li><p>希望找到出现次数超过1000次的单词:</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> map.search(threshold, (k, v) -&gt; v &gt; <span class="number">1000</span> ? k : <span class="literal">null</span>);</span><br><span class="line"><span class="comment">// res最终是第一个匹配的单词, 如果所有单词都不匹配, 则res是null</span></span><br></pre></td></tr></table></figure></li><li><p><code>forEach</code>有两种形式, 第一种是为每个条目应用一个消费者函数</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">map.forEach(threshold, (k, v) -&gt; System.out.println(k + <span class="string">&quot;-&gt;&quot;</span> + v));</span><br></pre></td></tr></table></figure></li><li><p>第二种形式接受一个额外的转换器, 先应用转换器, 再传递到消费者函数</p></li><li><p>比如只打印很大的条目:</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">map.forEach(threshold, </span><br><span class="line">    (k, v) -&gt; v &gt; <span class="number">1000</span> ? k + <span class="string">&quot;-&gt;&quot;</span> + v : <span class="literal">null</span>,</span><br><span class="line">    System.out::prinln);</span><br></pre></td></tr></table></figure></li><li><p><code>reduce</code>操作用一个累加函数组合输入, 比如计算所有值的总和</p><ul><li><code>Long sum = map.reduceValues(threshold, Long::sum);</code></li></ul></li><li><p>同样可以使用一个转换器函数, 比如计算最长的键的长度</p><ul><li><code>Integer mLen = map.reduceKeys(threshold, String::length, Integer::max);</code></li></ul></li><li><p><code>CopyOnWriteArrayList</code>和<code>CopyOnWriteArraySet</code>是线程安全的集合, 所有更改器会建立底层数组的副本</p><ul><li>如果迭代访问集合的线程数超过更改集合的线程数, 这个更改就很有用</li><li>构造一个迭代器, 包含对当前数组的引用, 如果这个数组后来被更改了, 迭代器仍然会引用原来的数组, 尽管集合的数组已经被替换了, 所以迭代器可以访问一致, 但是过时的视图, 并不存在同步开销</li></ul></li></ul><h3 id="并行数组算法">并行数组算法</h3><ul><li><p><code>Arrays</code>提供了大量的并行化操作, 比如<code>Arrays.parallelSort()</code>可以对一个基本类型或对象数组排序</p><ul><li>对对象数组排序, 可以提供一个<code>Comparator</code>数组 <code>Arrays.parallelSort(words, Comparator.comparing(String::length));</code></li></ul></li><li><p>任何集合类都可以使用同步包装器变成线程安全的</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">List&lt;E&gt; synchArrayList = Collections.synchronizedList(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;E&gt;());</span><br><span class="line">Map&lt;K, V&gt; synchHashMap = Collections.synchronizedMap(New HashMap&lt;K, V&gt;());</span><br></pre></td></tr></table></figure><ul><li>得到的集合方法会使用一个锁加以保护</li><li>如果希望迭代访问一个集合, 同时另一个线程仍然可能修改这个集合, 就要使用客户端锁定</li></ul>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(synchHashMap) &#123;</span><br><span class="line">    Iterator&lt;K&gt; iter = synchHashMap.keySet().iterator();</span><br><span class="line">    <span class="keyword">while</span>(iter.hasNext()) &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h2 id="任务和线程池">任务和线程池</h2><ul><li>如果程序中使用了大量生命周期很短的线程, 不能将每个任务映射到一个单独的线程, 而是应该使用一个线程池</li></ul><h3 id="Callable-Future"><code>Callable</code>, <code>Future</code></h3><ul><li><code>Runnable</code>封装了一个异步运行任务, 可以想象成一个没有参数和返回值的异步方法</li><li><code>Callable</code>与<code>Runnable</code>相似, 只是有返回值, <code>Callable</code>是一个参数化接口, 只有一个方法<code>call</code>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Callable</span>&lt;V&gt; &#123;</span><br><span class="line">    V <span class="title function_">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><code>Future</code>可以保存异步计算的结果, 可以启动一个计算, 将<code>Future</code>对象交给某个方法, 然后忘掉他</li><li>计算得到结果的时候, <code>Future</code>对象的所有者就会得到这个结果</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Future接口具有下面的方法</span></span><br><span class="line">V <span class="title function_">get</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// get()调用会阻塞, 直到计算完成</span></span><br><span class="line">V <span class="title function_">get</span><span class="params">(<span class="type">long</span> timeout, TimeUnit unit)</span>;</span><br><span class="line"><span class="comment">// 同样会阻塞, 不过如果超时了就会抛出一个TimeoutException</span></span><br><span class="line"><span class="comment">// 如果运行该计算的线程被中断, 两个方法都会抛出InterruptedException, 如果计算完成, 则get()立即返回</span></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">isDone</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 如果计算还在进行, 则isDone()返回false</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">cancel</span><span class="params">(<span class="type">boolean</span> mayInterrupt)</span>;</span><br><span class="line"><span class="comment">// cancel()取消计算, 如果计算还没有开始, 则永远不会开始了</span></span><br></pre></td></tr></table></figure><ul><li><p>取消一个任务涉及两个步骤, 找到并中断底层线程, <code>call()</code>方法中必须感知到中断, 并放弃工作</p></li><li><p>可以使用<code>FutureTask</code>执行<code>Callable</code>, 实现了<code>Future</code>和<code>Runnable</code>接口</p></li><li><p>也可以将一个<code>Callable</code>传递到执行器来执行</p></li><li><p><code>newCachedThreadPool()</code>构造一个线程池, 立即执行各种任务, 如果有空线程可以使用, 就使用空线程, 如果没有就创建</p></li><li><p><code>newFixedThreadPool()</code> 构造一个大小固定的线程池, 如果提交任务数大于空线程数, 没有得到服务的任务就放到队列中</p></li><li><p><code>newSingleThreadPool()</code> 退化的大小为1的线程池, 顺序执行所提交的任务</p></li><li><p>上述三个方法返回一个实现了<code>ExecutorService</code>接口的<code>ThreadPoolExecutor</code>类的对象</p></li><li><p>如果线程生存期很短, 或者大量时间都在阻塞, 可以使用一个缓存线程池</p></li><li><p>为了得到最优的运行速度, 并发线程数等于处理器内核个数, 这种情况应该使用固定线程池, 这样并发线程总数会有一个上限</p></li><li><p>单线程执行器对性能测试有帮助, 可以临时使用一个单线程池替换固定线程池, 测试不并发的情况下性能降低的量</p></li><li><p>使用线程池时所做的工作:</p><ul><li>调用<code>Executors</code>类的静态方法<code>newCachedTreadPool</code>或<code>newFixedThreadPool</code></li><li>调用<code>submit</code>提交<code>Runnable</code>或<code>Callable</code>对象</li><li>保留返回的<code>Future</code>对象, 以便得到结果或者取消任务</li><li>不想再提交任务时可以调用<code>shutdown</code></li></ul></li><li><p><code>invokeAny</code>提交一个<code>Callable</code>对象集合中的所有对象, 并返回某一个已经完成任务的结果, 不知道会返回哪个, 一般都是速度最快的</p></li><li><p>对于搜索问题, 可以使用这个方法</p></li><li><p><code>invokeAll</code>提交一个<code>Callable</code>对象集合中的所有对象, 方法阻塞, 直到所有任务都完成了, 并返回一个<code>Future</code>对象列表, 包含所有答案</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">List&lt;Callable&lt;T&gt;&gt; tasks = ...;</span><br><span class="line">List&lt;Future&lt;T&gt;&gt; res = executor.invokeAll(tasks);</span><br><span class="line"><span class="keyword">for</span> (Future&lt;T&gt; r: res) &#123;</span><br><span class="line">processFuture(r.get());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// get()方法会阻塞, 直到获得了一个结果</span></span><br></pre></td></tr></table></figure></li><li><p>可以使用<code>ExecutorCompletionService</code>管理, 将任务提交到这个完成服务中, 服务会管理一个<code>Future</code>对象的阻塞队列\</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExecutorCompletionService</span>&lt;T&gt;(executor);</span><br><span class="line"><span class="keyword">for</span> (Callable&lt;T&gt; task: tasks) service.submit(task);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tasks.size(); i ++) &#123;</span><br><span class="line">processFuture(service.task().get());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>有的应用使用大量线程, 但是大部分是空闲的</p><ul><li>比如服务器为每个连接使用一个线程;</li><li>或者处理器内核使用一个线程执行计算密集型任务, 比如图像或者视频处理</li><li><code>Java 7</code>引入了<code>fork-join</code>框架支持后一类应用</li><li>比如一个任务可以分解为两个子任务分别计算, 需要扩展<code>Recursive&lt;T&gt;</code>的类, 或者扩展<code>RecursiveAction</code>的类</li><li>前者生成一个<code>T</code>结果, 后者不生成结果, 再覆盖<code>compute()</code>生成并调用子任务, 合并结果</li></ul></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> <span class="keyword">extends</span> <span class="title class_">Recuresive</span>&lt;T&gt; &#123;</span><br><span class="line"><span class="keyword">protected</span> Integer <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (to - from &lt; THRESHOLD) &#123;</span><br><span class="line">...</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>后台中, <code>fork-join</code>框架使用了工作密取的启发式方法平衡可用线程的工作负载, 每个工作线程都有任务的一个双端队列<ul><li>一个工作线程将子任务压入双端队列的队头, 只有一个线程可以访问队头, 所以不需要加锁</li><li>一个工作线程空闲时, 会从另一个双端队列的队尾密取一个任务, 由于大的子任务都在队尾, 这种密取很少见</li><li><code>fork-join</code>是对非阻塞任务进行负载优化的, 对于阻塞任务就失效了, 需要使用<code>ForkJoinPool.ManagedBlocker</code>接口解决这个问题</li></ul></li></ul><h2 id="异步计算">异步计算</h2><h3 id="可完成Future">可完成<code>Future</code></h3><ul><li>如果有一个<code>Future</code>对象, 需要调用<code>get()</code>获得值, 方法会阻塞, 直到值可以使用</li><li><code>CompletableFuture</code>类实现了<code>Future</code>接口, 可以注册一个回调, 一旦结果可用, 就会在某个线程中利用该结果调用这个回调</li><li>采用这种方法, 一旦结果可用就可以对结果进行处理, 而不需要阻塞</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> CompletableFuture&lt;String&gt; <span class="title function_">readPage</span><span class="params">(Url url)</span> &#123;</span><br><span class="line"><span class="keyword">return</span> CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UncheckedIOException</span>(e);</span><br><span class="line">&#125;</span><br><span class="line">&#125;, executor);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果省略executor, 任务会在ForkJoinPool.commonPool()返回的执行器上运行</span></span><br><span class="line"><span class="comment">// supplyAsync()第一个参数是Supplier&lt;T&gt;, 而不是Callable&lt;T&gt;, 这两个接口都描述了无参并且返回类型为T的函数</span></span><br><span class="line"><span class="comment">// Supplier&lt;T&gt;允许抛出检查型异常, 但是Callable&lt;T&gt;不允许</span></span><br></pre></td></tr></table></figure><h2 id="进程">进程</h2><ul><li>有时候需要执行另一个程序, 可以使用<code>ProcessBuilder, Process</code>类<ul><li><code>Process</code>类在单一操作系统进程中执行一个命令</li><li><code>ProcessBuilder</code>类允许配置<code>Process</code>对象, 可以取代<code>Runtime.exec</code>调用</li></ul></li></ul><h3 id="创建进程">创建进程</h3><ul><li>指定需要执行的命令, 或者传入一个<code>List&lt;String&gt;</code><br><code>var builder = new ProcessBuilder(&quot;gcc&quot;, &quot;myapp.c&quot;);</code></li><li>可以使用<code>directory</code>改变工作目录<br><code>builder = builder.directory(path.toFile());</code></li><li>然后需要指定处理进程的标准输入, 输出, 错误流, 默认情况分别是一个管道  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">OutputStream</span> <span class="variable">processIn</span> <span class="operator">=</span> p.getOutputStream();</span><br><span class="line"><span class="type">InputStream</span> <span class="variable">processOut</span> <span class="operator">=</span> p.getInputStream();</span><br><span class="line"><span class="type">InputStream</span> <span class="variable">processErr</span> <span class="operator">=</span> p.getErrorStream();</span><br></pre></td></tr></table></figure></li><li>如果希望使用管道将一个进程的输出作为另一个进程的输入, 可以使用<code>Java 9</code>提供的<code>startpipeline()</code></li></ul><h3 id="进程句柄">进程句柄</h3><ul><li>可以用四种方法得到一个<code>ProcessHandler</code></li></ul><ol><li>给定一个<code>Process</code>对象<code>p</code>, <code>p.toHandler()</code>会生成他的<code>ProcessHandler</code></li><li>给定一个<code>Long</code>类型的进程<code>ID</code>, <code>ProcessHandler.of(ID)</code>可以生成这个进程的句柄</li><li><code>Process.current()</code>是运行这个<code>JVM</code>的进程句柄</li><li><code>ProcessHandler.allProcesses()</code>可以生成对当前进程可见的所有操作系统进程的<code>Stream&lt;ProcessHandler&gt;</code></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;并发&lt;/h1&gt;
&lt;h2 id=&quot;创建线程&quot;&gt;创建线程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;将线程任务放在一个类的&lt;code&gt;run()&lt;/code&gt;中, 这个类需要实现&lt;code&gt;Runnable&lt;/code&gt;接口&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highli</summary>
      
    
    
    
    <category term="Java" scheme="https://sangs3112.github.io/categories/Java/"/>
    
    
    <category term="Java" scheme="https://sangs3112.github.io/tags/Java/"/>
    
    <category term="Java核心技术(卷一)" scheme="https://sangs3112.github.io/tags/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF-%E5%8D%B7%E4%B8%80/"/>
    
  </entry>
  
  <entry>
    <title>Java笔记_12</title>
    <link href="https://sangs3112.github.io/posts/eac9e365.html"/>
    <id>https://sangs3112.github.io/posts/eac9e365.html</id>
    <published>2024-12-15T01:18:00.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>集合</h1><h2 id="Collection接口"><code>Collection</code>接口</h2><ul><li>集合类的基本接口是<code>Collection</code>接口, 有两个基本方法  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Collection</span>&lt;E&gt; &#123;</span><br><span class="line"><span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E element)</span>;</span><br><span class="line">Iterator&lt;E&gt; <span class="title function_">iterator</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Iterator</span>&lt;E&gt; &#123;</span><br><span class="line">E <span class="title function_">next</span><span class="params">()</span>;</span><br><span class="line"><span class="type">boolean</span> <span class="title function_">hasNext</span><span class="params">()</span>;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">remove</span><span class="params">()</span>;</span><br><span class="line"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">forEachRemaining</span><span class="params">(Consumer&lt;? <span class="built_in">super</span> E&gt; action)</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果到达末尾依然调用next(), 会抛出NoSuchElementException</span></span><br><span class="line"><span class="comment">// remove()会删除上一次调用next()返回的元素</span></span><br><span class="line"><span class="comment">// remove()和next()有依赖性, 如果不先调用next(), 会抛出IllegalStateException</span></span><br><span class="line"><span class="comment">// 如果需要删除两个元素</span></span><br><span class="line">it.next();</span><br><span class="line">it.remove();</span><br><span class="line">it.next();</span><br><span class="line">it.remove();</span><br></pre></td></tr></table></figure></li></ul><h3 id="LinkedList"><code>LinkedList</code></h3><ul><li>对于<code>LinkedList</code>, 还存在一个<code>previous()</code>, 支持从后往前遍历</li><li>如果调用了<code>previous()</code>, 则<code>remove</code>或<code>set</code>将修改右边的元素</li><li>如果有两个迭代器, 一个修改元素, 另一个在遍历元素, 就会产生错误, 抛出<code>ConcurrentModificationException</code><ul><li>这里的修改只有增加或删除<code>add(), remove()</code>, <code>set()</code>不会引发错误</li></ul></li><li><code>LinkedList</code>的<code>get()</code>方法不是随机访问, 因为是链表格式, 只是做了一点优化, 如果索引大于<code>n / 2</code>, 则从后往前调用<code>previous()</code>访问</li></ul><h3 id="ArrayList"><code>ArrayList</code></h3><ul><li>支持动态数组, 并且不是同步的</li><li><code>Vector</code>也是动态数组, 但是是同步的, 如果只有一个线程访问<code>Vector</code>, 会在同步操作上花费大量时间, 此时用<code>ArrayList</code>会比较好</li></ul><h3 id="散列表">散列表</h3><ul><li><code>Java</code>中散列表使用<code>LinkedList</code>实现, 每个列表称为一个桶</li><li>先计算一个对象的散列码, 然后与桶的个数取余, 得到的就是存储桶的索引</li><li><code>Java 8</code>中, 桶满了会从链表转换为平衡二叉树</li><li>散列表的键要尽可能属于实现了<code>Comparable</code>接口的类, 这样可以避免散列码分布不均的问题</li><li>桶的个数一般设计为预计元素个数的<code>75%~150%</code>, 默认桶个数是16个</li><li>默认装填因子是0.75, 也就是当哈希表中已经转满了75%就会触发再散列</li></ul><h3 id="TreeSet"><code>TreeSet</code></h3><ul><li><code>TreeSet</code>是一个有序集合, 使用红黑树实现</li><li>插入元素比普通散列表慢, 但是查找一个元素只需要<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>l</mi><mi>o</mi><msub><mi>g</mi><mn>2</mn></msub><mo stretchy="false">(</mo><mi>n</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">log_2(n)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mclose">)</span></span></span></span></li><li>因为是有序的, 所以其中的元素必须实现了<code>Comparable</code>接口</li></ul><h2 id="Map"><code>Map</code></h2><ul><li>同样分为<code>HashMap</code>, <code>TreeMap</code></li></ul><h3 id="更新">更新</h3><ul><li><code>counts.put(word, counts.get(word) + 1)</code>, 可能<code>word</code>本身不存在, 会抛出<code>NullPointerException</code></li><li><code>counts.put(word, counts.getOrDefault(word, 0) + 1);</code></li><li><code>counts.putIfAbsent(word, 0)</code>, 表示如果<code>word</code>不存在, 则赋值为0, 然后调用<code>put</code>即可正常更新, 避免<code>null</code></li><li>也可以使用<code>merge</code>简化操作, <code>counts.merge(word, 1, Integer::sum)</code><ul><li>如果<code>word</code>不存在, 就设置为1</li><li>如果存在, 就使用<code>Integer::sum</code>设置为<code>word</code>与1的求和</li></ul></li></ul><h2 id="视图">视图</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Set&lt;String&gt; keys = map.keySet();</span><br><span class="line"><span class="keyword">for</span> (String key : keys) &#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (Map.Entry&lt;String, E&gt; entry : map.entrySet()) &#123;</span><br><span class="line"><span class="type">String</span> <span class="variable">k</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line"><span class="type">E</span> <span class="variable">e</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> entry: map.entrySet) &#123;</span><br><span class="line">..</span><br><span class="line">&#125;</span><br><span class="line">map.forEach((k, v) -&gt; &#123;</span><br><span class="line">...</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ul><li>可以在视图上调用<code>remove()</code>删除散列表的元素, 但是不能添加元素</li><li><code>keySet</code>方法返回了一个<code>Set</code>接口的类对象</li></ul><h2 id="WeakHashMap"><code>WeakHashMap</code></h2><ul><li>如果一个<code>HashMap</code>中删除了元素, 但是因为<code>HashMap</code>还在使用, 所以<code>GC</code>不会释放这个元素的空间</li><li>因此使用<code>WeakHashMap</code>可以释放删除元素的空间, 使用弱引用保存键</li></ul><h2 id="LinkedHashSet-与-LinkedHashMap"><code>LinkedHashSet</code> 与 <code>LinkedHashMap</code></h2><ul><li>会记住插入元素的顺序, 在元素加入哈希表后, 会添加到<code>LinkedList</code>双向链表中</li></ul><h2 id="EnumSet"><code>EnumSet</code></h2><ul><li>使用位序列实现, 如果对应的值出现了, 则在相应的位置设为1</li><li>没有公共构造器, 要使用静态工厂方法构造  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Weekday</span> &#123;MONDAY, TUESDAY, ...&#125;;</span><br><span class="line">EnumSet&lt;Weekday&gt; always = EnumSet.allOf(Weekday.class);</span><br><span class="line">EnumSet&lt;Weekday&gt; never = EnumSet.noneOf(Weekday.class);</span><br></pre></td></tr></table></figure></li></ul><h2 id="标识散列映射">标识散列映射</h2><ul><li><code>IdentityHashMap</code>, 键的散列值不是通过<code>hashCode()</code>计算得到, 而是使用<code>System.indentityHashCode()</code>计算</li><li><code>Object.hashCode()</code>计算散列码就使用这个方法, 比较两个对象, <code>IdentityHashMap</code>使用<code>==</code>, 而不是<code>equals</code></li></ul><h2 id="小集合">小集合</h2><ul><li><p><code>Java 9</code>引入了一些静态方法, 可以生成给定元素的集合或列表, 以及给定的键值对</p><ul><li><code>List&lt;String&gt; names = List.of(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;);</code></li><li><code>Set&lt;Integer&gt; nums = Set.of(1, 2, 3);</code></li><li><code>Map&lt;String, Integer&gt; m = Map.of(&quot;1&quot;, 1, &quot;2&quot;, 2, &quot;3&quot;, 3);</code></li></ul></li><li><p><code>List, Set</code>有11个<code>of</code>方法, 还有一个参数可变的<code>of</code>方法, 这样是为了提高效率</p></li><li><p><code>Map</code>没有这个参数可变的版本, 因为参数类型会交替为键类型和值类型</p><ul><li>不过有一个<code>ofEntries</code>, 可以接受任意多个<code>Map.Entry&lt;K, V&gt;</code>对象<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Map&lt;Stirng, Integer&gt; socres = ofEntries(</span><br><span class="line">entry(<span class="string">&quot;1&quot;</span>, <span class="number">1</span>),</span><br><span class="line">entry(<span class="string">&quot;2&quot;</span>, <span class="number">2</span>),</span><br><span class="line">entry(<span class="string">&quot;3&quot;</span>, <span class="number">3</span>));</span><br></pre></td></tr></table></figure></li></ul></li><li><p>集合对象是不可修改的, 会导致<code>UnsupportedOperationException</code></p></li><li><p>如果需要一个可修改的集合, 可以把这个不可修改的集合传递到构造器中 <code>var names = new ArrayList&lt;&gt;(List.of(&quot;1&quot;, &quot;2&quot;));</code></p></li><li><p><code>Collections.nCopies(n, anObject)</code>会返回一个实现了<code>List</code>接口的不可变对象</p><ul><li><code>List&lt;String&gt; settings = Collections.nCopies(100, &quot;DEFAULT&quot;);</code></li><li>这样构造包含了100个<code>&quot;DEFAULT&quot;</code>的<code>List</code>, 存储开销很小, 对象只会存储一次</li></ul></li><li><p><code>Array.asList</code>会返回一个可以更改, 但是大小不可改变的列表, 即可以调用<code>set()</code>, 但是不能使用<code>add(), remove()</code></p></li></ul><h3 id="不可修改的副本和视图">不可修改的副本和视图</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ArrayList&lt;String&gt; names = ...</span><br><span class="line">Set&lt;String&gt; nameSet = Set.copyOf(names);</span><br><span class="line">List&lt;String&gt; nameList = List.copyOf(names);</span><br><span class="line"><span class="comment">// 每个copyOf()方法会建立集合的一个副本, 修改了原集合这个副本不会受到影响</span></span><br><span class="line"><span class="comment">// 如果原集合正好不可修改, 则copyOf会直接返回原集合</span></span><br></pre></td></tr></table></figure><h2 id="同步视图">同步视图</h2><ul><li><p>如果多个线程访问集合, 一个在修改, 另一个在查看, 结果会发生错误</p></li><li><p><code>synchronizedMap()</code>可以将任何一个映射转换成有同步访问方法的<code>Map</code></p><ul><li><code>var map = Collections.synchronizedMap(new HashMap&lt;String, Integer&gt;());</code></li><li>这样, <code>get(), put()</code>方法就是同步的, 只有一个执行完了才会调用另一个</li></ul></li><li><p>对链表排序可以使用归并排序, 但是<code>Java</code>中是先将链表中的数据复制到数组中, 然后在数组中排序完以后再复制回链表</p></li><li><p><code>Collections.sort()</code>可以对<code>List</code>数据进行排序</p></li><li><p><code>Collections.binarySearch</code>可以对实现了<code>List</code>的接口的列表去进行二分搜索</p><ul><li>如果集合没有采用<code>Comparable</code>接口的<code>compareTo</code>方法进行排序, 则还需要提供一个比较器对象</li><li>如果为<code>binarySearch()</code>提供一个链表, 则会退化为线性查找</li><li><code>binarySearch()</code>返回值<code>ret</code>如果小于零, 则<code>-ret - 1</code>的位置就是这个元素应该插入的位置</li></ul></li></ul><h2 id="批操作">批操作</h2><ul><li><p><code>coll1.removeAll(coll2)</code>, 在<code>coll1</code>中删除所有<code>coll2</code>包含的元素 (补)</p></li><li><p><code>coll1.retainAll(coll2)</code>, 在<code>coll1</code>中删除所有<code>coll2</code>中没有包含的元素 (交)</p></li><li><p>数组转集合</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">String[] names = ...;</span><br><span class="line">List&lt;String&gt; staff = List.of(names);</span><br></pre></td></tr></table></figure></li><li><p>集合转数组</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Object[] names = staff.toArray();</span><br><span class="line"><span class="comment">// 得到一个Object[]数组, 无法进行类型转换</span></span><br><span class="line">String[] names = (String[]) staff.toArray(); <span class="comment">// Error</span></span><br><span class="line"><span class="comment">// 需要向toArray传入一个数组构造器表达式</span></span><br><span class="line">String[] names = staff.toArray(String[]::<span class="keyword">new</span>);</span><br></pre></td></tr></table></figure></li></ul><h2 id="遗留">遗留</h2><ul><li><p><code>Hashtable</code>类和<code>HashMap</code>类作用相同, 但是<code>HashTable</code>是同步的, 不需要兼容性应该使用<code>HashMap</code>, 需要并发应该使用<code>ConcurrentHashMap</code></p></li><li><p><code>Enumeration</code>有两个方法<code>hasMoreElements(), nextElements()</code>, 可以使用<code>Collections.list</code>将元素收集到一个<code>ArrayList</code></p></li><li><p>属性映射的特性</p><ul><li>键值都是字符串</li><li>映射可以保存到文件并从文件加载</li><li>有一个二级表存放默认值</li></ul></li><li><p>实现属性映射的<code>Java</code>平台类名为<code>Properties</code>, 对于配置项很有用</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">settings</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">settings.setProperty(<span class="string">&quot;1&quot;</span>, <span class="number">1</span>);</span><br><span class="line">settings.setProperty(<span class="string">&quot;filename&quot;</span>, <span class="string">&quot;/home);</span></span><br></pre></td></tr></table></figure></li><li><p>使用<code>store()</code>将属性映射表保存到一个文件中, 比如<code>program.properties</code></p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">out</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileWriter</span>(<span class="string">&quot;program.properties&quot;</span>, StandardCharsets.UTF_8);</span><br><span class="line">settings.store(out, <span class="string">&quot;Program Properties&quot;</span>);</span><br><span class="line"><span class="type">var</span> <span class="variable">in</span>  <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileReader</span>(<span class="string">&quot;program.properties&quot;</span>, StandardCharsets.UTF_8);</span><br><span class="line">settings.load(in);</span><br><span class="line"><span class="type">String</span> <span class="variable">userDir</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;user.home&quot;</span>);</span><br></pre></td></tr></table></figure></li><li><p><code>Properties</code>因为历史原因实现了<code>Map&lt;&gt;</code>, 因此可以使用<code>get(), put()</code>, 但是<code>get()</code>返回类型是<code>Object</code>, <code>put()</code>允许插入任意对象</p><ul><li>所以最好使用处理字符串的<code>getProperty(), setProperty()</code></li></ul></li><li><p>栈, <code>Stack</code>, 扩展的<code>Vector</code>, 但是可以使用<code>insert(), remove()</code>添加删除任意的位置</p></li><li><p>位集, <code>BitSet</code>, 如果需要存储一个位序列, 比如标志, 可以使用位集</p><ul><li>因为位集将位包装在字节里, 所以使用位集比使用<code>Boolean, ArrayList</code>高效</li><li><code>bitSet.get(i)</code>会返回第<code>i</code>位的结果, <code>true, false</code></li><li><code>bitSet.set(i)</code>将第<code>i</code>位的结果设置为<code>true</code></li><li><code>bitSet.clear(i)</code>将第<code>i</code>位的结果设置为<code>false</code></li></ul></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;集合&lt;/h1&gt;
&lt;h2 id=&quot;Collection接口&quot;&gt;&lt;code&gt;Collection&lt;/code&gt;接口&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;集合类的基本接口是&lt;code&gt;Collection&lt;/code&gt;接口, 有两个基本方法  &lt;figure class=&quot;highli</summary>
      
    
    
    
    <category term="Java" scheme="https://sangs3112.github.io/categories/Java/"/>
    
    
    <category term="Java" scheme="https://sangs3112.github.io/tags/Java/"/>
    
    <category term="Java核心技术(卷一)" scheme="https://sangs3112.github.io/tags/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF-%E5%8D%B7%E4%B8%80/"/>
    
  </entry>
  
  <entry>
    <title>Java笔记_10</title>
    <link href="https://sangs3112.github.io/posts/4c78249.html"/>
    <id>https://sangs3112.github.io/posts/4c78249.html</id>
    <published>2024-12-12T01:18:00.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>异常</h1><ul><li>所有的异常都是由<code>Throwable</code>继承而来, 下一层有两个分支, <code>Error</code>和<code>Exception</code><ul><li><code>Error</code>类描述了<code>Java</code>运行时系统的内部错误和资源耗尽问题, 一般不要抛出这个类型</li><li><code>Exception</code>层次分为两个分支, <code>RuntimeException</code>和其他异常<ul><li>编程错误导致的是<code>RuntimeException</code></li><li>由于<code>IO</code>错误导致的是其他异常</li><li>继承自<code>RuntimeException</code>异常包括: 错误的强制类型转换; 数组越界; 访问<code>null</code>指针</li><li>不继承自<code>RuntimeException</code>异常包括: 打开不存在的文件; 越过文件末尾读取数据; 根据给定字符串查找<code>Class</code>, 但是这个类并不存在</li></ul></li><li>所有派生于<code>Error</code>和<code>RuntimeException</code>的异常是非检查型异常, 其他异常都是检查型异常</li></ul></li></ul><h2 id="抛出异常的情况">抛出异常的情况</h2><ol><li>调用了某个会抛出异常的方法, 比如<code>FileInputStream</code>构造器</li><li>检测到一个错误, 使用<code>throw</code>语句抛出异常</li><li>程序出现错误, 比如<code>a[-1]</code>抛出一个非检查型异常</li><li><code>JVM</code>内部错误</li></ol><ul><li>一个方法必须声明所有可能抛出的检查型异常, 也可以捕获一个异常, 这样也不需要抛出了</li><li>如果子类覆盖了超类的一个方法, 子类抛出的异常不能比超类的更通用, 如果超类没有抛出异常, 则子类也不能抛出异常</li></ul><h2 id="创建异常">创建异常</h2><ul><li>定义一个派生于<code>Exception</code>或者他子类的一个类, 包含两个构造器, 一个是无参构造器, 另一个是包含详细信息的构造器  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FileFormatException</span> <span class="keyword">extends</span> <span class="title class_">IOException</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FileFormatException</span><span class="params">()</span>&#123;&#125;;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FileFormatException</span><span class="params">(String gripe)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(gripe);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h2 id="捕获异常">捕获异常</h2><ul><li>如果<code>try</code>语句块中任何代码抛出了<code>catch</code>指定的一个异常类<ol><li>跳过<code>try</code>语句块剩余执行内容</li><li>执行<code>catch</code>语句块代码</li></ol></li><li>如果没有抛出异常, 则直接跳过<code>catch</code>部分</li><li>如果抛出了异常, 但是不在<code>catch</code>中, 则方法会直接退出</li><li>一般是捕获知道如何处理的异常, 抛出不知道如何处理的异常</li><li>一个<code>try</code>语句块可能抛出多种不同的异常, 每个异常需要一个<code>catch</code>语句块<ul><li>如果两个异常的捕获动作一样的话, 可以使用<code>catch(Exception e1 | Exception e2)</code>合并</li><li>捕获多个变量时, 异常变量银行了<code>final</code></li></ul></li></ul><h2 id="再次抛出">再次抛出</h2><ul><li>有时候只想记录一个异常, 再次抛出  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    logger.log(level, message, e);</span><br><span class="line">    <span class="keyword">throw</span> e;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果这段代码存在于 public void update() throws SQLException中</span></span><br><span class="line"><span class="comment">// 在Java 7之前会报错, 因为 throw e可能抛出其他类型的异常, 而不是SQLException</span></span><br><span class="line"><span class="comment">// 现在改变了, 编译器会跟踪到e来自try代码块, try代码块中仅有的检查型异常是SQLException实例, 并且</span></span><br><span class="line"><span class="comment">// e在catch块中没有改变, 那么外围方法声明为throws SQLException就是合法的</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="finally子句"><code>finally</code>子句</h2><ul><li><p>代码抛出异常, 剩下的代码就不会运行, 如果这时候已经获取到了一些资源, 在退出之前需要释放.</p><ul><li>可以先捕获所有异常, 然后释放资源, 再重新抛出异常</li><li>也可以使用<code>finally</code>子句, 无论是否抛出异常, <code>finally</code>子句部分一定都会执行</li><li><code>Java 7</code>之后可以使用<code>try-with-resources</code>, 这个更常用</li></ul></li><li><p><code>try</code>语句可以只有<code>finally</code>, 没有<code>catch</code></p></li><li><p><code>finally</code>语句中不要放<code>throw, continue, break, return</code>这种改变程序执行顺序的语句</p></li></ul><h2 id="try-with-resources"><code>try-with-resources</code></h2><ul><li><code>AutoCloseable</code>接口有一个方法 <code>void close() throwa Exception</code></li><li><code>Closeable</code>接口是<code>AutoCloseable</code>接口的子接口, 同样只包含<code>close()</code>, 但是抛出的是<code>IOException</code></li><li><code>try-with-resources</code>语句的最简单形式是:  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> (<span class="type">Resources</span> <span class="variable">res</span> <span class="operator">=</span> ...) &#123;</span><br><span class="line">    work with res</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 这里try块退出时, 会自动调用res.close()</span></span><br><span class="line"><span class="keyword">try</span> (<span class="type">var</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(Path.of(<span class="string">&quot;1.txt&quot;</span>), StandardCharsets.UTF_8);</span><br><span class="line">    <span class="type">var</span> <span class="variable">out</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PrintWriter</span>(<span class="string">&quot;2.txt&quot;</span>, StandardCharsets.UTF_8)) &#123;</span><br><span class="line">    <span class="keyword">while</span> (in.hasNext()) &#123;</span><br><span class="line">        out.println(in.next().toUpperCase());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 上述代码不论是如何退出的, 都一定会自动关闭in和out</span></span><br></pre></td></tr></table></figure></li><li>如果是<code>try-catch-finally</code>语句, 在<code>try</code>中抛出了异常, 然后<code>finally</code>调用<code>in.close()</code>又抛出了异常就会产生问题<ul><li>此时使用<code>try-with-resources</code>就可以解决这个问题</li><li>原来的异常会重新抛出, <code>close()</code>产生的异常会被抑制, 自动捕获, 由<code>addSuppressed()</code> 添加到原来的异常方法中</li><li>可以调用<code>getSuppressed()</code>, 会生成一个数组, 包含其中从<code>close()</code>方法中抛出的被抑制的异常</li></ul></li><li><code>try-with-resources</code>语句本身可以有<code>catch, finally</code>语句, 这些子句会在关闭资源以后才执行</li></ul><h2 id="栈轨迹">栈轨迹</h2><ul><li>栈轨迹是程序执行中某个特定点所有挂起的方法调用的一个列表</li><li><code>Throwable</code>类的<code>printStackTrace()</code>可以打印  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Throwable</span>();</span><br><span class="line"><span class="type">var</span> <span class="variable">out</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringWriter</span>();</span><br><span class="line">t.printStackTrace(<span class="keyword">new</span> <span class="title class_">PrintWriter</span>(out));</span><br><span class="line"><span class="type">String</span> <span class="variable">des</span> <span class="operator">=</span> out.toString();</span><br><span class="line"><span class="comment">// Java 9之前, Throwable.printStackTrace()会生成一个StackTraceElement[]</span></span><br><span class="line"><span class="comment">// 数组中包含了和StackWalker.StackFrame类似的信息, 效率较低</span></span><br><span class="line"><span class="comment">// 因为会得到整个栈, 但是调用者只需要几个栈帧, 并且只允许访问挂起方法的类名, 不能访问类对象</span></span><br></pre></td></tr></table></figure></li><li>还可以使用<code>StackWalker</code>类, 生成一个<code>StackWalker.StackFrame</code>实例流, 其中每个实例表示一个栈帧  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">StackWalker</span> <span class="variable">walker</span> <span class="operator">=</span> StackWalker.getInstance();</span><br><span class="line">walker.forEach(frame -&gt; ananlyze frame)</span><br></pre></td></tr></table></figure></li></ul><div class="note info flat"><ul><li>栈轨迹一般显示在<code>System.err</code>上, 如果想要记录栈轨迹, 可以捕获到字符串中</li><li>也可以记录到文件中, 不过如果是错误的话, 就会发送到<code>System.err</code>中, 所以就不能使用下面的代码<ul><li><code>java MyApp &gt; errors.txt</code>, 而是应该使用 <code>java MyApp 2&gt; errors.txt</code></li><li>如果需要在同一个文件中同时保存<code>System.out, System.err</code>可以使用如下代码</li><li><code>java MyApp 1&gt; errors.txt 2&gt;&amp;1</code></li></ul></li><li>可以使用静态方法<code>Thread.setDefaultUncaughtExceptionHandler</code>改变没有捕获异常的处理器</li><li>启动<code>JVM</code>可以使用<code>-verbose</code>看到类加载器加载过程</li><li><code>Xlint</code>选项可以告诉编译器找出常见的代码问题, 比如<code>javac -Xlint sourceFiles</code></li><li><code>JDK</code>提供了<code>jconsole</code>可以显示<code>JVM</code>性能统计结果</li></ul></div><h2 id="使用异常技巧">使用异常技巧</h2><ol><li>异常不能代替简单测试, 使用捕获异常会导致程序耗时大大增加, 因此只在异常情况下使用异常</li><li>不要过分细化异常, 否则一个异常一个<code>catch</code>会导致代码量激增</li><li>合理使用异常层次<ul><li>不要只抛出<code>RuntimeException</code>, 应该需要寻找一个合适的子类, 或者创建自己的异常类</li><li>不要只捕获<code>Throwable</code>异常, 否则代码会很难读懂</li><li>考虑检查型异常和非检查型异常, 检查型异常本质上开销较大</li></ul></li><li>不要压制异常, 可以使用</li><li>使用标准方法报告<code>null</code>指针和越界异常</li><li>不要向用户展示最终的栈轨迹</li></ol><h1>断言</h1><ul><li>断言允许在测试期间在代码中插入一些检查, 在生产代码中自动删除这些  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">assert</span> condition;</span><br><span class="line"><span class="keyword">assert</span> condition : expression;</span><br><span class="line"><span class="comment">// 两个写法都会计算condition, 如果为false, 会抛出AssertionError异常</span></span><br><span class="line"><span class="comment">// 第二个语句会将expression传入到AssertionError构造器中, 转换为一个消息字符串</span></span><br></pre></td></tr></table></figure></li><li>默认情况下禁用断言, 运行是使用<code>java -enableassertions MyApp</code> 或者<code>java -ea MyApp</code>启用</li><li>不需要重新编译启用断言, 因为断言是类加载器的功能, 禁用断言的时候类加载器会自动删去断言的代码, 不会降低速度</li><li>可以在特定的类或整个包中打开断言<code>java -ea:MyClass -ea:com.mycompany.mylib MyApp</code><ul><li>这样会在<code>MyClass</code>类, <code>com.mycompany.mylib</code>包及其子包中的所有类打开断言</li><li>同样可以使用<code>-da</code> 或者<code>-disableassertions</code>禁用断言</li><li><code>-ea</code>和<code>-da</code>不能应用于没有类加载器的系统类, 需要使用<code>-esa</code>或者<code>enablesystemassertions</code>开启系统类断言</li></ul></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;异常&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;所有的异常都是由&lt;code&gt;Throwable&lt;/code&gt;继承而来, 下一层有两个分支, &lt;code&gt;Error&lt;/code&gt;和&lt;code&gt;Exception&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Error&lt;/code&gt;类描述了</summary>
      
    
    
    
    <category term="Java" scheme="https://sangs3112.github.io/categories/Java/"/>
    
    
    <category term="Java" scheme="https://sangs3112.github.io/tags/Java/"/>
    
    <category term="Java核心技术(卷一)" scheme="https://sangs3112.github.io/tags/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF-%E5%8D%B7%E4%B8%80/"/>
    
  </entry>
  
  <entry>
    <title>Java笔记_11</title>
    <link href="https://sangs3112.github.io/posts/73c0b2df.html"/>
    <id>https://sangs3112.github.io/posts/73c0b2df.html</id>
    <published>2024-12-12T01:18:00.000Z</published>
    <updated>2025-12-30T05:59:16.708Z</updated>
    
    <content type="html"><![CDATA[<h1>泛型</h1><ul><li>没有泛型类的时候, 泛型程序用继承实现, <code>ArrayList</code>类只维护一个<code>Object</code>引用数组, 这样就会每次都需要进行强制类型转换</li><li>现在是使用尖括号<code>&lt;String&gt;</code></li><li><code>Java</code>类库使用<code>E</code>表示集合类型, <code>K,V</code>表示键值对, <code>T,U</code>等表示任意类型</li></ul><h2 id="泛型类">泛型类</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Pair</span>&lt;T&gt; &#123;</span><br><span class="line">    T first;</span><br><span class="line">    T second;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Pair</span><span class="params">()</span> &#123; </span><br><span class="line">        first = <span class="literal">null</span>;</span><br><span class="line">        second = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Pair</span><span class="params">(T first, T second)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.first = first;</span><br><span class="line">        <span class="built_in">this</span>.second = second;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">getFirst</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> first;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">getSecond</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> second;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setFirst</span><span class="params">(T newV)</span> &#123;</span><br><span class="line">        first = newV;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSecond</span><span class="params">(T newV)</span> &#123;</span><br><span class="line">        second = newV;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="泛型方法">泛型方法</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; T <span class="title function_">getMiddle</span><span class="params">(T...a)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> a[a.length / <span class="number">2</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="泛型代码和虚拟机">泛型代码和虚拟机</h2><ul><li><p>无论何时定义一个泛型类, 都会自动提供一个相应的原始类型, 就是去掉类型参数以后的泛型类型名</p></li><li><p>类型变量会被擦除, 替换为其限定类型, 如果没有限定类型就是<code>Object</code></p></li><li><p>泛型方法的类型擦除会存在一些问题:</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">D</span> <span class="keyword">extends</span> <span class="title class_">Pair</span>&lt;LocalDate&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSecond</span><span class="params">(LocalDate second)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (second.compareTo(getFirst()) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">super</span>.setSecond(second);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 日期区间是一对LocalDate对象, 覆盖这个方法保证第二个值永远不小于第一个值, 类擦除以后就是</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">D</span> <span class="keyword">extends</span> <span class="title class_">Pair</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSecond</span><span class="params">(LocalDate second)</span> &#123;...&#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 但是还有另一个从Pair继承的setSecond方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSecond</span><span class="params">(Object second)</span> &#123;&#125;</span><br><span class="line"><span class="comment">// 这两个方法参数不同, 所以是不同的方法, 但是他们不应该不同</span></span><br><span class="line"><span class="comment">// 此时类型擦除和多态就产生了冲突, 编译器就会在D类中生成一个桥方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSecond</span><span class="params">(Object second)</span> &#123;setSecond((LocalDate) second);&#125;</span><br></pre></td></tr></table></figure>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 同样, 在编译器中, 如果是getSecond(), 会同时存在两个</span></span><br><span class="line"><span class="keyword">public</span> LocalDate <span class="title function_">getSecond</span><span class="params">()</span>;</span><br><span class="line"><span class="keyword">public</span> Object <span class="title function_">getSecond</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 这两个方法只有返回类型不同, 不能这么编写代码</span></span><br><span class="line"><span class="comment">// 但是虚拟机会根据参数类型和返回类型共同指定一个方法, 所以虚拟机可以正确处理这两个方法</span></span><br><span class="line"><span class="comment">// 所以编译器可以为只有返回类型不同的两个方法生成字节码文件</span></span><br><span class="line"><span class="comment">// 只是自己编写这样的代码不合法</span></span><br></pre></td></tr></table></figure></li></ul><div class="note info flat"><ul><li>虚拟机中没有泛型, 只有普通的类和方法</li><li>所有的类型参数都会替换为他们的限定类型</li><li>会合成桥方法来保持多态</li><li>会插入强制类型转换来保持类型安全性</li></ul></div><h2 id="限制">限制</h2><ol><li>不能使用基本类型实例化类型参数<ul><li>不能使用<code>Pair&lt;double&gt;</code>, 因为在类型擦除后的<code>Object</code>类中不能存储基本类型, 只能存储<code>Double</code></li></ul></li><li>运行时类型查询只适用于原始类型 <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (a <span class="keyword">instanceof</span> Pair&lt;String&gt;) <span class="comment">// Error 实际上只会检测a是否是Pair的任意一个类型实例</span></span><br><span class="line"><span class="keyword">if</span> (a <span class="keyword">instanceof</span> Pair&lt;T&gt;) <span class="comment">// Error</span></span><br><span class="line">Pair&lt;String&gt; p = (Pair&lt;String&gt;) a; <span class="comment">// Warn</span></span><br><span class="line"></span><br><span class="line">Pair&lt;String&gt; stringp = ...;</span><br><span class="line">Pair&lt;E&gt; ep = ...;</span><br><span class="line"><span class="keyword">if</span> (stringp.getClass() == ep.getClass()) &#123;&#125;</span><br><span class="line"><span class="comment">// 这里一定相等, 因为两个getClass()返回值都是Pair.Class</span></span><br></pre></td></tr></table></figure></li><li>不能创建参数化类型的数组 <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">table</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Pair</span>&lt;String&gt;[<span class="number">10</span>]; <span class="comment">// Error</span></span><br><span class="line"><span class="comment">// 因为类型擦除以后, table的类型是Object[], 存储不正确的元素会抛出ArrayStoreException</span></span><br><span class="line"><span class="comment">// 只是不允许创建, 但是声明依旧合法 即Pair&lt;String&gt;[]变量是合法的</span></span><br><span class="line"><span class="comment">// 但是new Pair&lt;String&gt;[10]初始化会抛出异常</span></span><br><span class="line"><span class="comment">// 可以声明通配符类型数组, 然后进行强制类型转换</span></span><br><span class="line"><span class="type">var</span> <span class="variable">table</span> <span class="operator">=</span> (Pair&lt;String&gt;[]) <span class="keyword">new</span> <span class="title class_">Pair</span>&lt;?&gt;[<span class="number">10</span>];</span><br><span class="line"><span class="comment">// 结果不安全, 比如table[0]存储一个Pair&lt;E&gt;, 然后table[0].getFirst()调用一个String方法, 就会抛出ClassCastException</span></span><br><span class="line"><span class="comment">// 可以直接使用ArrayList: ArrayList&lt;Pair&lt;String&gt;&gt;</span></span><br></pre></td></tr></table></figure></li></ol><h2 id="Varargs警告"><code>Varargs</code>警告</h2><ul><li>如果向可变参数的方法传递一个泛型类型实例</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">addAll</span><span class="params">(Collection&lt;T&gt; coll, T... ts)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (T t : ts) &#123;</span><br><span class="line">        coll.add(t);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ts是一个数组, 包含所有提供的实参</span></span><br><span class="line">Collection&lt;Pair&lt;String&gt;&gt; table = ...</span><br><span class="line">Pair&lt;String&gt; p1 = ...</span><br><span class="line">Pair&lt;String&gt; p2 = ...</span><br><span class="line">addAll(table, p1, p2);</span><br><span class="line"><span class="comment">// 此时JVM必须创建一个Pair&lt;String&gt;类型数组, 违反规则</span></span><br><span class="line"><span class="comment">// 但是有所放松, 只会给出警告</span></span><br><span class="line"><span class="comment">// 使用@SuppressWarnings(&quot;unchecked&quot;) 去掉警告</span></span><br><span class="line"><span class="comment">// 或者Java 7还可以使用@SafeVarargs注解addAll方法</span></span><br></pre></td></tr></table></figure><div class="note info flat"><ul><li><code>@SafeVarargs</code>注解只能用于<code>static, final</code>或<code>private</code>的构造器或方法中, 其他方法都可能被覆盖, 会使这注解失去作用</li></ul></div><h2 id="不能实例化类型变量">不能实例化类型变量</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Pair</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> T first;</span><br><span class="line">    <span class="keyword">private</span> T second;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Pair</span><span class="params">()</span> &#123;</span><br><span class="line">        first = <span class="keyword">new</span> <span class="title class_">T</span>();</span><br><span class="line">        second = <span class="keyword">new</span> <span class="title class_">T</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 这个无参构造函数是不合法的, 因为类型擦除以后会变成new Object()</span></span><br><span class="line"><span class="comment">// Java 8之后, 可以使用构造器表达式解决这个问题</span></span><br><span class="line">Pair&lt;String&gt; p = Pair.makePair(String::<span class="keyword">new</span>);</span><br><span class="line"><span class="comment">// makePair()接收一个Supplier&lt;T&gt;, 一个函数式接口, 表示一个无参并且返回类型为T的函数</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Pair&lt;T&gt; <span class="title function_">makePair</span><span class="params">(Supplier&lt;T&gt; constr)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Pair</span>&lt;&gt;(constr.get, constr.get());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 传统解决方法是调用反射Constructor.newInstance()构造泛型对象</span></span><br><span class="line">first = T.class.getConstructor().newInstance(); <span class="comment">// Error</span></span><br><span class="line"><span class="comment">// 因为T.class不合法, 会类型擦除为Object.class, 所以应该用如下方法:</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Pair&lt;T&gt; <span class="title function_">makePair</span><span class="params">(Class&lt;T&gt; cl)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Pair</span>&lt;&gt;(cl.getConstructor.newInstance(), cl.getConstructor().newInstance());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">Pair&lt;String&gt; p = Pair.makePair(String.class);</span><br><span class="line"><span class="comment">// Class 类本身是泛型的, String.class 是Class&lt;String&gt;的唯一实例, 所以makePair()可以推断pair的类型</span></span><br></pre></td></tr></table></figure><h2 id="不能构造泛型数组">不能构造泛型数组</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&gt; T[] minmax(T... a) &#123;</span><br><span class="line">    T[] mm = <span class="keyword">new</span> <span class="title class_">T</span>[<span class="number">2</span>]; <span class="comment">// Error</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 类型擦除, 所以总会构造一个Comparable[2]数组</span></span><br><span class="line"><span class="comment">// 如果数组只会作为类的私有实例字段, 可以将这个数组元素类型声明为擦除以后的类型, 再强制类型转换, 比如ArrayList</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArrayList</span>&lt;E&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> Object[] elements;</span><br><span class="line">    ...</span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span> </span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">get</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (E) elements[n];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(<span class="type">int</span> n, E e)</span> &#123;</span><br><span class="line">        elements[n] = e;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 但是minmax返回一个T[], 就无法使用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&gt; minmax(T... a) &#123;</span><br><span class="line">    <span class="type">var</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Comparable</span>[<span class="number">2</span>];</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> (T[]) result; <span class="comment">// Warnings</span></span><br><span class="line">&#125; </span><br><span class="line">String[] names = A.minmax(<span class="string">&quot;T&quot;</span>, <span class="string">&quot;A&quot;</span>, <span class="string">&quot;N&quot;</span>);</span><br><span class="line"><span class="comment">// 编译器不会有警告, 但是Comparable[]转换为String[]会出现ClassCastException</span></span><br><span class="line"><span class="comment">// 所以最好提供一个构造器表达式</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&gt; T[] minmax(IntFunction&lt;T[]&gt; constr, T... a) &#123;</span><br><span class="line">    T[] result = constr.apply(<span class="number">2</span>);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line">String[] names = A.minmax(String::<span class="keyword">new</span>, <span class="string">&quot;T&quot;</span>, <span class="string">&quot;A&quot;</span>, <span class="string">&quot;N&quot;</span>);</span><br><span class="line"><span class="comment">// 或者利用反射</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&gt; T[] minmax(T... a) &#123;</span><br><span class="line">    <span class="type">var</span> <span class="variable">result</span> <span class="operator">=</span> (T[]) Array.newInstance(a.getClass().getComponentType(), <span class="number">2</span>);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="泛型类的静态上下文中类型变量无效">泛型类的静态上下文中类型变量无效</h2><ul><li>不能在静态方法或字段中引用类型变量</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">S</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> T si; <span class="comment">// Error</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> T <span class="title function_">getSi</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (si == <span class="literal">null</span>) construct <span class="keyword">new</span> <span class="title class_">instanceof</span> T;</span><br><span class="line">        <span class="keyword">return</span> si;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 类型擦除以后, 只剩下S类, 只包含一个si字段, 所以非法</span></span><br></pre></td></tr></table></figure><h2 id="不能抛出或捕获泛型类的实例">不能抛出或捕获泛型类的实例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不能扩展Exception</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Problem</span>&lt;T&gt; <span class="keyword">extends</span> <span class="title class_">Exception</span> &#123;...&#125; <span class="comment">// Error </span></span><br><span class="line"><span class="comment">// catch 子句不能使用类型变量</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">doWork</span><span class="params">(Class&lt;T&gt; t)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;...&#125;</span><br><span class="line">    <span class="keyword">catch</span> (T e) &#123; <span class="comment">// Error</span></span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 但是在异常规范中允许使用类型变量</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">doWork</span><span class="params">(T t)</span> <span class="keyword">throws</span> T &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;...&#125;</span><br><span class="line">    <span class="keyword">catch</span> (Throwable realCause) &#123;</span><br><span class="line">        t.initCause(realCause);</span><br><span class="line">        <span class="keyword">throw</span> t;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="可以取消对检查型异常的检查">可以取消对检查型异常的检查</h2><ul><li>正常必须对所有检查型异常提供一个处理器, 但是可以利用泛型取消这个检查</li><li>只需要使用泛型类, 类型擦除和<code>@SuppressWarnings(&quot;unchecked&quot;)</code></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line"><span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">throwAs</span><span class="params">(Throwable t)</span> <span class="keyword">throws</span> T &#123;</span><br><span class="line">    <span class="keyword">throw</span> (T) t;</span><br><span class="line">&#125;</span><br><span class="line">Task.&lt;RuntimeException&gt;throwAs(e);</span><br><span class="line"><span class="comment">// 编译器就会认为e是一个非检查型异常</span></span><br><span class="line"><span class="comment">// 下面的代码会将所有的异常都转换为非检查型异常</span></span><br><span class="line"><span class="keyword">try</span> &#123;...&#125; </span><br><span class="line"><span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">    Task.&lt;RuntimeException&gt;throwAs(t);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="擦除以后可能会存在冲突">擦除以后可能会存在冲突</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Pair</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(T value)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> first.equals(value) &amp;&amp; second.equals(value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 实际上Pair有两个equals方法, boolean equals(String); boolean equals(Object);</span></span><br><span class="line"><span class="comment">// 只能重新命名这个方法</span></span><br></pre></td></tr></table></figure><ul><li>假如两个接口类型是同一个接口的不同参数化, 一个类或者类型变量就不能同时作为这两个接口类型的子类</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">E</span> <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;E&gt; &#123;...&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">M</span> <span class="keyword">extends</span> <span class="title class_">E</span> <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;M&gt; &#123;...&#125; <span class="comment">// Error</span></span><br><span class="line"><span class="comment">// 如果上述代码可行, 则M就会同时实现Comparable&lt;E&gt; 和Comparable&lt;M&gt;</span></span><br></pre></td></tr></table></figure><h2 id="泛型继承规则">泛型继承规则</h2><ul><li>如果<code>M</code>是<code>E</code>的子类, <code>Pair&lt;M&gt;</code>不会是<code>Pair&lt;E&gt;</code>的子类</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Pair&lt;E&gt; b = <span class="keyword">new</span> <span class="title class_">Pair</span>&lt;M&gt;(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">// 非法</span></span><br></pre></td></tr></table></figure><ul><li>可以将参数化类型转换为一个原始类型</li></ul><h2 id="通配符类型">通配符类型</h2><ul><li><p><code>Pair&lt;? extends E&gt;</code>允许类型参数变化, 表示任何<code>Pair</code>类型, 只要是<code>E</code>的子类</p></li><li><p><code>Pair&lt;M&gt;</code>是<code>Pair&lt;? extends E&gt;</code>的子类型</p></li><li><p>可以使用<code>? super M</code>指定一个超类型限定, 限制为<code>M</code>的所有超类型</p></li><li><p>带超类限定的通配符允许写入一个泛型对象, 带子类限定的通配符允许读取一个泛型对象</p></li><li><p><code>Comparable</code>接口本身就是一个泛型类型</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Comparable</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(T other)</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 类型变量指示了other参数的类型, String类实现了Comparable&lt;String&gt;</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(String other)</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&lt;? <span class="built_in">super</span> T&gt;&gt; pair&lt;T&gt; <span class="title function_">minmax</span><span class="params">(T[] a)</span></span><br></pre></td></tr></table></figure></li><li><p>无限定通配符<code>Pair&lt;?&gt;</code></p></li><li><p><code>Pair&lt;?&gt;</code>和<code>Pair</code>本质区别在于可以使用任意的<code>Object</code>对象调用原始<code>Pair</code>类的<code>setFirst()</code></p></li><li><p>测试一个<code>Pair</code>是否包含<code>Null</code>引用, 不需要具体的类型</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">hasNulls</span><span class="params">(Pair&lt;?&gt; p)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> p.getFirst() == <span class="literal">null</span> || p.getSecond() == <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 将hasNulls转换为泛型方法, 可以避免使用通配符类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; <span class="type">boolean</span> <span class="title function_">hasNulls</span><span class="params">(Pair&lt;T&gt; p)</span>;</span><br><span class="line"><span class="comment">// 带有通配符版本的可读性更强</span></span><br></pre></td></tr></table></figure></li><li><p>交换<code>Pair</code>元素</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">swap</span><span class="params">(Pair&lt;?&gt; p)</span>;</span><br><span class="line"><span class="comment">// 通配符不是一个类型变量, 所以不能作为类型编码</span></span><br><span class="line">? t = p.getFirst(); <span class="comment">// Error</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">swapHelper</span><span class="params">(Pair&lt;T&gt; p)</span> &#123;</span><br><span class="line">    <span class="type">T</span> <span class="variable">t</span> <span class="operator">=</span> p.getFirst();</span><br><span class="line">    p.setFirst(p.getSecond());</span><br><span class="line">    p.setSecond(t);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// swapHelper是一个泛型方法, 但是swap不是, 它有一个固定的Pair&lt;?&gt;类型参数</span></span><br><span class="line"><span class="comment">// 可以由swap调用swapHelper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">swap</span><span class="params">(Pair&lt;?&gt; p)</span> &#123;swapHelper(p);&#125;</span><br><span class="line"><span class="comment">// 此时swapHelper()参数T捕获通配符, 并不知道通配符指示什么类型, 但是是一个明确的类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">minmaxB</span><span class="params">(M[] a, Pair&lt;? <span class="built_in">super</span> M&gt; res)</span> &#123;</span><br><span class="line">    minmaxB(a, res);</span><br><span class="line">    PairAlg.swapHelper(res);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 只有在编译器能够保证通配符表示单个确定的类型时才不会报错</span></span><br><span class="line"><span class="comment">// ArrayList&lt;Pair&lt;T&gt;&gt;绝对不能捕获ArrayList&lt;Pair&lt;?&gt;&gt;中的通配符, 因为ArrayList可能包含两个Pair&lt;?&gt;, 而且指向了不同的类型</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="反射和泛型">反射和泛型</h2><h3 id="泛型Class类">泛型<code>Class</code>类</h3><ul><li><code>Class</code>类是泛型类, <code>String.class</code>实际上是<code>Class&lt;String&gt;</code>类的对象</li><li>类型参数非常有用, 允许<code>Class&lt;T&gt;</code>的方法有更加特定的返回类型  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">T <span class="title function_">newInstance</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 返回这个类的实例, 由无参构造器获得, 返回类型声明为T, 避免强制类型转换</span></span><br><span class="line">T <span class="title function_">cast</span><span class="params">(Object obj)</span>;</span><br><span class="line"><span class="comment">// 返回特定的对象, 给定对象的额实际类型是T的一个子类型, 会声明为T, 否则会抛出一个BadCastException</span></span><br><span class="line">T[] getEnumConstants();</span><br><span class="line"><span class="comment">// 如果这个类不是一个Enum或者T类型枚举值的一个数组, 就会返回null</span></span><br><span class="line">Class&lt;? <span class="built_in">super</span> T&gt; getSuperclass();</span><br><span class="line"><span class="comment">// 返回这个类的超类, 如果T不是一个类, 或者T是Object, 则返回null</span></span><br><span class="line">Constructor&lt;T&gt; <span class="title function_">getConstructor</span><span class="params">(Class... parameterTypes)</span>;</span><br><span class="line">Constructor&lt;T&gt; <span class="title function_">getDeclaredConstructor</span><span class="params">(Class... parameterTypes)</span>;</span><br><span class="line"><span class="comment">// 获得公共构造器, 或者有给定参数类型的构造器</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="使用Class-T-参数进行类型匹配">使用<code>Class&lt;T&gt;</code>参数进行类型匹配</h3><pre><code><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Pair&lt;T&gt; <span class="title function_">makePair</span><span class="params">(Class&lt;T&gt; c)</span> <span class="keyword">throws</span> InstantiationException, IllegalAccessException &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Pair</span>&lt;&gt;(c.newInstance(), c.newInstance());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 调用makePair(E.class), E.class是一个Class&lt;E&gt;类型的对象</span></span><br><span class="line"><span class="comment">// makePair()的类型参数T 与E匹配, 编译器可以推断出这个方法返回一个Pair&lt;E&gt;</span></span><br></pre></td></tr></table></figure></code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;泛型&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;没有泛型类的时候, 泛型程序用继承实现, &lt;code&gt;ArrayList&lt;/code&gt;类只维护一个&lt;code&gt;Object&lt;/code&gt;引用数组, 这样就会每次都需要进行强制类型转换&lt;/li&gt;
&lt;li&gt;现在是使用尖括号&lt;code&gt;&amp;lt;</summary>
      
    
    
    
    <category term="Java" scheme="https://sangs3112.github.io/categories/Java/"/>
    
    
    <category term="Java" scheme="https://sangs3112.github.io/tags/Java/"/>
    
    <category term="Java核心技术(卷一)" scheme="https://sangs3112.github.io/tags/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF-%E5%8D%B7%E4%B8%80/"/>
    
  </entry>
  
  <entry>
    <title>Java笔记_9</title>
    <link href="https://sangs3112.github.io/posts/1d15a4a2.html"/>
    <id>https://sangs3112.github.io/posts/1d15a4a2.html</id>
    <published>2024-12-07T01:37:00.000Z</published>
    <updated>2025-12-30T05:59:16.709Z</updated>
    
    <content type="html"><![CDATA[<h1>内部类</h1><h2 id="使用内部类的原因">使用内部类的原因</h2><ol><li>对同一个包中的其他类隐藏</li><li>访问定义这些方法的作用域的数据, 包括原本的私有数据</li></ol><div class="note info flat"><ul><li>内部类对象会有一个隐式引用, 指向实例化这个对象的外部类对象, 可以访问外部对象的全部状态</li><li>但是<code>Java</code>中静态内部类没有这个指针, 所以<code>Java</code>静态内部类等于<code>CPP</code>中的嵌套类</li><li>可以使用<code>OuterClass.this</code>表示外部类的引用, 比如 <code>T.this.b</code></li><li>可以使用<code>outerObject.new InnerClass()</code>编写内部类的构造器, 比如<code>A listen = this.new B();</code></li><li>在外部类的作用域之外, 可以使用<code>OutClass.InnerClass</code>引用</li></ul></div><ul><li>内部类声明的所有静态字段都必须是<code>final</code>, 初始化为一个编译时常量</li><li>内部类不能有<code>static</code>方法</li><li>使用<code>$</code>和<code>javap -private ClassName</code>可以将内部类文件转换为常规类文件</li></ul><h2 id="局部类">局部类</h2><ul><li>可以在一个方法中声明局部类, 这个类不能用<code>public</code>或者<code>private</code>访问修饰符, 对外部完全隐藏, 除了这个方法外都不知道这个局部类的存在</li><li>局部类不仅可以访问外部类的字段, 还可以访问局部变量, 不过这些局部变量都需要是只能赋值一次就不会改变的事实最终变量</li></ul><h2 id="匿名内部类">匿名内部类</h2><ul><li>如果只想要创建类的一个对象, 不需要为类指定名字  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(<span class="type">int</span> interval, <span class="type">boolean</span> flag)</span> &#123;</span><br><span class="line">    <span class="type">var</span> <span class="variable">listener</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ActionListener</span>() &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">actionPerformed</span><span class="params">(ActionEvent event)</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (flag) Toolkit.getDefaultToolkit().flag();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 创建了一个新的对象listener, 这个类实现了ActionListener接口</span></span><br><span class="line"><span class="comment">// 接口实现了actionPerformed方法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以是接口, 也可以是一个类</span></span><br><span class="line"><span class="comment">// 因为匿名内部类没有名字, 所以没有构造器</span></span><br></pre></td></tr></table></figure></li><li>对比构造类对象和构造匿名内部类  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;1&quot;</span>);</span><br><span class="line"><span class="type">var</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;2&quot;</span>) &#123;...&#125;;</span><br><span class="line"><span class="comment">// a 是类Person的一个对象</span></span><br><span class="line"><span class="comment">// b 是匿名内部类的一个对象, 这个匿名内部类是Person的子类</span></span><br><span class="line"><span class="comment">// 如果构造类的时候小括号后面跟上了大括号, 那么就是一个匿名内部类</span></span><br><span class="line"><span class="type">var</span> <span class="variable">c</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>() &#123;<span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;c&quot;</span>;&#125;</span><br><span class="line">System.out.println(c.name);</span><br><span class="line"><span class="comment">// 如果声明c的类型为Object, c.name就无法编译了, 因为Object是不可指示的</span></span><br></pre></td></tr></table></figure></li></ul><div class="note info flat"><ul><li>如果在一个方法中, 想要打印类名<br><code>System.out.println(getClass())</code></li><li>普通方法可以调用<code>this</code>, 但是静态方法没有<code>this</code></li><li>如果想要打印静态方法的类名, 可以使用匿名内部类<br><code>System.out.println(new Object(){}.getClass().getEnclosingClass())</code></li><li>这里的<code>getEnclosingClass()</code>是得到这个静态方法的外围类</li></ul></div><h2 id="静态内部类">静态内部类</h2><ul><li>如果生成内部类只是为了隐藏这个类, 并不想生成这个内部类的引用, 可以使用<code>static</code>修饰</li><li>比如要计算一个数组的最大值和最小值<ul><li>可以遍历数组两遍</li><li>也可以定义一个类, 其中包含两个私有字段, 分别记录最大值和最小值</li><li>但是这个类的类名可能会重复, 所以可以定义内部类隐藏类名</li><li>可以将内部类声明为<code>static</code>的, 避免包含其他类的引用</li><li>如果内部类是在一个静态方法中构造的, 则这个内部类必须要声明为静态内部类</li></ul></li></ul><h1>代理</h1><ul><li>代理可以在运行时创建一组给定接口的新类</li><li>只有在编译时无法确定需要实现哪个接口的时候才需要使用代理</li><li>代理类可以在运行时创建一个全新的类, 能够实现指定的接口</li><li>一个代理类包含指定接口需要实现的方法, 以及<code>Object</code>类中的所有方法, 比如<code>toString(), equals()</code></li><li>必须提供一个调用处理器, 调用处理器是实现了<code>InvocaitonHandler</code>接口的类对象, 这个接口只有一个方法, <code>Object invoke()</code></li><li>只要调用代理方法, 就会调用这个<code>invoke</code>方法</li></ul><h2 id="创建代理对象">创建代理对象</h2><ul><li><p>需要使用<code>Proxy</code>类的<code>newProxyInstance()</code>, 包含三个参数</p><ol><li>类加载器</li><li><code>Class</code>对象数组</li><li>一个调用处理器</li></ol></li><li><p>使用目的:</p><ol><li>方法调用 路由到远程服务器</li><li>用户界面事件关联运行中的程序动作</li><li>调试跟踪方法调用</li></ol></li><li><p>定义一个<code>TraceHandler</code>包装器类存储一个包装的对象, <code>invoke()</code>打印所调用方法的名字和参数, 然后调用这个方法, 提供包装的对象作为隐式参数</p>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TraceHandler</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Object target;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">new</span> <span class="title class_">TraceHandler</span>(Object t) &#123;</span><br><span class="line">        target = t;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method m, Object[] args)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="comment">// print</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> m.invoke(target, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">Object</span> <span class="variable">val</span> <span class="operator">=</span> ...;</span><br><span class="line"><span class="type">var</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TraceHandler</span>(val);</span><br><span class="line"><span class="type">var</span> <span class="variable">interfaces</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123;Comparable.class&#125;;</span><br><span class="line"><span class="type">Object</span> <span class="variable">proxy</span> <span class="operator">=</span> Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123;Comparable.class&#125;, handler);</span><br></pre></td></tr></table></figure></li></ul><h2 id="特性">特性</h2><ol><li>代理类是程序运行过程中动态创建的, 一旦创建就是常规的类</li><li>所有的代理类都扩展<code>Proxy</code>类, 一个代理类只有一个实例字段, 也就是调用处理器, 在<code>Proxy</code>超类中定义<ul><li>完成代理对象任务所需要的任何额外的数据都需要存储在调用处理器中</li></ul></li><li>所有的代理类都需要覆盖<code>Object</code>类的<code>toString(), equals(), hashCode()</code>, 这些方法只是在调用处理器上调用<code>invoke()</code><ul><li><code>Object</code>类中的其他方法, <code>clone(), getClass()</code>没有重新定义</li></ul></li><li>没有定义代理类的名字, 虚拟机中的<code>Proxy</code>类会生成<code>$Proxy</code>开头的类名</li><li>一个特定的类加载器和一组接口, 只能有一个代理类, 同样可以使用<code>Class proxyClass = Proxy.getProxyClass(null, interface)</code>得到这个类</li><li>代理类一定是<code>public, final</code>的, 如果代理类实现的所有接口都是<code>public</code>的, 那么这个代理类就不属于任何包, 否则一定属于某一个包</li><li>可以通过调用<code>Proxy.isProxyClass()</code>检测一个特定的<code>Class</code>对象是否表示一个代理类</li></ol><ul><li>调用一个目标代理的默认方法会触发调用处理器, 使用<code>InvocationHandler</code>接口的静态方法<code>invokeDefault</code>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">InvocationHandler</span> <span class="variable">handler</span> <span class="operator">=</span> (proxy, method, args) -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (method.isDefault()) &#123;</span><br><span class="line">        <span class="keyword">return</span> InvocationHandler.invokeDefault(proxy, method, args);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> method.invoke(target, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;内部类&lt;/h1&gt;
&lt;h2 id=&quot;使用内部类的原因&quot;&gt;使用内部类的原因&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;对同一个包中的其他类隐藏&lt;/li&gt;
&lt;li&gt;访问定义这些方法的作用域的数据, 包括原本的私有数据&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;note info flat</summary>
      
    
    
    
    <category term="Java" scheme="https://sangs3112.github.io/categories/Java/"/>
    
    
    <category term="Java" scheme="https://sangs3112.github.io/tags/Java/"/>
    
    <category term="Java核心技术(卷一)" scheme="https://sangs3112.github.io/tags/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF-%E5%8D%B7%E4%B8%80/"/>
    
  </entry>
  
  <entry>
    <title>Java笔记_7</title>
    <link href="https://sangs3112.github.io/posts/faad89a5.html"/>
    <id>https://sangs3112.github.io/posts/faad89a5.html</id>
    <published>2024-12-01T10:21:00.000Z</published>
    <updated>2025-12-30T05:59:16.709Z</updated>
    
    <content type="html"><![CDATA[<h1>继承</h1><blockquote><p>基本思想: 基于已有的类创建新的类, 复用已有类的方法, 同时可以增加一些新的方法和字段</p></blockquote><ul><li>反射是程序在运行期间更多地了解类以及属性的能力</li></ul><div class="note info flat"><ul><li><code>CPP</code>中使用<code>:</code>表示继承, 除了公共继承以外, 还存在私有继承和保护继承</li><li><code>Java</code>中<code>extends</code>关键字表示继承, 所有继承都是<strong>公共继承</strong></li></ul></div><ul><li><code>extends</code>表示正在构造的类(子类, 派生类)派生于一个已经存在的类(超类, 基类, 父类)<ul><li>子类比超类拥有更多的功能</li></ul></li></ul><div class="note info flat"><ul><li>“声明为私有的类成员不会被这个类的子类继承”<ul><li>这里其实是子类不能<strong>直接</strong>访问这些私有成员</li><li>但是子类的每个实例对象中依然会包含超类中的私有字段</li></ul></li></ul></div><ul><li>记录不能被扩展, 记录也不能继承别的类</li><li>如果希望使用超类中的方法, 就使用<code>super</code>关键字, <code>super</code>只是用于指示编译器调用超类方法的特殊关键字<ul><li>同时可以使用<code>super()</code>来调用超类中对应的构造器</li><li>不管是<code>this</code>还是<code>super</code>, <strong>在调用其他构造器的时候</strong>, 都必须放在第一行, 否则会报错</li></ul></li></ul><div class="note info flat"><ul><li>一个对象可以指示多种实际类型<ul><li>比如一个类的超类以及他的子类都在一个数组中, 使用<code>foreach</code>循环的时候, 循环变量可以同时指示多个不同的类, 那么这就是多态</li></ul></li><li>运行的时候可以自动的选择适合的方法, 就是动态绑定</li><li><code>CPP</code>中, 如果希望实现动态绑定, 可以将成员函数设置为<code>virtual</code></li><li><code>Java</code>中默认会执行动态绑定, 如果不希望方法是虚拟的, 可以使用<code>final</code>关键字</li><li><code>CPP</code>中, 一个类可以有多个超类</li><li><code>Java</code>中不支持多重继承, 但是可以使用接口实现多重继承的功能</li></ul></div><h2 id="对象方法调用过程">对象方法调用过程</h2><blockquote><p>假设需要<code>x.f(args)</code>, <code>x</code>是类<code>C</code>的一个对象, 具体调用过程如下:</p></blockquote><ol><li>编译器查看对象的声明类型和方法名, 可能存在多个名为<code>f</code>的方法, 他们具有不同的参数类型, 编译器会一一列举出<code>C</code>中的所有名为<code>f</code>的方法, 以及<code>C</code>的超类中所有名为<code>f</code>的<strong>非私有方法</strong></li><li>重载解析: 编译器确定方法调用中提供的参数类型, 如果所有名为<code>f</code>的方法中存在一个与所提供的参数类型完全匹配的方法, 就直接使用这个方法<ul><li>如果找不到匹配的方法, 或者找到了多个匹配的方法, 编译器就会抛出异常</li><li>方法的名字与参数类型会组成签名, 如果子类的签名和超类的重复了, 子类的方法会覆盖超类的方法</li><li>尽管返回类型不是签名的一部分, 但是在覆盖的时候, 需要保证子类的返回类型是超类返回类型的子类型</li></ul></li><li>如果是<code>private</code>, <code>static</code>, <code>final</code>方法 或者 构造器方法, 编译器就可以准确知道调用哪个方法, 这称为静态绑定. 如果调用方法依赖于隐式参数的实际类型, 就是动态绑定. 所以只要不是上述四种方法, 就是动态绑定</li><li>程序使用动态绑定时, 虚拟机必须调用与<code>x</code>引用对象实际类型对应的方法, 比如<code>x</code>的实际类型是<code>D</code>, <code>D</code>是<code>C</code>的子类, 如果<code>D</code>定义了方法<code>f(String)</code>, 那么就会调用这个方法, 否则就会在<code>D</code>的超类中寻找这个方法<ul><li>如果每一次调用方法都需要执行一次搜索的话, 时间消耗非常大, 所以虚拟机预先为每个类计算了一个方法表, 列出了所有方法的签名和需要调用的方法</li><li>虚拟机加载一个类以后就可以构建这个方法表</li></ul></li></ol><div class="note info flat"><ul><li>覆盖一个方法的时候, 子类方法的可见性不能低于超类方法的可见性</li><li>如果超类方法是<code>public</code>, 子类也必须要是<code>public</code>方法, 如果漏了, 就会报错</li></ul></div><h2 id="final方法"><code>final</code>方法</h2><ul><li>使用<code>final</code>修饰一个类, 就可以阻止定义这个类的子类  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">E</span> <span class="keyword">extends</span> <span class="title class_">M</span> &#123;&#125;</span><br></pre></td></tr></table></figure><ul><li><code>final</code>类的方法自动变为<code>final</code>方法</li><li><code>final</code>类的字段<strong>不会自动</strong>变为<code>final</code>字段</li></ul></li><li>如果将类中的某个方法设定为<code>final</code>, 则该类的所有子类都不能覆盖这个方法  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">E</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><code>final</code>字段表示以后都不会改变的字段</li><li>枚举和记录总是<code>final</code>的, 因为他们不允许被扩展</li></ul><h2 id="对象引用的强制类型转换">对象引用的强制类型转换</h2><ul><li>现在有<code>M</code>类是<code>E</code>的子类  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">E</span>[<span class="number">3</span>];</span><br><span class="line">s[<span class="number">0</span>] = <span class="keyword">new</span> <span class="title class_">M</span>();</span><br><span class="line">s[<span class="number">1</span>] = <span class="keyword">new</span> <span class="title class_">E</span>();</span><br><span class="line">s[<span class="number">2</span>] = <span class="keyword">new</span> <span class="title class_">E</span>();</span><br><span class="line"><span class="type">M</span> <span class="variable">b</span> <span class="operator">=</span> (M) s[<span class="number">0</span>]; <span class="comment">// 强制类型转换</span></span><br><span class="line"><span class="type">M</span> <span class="variable">c</span> <span class="operator">=</span> (M) s[<span class="number">1</span>]; <span class="comment">// 抛出异常 ClassCastException</span></span><br></pre></td></tr></table></figure></li><li>为了避免抛出异常, 可以使用<code>instanceof</code>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (s[i] <span class="keyword">instanceof</span> M) &#123;</span><br><span class="line">    b = (M) s[i];</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><ul><li><code>JDK 16</code>中有更加简单的写法, 可以直接在<code>instanceof</code>语句中声明子类变量<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (s[i] <span class="keyword">instanceof</span> M b) &#123;</span><br><span class="line">    b.setB();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ul><div class="note info flat"><ul><li>只能在继承层次内使用强制类型转换</li><li>将超类转换为子类之前, 需要使用<code>instanceof</code>检查类型</li><li>如果<code>x</code>是<code>null</code>, <code>x instanceof C</code>不会抛出异常, 返回<code>false</code></li><li>一般情况下最好少用强制类型转换和<code>instanceof</code></li></ul></div><h2 id="protected"><code>protected</code></h2><ul><li>将超类中的某个字段声明为<code>protected</code>, 子类就可以进行访问</li><li>受保护的字段只能由同一个包中的类进行访问, 如果子类在不同的包中, 就不能访问了</li><li>相比之下, <code>protected</code>方法更有意义, 表示可以相信子类能够正确的使用这个方法</li><li>所以<code>Java</code>中的<code>protected</code>允许所有子类, 以及同一个包中的所有其他类访问, 不如<code>CPP</code>中的安全</li></ul><h1><code>Object</code></h1><blockquote><p><code>Object</code>类是<code>Java</code>中所有类的超类</p></blockquote><h2 id="写equals方法">写<code>equals</code>方法</h2><ol><li>显式参数命名为<code>otherObject</code></li><li>检测<code>this</code>与<code>otherObject</code>是否相同</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span> == otherObject) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>检测<code>otherObject</code>是否为<code>null</code>, 如果为<code>null</code>, 则返回<code>false</code></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (otherObject == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>比较<code>this</code>与<code>otherObject</code>的类<br>如果<code>equals</code>的语义可以在子类中改变, 就使用<code>getClass</code>检测</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (getClass() != otherObject.getClass()) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">ClassName</span> <span class="variable">other</span> <span class="operator">=</span> (ClassName) otherObject;</span><br><span class="line"><span class="comment">// 这个判断对于匿名子类会失败</span></span><br></pre></td></tr></table></figure><p>如果所有的子类都有相同的相等性语义, 就可以使用<code>instanceof</code>检测</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!(otherObject <span class="keyword">instanceof</span> ClassName other)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>使用相等性概念来比较字段, 使用<code>==</code>比较基本类型字段, 使用<code>Objects.equals</code>比较对象字段, 如果所有的字段都匹配, 返回<code>true</code></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> field1 == other.field1 &amp;&amp; Objects.equals(field2, other.field2) &amp;&amp; ... ;</span><br></pre></td></tr></table></figure><ul><li>对于数组类型, 使用<code>Arrays.equals()</code>方法检查相应的数组元素, 如果是多维数组, 可以使用<code>Arrays.deepEquals()</code></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">E</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(E other)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> other != <span class="literal">null</span> </span><br><span class="line">            &amp;&amp; getClass() == other.getClass() </span><br><span class="line">            &amp;&amp; Objects.equals(name, other.name) </span><br><span class="line">            &amp;&amp; salary == other.salary </span><br><span class="line">            &amp;&amp; Objects.equals(hireDay, other.hireDay);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 这里有错误, 因为参数类型是E, 没有覆盖Object类的equals方法, 而是定义了一个新的方法</span></span><br><span class="line"><span class="comment">// 为了避免这个错误, 可以使用@Override public boolean equals(Object other)</span></span><br><span class="line"><span class="comment">// 此时编译器就会给出报错信息, 因为当前方法没有覆盖Object中的任何方法</span></span><br></pre></td></tr></table></figure><h2 id="hashCode"><code>hashCode</code></h2><blockquote><p><code>hashCode</code>方法定义在<code>Object</code>类中, 所以每个对象都有一个默认的散列码, 由对象的存储地址得出</p></blockquote><ul><li>自定义<code>hashCode</code>方法</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">E</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">7</span> * name.hashCode() </span><br><span class="line">            + <span class="number">11</span> * Double.valueOf(s).hashCode() </span><br><span class="line">            + <span class="number">13</span> * hireDay.hashCode();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 可以使用Objects.hashCode(), 这是null安全的, 如果参数为null, 会直接返回0</span></span><br><span class="line"><span class="comment">// 可以使用静态方法Double.hashCode()避免创建一个Double对象</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">E</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">7</span> * Objects.hashCode(name) </span><br><span class="line">            + <span class="number">11</span> * Double.hashCode(s) </span><br><span class="line">            + <span class="number">13</span> * Objects.hashCode(hireDay);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果有多个内容需要hash, 可以直接调用Objects.hash()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">E</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(name, s, hireDay);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>如果有数组类型, 可以使用静态<code>Arrays.hashCode()</code>计算一个散列码, 这个散列码由数组元素的散列码组成</li><li>记录类型会自动提供一个<code>hashCode()</code>, 由字段值的散列码得到一个散列码</li></ul><h2 id="toString"><code>toString()</code></h2><ul><li>使用<code>getClass().getName()</code>获得类名的字符串</li><li>每一个子类都应该实现自己的<code>toString()</code>, 如果超类中实现了, 可以直接使用<code>super.toString()</code></li><li>只要一个对象与一个字符串通过<code>+</code>连接, 编译器就会自动调用<code>toString()</code>方法获得这个字符串的描述</li><li>如果<code>x</code>是任意一个对象, 使用<code>System.out.println(x)</code>也会自动调用<code>x.toString()</code></li><li><code>Objects</code>定义了<code>toString()</code>, 会打印对象的类名和散列码</li></ul><h1><code>ArrayList</code></h1><blockquote><p><code>ArrayList</code>是一个有类型参数的泛型类</p></blockquote><ul><li>使用<code>var</code>可以避免重复写类型 <code>var staff = new ArrayList&lt;E&gt;();</code><ul><li>如果使用了<code>var</code>, 就需要声明类型; 如果不使用<code>var</code>, 可以使用菱形语法 <code>ArrayList&lt;E&gt; staff = new Arraylist&lt;&gt;();</code></li></ul></li><li>使用<code>add</code>添加元素, 如果满了会自动扩容</li><li>如果可以估计大小, 可以在添加元素之前使用<code>staff.ensureCapacity(nums)</code>来设置分配的空间<ul><li>也可以使用<code>var staff = new ArrayList&lt;E&gt;(nums)</code>将初始容量传递给构造器</li></ul></li><li>如果数组的大小保持恒定不会发生变化了, 可以使用<code>staff.trimToSize()</code>将内存块的大小调整为当前所需空间, <code>GC</code>回收多余的空间</li></ul><h1>对象包装器</h1><ul><li>有时候需要将<code>int</code>转换为对象, 所有基本类型都有一个与之对应的类</li><li><code>Integer, Long, Float, Double, Short, Byte, Character, Boolean</code> 前六个派生于公共超类<code>Number</code><ul><li>包装类不可变, 一旦构造了包装器, 就不能更改其中的值</li><li>包装器类还是<code>final</code>, 所以不能派生子类</li><li>尖括号中的类型参数不能是基本数据类型, 必须要是包装器类型</li><li>因为每一个值都包装在对象中, 所以<code>ArrayList&lt;integer&gt;</code> 效率低于<code>int[]</code></li></ul></li><li>当使用<code>list.add(3)</code>的时候, 会自动转换为<code>list.add(Integer.valueOf(3))</code>, 这就是自动装箱</li><li>同样的, 当我们使用<code>int n = list.get(i)</code>的时候, 实际上是将<code>Integer</code>对象赋值给<code>int</code>类型, 这是自动拆箱, 等价于<code>list.get(i).intValue()</code></li><li><code>Integer n = 3; n ++;</code>这里面实际上先自动拆箱, 然后<code>+1</code>, 再自动装箱</li><li>不要使用包装器类构造器, 可以使用<code>Integer.valueOf(i)</code>, 也可以依赖自动装箱:<code>Integer a = i</code>, 不要使用<code>new Integer(i)</code>, 这个将会被删除</li><li>包装器类引用可以为<code>null</code>, 所以会触发<code>NPE</code></li><li>如果表达式中混用了<code>Integer, Double</code>, 则<code>Integer</code>会自动拆箱, 提升为<code>double</code>, 再自动装箱为<code>Double</code></li><li>自动装箱和拆箱是编译器做的工作, 不是虚拟机</li></ul><h1>可变参数个数方法</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PrintStream</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> PrintStream <span class="title function_">printf</span><span class="params">(String fmt, Object... args)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> format(fmt, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>这里的<code>...</code>表示接受任意数量的对象, 是一个<code>Object</code>数组, 保存着除了<code>fmt</code>之外的其他参数</li><li>如果调用者给了其他类型或者基本类型的值, 就会自动装箱为对象, 现在就只需要<code>fmt</code>中扫描到第<code>i</code>个格式说明, 与<code>args[i]</code>值匹配</li></ul><h1>抽象类</h1><ul><li>如果一个类中存在抽象方法, 类本身必须声明为抽象的; 抽象类<strong>可以没有</strong>抽象方法; 抽象类可以有具体字段和方法  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">P</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> String <span class="title function_">getD</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>抽象类的子类可以保留抽象类中的部分或所有抽象方法, 那么子类依然是抽象的; 也可以全部实现, 则子类不是抽象的</li><li>抽象类不能被实例化, 但是可以存在抽象类的变量, 只是只能引用其非抽象子类  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">P</span> <span class="variable">p</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">S</span>();</span><br></pre></td></tr></table></figure></li><li>接口是抽象类的泛化</li></ul><h1>密封类</h1><ul><li>比如有一个抽象类<code>JSONValue</code>, 还有两个<code>final</code>子类, 分别是<code>JSONNumber, JSONArray</code></li><li>两个子类是<code>final</code>的, 所以无法被派生了, 但是不能阻止别人派生<code>JSONValue</code></li><li>可以将<code>JSONValue</code>声明为密封类  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title class_">JSONValue</span></span><br><span class="line">    <span class="keyword">permits</span> JSONArray, JSONNumber, JSONString, JSONBoolean, JSONObject, JSONNull &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">// 这样使用sealed声明为密封类, 可以保证JSONValue只有六个定义好的子类, 无法派生别的子类了</span></span><br></pre></td></tr></table></figure></li><li>一个密封类的子类必须是可以访问的, 不能是嵌套在别的类中的私有类, 也不能位于另一个包中</li><li>密封类允许的公共子类, 必须要在同一个包中, 如果使用了模块, 还必须要在同一个模块中</li><li>声明密封类可以不加<code>permits</code>, 但是这样的话所有子类都必须要在同一个文件中声明, 这样的话, 子类就不是公共的了</li><li>密封类的子类必须声明为<code>sealed, final, non-sealed</code>中的一种，最后一种允许继续派生</li></ul><h1>反射</h1><h2 id="Class类"><code>Class</code>类</h2><ul><li><code>Java</code>始终为所有对象维护一个运行时类型标识, 跟踪每个对象所属的类</li><li>可以用<code>Class</code>类访问这些信息, <code>Object.getClass()</code>返回一个<code>Class</code>对象的实例<ul><li>最常用的方法就是<code>getName()</code>, 返回一个对象类型的名称, 包名也作为类名的一部分</li></ul></li><li>也可以直接使用<code>类名.class</code>的方法访问这个类</li><li>虚拟机为每个类型管理一个唯一的<code>Class</code>对象, 所以可以使用<code>==</code>比较  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (e.getClass() == E.class) &#123;&#125;</span><br><span class="line"><span class="comment">// 如果e是一个E的实例, 则为true, 如果e是M的实例, 其中M是E的子类, 则为false</span></span><br><span class="line"><span class="comment">// 如果是 e instanceof E的话, 那么当e是M的实例时, 依然会返回true</span></span><br></pre></td></tr></table></figure></li><li>如果有一个<code>Class</code>类型的对象, 可以用他构造实例  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">className</span> <span class="operator">=</span> <span class="string">&quot;java.uril.Random&quot;</span>;</span><br><span class="line"><span class="type">Class</span> <span class="variable">cl</span> <span class="operator">=</span> Class.forName(className);</span><br><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> cl.getConstructor().newInstance();</span><br><span class="line"><span class="comment">// 如果这个类没有无参构造器, 则会抛出异常InvocationTargetException</span></span><br></pre></td></tr></table></figure></li><li><code>Class.forName()</code>会抛出一个检查型异常, 没有办法保证指定名字的类一定存在, 所以需要在函数后面加上<code>throws ReflectOperationException</code></li></ul><h2 id="异常">异常</h2><ul><li>分两种: 非检查型异常和检查型异常<ul><li>检查型异常: 编译器会检查你是否知道这个异常, 并做好准备处理</li><li>非检查型异常: 比如数组越界, <code>null</code>引用访问, 编译器不期望你为这些异常提供处理方法</li></ul></li></ul><h2 id="应用">应用</h2><ol><li><p>资源文件加载</p><ul><li>获得拥有资源的类的<code>Class</code>对象, 比如<code>ResourcesTest.class</code></li><li>调用部分可以接受描述资源位置<code>URL</code>的方法, 比如<code>URL url = cl.getResource(&quot;about.txt&quot;);</code></li><li>否则, 使用<code>getResourceStream()</code>得到输入流读取文件</li></ul></li><li><p>国际化</p><ul><li>与语言相关的字符串都放在资源文件中, 每个语言对应一个文件</li></ul></li></ol><h2 id="利用反射分析类">利用反射分析类</h2><ul><li><code>java.util.reflect</code>包中有三个类: <code>Field, Method, Constructor</code>, 分别用于描述类的字段, 方法和构造器<ul><li>三个类都有一个方法, 名为<code>getName()</code></li><li><code>Field.getType()</code>可以返回字段类型的一个对象, 对象的类型同样是<code>Class</code></li><li><code>Method, Constructor</code>有报告类型参数的方法, <code>Method</code>有报告返回类型的方法, 三者都有<code>getModifiers()</code> 返回一个整数, 用不同的<code>0/1</code>位描述修饰符, 比如<code>public, static</code></li><li>可以使用<code>Modifier</code>类的静态方法分析<code>getModifiers()</code>返回的整数, 需要做的就是在返回的整数基础上, 调用<code>Modifier</code>类中适当的方法</li><li>可以用<code>Modifier.toString()</code>打印修饰符</li></ul></li><li><code>Class</code>中的<code>getFields(), getMethods(), getConstructors()</code>分别返回这个类支持的公共字段, 方法和构造器</li><li><code>Class</code>中的<code>getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors()</code>返回这个类声明的全部字段, 方法和构造器, 包括私有成员， 包成员, <code>protected</code>成员, 有包访问权限的成员, 但是不会包括超类的成员</li></ul><h2 id="利用反射分析对象">利用反射分析对象</h2><ul><li>利用反射可以查看在编译时还不知道的对象字段</li><li>利用<code>Field</code>中的<code>get</code>, 比如<code>f</code>是一个<code>Field</code>类型的对象, <code>obj</code>是包含<code>f</code>字段的类的对象, 则<code>f.get(obj)</code>将会返回一个对象, 值为<code>obj</code>的当前字段值  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">var</span> <span class="variable">h</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">E</span>();</span><br><span class="line"><span class="type">Class</span> <span class="variable">cl</span> <span class="operator">=</span> h.getClass();</span><br><span class="line"><span class="type">Field</span> <span class="variable">f</span> <span class="operator">=</span> cl.getDeclaredField(<span class="string">&quot;name&quot;</span>);</span><br><span class="line"><span class="type">Object</span> <span class="variable">v</span> <span class="operator">=</span> f.get(h);</span><br></pre></td></tr></table></figure></li><li>同样可使用<code>f.set(obj, val)</code>设置值, 但是如果<code>name</code>是一个私有字段, 则不能使用<code>get, set</code>, 会抛出<code>IllageAccessException</code></li><li>只能对可以访问的字段使用<code>get, set</code>, <code>Java</code>允许查看一个对象中的字段, 但是无法访问</li><li>不过可以调用<code>f.setAccessible(true)</code>覆盖<code>Java</code>的访问控制</li><li><code>setAccessible()</code>是<code>Field, Method, Constructor</code>的公共超类<code>AccessibleObject</code>中的方法</li></ul><h2 id="通用toString">通用<code>toString()</code></h2><ul><li>使用<code>getDeclaredFields</code>获得实例字段, 使用<code>setAccessible()</code>将字段设置为可以访问的, 再对每个字段调用<code>toString()</code></li><li>不过如果引用循环会导致无限递归, <code>ObjectAnalyzer</code>会跟踪已经访问过的对象</li></ul><h2 id="使用反射编写泛型数组">使用反射编写泛型数组</h2><ul><li><code>java.util.reflect</code>中的<code>Array</code>类, <code>Arrays.copyOf()</code>就使用了这个类, 这个方法可以用来扩展一个数组  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">goodCopyOf</span><span class="params">(Object a, <span class="type">int</span> newLength)</span> &#123;</span><br><span class="line">    <span class="type">Class</span> <span class="variable">cl</span> <span class="operator">=</span> a.getClass();</span><br><span class="line">    <span class="keyword">if</span> (!cl.isArray()) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">Class</span> <span class="variable">componentType</span> <span class="operator">=</span> cl.getComponentType();</span><br><span class="line">    <span class="comment">// 如果对象是一个数组类型, 返回对应元素的Class, 否则返回null</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> Array.getLength(a);</span><br><span class="line">    <span class="type">Object</span> <span class="variable">newArray</span> <span class="operator">=</span> Array.newInstance(componentType, newLength);</span><br><span class="line">    System.arraycopy(a, <span class="number">0</span>, newArray, <span class="number">0</span>, Math.min(length, newLength));</span><br><span class="line">    <span class="keyword">return</span> newArray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>这个<code>goodCopyOf</code>可以扩展任意数组, 参数声明为<code>Object</code>类型, 而不是<code>Object[]</code>, 因为<code>int[]</code>可以转换为一个<code>Object</code>, 而不是转换成对象数组</li><li>如果是<code>Object[]</code>的话, 在强制类型转换回去的时候会抛出异常<code>ClassCastException</code></li></ul><h2 id="使用反射调用任意的方法">使用反射调用任意的方法</h2><ul><li>可以使用<code>Field.get()</code>查看一个方法的字段, 使用<code>Method.invoke()</code>调用包装在当前<code>Method</code>中的方法  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Object <span class="title function_">invoke</span><span class="params">(Object obj, Object... args)</span>;</span><br><span class="line"><span class="comment">// 第一个参数是隐式参数, 其他参数是显式参数</span></span><br><span class="line"><span class="comment">// 如果是静态方法, 则第一个参数会被忽略为null</span></span><br><span class="line"><span class="comment">// 比如m1表示E类中的getName()</span></span><br><span class="line"><span class="type">String</span> <span class="variable">n</span> <span class="operator">=</span> (String) m1.invoke(h);</span><br><span class="line"><span class="comment">// 如果返回的是基本数据类型, invoke会返回其包装类型, 需要强制类型转换后使用自动拆箱</span></span><br><span class="line"><span class="comment">// 比如m2表示E类中的getSalary()</span></span><br><span class="line"><span class="type">double</span> <span class="variable">d</span> <span class="operator">=</span> (Double) m2.invoke(h);</span><br><span class="line"><span class="comment">// 使用getMethod()可以得到一个类中的方法</span></span><br><span class="line"><span class="type">Method</span> <span class="variable">m1</span> <span class="operator">=</span> E.class.getMethod(<span class="string">&quot;getName&quot;</span>);</span><br><span class="line"><span class="type">Method</span> <span class="variable">m2</span> <span class="operator">=</span> E.class.getMethod(<span class="string">&quot;getSalary&quot;</span>, <span class="type">double</span>.class);</span><br><span class="line"><span class="comment">// 可以获得构造器方法</span></span><br><span class="line"><span class="type">Class</span> <span class="variable">cl</span> <span class="operator">=</span> Random.class;</span><br><span class="line"><span class="type">Constructor</span> <span class="variable">cons</span> <span class="operator">=</span> cl.getConstructor(<span class="type">long</span>.class);</span><br><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> cons.newInstance(<span class="number">42L</span>);</span><br></pre></td></tr></table></figure></li><li><code>Method, Construtor</code>类扩展了<code>Executable</code>类, 并且<code>Executable</code>是<code>sealed</code>的, 只允许<code>Method, Constructor</code>作为子类</li><li>比如调用<code>Math.sqrt()</code>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">double</span> <span class="variable">dx</span> <span class="operator">=</span> (to - from) / (n - <span class="number">1</span>);</span><br><span class="line">Math.class.getMethod(<span class="string">&quot;sqrt&quot;</span>, <span class="type">double</span>.class);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">double</span> <span class="variable">x</span> <span class="operator">=</span> from; x &lt;= to; x += dx) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">double</span> <span class="variable">y</span> <span class="operator">=</span> (Double) f.invoke(<span class="literal">null</span>, x);</span><br><span class="line">    <span class="comment">// 因为Math.sqrt是一个静态方法, 所以invoke的第一个参数为null</span></span><br><span class="line">    System.out.printf(<span class="string">&quot;%10.4f | %10.4f%n&quot;</span>, x, y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>反射能完成所有操作, 但是很不方便, <code>invoke</code>参数错误还会抛出异常. 同时<code>invoke</code>返回类型一定是<code>Object</code>的, 所以必须来回强制类型转换</li><li>编译器就丧失了检查代码的机会, 反射获得方法指针的代码比直接调用慢的多</li><li>所以一般只有绝对必要的时候才会引入<code>Method</code>对象, 更好的方法是使用<code>lambda</code>表达式</li><li>不要使用回调函数的<code>Method</code>对象, 要是用回调的接口, 这样执行速度更快, 也更好维护</li></ul><h1>继承设计技巧</h1><ol><li>公共字段和方法放在超类中</li><li>不要使用<code>protected</code></li><li>使用继承实现<code>is-a</code>关系</li><li>除非所有继承的方法都有意义, 否则不要使用继承</li><li>覆盖方法不要改变预期的行为</li><li>不要滥用反射</li><li>使用多态, 不要使用类型信息 <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (x is of type <span class="number">1</span>) </span><br><span class="line">    action1(x)</span><br><span class="line"><span class="keyword">else</span> </span><br><span class="line">    action2(x)</span><br><span class="line"><span class="comment">// 这种形式的代码都可以用多态实现</span></span><br><span class="line"><span class="comment">// 如果action1() action2()表示通用的概念, 可以定义为这两个类型的公共超类或接口中的方法, 然后可以调用</span></span><br><span class="line"><span class="comment">// x.action(), 利用多态的动态分配机制执行正确的动作</span></span><br></pre></td></tr></table></figure></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;继承&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;基本思想: 基于已有的类创建新的类, 复用已有类的方法, 同时可以增加一些新的方法和字段&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;反射是程序在运行期间更多地了解类以及属性的能力&lt;/li&gt;
&lt;/ul&gt;
&lt;div </summary>
      
    
    
    
    <category term="Java" scheme="https://sangs3112.github.io/categories/Java/"/>
    
    
    <category term="Java" scheme="https://sangs3112.github.io/tags/Java/"/>
    
    <category term="Java核心技术(卷一)" scheme="https://sangs3112.github.io/tags/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF-%E5%8D%B7%E4%B8%80/"/>
    
  </entry>
  
</feed>
