YSMull https://ysmull.cn Thread Local 之我见 <ul id="markdown-toc"> <li><a href="#id-引子" id="markdown-toc-id-引子">引子</a></li> <li><a href="#id-threadlocalmap-的实现" id="markdown-toc-id-threadlocalmap-的实现">ThreadLocalMap 的实现</a></li> <li><a href="#id-注意事项" id="markdown-toc-id-注意事项">注意事项</a></li> </ul> <h2 id="id-引子">引子</h2> <p>我们有了一个线程</p> <pre><code class="language-java">Thread t = new Thread(); </code></pre> <p>我们希望放置一些数据,使得在该线程的任何地方都可以访问和修改这些数据,这就是 ThreadLocal 对象。理所应当的,我们应该把这个些数据存放在当前 Thread 的对象上,这也就是为什么 Thread 类上有一个 threadLocals 字段,它的类型是 ThreadLocalMap,ThreadLocalMap 顾名思义是一个 Map,key 为 ThreadLocal 对象,value 是我们要存储的对象。</p> <pre class="line-numbers"><code class="language-java">/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; </code></pre> <p>每一个 threadLocal 对象只能存放「一个东西」。如果你想存放苹果,再存放书,得分别为要存放的东西创建 ThreadLocal 对象:</p> <pre><code class="language-java">ThreadLocal&lt;Apple&gt; appleThreadLocal = new ThreadLocal&lt;&gt;(); ThreadLocal&lt;Book&gt; bookThreadLocal = new ThreadLocal&lt;&gt;(); </code></pre> <p>不过,到目前为止,仍然是什么特殊的事情都没有发生,因为 <strong>ThreadLocal 的构造函数是空的</strong> :</p> <pre><code class="language-java">/** * Creates a thread local variable. * @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { } </code></pre> <p>多个线程可以使用同一个 ThreadLocal 对象:</p> <pre><code class="language-java">public static ThreadLocal&lt;Apple&gt; appThreadLocal = new ThreadLocal&lt;&gt;(); // thread1 // thread2 ... ... Apple apple1 = new Apple(); Apple apple2 = new Apple(); appThreadLocal.set(apple1); appThreadLocal.set(apple2); ... ... appThreadLocal.get(); // apple1 appThreadLocal.get(); // apple2 </code></pre> <p>在整个 Thread.java 的源码中,只有 exit() 方法使用到了 threadLocals</p> <pre><code class="language-java">private void exit() { ... /* Speed the release of some of these resources */ threadLocals = null; ... } </code></pre> <p>而 threadLocalMap 的初始化,是在第一个 ThreadLocal 对象调用 set 或 get 的时候发生的</p> <pre><code class="language-java">/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } </code></pre> <p>我们来看一下set方法是如何执行的</p> <pre><code class="language-java">/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } </code></pre> <p>从 <code>map.set(this, value);</code> 可以看到,我们把要存储的值放到了 key 为当前 ThreadLocal 对象的 ThreadLocalMap 中去了。取值的时候也是从这个 map 中取,所以问题的关键就在于 ThreadLocalMap 的实现了。</p> <h2 id="id-threadlocalmap-的实现">ThreadLocalMap 的实现</h2> <p>跟 HashMap 类似,也是用数组来实现的</p> <pre><code class="language-java">private Entry[] table; </code></pre> <p>然而这里的Entry 是 WeakReference 的子类</p> <pre><code class="language-java">/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal&lt;?&gt; k, Object v) { super(k); value = v; } } </code></pre> <p>由于弱引用的特性,当 ThreadLocal 对象不可达的时候,ThreadLocalMap 的 key 就会被 GC 回收掉。试想,如果这里不是弱引用,而是强引用,那么 ThreadLocal 对象将永远不会被回收,除非线程终止。因为 Thread 持有 threadLocals,threadLocals 的 Entry 因为不是弱引用,就会持有 threadLocal 对象的强引用,如果不显式调用 ThreadLocal 的 remove 去掉这里的强引用,<strong>线程生命周期内,threadLocal 就不会被回收</strong>。当线程结束后,Thread 的 exit() 方法会执行 <code>threadLocals = null</code> 使得线程内所有的 ThreadLocal 对象以及其上面带有的 value 不可达,导致被回收。不过 exit 方法的注释上说,这里只是为了<em>加速</em> 资源的释放,即便线程执行 exit 的时候不释放,线程本身最终也会不可达,所有线程相关的资源最终还是会被回收。</p> <p><strong>我再画一幅图解释一下:(人类的本质是复读机!)</strong></p> <pre><code class="language-java">Thread t // 某一个线程 ↓ ThreadLocal.ThreadLocalMap t.threadLocals // 线程持有的 Map 对象 ↓ Entry table[i] // Map 持有的某一个 Entry 对象 </code></pre> <ol> <li> <p>假设 Entry 持有 key 和 value 的强引用,那么如果 ThreadLocalMap 不自己清理,key 和 value 就会伴随整个 Thread 的生命周期。</p> </li> <li> <p>假设 Entry 是 key 的弱引用,那么 key 不可达时(也就是 threadLocal 不可达时),key 会被 gc,<strong>value 仍然伴随 Thread 的生命周期。</strong></p> </li> </ol> <p>那么 ThreadLocal 对象在什么时候才会不可达呢?最简单的情况是,离开了函数调用栈空间了,我们来看下面这个例子</p> <pre><code class="language-java">public static void test() { ThreadLocal&lt;String&gt; t = new ThreadLocal&lt;&gt;(); t.set("abc"); } public static void main(String[] args) { new Thread(() -&gt; { test(); // 1 System.gc(); // 2 }).start(); } </code></pre> <p>在线程执行完 test() 方法后,执行 System.gc() 之前,也就是上述代码注释 1 的位置,当前线程的 threadLocals 对象如下:<br /> <img src="http://image.ysmull.cn/2019-08-03-123120.png" alt="" /><br /> 可以看见 ThreadLocalMap 的 Entry 的 referent 和 value 都还在,当执行 System.gc() 后,我们看一下有什么变化:<br /> <img src="http://image.ysmull.cn/2019-08-03-123316.png" alt="" /><br /> 此时由于 threadLocal 对象已经不可达,所以会被 gc 回收。然而,<strong>value 对象还没回收呢</strong>!</p> <p>怎样才能让 value 对象也可以回收呢?<strong>上面已经说过很多次了</strong>,value 对象被回收当且仅当:</p> <ol> <li>ThreadLocaMap 自己清理</li> <li>Thread 对象被回收</li> </ol> <p>下面我们来讲解 ThreadLocalMap 什么情况下才会自己清理 key 为 null 的 Entry</p> <p>在 ThreadLocalMap 类中 ,真正可以回收 value (以及 key) 的方法是 expungeStaleEntry(int staleSlot) 方法:</p> <pre><code class="language-java">private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null ... } </code></pre> <p>这个方法的实现是比较复杂的,不仅直接删除指定 index 的元素,并且在 rehash 的过程中也会清理一些其它的元素。</p> <p>我们通过 IDE 分析调用链可以知道,只有在 ThreadLocal 的 get 、set、remove 方法被调用时,才有可能会进行清理。</p> <h2 id="id-注意事项">注意事项</h2> <p>我们使用 ThreadLocal 最常见的场景是把 ThreadLocal 作为一个类的静态字段(即便不是静态字段,在 IOC 环境下,也可能是个全局的单例对象)多个线程共用同一个 ThreadLocal 对象。(也就是使用同一个 ThreadLocal 对象作为 key 放到各自线程 ThreadLocalMap 里)。</p> <p>在这种场景下,ThreadLocal 对象的生命周期与承载它的对象的生命周期相同,<strong>在大多数 Web 场景下,这个 ThreadLocal 对象几乎在整个应用的生命周期中都是可达的</strong>。所以每个线程各自的 ThreadLocalMap 的 key 并不会因为使用了弱引用而被 GC 回收掉。我们只能够在每个线程中<strong>手动调用 ThreadLocal 的 remove 方法</strong>对 key 和 value 进行置 null,使得 GC 可以清理掉线程持有的 value(GC 清理不了 key,因为 key 总是可达)。如果不手动调用 remove,那么只有在线程被清理的时候,value 才会被清理(这里说了很多遍了,看不懂请重读上文)。</p> <p>同时,在 web 场景下,大多数 http server 会使用到线程池,如果在上一次请求中没有主动调用 remove 进行清理,线程池为下一次请求分配的可能是同一个线程,那么调用 ThreadLocal 的 get 方法拿到的就是上一次线程存储的值,这可能会导致错误的业务逻辑。</p> <p>因此,我们在使用 ThreadLocal 时,应该总是在当前线程结束前手动调用 remove 方法来清理这个线程的 ThreadLocalMap,否则,是有可能内存泄漏的(线程池中的每个线程的 threadLocals 都持有一个相同的 key 的 Entry,以及一个无用的 value)。</p> 2019-08-03T19:35:00+00:00 https://ysmull.cn/blog/thraedLocal.html https://ysmull.cn/blog/thraedLocal.html 「细谈」Java 中的弱引用(WeakReference) <ul id="markdown-toc"> <li><a href="#id-弱引用weak-reference" id="markdown-toc-id-弱引用weak-reference">弱引用(Weak Reference)</a> <ul> <li><a href="#id-几种典型场景" id="markdown-toc-id-几种典型场景">几种典型场景</a></li> </ul> </li> <li><a href="#id-referencequeue" id="markdown-toc-id-referencequeue">ReferenceQueue</a> <ul> <li><a href="#id-特殊情况" id="markdown-toc-id-特殊情况">特殊情况</a></li> </ul> </li> </ul> <p>本文只讲弱引用,文章中的内容均来自本人的实践的总结,某些观点不常见于其它文章。<br /> 该文是理解 ThreadLocal 的基础。</p> <h2 id="id-弱引用weak-reference">弱引用(Weak Reference)</h2> <pre><code class="language-java">WeakReference&lt;T&gt; weakRef = new WeakReference&lt;&gt;(referent); </code></pre> <p>当发生 gc 时, 如果 referent 对象满足下述条件则一定会被回收:</p> <ol> <li><code>referent</code> 没有强引用</li> <li><code>referent</code> 没有软引用</li> </ol> <h3 id="id-几种典型场景">几种典型场景</h3> <ul> <li>最简单的例子 <pre><code class="language-java">WeakReference&lt;List&gt; arrRef = new WeakReference&lt;&gt;(Arrays.asList(1,2,3)); System.out.println(arrRef.get()); // [1, 2, 3] System.gc(); System.out.println(arrRef.get()); // null </code></pre> </li> <li>有强引用无法回收 <pre><code class="language-java">List&lt;Integer&gt; arr = Arrays.asList(1,2,3); WeakReference&lt;List&gt; arrRef = new WeakReference&lt;&gt;(arr); System.out.println(arrRef.get()); // [1, 2, 3] System.gc(); System.out.println(arrRef.get()); // [1, 2, 3] 还有 arr 强引用,无法回收 arr = null; System.gc(); System.out.println(arrRef.get()); // null </code></pre> </li> <li>final 的东西无法回收 <pre><code class="language-java">WeakReference&lt;String&gt; strRef1 = new WeakReference&lt;&gt;(new String("abc")); System.gc(); System.out.println(strRef1.get()); // null WeakReference&lt;String&gt; strRef2 = new WeakReference&lt;&gt;("abc"); System.gc(); System.out.println(strRef2.get()); // abc </code></pre> </li> <li>WeakReference 数组内的元素会被回收(弱引用数组的每个 item 都是弱引用) <pre><code class="language-java">WeakReference&lt;String&gt; a = new WeakReference&lt;&gt;(new String("aaa")); WeakReference&lt;String&gt; b = new WeakReference&lt;&gt;(new String("bbb")); WeakReference[] tab = new WeakReference[] {a, b}; System.out.println(a.get()); // aaa System.out.println(b.get()); // bbb System.gc(); System.out.println(a.get()); // null System.out.println(b.get()); // null </code></pre> </li> </ul> <h2 id="id-referencequeue">ReferenceQueue</h2> <p>用来监视被引用的对象是否已经被回收了。下面我们用 referenceQueue 来探究一下 WeakReference 的回收。</p> <p><strong><em>(为求代码清晰,下面的代码一律不捕获 <code>InterruptedException</code>)</em></strong></p> <p>首先创建一个 referenceQueue, 在另一个线程中调用 remove,该调用是阻塞调用,如果有引用被回收,那么会调用成功,返回该该引用的 this。<br /> 最简单的例子:</p> <pre><code class="language-java">ReferenceQueue&lt;String&gt; referenceQueue = new ReferenceQueue&lt;&gt;(); // 监视线程 new Thread(() -&gt; { Reference remove = referenceQueue.remove(); System.out.println("remove:" + remove); }).start(); WeakReference&lt;String&gt; ref = new WeakReference&lt;&gt;(new String("111"), referenceQueue); System.gc(); // remove:java.lang.ref.WeakReference@322b4b46 </code></pre> <h3 id="id-特殊情况">特殊情况</h3> <ol> <li>下例说明,<strong>不把 new 出来的 WeakReference 赋值给任何变量,那么可能虚拟机可能当场就回收了</strong>,因为 referenceQueue 并没有捕获到回收消息。 <pre><code class="language-java">1 ReferenceQueue&lt;String&gt; referenceQueue = new ReferenceQueue&lt;&gt;(); // 监视线程 new Thread(() -&gt; { Reference remove = referenceQueue.remove(); System.out.println("remove:" + remove); }).start(); new WeakReference&lt;&gt;(new String("111"), referenceQueue); System.gc(); // 什么都不打印 </code></pre> </li> <li>下例说明,<strong>显式调用 System.gc() 的线程,如果没有在该线程的任何地方使用到这个 ref,那么并不会触发 ref 的回收</strong>。想要 ref 能够被回收,那么需要在 gc 线程的任意一个位置出现 ref。 <pre><code class="language-java">ReferenceQueue&lt;String&gt; referenceQueue = new ReferenceQueue&lt;&gt;(); // 监视线程 new Thread(() -&gt; { Reference remove = referenceQueue.remove(); // 不会捕获到引用的回收 System.out.println("remove:" + remove); }).start(); WeakReference&lt;String&gt; ref = new WeakReference&lt;&gt;(new String("111"), referenceQueue); // gc 线程 new Thread(() -&gt; { // Object a = ref; // 取消注释,则可以捕获到回收 Thread.sleep(1000); System.out.println("gc"); System.gc(); // 在另一个线程 System.gc() }).start(); </code></pre> </li> </ol> 2019-08-03T11:13:00+00:00 https://ysmull.cn/blog/reference.html https://ysmull.cn/blog/reference.html 如何加速你的 count 查询 <ul id="markdown-toc"> <li><a href="#id-背景" id="markdown-toc-id-背景">背景</a></li> <li><a href="#id-问题" id="markdown-toc-id-问题">问题</a></li> <li><a href="#id-解决" id="markdown-toc-id-解决">解决</a></li> <li><a href="#id-延伸" id="markdown-toc-id-延伸">延伸</a></li> </ul> <h2 id="id-背景">背景</h2> <p>项目中,经常会有一些统计类的查询,大概模式是:</p> <pre><code class="language-SQL">select count(*) from table_name where ... </code></pre> <p>如何加速此类查询,大部分人首先想到的办法可能就是,「根据 where 条件」给表加索引。<br /> 那么如果是整表 count 呢,还有提高速度的办法吗?答案是,有的。</p> <h2 id="id-问题">问题</h2> <p>线上有一张 news 表,会有爬虫不断的向该表插入爬取的新闻数据(所以下文中每次count的结果总不相同是因为数据确实增加了)</p> <pre><code class="language-SQL">create table news ( id int auto_increment primary key, type varchar(20) default '' not null comment '新闻分类', title text null, description text null, content mediumtext null, pics json null, source varchar(255) null, create_time timestamp default CURRENT_TIMESTAMP null ) collate = utf8mb4_unicode_ci; </code></pre> <p>直接执行</p> <pre><code class="language-SQL">mysql&gt; select count(*) from news; +----------+ | count(*) | +----------+ | 678532 | +----------+ 1 row in set (1 min 3.57 sec) mysql&gt; select count(*) from news; +----------+ | count(*) | +----------+ | 678538 | +----------+ 1 row in set (50.18 sec) </code></pre> <p>可以看到,执行该 SQL 非常的耗时,我们查看一下执行计划:</p> <pre><code class="language-SQL">mysql&gt; explain select count(*) from news \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: news partitions: NULL type: index possible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 440632 filtered: 100.00 Extra: Using index 1 row in set, 1 warning (0.00 sec) </code></pre> <p>执行计划说,该查询使用了主键索引,由于是主键索引,该查询自然而然的也是覆盖索引,并且 <code>filtered</code> 也是 <code>100.00</code>。那么如何对这句SQL进行加速呢?</p> <h2 id="id-解决">解决</h2> <p>由于 count(*) 类型的查询不需要去任何一列的数据,主键索引(clustered index)的叶子节点存储了完整的行数据(上例中甚至还有很多的 blob 类字段,数据页都存储不下了),遍历主键索引会带来较大的磁盘 IO,所以解决办法就呼之欲出了———随便找个列,建一个小巧的辅助索引。</p> <p>我们选择 source 列来建索引。</p> <pre><code class="language-SQL">create index news_source_index on news (source); </code></pre> <p>索引建好后,我们查看执行计划:</p> <pre><code class="language-SQL">mysql&gt; explain select count(*) from news \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: news partitions: NULL type: index possible_keys: NULL key: news_source_index key_len: 1023 ref: NULL rows: 440697 filtered: 100.00 Extra: Using index 1 row in set, 1 warning (0.02 sec) </code></pre> <p>根据 Extra 字段,你会发现,该查询仍然是覆盖索引,但是 InnoDB 选择了使用我们刚刚创建的辅助索引,这是因为,<strong>辅助索引远小于主键索引</strong>,遍历辅助索引的代价较小一些。再一次执行查询:</p> <pre><code class="language-SQL">mysql&gt; select count(*) from news; +----------+ | count(*) | +----------+ | 678129 | +----------+ 1 row in set (1.32 sec) mysql&gt; select count(*) from news; +----------+ | count(*) | +----------+ | 678129 | +----------+ 1 row in set (0.09 sec) </code></pre> <p>连续两次执行该SQL的时间分别从 <em><code>1 min 3.57 sec</code>、<code>50.18 sec</code></em> 变成了 <em><code>1.32 sec</code>、<code>0.09 sec</code></em></p> <h2 id="id-延伸">延伸</h2> <p>上文讲解的例子是一个没有 where 条件的 SQL,提高 <code>count(*)</code> 速度的方法是随便建一个代价较小的辅助索引。如果是带有查询条件的查询呢?我们是不是一定要建立精准命中查询条件的索引呢?答案是否定的。</p> <p><strong>对于 count 型查询,我们不一定需要一个完美切合查询条件的索引。</strong></p> <p>下面用一个例子来说明,首先还是举一个创建精确索引的例子。</p> <p>还是上面的 news 表,我们想执行一个范围查询:</p> <pre><code class="language-SQL">mysql&gt; select count(*) from news where create_time &gt; curdate(); +----------+ | count(*) | +----------+ | 996 | +----------+ 1 row in set (1 min 44.76 sec) </code></pre> <p>该查询用不到任何索引,所以速度非常慢,因此,我们首先想到的是,给 <code>create_time</code> 字段建立索引。</p> <pre><code class="language-SQL">mysql&gt; create index news_create_time_index on news (create_time); Query OK, 0 rows affected (1 min 16.94 sec) Records: 0 Duplicates: 0 Warnings: 0 </code></pre> <p>索引建好后,我们查看执行计划,可以用上这个索引,并且查询时间已经可以忽略不计了:</p> <pre><code class="language-SQL">mysql&gt; explain select count(*) from news where create_time &gt; curdate() \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: news partitions: NULL type: range possible_keys: news_create_time_index key: news_create_time_index key_len: 5 ref: NULL rows: 1104 filtered: 100.00 Extra: Using where; Using index 1 row in set, 1 warning (0.00 sec) mysql&gt; select count(*) from news where create_time &gt; curdate(); +----------+ | count(*) | +----------+ | 1104 | +----------+ 1 row in set (0.01 sec) </code></pre> <p><strong><em>(注意下面的转折)</em></strong></p> <p>然而,假设如果这张表已经有了<strong>包含 create_time 的联合索引</strong>,并且 create_time <strong>不是联合索引的第一列</strong>,那么 InnoDB 可否使用那个联合索引来优化这个查询呢?</p> <p>比如假设我们已经有了(source, create_time) 的联合索引,仅仅看 where 条件,是对 create_time 进行范围查找,显然是用不上这个联合索引的,因为联合索引只有第一列是有序的,其它列只相对于前一列保持有序。下面我们来实验一下,看看实际情况如何。</p> <p>首先我们删除已经存在的 create_time 的索引,然后建立一个 (source, create_time) 的联合索引:</p> <pre><code class="language-SQL">mysql&gt; create index news_source_create_time_index on news (source, create_time); Query OK, 0 rows affected (40.95 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql&gt; drop index news_create_time_index on news; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 </code></pre> <p>(吃了个中饭回来)<br /> 然后我们看一看这条查询的执行计划:</p> <pre><code class="language-SQL">mysql&gt; explain select count(*) from news where create_time &gt; curdate() \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: news partitions: NULL type: index possible_keys: news_source_create_time_index key: news_source_create_time_index key_len: 1028 ref: NULL rows: 441733 filtered: 33.33 Extra: Using where; Using index 1 row in set, 1 warning (0.03 sec) </code></pre> <p>根据执行计划我们发现,对 create_time 的范围查询,竟然可以用到 (source, create_time) 的联合索引,实际执行该查询:</p> <pre><code class="language-SQL">mysql&gt; select count(*) from news where create_time &gt; curdate(); +----------+ | count(*) | +----------+ | 1173 | +----------+ 1 row in set (0.27 sec) </code></pre> <p>查询仍然非常的高效,这是为什么呢?</p> <p>原因在于,尽管 create_time 是无序的,但是对于 count(*) 类型的查询,我们需要使用到的字段其实只有 create_time 而已,而辅助索引 (source, create_time) 的 B+ 树中,已经包含了所有的 create_time 信息,可以「覆盖」我们的查询条件,足够我们对「记录数」进行统计,因此,不需要去遍历巨大的主键索引的 B+ 树。然而,由于 create_time 是无序的,我们仍然需要对所有的索引进行排序之后再筛选出满足条件的记录,所以 Extra 中包含了 <code>Using where</code>,但这比直接在主键索引中进行筛选,已经减少了很多的磁盘IO,所以仍然保持了查询性能。</p> 2019-07-28T00:00:00+00:00 https://ysmull.cn/blog/count.html https://ysmull.cn/blog/count.html InnoDB存储引擎之 —— 索引算法(一)(草稿...) <p>InnoDB 存储引擎表示索引组织表,表中数据按照主键顺序存放。<br /> 索引应该在一开始开发的时候就进行添加。<br /> 索引太少,影响性能。索引太多,也影响性能,比如太多索引会导致磁盘使用率非常高。</p> <p>索引的类型</p> <ol> <li>B+ 树索引<br /> 传统意义上的索引,最常用,最有效。<br /> B+ 树索引不能定位到具体的行,只能定位到具体的页,然后把页读到内存中查找满足条件的行。</li> <li>全文索引</li> <li>哈希索引<br /> InnoDB 的哈希索引是自适应的,会根据表的使用情况自动为表生成,不能认为干预。</li> </ol> <p>背景知识:<br /> 二分查找:<br /> 第一个二分查找在1946年出现,第一个正确的二分查找法在1962年才出现。</p> <p>B+树:<br /> 省略</p> <p>B+树索引:</p> <ol> <li> <p>聚集索引(clustered index)<br /> 根据每张表的主键构建的一颗B+树(一张表只能有一个聚集索引),叶子节点存放的是一整行的信息。聚集索引的叶子也称作<strong>数据页</strong>,每个数据页通过双向链表链接。</p> </li> <li> <p>辅助索引(secondary index)<br /> 叶子节点存放的<strong>不是</strong>一整行的信息。</p> </li> </ol> 2019-07-27T00:00:00+00:00 https://ysmull.cn/blog/key1.html https://ysmull.cn/blog/key1.html InnoDB存储引擎之 —— 锁(二) <ul id="markdown-toc"> <li><a href="#id-锁的算法" id="markdown-toc-id-锁的算法">锁的算法</a> <ul> <li><a href="#id-锁的例子" id="markdown-toc-id-锁的例子">锁的例子</a></li> </ul> </li> <li><a href="#id-阻塞超时" id="markdown-toc-id-阻塞超时">阻塞&amp;超时</a></li> <li><a href="#id-死锁" id="markdown-toc-id-死锁">死锁</a> <ul> <li><a href="#id-死锁例子" id="markdown-toc-id-死锁例子">死锁例子</a></li> </ul> </li> <li><a href="#id-锁升级" id="markdown-toc-id-锁升级">锁升级</a></li> </ul> <h2 id="id-锁的算法">锁的算法</h2> <ol> <li> <p>Record Lock<br /> 只锁住行。总是去锁定索引,如果没有建索引,就锁主键。</p> </li> <li> <p>Gap Lock<br /> 锁住一个范围,不包含记录本身</p> </li> <li> <p>Next-Key Lock (Previous-Key Lock)<br /> 锁住一个范围,包含记录本身。</p> </li> </ol> <p>假设一个索引有10,11,13,20这四个值。那么有如下五个区间:</p> <pre><code class="language-text">(-∞, 10) (10, 11) (11, 13) (13, 20) (20, +∞) </code></pre> <ul> <li><code>Gap Lock</code> 锁的就是上述区间。</li> <li><code>Next-Key Locking</code> 锁的是上述区间的左开右闭区间。</li> <li><code>Previous-Key Locking</code> 锁的是上述区间的左闭右开区间。</li> </ul> <p>InnoDB 加锁有如下三条特殊规则:</p> <ol> <li><strong>当查询的索引仅含有唯一索引的时候,<code>Next-Key Lock</code> 会降级为 <code>Record Lock</code>。</strong>(联合唯一索引,每一个索引列都要查)</li> <li><strong>InnoDB 还会对锁住的辅助索引加 <code>Next-Key Lock</code>,并且会给下一个键值加 <code>Gap Lock</code></strong></li> <li><strong>插入某记录时候,会检查插入记录的下一条记录是否被锁住了,如果锁住了,则不允许插入(阻塞)。</strong></li> </ol> <p>InnoDB 在默认的 REPETABLE READ 隔离级别下,使用 Next-Key Lock,在 READ COMMITED 隔离级别下,仅使用 Record Lock</p> <h3 id="id-锁的例子">锁的例子</h3> <p>下面举几个例子来讲解:</p> <pre><code class="language-SQL">CREATE TABLE z (a INT, b, INT, PRIMARY KEY(a), KEY(b)) INSERT INTO z SELECT 1, 1; INSERT INTO z SELECT 3, 1; INSERT INTO z SELECT 5, 3; INSERT INTO z SELECT 7, 6; INSERT INTO z SELECT 10, 8 </code></pre> <p><strong>会话A</strong>:执行</p> <pre><code class="language-SQL">SELECT * FROM z WHERE b=3 FOR UPDATE; </code></pre> <p>我们来分析会锁住哪些记录:<br /> a 是聚集索引,加 Record Lock<br /> b 是辅助索引,加 Next-Key Lock,下一个键值加 Gap Lock</p> <p>索引 a 被锁住的记录为:</p> <ul> <li>5 — Record Lock</li> </ul> <p>索引 b 被锁住的记录为:</p> <ul> <li>(1, 3] — Next-Key Lock</li> <li>(3, 6) — Gap Lock</li> </ul> <p><strong>会话B</strong>:如果执行下面这些 SQL ,则都会被阻塞:</p> <ul> <li> <pre><code class="language-SQL">SELECT FROM z WHERE a = 5 LOCK IN SHARE MODE; </code></pre> <p><strong>原因:</strong>a = 5 的索引已经被加了 Record Lock 的排它锁,所以无法再加一个共享锁了。</p> </li> <li> <pre><code class="language-SQL">INSERT INTO z SELECT 4, 2; </code></pre> <p><strong>原因:</strong> 2 落在区间 <code>(1, 3]</code> Next-Key Lock 中</p> </li> <li> <pre><code class="language-SQL">INSERT INTO z SELECT 6, 5; </code></pre> <p><strong>原因:</strong> 5 落在区间 <code>(3, 6)</code> Gap Lock 中</p> </li> <li> <pre><code class="language-SQL">INSERT INTO z SELECT 2, 2; </code></pre> <p><strong>原因:</strong> 欲插入 b = 2 的记录,而 b = 3 的记录已经被锁住了。因此插入被阻塞。插入 b = 0 的记录没有问题,因为 b = 1 没有被锁。(<em>实践发现这里 b 等于 1 的记录也可以插入,但是 b = 2 的记录被 next-key lock 锁住了呀,为什么呢?</em>)</p> </li> </ul> <p>实践了一些其它例子:</p> <ul> <li> <pre><code class="language-SQL">select * from z where b &gt; 3; </code></pre> <p>不会锁住 a = 5 的记录。同时 b 的 next-key lock 区间为 (3, +∞)<br /> 可以给 b = 3 的记录加 S 锁 : <code>select * from z where b = 3 lock in share mode;</code><br /> 不可以 insert b = 3 的记录: <code>INSERT INTO z SELECT 2, 3;</code></p> </li> <li> <pre><code class="language-SQL">select * from z where b &gt;= 3; </code></pre> <p>会锁住 a = 5 的记录。同时 b 的 next-key lock 区间为 [3, +∞)<br /> 不可以给 b = 3 的记录加 S 锁 : <code>select * from z where b = 3 lock in share mode;</code><br /> 不可以 insert b = 3 的记录: <code>INSERT INTO z SELECT 2, 3;</code></p> </li> </ul> <h2 id="id-阻塞超时">阻塞&amp;超时</h2> <p><strong>错误代码:</strong> 1205</p> <ul> <li><strong>innodb_lock_wait_timeout</strong>: 用来控制等待的时间(默认是 50 秒),<strong>可以在 MYSQL 数据库运行时进行调整</strong>。</li> <li><strong>innodb_rollback_on_timeout</strong>: 用来设定是否在等待超时时对进行中的事务进行回滚操作(默认是 OFF,代表不回滚)。<strong>不可在启动后进行修改。</strong></li> </ul> <p>当发生超时,MYSQL 数据库会抛出一个 1205 的错误,事务出现异常,在大多数情况下不会自动回滚,需要应用层自己去控制是commit还是rollback。</p> <h2 id="id-死锁">死锁</h2> <p>多个事务因争夺锁资源,造成相互等待,若无外力作用,无法推进下去。<br /> <strong>错误代码:</strong> 1213<br /> 两种方案:</p> <ol> <li>超时机制<br /> FIFO。谁先等待,谁先回滚。</li> <li>wait-for graph<br /> <strong>主动监测死锁,回滚 undo 代价最小的事务</strong>。<br /> 具体实现细节需要看源码,可能是通过如下两个数据结构去构造有向图: <ul> <li>Transaction Wait Lists</li> <li>Lock Lists<br /> <img src="http://image.ysmull.cn/2019-07-25-100908.png" alt="" /><br /> 死锁检测通常采用深度优先的算法实现,在 INNODB1.2 版本之前,都是采用递归方式实现。而从 1.2 版本开始,对 wait- for graph 的死锁检测进行了优化,用非递归的方式实现。</li> </ul> </li> </ol> <h3 id="id-死锁例子">死锁例子</h3> <ul> <li>例子一<br /> <img src="http://image.ysmull.cn/2019-07-25-101419.png" alt="" /></li> <li>例子二<br /> <img src="http://image.ysmull.cn/2019-07-25-101438.png" alt="" /></li> </ul> <h2 id="id-锁升级">锁升级</h2> <blockquote> <p>锁升级(Lock Escalation)是指将当前锁的粒度降低。举例来说,数据库可以把个表的 1000 个行锁升级为一个页锁,或者将页锁升级为表锁。如果在数据库的设计中认为锁是一种稀有资源,而且想避免锁的开销,那数据库中会频繁出现锁升级现象。<br /> Microsoft SQL Server 数据库的设计认为锁是一种稀有的资源,在适合的时候会自动地将行、键或分页锁升级为更粗粒度的表级锁。这种升级保护了系统资源,防止系统使用太多的内存来维护锁,在一定程度上提高了效率。<br /> 即使在 Microsoft SQL Server20 版本之后,SQL Server 数据库支持了行锁,但是其设计和 INNODB 存储引擎完全不同,在以下情况下依然可能发生锁升级:</p> <ol> <li>由一句单独的 SQL 语句在一个对象上持有的锁的数量超过了阈值,默认这个阈值为 5000。值得注意的是,如果是不同对象,则不会发生锁升级口</li> <li>锁资源占用的内存超过了激活内存的 40%时就会发生锁升级</li> </ol> <p>在 Microsoft SQL Server 数据库中,由于锁是一种稀有的资源,因此锁升级会带来一定的效率提高。但是锁升级带来的一个问题却是因为锁粒度的降低而导致并发性能的降低。</p> <p>INNODB 存储引擎不存在锁升级的问题。因为其不是根据每个记录来产生行锁的,相反,其根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。因此不管个事务锁住页中一个记录还是多个记录,其开销通常都是一致的。</p> </blockquote> 2019-07-25T00:00:00+00:00 https://ysmull.cn/blog/lock2.html https://ysmull.cn/blog/lock2.html 项目中遇到的锁与数据一致性问题 <ul id="markdown-toc"> <li><a href="#id-背景" id="markdown-toc-id-背景">背景</a></li> <li><a href="#id-问题" id="markdown-toc-id-问题">问题</a></li> <li><a href="#id-解决方案" id="markdown-toc-id-解决方案">解决方案</a> <ul> <li><a href="#id-方案一-外键约束" id="markdown-toc-id-方案一-外键约束">方案一: 外键约束</a></li> <li><a href="#id-方案二-一致性锁定读" id="markdown-toc-id-方案二-一致性锁定读">方案二: 一致性锁定读</a> <ul> <li><a href="#id-方案二的证明" id="markdown-toc-id-方案二的证明">方案二的证明</a></li> <li><a href="#id-方案二死锁举例" id="markdown-toc-id-方案二死锁举例">方案二死锁举例</a></li> </ul> </li> </ul> </li> </ul> <h2 id="id-背景">背景</h2> <p>项目表: project(id, name)</p> <p>报告表: report(id, project_id, name)</p> <p>有两个操作:</p> <ol> <li>操作一:删除项目 A (id = 1) <pre><code class="language-SQL">delete from project where id = 1; delete from report where project_id = 1; </code></pre> </li> <li>操作二:给项目 A 新增一个报告 <pre><code class="language-SQL">insert into report(`project_id`, `name`) values(1, '报告1'); </code></pre> </li> </ol> <h2 id="id-问题">问题</h2> <p>如何保证两个操作并行进行之后的数据一致性,即:操作一和操作二都结束后,数据库里没有项目 ID = 1 的报告。</p> <h2 id="id-解决方案">解决方案</h2> <h3 id="id-方案一-外键约束">方案一: 外键约束</h3> <p><strong>给 report 表的 project_id 字段加外键。</strong><br /> 在有外键之后,在插入 report 的时候,会尝试获取 project 表的相关记录的 X 锁。</p> <ol> <li>如果此时可以获取锁,那么此时操作一一定还没有执行删除项目操作,报告会添加成功。随后操作一在删除项目的时候,会把该报告删除。</li> <li>如果此时不能获取锁,说明操作一正在删除该插入报告所在的项目。等操作一的事务提交后,操作二会插入失败,报外键错误。</li> </ol> <p>注意,此时操作一所在的事务,SQL一定得按如下顺序执行,先删除依赖,否则报外键错误</p> <pre><code class="language-SQL">delete from report where project_id = 1; delete from project where id = 1; </code></pre> <h3 id="id-方案二-一致性锁定读">方案二: 一致性锁定读</h3> <p>操作二使用如下逻辑操作,在插入报告之前,先判断是否存在该项目,查询时给项目加上 S 锁(也可以 for update 加上 X 锁)。我们用存储过程描述这个过程:</p> <pre><code class="language-SQL">DROP PROCEDURE IF EXISTS insert_report; CREATE procedure insert_report(IN pid int, IN title varchar(255)) BEGIN start transaction ; SELECT id into @project_id FROM project WHERE id = 1 lock in share mode; if @project_id is not null then insert into new_report(`project_id`, `name`) value (pid, title); end if; commit; END; call insert_report(1, '报告1'); </code></pre> <p>事务一的两句SQL语句还可以交换顺序,两个事务的操作排列组合之后非常多,我们怎么知道在任何执行顺序下,这样可以解决数据一致性问题呢?</p> <h4 id="id-方案二的证明">方案二的证明</h4> <p><strong>证明:</strong><br /> 当事务二执行 select id from project where id = 1 lock in share mode 返回结果后,到事务二commit之前的这段时间之内</p> <p>事务一要么「<strong>已经</strong>删除」了 project 表中 id = 1 的记录,要么还在「<strong>等待</strong>删除」 project 表中 id = 1 的记录。</p> <ol> <li> <p>事务一「已经删除」 project 表中 id 为 1 的记录<br /> 那么事务二不会执行 insert 语句添加 project_id 为 1 的 记录,最终保持了数据一致。</p> </li> <li> <p>事务一在「等待删除」project 表中 id 为 1 的记录,那么事务二接下来将插入 project_id=1 的 report</p> <ul> <li>2.1 <em>如果事务一已经删除了 project_id=1 的report</em><br /> 那么事务二无法插入 project_id=1 的 report ,导致死锁,触发 MySQL 回滚。</li> <li>2.2 <em>如果事务一还没有删除 project_id=1 的report</em><br /> 那么事务二可以插入 project_id=1 的 report ,并且可以commit,事务二comimt后,会释放 project 表中 id = 1 的记录的 S 锁,从而事务一可以拿到 project 中 id = 1 的记录的 X 锁,删除 project 表中 id= 1 的记录,然后删除 report 表中所有 project_id = 1 的记录。</li> </ul> <p>无论是 2.1 还是 2.2,最终数据库中没有 id=1 的 project,也没有 project_id=1 的 report,保持了一致性。</p> </li> </ol> <h4 id="id-方案二死锁举例">方案二死锁举例</h4> <p>上面的证明提到了可能会带来死锁,下面给出死锁的情况。<br /> 考虑如下的操作序列</p> <table> <thead> <tr> <th>事务1</th> <th>事务2</th> </tr> </thead> <tbody> <tr> <td><code>delete from report where project_id = 1; -- 给 report 表加了 X 锁</code></td> <td> </td> </tr> <tr> <td> </td> <td>SELECT id into @project_id FROM project WHERE id = 1 lock in share mode; – 给 project 表加了 S 锁</td> </tr> <tr> <td>delete from project where id = 1; – 尝试获取获取 project 表的 X 锁,<strong>阻塞</strong></td> <td> </td> </tr> <tr> <td> </td> <td>insert into report(project_id, name) value (1, ‘报告1’); –尝试获取 report 表的 X 锁,<strong>阻塞</strong></td> </tr> </tbody> </table> <p>这样就导致了死锁。还好 MySQL 会自动回滚其中一个事务,算是保证了数据一致性。</p> 2019-07-25T00:00:00+00:00 https://ysmull.cn/blog/lock-case.html https://ysmull.cn/blog/lock-case.html 记录一次数据库迁移 <ul id="markdown-toc"> <li><a href="#id-前情提要" id="markdown-toc-id-前情提要">前情提要</a></li> <li><a href="#id-问题发现" id="markdown-toc-id-问题发现">问题发现</a></li> <li><a href="#id-问题解决" id="markdown-toc-id-问题解决">问题解决</a></li> </ul> <h3 id="id-前情提要">前情提要</h3> <p>几个月前帮同学做了个新闻app,后端基本是(Spring Boot + Kotlin)技术栈,「后台管理」的前端用 react ,打包到 spring boot 的 jar 包里一起启动。服务器购买的华为云。</p> <p>整个产品由以下几个模块组成:</p> <ol> <li>爬虫(爬新闻,插入 MySQL 数据库)</li> <li>后台管理(多用户登陆、用户管理、广告管理、报表展示)</li> <li>新闻后端(给安卓客户端提供后端服务)</li> <li>安卓客户端(请另一个同学写的)</li> </ol> <p>同时我使用了<del>给我发工资的</del>优秀的<a href="https://youdata.163.com/">网易有数</a>来监控爬虫情况,每天发送爬虫日报。</p> <h3 id="id-问题发现">问题发现</h3> <p>周一发现,连续三天新闻量没有增加。<br /> <img src="http://image.ysmull.cn/2019-07-23-075249.jpg" alt="" /></p> <p>登陆服务器后发现爬虫服务的日志如下:<br /> <img src="http://image.ysmull.cn/2019-07-23-075250.jpg" alt="" /></p> <p>使用 <code>df -h</code> 命令查看,发现硬盘已经满了(云服务默认40G硬盘)。</p> <h3 id="id-问题解决">问题解决</h3> <p>首先,想着去数据库上删除一些记录来节省空间。结果发现,MySQL 只能执行 SELECT 语句,无法执行 DELETE 语句,哪怕一条记录也删不掉。<strong>(当磁盘满了之后,为什么delete语句也无法执行,有意思的问题)</strong></p> <p>存储新闻的 news 表有十几G,小水管根本备份不下来。删也删不掉,下载也下载不下来,数据又不能全部丢掉,因此只能考虑给硬盘扩容了。</p> <p>华为云上可以在不重启的情况下,给已有的云硬盘扩容。我先花了十几块钱,买了一个 5 GB 的扩容包,发现果然所有服务都恢复正常了(可以正常插入数据)。</p> <p>然后本着折腾的心态,申请了经费,又买了一块单独的 100 GB 云硬盘,决定用这个空白的 100 G 硬盘专门存放 MySQL 数据,防止数据过几天又满了的问题,同事等数据再多些,准备上 ELK 技术栈玩玩。</p> <p><img src="http://image.ysmull.cn/2019-07-23-080933.png" alt="" /><br /> 购买后,使用 <code>fdisk -l</code> 看到已经有新硬盘挂载上来了:<br /> <img src="http://image.ysmull.cn/2019-07-23-075251.jpg" alt="" /></p> <p>依次使用如下的命令初始化硬盘,并挂载硬盘到指定目录:</p> <ol> <li>使用 <code>fdisk /dev/vdb</code> 格式化新硬盘</li> <li>使用 <code>fmkfs.ext4 /dev/vdb</code> 创建分区</li> <li>使用 <code>mount /dev/vdb /root/dbDisk</code> 挂载硬盘到指定目录(/root/dbDisk)</li> </ol> <p>接下来,我们删除 MySQL 容器,把所有 MySQL 的数据文件移动到新目录。修改 docker-compose.yml 文件的 volumes 字段,使用 <code>docker-compose up -d</code> 命令启动 MySQL。</p> <pre><code class="language-yaml">version: "3" services: db: image: "mysql:8.0.16" restart: always command: --max_allowed_packet=20971520 --default-authentication-plugin=mysql_native_password environment: MYSQL_ROOT_PASSWORD: ''****************'' MYSQL_USER: ''****************'' MYSQL_PASSWORD: '****************' MYSQL_DATABASE: 'news' MYSQL_ROOT_HOST: '%' TZ: 'Asia/Shanghai' volumes: - /root/dbDisk/dbData:/var/lib/mysql ports: - '4396:3306' </code></pre> <p>至此,MySQL 数据迁移结束。</p> 2019-07-23T00:00:00+00:00 https://ysmull.cn/blog/mysql-migrate.html https://ysmull.cn/blog/mysql-migrate.html InnoDB存储引擎之 —— 锁(一) <ul id="markdown-toc"> <li><a href="#id-sql-server" id="markdown-toc-id-sql-server">SQL Server</a></li> <li><a href="#id-myisam" id="markdown-toc-id-myisam">MyISAM</a></li> <li><a href="#id-innodb" id="markdown-toc-id-innodb">InnoDB</a> <ul> <li><a href="#id-innodb-锁的类型" id="markdown-toc-id-innodb-锁的类型">InnoDB 锁的类型</a></li> <li><a href="#id-几张查看锁和事务的表" id="markdown-toc-id-几张查看锁和事务的表">几张查看锁和事务的表</a></li> <li><a href="#id-一致性非锁定读" id="markdown-toc-id-一致性非锁定读">一致性非锁定读</a></li> <li><a href="#id-一致性锁定读" id="markdown-toc-id-一致性锁定读">一致性锁定读</a></li> <li><a href="#id-自增锁2种实现方式" id="markdown-toc-id-自增锁2种实现方式">自增锁(2种实现方式)</a> <ul> <li><a href="#id-auto--inc-locking特殊的表锁" id="markdown-toc-id-auto--inc-locking特殊的表锁">AUTO- INC Locking(特殊的表锁)</a></li> <li><a href="#id-互斥量" id="markdown-toc-id-互斥量">互斥量</a></li> </ul> </li> <li><a href="#id-外键与锁" id="markdown-toc-id-外键与锁">外键与锁</a></li> </ul> </li> </ul> <h2 id="id-sql-server">SQL Server</h2> <p>2005 之前是页锁,页锁容易实现,并发写入性能比 MyISAM 要好。但是对于热点数据更新还是无能为力。</p> <p>2005 之后是<code>乐观并发</code>和<code>悲观并发</code>,乐观并发下支持行级锁,但与 InnoDB 的实现方式完全不同。在 SQL Server 下,锁是一种稀缺资源,锁越多开销越大,因此会导致锁升级,行锁会升级到表锁。</p> <h2 id="id-myisam">MyISAM</h2> <p>MyISAM 引擎是表锁设计,并发读性能没问题,并发写性能有问题,若插入在“底部”,还能有一定的并发写入性能。</p> <h2 id="id-innodb">InnoDB</h2> <p>InnoDB 锁的实现与 Oracle 比较像,提供<code>一致性非锁定读</code>和<code>行级锁</code>,行级锁没有额外开销,支持并发性和一致性。</p> <h3 id="id-innodb-锁的类型">InnoDB 锁的类型</h3> <ol> <li>共享锁(S Lock)<br /> 允许事务读一行数据</li> <li>排它锁(X Lock)<br /> 允许事务删除或更改一条数据</li> <li>意向共享锁(IS Lock)<br /> 事务想要获取一张表中某几行的共享锁</li> <li>意向排它锁(IX Lock)<br /> 事务想要获取一张表中某几行的排它锁</li> <li>自增锁</li> </ol> <p><img src="http://image.ysmull.cn/2019-07-20-074233.png" alt="" /><br /> 例如,如果一个表上已经有了S表锁,那么尝试修改该表的几行记录,是不可以的,因为IX锁与S锁是不兼容的。</p> <h3 id="id-几张查看锁和事务的表">几张查看锁和事务的表</h3> <ol> <li>INNODB_TRX<br /> 可以查看事务的状态<br /> <img src="http://image.ysmull.cn/2019-07-20-074836.png" alt="" /></li> <li>INNODB_LOCKS<br /> 可以查看哪些表哪些记录被加了哪些锁。<br /> <img src="http://image.ysmull.cn/2019-07-20-074941.png" alt="" /></li> <li>INNODB_LOCK_WAITS<br /> 已经有了前面两张表,判断事务的等待情况,<strong>可以很清楚的看到一个事务被哪个事务和锁阻塞</strong><br /> <img src="http://image.ysmull.cn/2019-07-20-075100.png" alt="" /></li> </ol> <h3 id="id-一致性非锁定读">一致性非锁定读</h3> <p>InnoDB 存储引擎通过多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会因此去等待行上锁的释放。而是去读取行的一个快照数据,如图所示:<br /> <img src="http://image.ysmull.cn/2019-07-20-075600.png" alt="" /></p> <p>快照数据是指该行的之前版本的数据,该实现是通过 undo 段来完成。而 undo 用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史的数据进行修改操作。</p> <p>快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本。一个行记录可能有不止一个快照数据,一般称这种技术为行多版本技术。由此带来的并发控制,称之为<strong>多版本并发控制(Multi Version Concurrency Control, MVCC)</strong></p> <p><strong>非锁定读机制极大地提高了数据库的并发性</strong>。在事务隔离级别 READ COMMITTED 和 REPEATABLE READ (INNODB 存储引擎的默认事务隔离级别)下,INNODB 存储引擎使用非锁定的一致性读。然而,对于快照数据的定义却不相同。对于 READ COMMITTED 的事务隔离级别,它总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照(<strong>也就是当前事务自己建立的那个快照</strong>)。而对于 REPEATABLE READ 的事务隔离级别,总是读取事务开始时的行数据(<em>这里应该也包含自己建立的快照数据</em>)。</p> <h3 id="id-一致性锁定读">一致性锁定读</h3> <p>如下两种语句必须在事务中使用,当事务提交了,锁也就释放了。</p> <ul> <li><strong>SELECT … FOR UPDATE</strong><br /> 对读取的行记录加一个 X 锁,其他事务不能对已锁定的行加上任何锁。<br /> <strong>即使读取的行已被执行了 SELECT… FOR UPDATE,还是可以进行一致性非锁定读的。</strong></li> <li><strong>SELECT … LOCK IN SHARE MODE</strong><br /> 对读取的行记录加一个 S 锁,其他事务可以向被锁定的行加 S 锁,但是如果加 X 锁,则会被阻塞。</li> </ul> <h3 id="id-自增锁2种实现方式">自增锁(2种实现方式)</h3> <h4 id="id-auto--inc-locking特殊的表锁">AUTO- INC Locking(特殊的表锁)</h4> <p>在 INNODB 存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器(auto-increment counter)。当对含有自增长的计数器的表进行<strong>插入操作时</strong>,这个计数器会被<strong>初始化</strong>,执行如下的语句来得到计数器的值</p> <pre><code class="language-SQL">SELECT MAX (auto inc col) FROM t FOR UPDATE: </code></pre> <blockquote> <p>每张表的自増长值并不保存在磁盘上进行持久化,而是在每次 INNODB 存储引檠启动时初始化。<br /> —— 来自另一本硬核源码书</p> </blockquote> <p>插人操作会依据这个自增长的计数器值加 1 赋予自增长列。这种锁其实是采用一种特殊的表锁机制,为了提高插入的性能,<strong>锁不是在一个事务完成后オ释放,而是在完成对自增长值插入的 SQL 语句后立即释放</strong>。但对于有自增长值的列的并发插人性能较差,事务必须等待前一个插入的完成(虽然不用等待事务的完成)。</p> <h4 id="id-互斥量">互斥量</h4> <p>从 MYSQL5.1.22 版本开始,INNODB 存储引擎中提供了一种轻量级互斥量的自增长实现机制,这种机制大大提高了自增长值插入的性能。并且从该版本开始,INNODB 存储引擎提供了一个参数 <code>innodb_autoinc_lock_mode</code> 来控制自增长的模式,该参数的默认值为 1</p> <h5 id="id-三种插入类型">三种插入类型</h5> <p><img src="http://image.ysmull.cn/2019-07-20-084854.png" alt="" /></p> <h5 id="id-innodb_autoinc_lock_mode-说明">innodb_autoinc_lock_mode 说明</h5> <p><img src="http://image.ysmull.cn/2019-07-20-085106.png" alt="" /></p> <p>需要注意 InnoDB 存储引擎中自增长的实现和 MYISAM 不同。MYISAM 存储引擎是表锁设计,自增长不用考虑并发插人的问题。另外,在 INNODB 存储引擎中,自增长值的列必须是索引,同时必须是索引的第一个列。如果不是第一个列,则 MYSQL 数据库会抛出异常,而 MYISAM 存储引擎没有这问题。</p> <h3 id="id-外键与锁">外键与锁</h3> <p>外键主要用于引用完整性的约束检査。在 INNODB 存储引擎中,对于一个外键列,如果没有显式地对这个列加索引,INNODB 存储引擎自动对其加一个索引,因为这样可以避免表锁,Oracle 数据库不会自动添加索引,用户必须自己手动添加,这也导致了 Oracle 数据库中可能产生死锁。</p> <p>对于外键值的插入或更新,首先需要査询父表中的记录,即 SELECT 父表。但是对于父表的 SELECT 操作,不是使用一致性非锁定读的方式,因为这样会发生数据不一致的问题,因此这时使用的是 SELECT… LOCK IN SHARE MODE 方式,即主动对父表加个 S 锁。如果这时父表上已经加了 X 锁,子表上的操作会被阻塞。</p> 2019-07-20T00:00:00+00:00 https://ysmull.cn/blog/lock1.html https://ysmull.cn/blog/lock1.html Haskell 回顾(一) --- 函子、应用函子、单子 <p>距离上次学习Haskell已经过去八个多月了,以前一起讨论的同事都跑路了,<strong>谨以此系列纪念我优秀的前同事们</strong>。。。</p> <p><strong>函子:</strong>(Functor) 是把一个普通的函数映射为盒子上的函数,或者说,可以把普通函数应用到盒子内的值,列表的fmap 就是 map。</p> <pre><code class="language-haskell">fmap :: Functor f =&gt; (a -&gt; b) -&gt; f a -&gt; f b </code></pre> <p><strong>应用函子:</strong>(Applicative) 首先是个函子,额外提供如下俩操作</p> <ol> <li>把普通值包裹为盒子值,比如普通的list作为应用函子时,pure a = [a] <pre><code class="language-haskell"> pure :: Applicative f =&gt; a =&gt; f a </code></pre> </li> <li> <p>&lt;*&gt; (应用)是把盒子内的函数应用给另一个相同盒子内的值,比如 <code>[\a-&gt;a+1, \a -&gt; a * 2] &lt;*&gt; [1,2,3] 可以得到 [2,3,4,2,4,6]</code></p> <pre><code class="language-haskell"> (&lt;*&gt;) :: Applicative f =&gt; f (a -&gt; b) -&gt; f a -&gt; f b </code></pre> </li> </ol> <p><strong>单子:</strong>(Monad) 首先是个应用函子(尽管从历史上说,是先有的Monad后有的Applicative),额外提供如下bind操作,可以把一个单子 m a 喂给另一个 (a -&gt; m b) 的函数,返回一个新的单子,比如 <code>[1,2,3] &gt;&gt;= \x -&gt; [x+1, x*2] = [2,2,3,4,4,6]</code></p> <pre><code class="language-haskell">(&gt;&gt;=) :: Monad m =&gt; m a -&gt; (a -&gt; m b) -&gt; m b </code></pre> <p>很多语言里的flatMap,在haskell中叫做 join ,可以把盒子的盒子,变成盒子类型。比如 <code>join [[1],[2],[3]] = [1,2,3]</code></p> <pre><code class="language-haskell">join :: Monad m =&gt; m (m a) -&gt; m a join x = x &gt;&gt;= id (想想为什么这么实现) </code></pre> <p><code>join</code> 和 <code>bind (&gt;&gt;=)</code> 可以互相实现:</p> <pre><code class="language-haskell">m &gt;&gt;= f = join (fmap f m) </code></pre> <p>所以在别的语言中,才会有「实现了flatMap就是Monad了」这种说法,从以上的描述看起来,这是不够严谨的,忽略了Monad同时也得是Functor,以及也要具有Applicative的操作,并且需要符合函子定律,应用函子定律和单子定律。</p> 2019-07-19T00:00:00+00:00 https://ysmull.cn/blog/hs.html https://ysmull.cn/blog/hs.html SICP - chapter 2 <ul id="markdown-toc"> <li><a href="#id-21" id="markdown-toc-id-21">2.1</a></li> <li><a href="#id-22" id="markdown-toc-id-22">2.2</a></li> <li><a href="#id-23" id="markdown-toc-id-23">2.3</a></li> <li><a href="#id-24" id="markdown-toc-id-24">2.4</a></li> <li><a href="#id-25" id="markdown-toc-id-25">2.5</a></li> <li><a href="#id-217" id="markdown-toc-id-217">2.17</a></li> <li><a href="#id-218" id="markdown-toc-id-218">2.18</a></li> <li><a href="#id-220" id="markdown-toc-id-220">2.20</a></li> <li><a href="#id-223" id="markdown-toc-id-223">2.23</a></li> <li><a href="#id-225" id="markdown-toc-id-225">2.25</a></li> <li><a href="#id-226" id="markdown-toc-id-226">2.26</a></li> <li><a href="#id-227" id="markdown-toc-id-227">2.27</a></li> <li><a href="#id-228" id="markdown-toc-id-228">2.28</a></li> </ul> <h2 id="id-21">2.1</h2> <pre><code class="language-scheme">(define (make-rat n d) (let ((t1 (if (&lt; n 0) -1 1)) ;获取n和d的符号位 (t2 (if (&lt; d 0) -1 1))) (cond ((= (* t1 t2) 1) (cons (* t1 n) (* t2 d))) ((= t1 -1) (cons n d)) ((= t2 -1) (cons (* t2 n) (* t2 d)))))) (displayln (make-rat 1 22)) (displayln (make-rat 1 -22)) (displayln (make-rat -1 22)) (displayln (make-rat -1 -22)) </code></pre> <h2 id="id-22">2.2</h2> <pre><code class="language-scheme">(define (make-point x y) (cons x y)) (define (x-point p) (car p)) (define (y-point p) (cdr p)) (define (make-segment x y) (cons x y)) (define (start-segment s) (car s)) (define (end-segment s) (cdr s)) (define (mid-segment s) (let ((startP (start-segment s)) (endP (end-segment s))) (let ((x1 (x-point startP)) (x2 (x-point endP)) (y1 (y-point startP)) (y2 (y-point endP))) (make-point (/ (+ x1 x2) 2) (/ (+ y1 y2) 2))))) (define (print-point p) (display "(") (display (x-point p)) (display ",") (display (y-point p)) (display ")")) (define seg1 (make-segment (make-point 1 1) (make-point 3 3))) (print-point (mid-segment seg1)) </code></pre> <h2 id="id-23">2.3</h2> <pre><code class="language-scheme">; 需要运行2.2 (define (make-rec p1 p2) (cons p1 p2)) (define (rec-length p) (let ((p1 (car p)) (p2 (cdr p))) (let ((x1 (x-point p1)) (x2 (x-point p2))) (abs (- x1 x2))))) (define (rec-width p) (let ((p1 (car p)) (p2 (cdr p))) (let ((y1 (y-point p1)) (y2 (y-point p2))) (abs (- y1 y2))))) (define (rec-area p) (* (rec-length p) (rec-width p))) (define (rec-perimeter p) (* 2 (+ (rec-length p) (rec-width p)))) (define rec1 (make-rec (make-point 0 0) (make-point 3 2))) (displayln (rec-area rec1)) (displayln (rec-perimeter rec1)) ; 为了使rec-area和rec-perimeter过程不改变,另一种定义方式只要提供rec-length和rec-width接口即可。 </code></pre> <h2 id="id-24">2.4</h2> <pre><code class="language-scheme">(define (cons1 x y) (lambda (m) (m x y))) (define (car1 z) (z (lambda (p q) p))) (define (cdr1 z) (z (lambda (p q) q))) (car1 (cons1 1 2)) (cdr1 (cons1 1 2)) (cdr1 (cons1 1 2)) ; =&gt; (cdr1 (lambda (m) (m 1 2))) ; =&gt; ((lambda (m) (m 1 2)) (lambda (p q) q)) ;=&gt; ((lambda (p q) q) 1 2) </code></pre> <h2 id="id-25">2.5</h2> <pre><code class="language-scheme"> </code></pre> <h2 id="id-217">2.17</h2> <pre><code class="language-scheme">(displayln "----2.17") (define (last-pair l) (if (null? l) null (if (null? (cdr l)) (car l) (last-pair (cdr l))))) (last-pair (list 1 2 3)) (last-pair (list)) </code></pre> <h2 id="id-218">2.18</h2> <pre><code class="language-scheme">(define (reverse l) (if (null? l) null (append (reverse (cdr l)) (list (car l))))) (reverse (list)) (reverse (list 1)) (reverse (list 1 2 3)) </code></pre> <h2 id="id-220">2.20</h2> <pre><code class="language-scheme">(define (same-parity x . xs) (define (same-p n) (= (remainder x 2) (remainder n 2))) (if (null? xs) null (if (= (remainder x 2) (remainder (car xs) 2)) (cons (car xs) (apply same-parity (cons x (cdr xs)))) (apply same-parity (cons x (cdr xs)))))) (same-parity 111 2 3 4 5 6 7 8 9) (same-parity 100 2 3 4 5 6 7 8 9) </code></pre> <h2 id="id-223">2.23</h2> <pre><code class="language-scheme">(define (for-each f xs) (if (null? xs) #t (begin (f (car xs)) (for-each f (cdr xs))))) (for-each (lambda (x) (displayln x)) (list 1 2 3)) </code></pre> <h2 id="id-225">2.25</h2> <pre><code class="language-scheme">(define x25-1 (list 1 3 (list 5 7) 9)) (car (cdr (car (cdr (cdr x25-1))))) (define x25-2 (list (list 7))) (car (car x25-2)) (define x25-3 (list 1 (list 2 (list 3 (list 4 (list 5 (list 6 7))))))) (car (cdr (car (cdr (car (cdr (car (cdr (car (cdr (car (cdr x25-3)))))))))))) </code></pre> <h2 id="id-226">2.26</h2> <pre><code class="language-scheme">(define x26 (list 1 2 3)) (define y26 (list 4 5 6)) (append x26 y26) (cons x26 y26) (list x26 y26) </code></pre> <h2 id="id-227">2.27</h2> <pre><code class="language-scheme">(define (deep-reverse l) (if (null? l) null (if (list? (car l)) (append (deep-reverse (cdr l)) (list (deep-reverse (car l)))) (append (deep-reverse (cdr l)) (list (car l)))))) (deep-reverse '()) (deep-reverse '(1)) (deep-reverse '(1 2)) (deep-reverse (list (list 1 2) (list 3 4))) (deep-reverse '((1 2) (3 4) 5 6 (7 8))) </code></pre> <h2 id="id-228">2.28</h2> <pre><code class="language-scheme">(displayln "二叉树版抽象") (define (left-tree tree) (car tree)) (define (right-tree tree) (car (cdr tree))) (define (fringe-bin tree) (cond ((null? tree) null) ((pair? tree) (append (fringe-bin (left-tree tree)) (fringe-bin (right-tree tree)))) (else (list tree)))) ; 多叉树无法work ;(fringe-bin (list (list 1 2 3) (list 4 5 6) (list 7 8) 9)) (define x28 (list (list 1 2) (list 3 4))) (fringe-bin x28) (fringe-bin (list x28 x28)) ; 多叉树work版 (define (fringe tree) (cond ((null? tree) null) ((pair? tree) (append (fringe (car tree)) (fringe (cdr tree)))) (else (list tree)))) (fringe (list (list 1 2 3) (list 4 5 6) (list 7 8) 9)) </code></pre> 2018-05-04T00:00:00+00:00 https://ysmull.cn/blog/sicp2.html https://ysmull.cn/blog/sicp2.html SICP - chapter 1 <ul id="markdown-toc"> <li><a href="#id-13" id="markdown-toc-id-13">1.3</a></li> <li><a href="#id-14" id="markdown-toc-id-14">1.4</a></li> <li><a href="#id-15" id="markdown-toc-id-15">1.5</a></li> <li><a href="#id-16" id="markdown-toc-id-16">1.6</a></li> <li><a href="#id-17" id="markdown-toc-id-17">1.7</a></li> <li><a href="#id-18" id="markdown-toc-id-18">1.8</a></li> <li><a href="#id-112" id="markdown-toc-id-112">1.12</a></li> <li><a href="#id-121" id="markdown-toc-id-121">1.21</a></li> <li><a href="#id-122" id="markdown-toc-id-122">1.22</a></li> <li><a href="#id-123" id="markdown-toc-id-123">1.23</a></li> <li><a href="#id-124" id="markdown-toc-id-124">1.24</a></li> <li><a href="#id-127" id="markdown-toc-id-127">1.27</a></li> <li><a href="#id-128" id="markdown-toc-id-128">1.28</a></li> </ul> <h2 id="id-13">1.3</h2> <pre><code class="language-scheme">(define (max_tow_sum a b c) (cond ((and (&gt;= a b) (&gt;= b c)) (+ a b)) ((and (&gt;= a c) (&gt;= c b)) (+ a c)) ((and (&gt;= b a) (&gt;= a c)) (+ b a)) ((and (&gt;= b c) (&gt;= c a)) (+ b c)) ((and (&gt;= c a) (&gt;= a b)) (+ c a)) ((and (&gt;= c b) (&gt;= b a)) (+ c b)))) (define (max_tow_sum2 a b c) (- (+ a b c) (min a b c))) (displayln (max_tow_sum2 1 2 3)) </code></pre> <h2 id="id-14">1.4</h2> <pre><code class="language-scheme">(define (a-plus-abs-b a b) ((if (&gt; b 0) + -) a b)) </code></pre> <h2 id="id-15">1.5</h2> <pre><code class="language-scheme">(define (p) (p)) (define (test x y) (if (= x 0) 0 y)) </code></pre> <h2 id="id-16">1.6</h2> <pre><code class="language-scheme">(define (decrease a) (new-if (&gt; a 0) (decrease (- a 1)) a)) (define (new-if predicate then-clause else-clause) (cond (predicate then-clause) (else else-clause))) </code></pre> <h2 id="id-17">1.7</h2> <pre><code class="language-scheme">;很大的数,good_enough会爆掉;很小的数,即使0.001作为衡量标准也很大了。 (define (improve guess n) (/ (+ guess (/ n guess)) 2)) (define (good-enough? x y) (&lt; (abs (- x y)) 0.000001)) (define (sqrt-iter1 last-guess cur-guess n) (if (good-enough? last-guess cur-guess) cur-guess (sqrt-iter1 cur-guess (improve cur-guess n) n))) (define (my-sqrt1 n) (sqrt-iter1 (/ n 2) (+ (/ n 2) 1.0) n)) (define (sqrt-iter2 guess n) (let ((next-guess (improve guess n))) (if (good-enough? guess next-guess) next-guess (sqrt-iter2 next-guess n)))) (define (my-sqrt2 n) (sqrt-iter2 1.0 n)) (displayln (my-sqrt2 4)) </code></pre> <h2 id="id-18">1.8</h2> <pre><code class="language-scheme">; 需要先执行1.7 (define (improve3 guess n) (/ (+ (/ n (* guess guess)) (* 2 guess)) 3)) (define (sqrt3-iter guess n) (let ((next-guess (improve3 guess n))) (if (good-enough? guess next-guess) next-guess (sqrt3-iter next-guess n)))) (define (my-sqrt3 n) (sqrt3-iter 1.0 n)) (displayln (my-sqrt3 27.0)) </code></pre> <h2 id="id-112">1.12</h2> <pre><code class="language-scheme">;1.12 (define (pascal row col) (cond ((= row 1) 1) ((= col row) 1) (else (+ (pascal (- row 1) col) (pascal (- row 1) (+ col 1)))))) (displayln (pascal 5 3)) </code></pre> <h2 id="id-121">1.21</h2> <pre><code class="language-scheme">; 1.21 (define (square n) (* n n)) (define (smallest-divisor n) (find-divisor n 2)) (define (find-divisor n test-divisor) (cond ((&gt; (square test-divisor) n) n) ((divides? test-divisor n) test-divisor) (else (find-divisor n (+ test-divisor 1))))) (define (divides? a b) (= (remainder b a) 0)) (define (prime? n) (= n (smallest-divisor n))) (displayln (smallest-divisor 199)) (displayln (smallest-divisor 1999)) (displayln (smallest-divisor 19999)) </code></pre> <h2 id="id-122">1.22</h2> <pre><code class="language-scheme">; 先运行1.21,,会比较卡 ; 1.22 (define (timed-prime-test n) (display "start-with: ") (displayln n) (start-prime-test n (current-milliseconds) 0)) (define (start-prime-test n start-time found-num) (if (= found-num 3) (report-prime (- (current-milliseconds) start-time)) (if (prime? n) (begin (display "found: ") (displayln n) (start-prime-test (+ 2 n) start-time (+ found-num 1))) (start-prime-test (+ 2 n) start-time found-num)))) (define (report-prime elapsed-time) (display "time elapsed: ") (displayln elapsed-time) (displayln "-----------")) (displayln (timed-prime-test 10000000001)) (displayln (timed-prime-test 100000000001)) ;(displayln (timed-prime-test 1000000000001)) ;(displayln (timed-prime-test 10000000000001)) ;(displayln (timed-prime-test 100000000000001)) ;(displayln (timed-prime-test 1000000000000001)) </code></pre> <h2 id="id-123">1.23</h2> <pre><code class="language-scheme">; 需要先运行1.22 ; 1.23 (define (smallest-divisor2 n) (find-divisor2 n 2)) (define (find-divisor2 n test-divisor) (cond ((&gt; (square test-divisor) n) n) ((divides? test-divisor n) test-divisor) (else (find-divisor2 n (next test-divisor))))) (define (next n) (if (= n 2) 3 (+ n 2))) (define (prime?2 n) (= n (smallest-divisor2 n))) (define (start-prime-test2 n start-time found-num) (if (= found-num 3) (report-prime (- (current-milliseconds) start-time)) (if (prime?2 n) (begin (display "found: ") (displayln n) (start-prime-test2 (+ 2 n) start-time (+ found-num 1))) (start-prime-test2 (+ 2 n) start-time found-num)))) (define (timed-prime-test2 n) (display "start-with: ") (displayln n) (start-prime-test2 n (current-milliseconds) 0)) ; 差距确实是两倍 (displayln (timed-prime-test 100000000001)) (displayln (timed-prime-test2 100000000001)) ; (displayln (timed-prime-test 1000000000001)) ; (displayln (timed-prime-test2 1000000000001)) ; (timed-prime-test 10000000000001) ; (timed-prime-test2 10000000000001) ; (timed-prime-test 100000000000001) ; (timed-prime-test2 100000000000001) ; (timed-prime-test 1000000000000001) ; (timed-prime-test2 1000000000000001) </code></pre> <h2 id="id-124">1.24</h2> <pre><code class="language-scheme">; 需要先运行1.23 ; 1.24 (define (expmod base exp m) (cond ((= exp 0) 1) ((even? exp) (remainder (square (expmod base (/ exp 2) m)) m)) (else (remainder (* base (expmod base (- exp 1) m)) m)))) (define (fermat-test n) (define (try-it a) (= (expmod a n n) a)) (try-it (+ 1 (random (- n 1))))) ; should be (random (- n 1)) (define (fast-prime? n times) (cond ((= times 0) #t) ((fermat-test n) (fast-prime? n (- times 1))) (else #f))) (define (start-prime-test3 n start-time found-num) (if (= found-num 3) (report-prime (- (current-milliseconds) start-time)) (if (fast-prime? n 100) (begin (display "found: ") (displayln n) (start-prime-test3 (+ 2 n) start-time (+ found-num 1))) (start-prime-test3 (+ 2 n) start-time found-num)))) (define (timed-prime-test3 n) (display "start-with: ") (displayln n) (start-prime-test3 n (current-milliseconds) 0)) (displayln (timed-prime-test 100000000001)) (displayln (timed-prime-test2 100000000001)) ;不知道为什么浏览器跑test3会死循环 ;(displayln (timed-prime-test3 100000000001)) </code></pre> <h2 id="id-127">1.27</h2> <pre><code class="language-scheme">; 需要先运行1.24 ; 1.27 (prime? 561) (fermat-test 561) (fermat-test 1105) (fermat-test 1729) (fermat-test 2465) </code></pre> <h2 id="id-128">1.28</h2> <pre><code class="language-scheme"> (define (square n) (* n n)) (define (expmod base exp m) (cond ((= exp 0) 1) ((even? exp) (remainder (square (expmod base (/ exp 2) m)) m)) (else (remainder (* base (expmod base (- exp 1) m)) m)))) (define (miller-rabin-test n) (define (test-p p k) (cond ((= k (- p 1)) true) ((= (remainder (* k k) p) 1) false) (else (test-p p (+ k 1))))) (define (try-it a) (= (expmod a (- n 1) n) 1)) (cond ((not (test-p n 2)) false) ((try-it (+ 1 (random (- n 1)))) true) (else false))) (define (fast-prime2? n times) (cond ((= n 2) true) ((= times 0) true) ((miller-rabin-test n) (fast-prime2? n (- times 1))) (else false))) ; 测试一下 (define (list-prime-less-than n) (cond ((= n 1) (displayln "")) ((fast-prime2? n 100) (displayln n) (list-prime-less-than (- n 1))) (else (list-prime-less-than (- n 1))))) (list-prime-less-than 100) </code></pre> 2018-04-18T00:00:00+00:00 https://ysmull.cn/blog/sicp1.html https://ysmull.cn/blog/sicp1.html 理解 JavaScript 异步编程(一) <p>先从同步操作说起。</p> <p>首先我们有一个函数,可以返回把传入参数加一的结果。</p> <pre><code class="language-javascript">var syncPlusOne = function(a) { return a+1 } </code></pre> <p>如果我们需要把每一次调用的结果作为下一次调用的参数,就这样调用:</p> <pre><code class="language-javascript">var continuousSyncPlusOne = function (s) { var a = syncPlusOne(s) var b = syncPlusOne(a) var c = syncPlusOne(b) } </code></pre> <p>如果我们现在有一个异步Plus函数呢?</p> <pre><code class="language-javascript">var wrongAsyncPlusOne = function(a) { setTimeout(function() { return a+1 }, Math.random()*1000) } </code></pre> <p>调用这个函数,会直接返回<code>undefined</code>,因为异步过程会直接返回,而不会阻塞。对于一个异步过程,我们有两种方法可以得知它的计算过程是否结束。</p> <p>一种办法是轮训,比如如下代码:</p> <pre><code class="language-javascript">var tv setTimeout(function (){ tv = 1 }, 1000); while (true) { if (tv != undefined) { console.log("tv is set to :" + tv); break; } } </code></pre> <p>我们在while循环中不断的轮询tv的值,直到tv被赋值。但<strong>很不幸</strong>,这个代码并不会按照我们的想法执行,这与javascript的运行机制有关,这里不展开讲,正确的写法如下:</p> <pre><code class="language-javascript">var tv setTimeout(function (){ tv = 1 }, 1000); var intervalId = setInterval(function() { if (tv != undefined) { console.log("tv is set to :" + tv); clearInterval(intervalId); } }, 0) </code></pre> <p>另一种方法,是这个函数执行结束后自己告诉我们:</p> <pre><code class="language-javascript">var tv setTimeout(function (){ tv = 1 console.log("tv is set to :" + tv) }, 1000); </code></pre> <p>我们在函数体内调用 <code>console.log()</code> 来表示 tv 的值已经被计算好了,本质上是将 tv 计算好之后的值,<strong>主动传递</strong>给了后续过程。如果把需要这个异步计算结果的函数作为参数传递进来,并在计算结束后将结果传递给它,那么这个函数就被称为<em>回调函数</em>。<br /> (<code>setTimeout</code>的第一个参数传入的也是一个回调函数,当计时结束后,会调用回调函数。其实 <code>setTimeout</code> 和 <code>setInterval</code> 的第三个可选参数可以给回调函数传参。)</p> <p>那么重新改写我们之前的异步Plus如下:</p> <pre><code class="language-javascript">var asyncPlusOne = function(a, callback) { setTimeout(function() { callback(a+1) }, Math.random()*1000) } </code></pre> <p>如果我们需要得到某个数字连续调用asyncPlusOne之后的结果,则要写为:</p> <pre><code class="language-javascript">var continuousAsyncPlusOne = function(s) { asyncPlusOne(s, function(a) { asyncPlusOne(a, function(b) { asyncPlusOne(b, function(c) { console.log(c); // 3 }) }) }) } continuousAsyncPlusOne(0); </code></pre> <p>es6语法可以这样写:</p> <pre><code class="language-javascript">var continuousAsyncPlusOne = function(s) { asyncPlusOne(s, (a) =&gt; { asyncPlusOne(a, (b) =&gt; { asyncPlusOne(b, (c) =&gt; { console.log(c); // 3 }) }) }) } </code></pre> <p>第一次写这种代码,感觉还挺酷的样子。但是久而久之,代码中到处都是这种callback结构,太不简洁了,这被称为 <strong><em>callback hell</em></strong>。</p> <p>那么我们如何把异步调用变成同步的书写方式呢?比如像这样子:</p> <pre><code class="language-javascript">var a = asyncPlusIWant(s) var b = asyncPlusIWant(a) var c = asyncPlusIWant(b) console.log(c); </code></pre> <p>我们希望:</p> <ol> <li>asyncPlusIWant() 是异步的</li> <li>它能立即返回计算之后的结果(而不是undifined)</li> </ol> <p>这两条不是自相矛盾吗。唉,如果我们的语句走到 asyncPlusIWant() 时,能够<strong>停下来</strong>,等待这个异步过程执行完成之后,<strong>再继续</strong>执行下面的语句,就好了。</p> <p>Wait! <strong>停下来</strong>、<strong>再继续</strong>,好熟悉的东西,ES6 引入的 Generator 不是可以做到这件事吗!<br /> 把我们的语句放到 Generator 中看看!</p> <pre><code class="language-javascript">var genAsync = function* (s) { var a = yield asyncPlusOne(s) console.log(a) // undefined var b = yield asyncPlusOne(a) var c = yield asyncPlusOne(b) } console.log(g.next().value); // undefined </code></pre> <p>并没有得到正确的输出,不是说好的会停下来的吗?<br /> 其实这里确实停了,只不过不是我们想象的那种停,这里两个地方输出都是<code>undefined</code>原因如下:</p> <ul> <li><code>console.log(a)</code> 输出 <code>undefined</code> 是因为 yield &lt;expression&gt; 的值取决于外部调用next时传入的值,外部调用g.next()时候并没有传值进去。</li> <li><code>console.log(g.next().value)</code> 输出 <code>undefined</code> 是因为 <code>g.next().value</code> 应该拿到的是表达式 <code>asyncPlusOne(s)</code> 的值,而 <code>asyncPlusOne</code> 是异步的,它直接返回了 <code>undefined</code>。</li> </ul> <p>那么如何才可以结合 Generator 函数实现异步过程的同步调用呢?</p> <p>为了实现我们的目标,考虑如下辅助函数:</p> <pre><code class="language-javascript">var g = function(f){ return function (args){ return function (callback){ return f(args, callback) } } } </code></pre> <p>那么</p> <script type="math/tex; mode=display">\mathrm{f(s, callback) = g(f)(s)(callback)}</script> <p>好吧,你看出来了,这里就是做了一个柯里化。</p> <p>那我们令</p> <script type="math/tex; mode=display">\mathrm{asyncPlusIWant = g(asyncPlusOne)}</script> <pre><code class="language-javascript">var asyncPlus = function (a, callback) { setTimeout(function () { callback(a + 1); }, Math.random() * 1000); } var asyncPlusIWant = g(asyncPlus) var genAsync = function* (s) { var a = yield asyncPlusIWant(s); console.log('async a: ' + a) var b = yield asyncPlusIWant(a); console.log('async b: ' + b) var c = yield asyncPlusIWant(b); console.log('async c: ' + c) return c+1 }; var run = function run(gen) { function go(lastRes) { // 1.[唤醒],把上一次计算出的值放到LHS(left-hand side),然后移动到下一个异步调用的位置停下来 var res = gen.next(lastRes); if (res.done) return; // 2.[执行],调用res.value(go)将开始执行异步计算,计算完成后调用会调用go继续唤醒generator res.value(go); } go() } run(genAsync(0)); </code></pre> <p>这里的辅助函数 g 被称作 <strong>Thunk 函数</strong>。</p> <pre><code class="language-javascript">var Thunk = function(fn){ return function (){ var args = Array.prototype.slice.call(arguments); return function (callback){ args.push(callback); return fn.apply(this, args); } }; }; </code></pre> <p>var thunkFunc = Thunk(func); 可以把fn包裹成另一个函数 thunkFunc<br /> 他改变了原来 func 的调用方式:</p> <pre><code class="language-text">func(arg_1, arg_2, ... , arg_k, callback) func(arg_1, arg_2, ... , arg_k)(callback) </code></pre> <p>通过上面的 run() 方法,执行 run(genAsync) 就可以依次执行三个异步函数了。</p> <p>这件很酷的事情本质是什么?</p> <p>使用 Thunk 和 Generator 使异步函数以同步的方式书写,本质是:</p> <p>Thunk 把异步函数的<strong>「执行」</strong>从 Generator 内,分离到了 Generator 外部,把 Generator 的<strong>「唤醒」</strong>放到了<strong>「在外部执行的异步操作的回调函数内」</strong>,所以整个执行流程变成了,在外部执行的异步函数,通过回调,不断的去唤醒 Generator 继续执行后续操作。</p> 2018-04-12T00:00:00+00:00 https://ysmull.cn/blog/asyncjs1.html https://ysmull.cn/blog/asyncjs1.html Sicp 2018-04-11T00:00:00+00:00 https://ysmull.cn/sicp https://ysmull.cn/sicp Microservice <ul id="markdown-toc"> <li><a href="#id-11-什么是微服务" id="markdown-toc-id-11-什么是微服务">1.1 什么是微服务</a> <ul> <li><a href="#id-111-小" id="markdown-toc-id-111-小">1.1.1 小</a></li> <li><a href="#id-112-自治" id="markdown-toc-id-112-自治">1.1.2 自治</a></li> </ul> </li> <li><a href="#id-12-主要好处" id="markdown-toc-id-12-主要好处">1.2 主要好处</a> <ul> <li><a href="#id-121-技术异构性" id="markdown-toc-id-121-技术异构性">1.2.1 技术异构性</a></li> <li><a href="#id-122-弹性" id="markdown-toc-id-122-弹性">1.2.2 弹性</a></li> <li><a href="#id-123-扩展" id="markdown-toc-id-123-扩展">1.2.3 扩展</a></li> <li><a href="#id-124-简化部署" id="markdown-toc-id-124-简化部署">1.2.4 简化部署</a></li> <li><a href="#id-125-与组织结构相匹配" id="markdown-toc-id-125-与组织结构相匹配">1.2.5 与组织结构相匹配</a></li> <li><a href="#id-126-可组合性" id="markdown-toc-id-126-可组合性">1.2.6 可组合性</a></li> <li><a href="#id-127-对可替代性的优化" id="markdown-toc-id-127-对可替代性的优化">1.2.7 对可替代性的优化</a></li> </ul> </li> <li><a href="#id-13-面向服务的架构" id="markdown-toc-id-13-面向服务的架构">1.3 面向服务的架构</a></li> <li><a href="#id-14-其它分解技术" id="markdown-toc-id-14-其它分解技术">1.4 其它分解技术</a> <ul> <li><a href="#id-141-共享库" id="markdown-toc-id-141-共享库">1.4.1 共享库</a></li> <li><a href="#id-142-模块" id="markdown-toc-id-142-模块">1.4.2 模块</a></li> </ul> </li> <li><a href="#id-15-没有银弹" id="markdown-toc-id-15-没有银弹">1.5 没有银弹</a></li> <li><a href="#id-32-什么样的服务是好服务" id="markdown-toc-id-32-什么样的服务是好服务">3.2 什么样的服务是好服务</a> <ul> <li><a href="#id-321-松耦合" id="markdown-toc-id-321-松耦合">3.2.1 松耦合</a></li> <li><a href="#id-322-高内聚" id="markdown-toc-id-322-高内聚">3.2.2 高内聚</a></li> </ul> </li> <li><a href="#id-33-限界上下文" id="markdown-toc-id-33-限界上下文">3.3 限界上下文</a></li> <li><a href="#id-43-共享数据库" id="markdown-toc-id-43-共享数据库">4.3 共享数据库</a></li> <li><a href="#id-44-同步与异步" id="markdown-toc-id-44-同步与异步">4.4 同步与异步</a></li> <li><a href="#id-45-编排与协同" id="markdown-toc-id-45-编排与协同">4.5 编排与协同</a></li> <li><a href="#id-46-远程过程调用" id="markdown-toc-id-46-远程过程调用">4.6 远程过程调用</a></li> </ul> <h1 id="id-第一章-微服务">第一章 微服务</h1> <h2 id="id-11-什么是微服务">1.1 什么是微服务</h2> <p>微服务就是一些协同工作的小而自治的服务。</p> <h3 id="id-111-小">1.1.1 小</h3> <ul> <li>单一职责原则</li> <li>能在两周内完全重写</li> </ul> <h3 id="id-112-自治">1.1.2 自治</h3> <p>服务之间均通过网络调用进行通信,从而加强服务之间的隔离性,避免紧耦合。</p> <h2 id="id-12-主要好处">1.2 主要好处</h2> <h3 id="id-121-技术异构性">1.2.1 技术异构性</h3> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-10-26-084412.png" alt="" width="500px" /></p> <h3 id="id-122-弹性">1.2.2 弹性</h3> <p>弹性工程学关键概念:<strong>舱壁</strong>。系统中一个组件不可用,不会导致级联故障,系统中的其它部分还可以正常运行。服务边界就是一个舱壁。</p> <p>单块系统如果故障,所有服务都不可用,解决方案:部署多个实例。<br /> 微服务系统能很好处理服务不可用和功能降级问题。</p> <h3 id="id-123-扩展">1.2.3 扩展</h3> <p>单块服务如果只是其中一小部分有性能问题,那么也得对整个服务进行扩展。<br /> 微服务则可以对需要扩展的服务进行扩展。</p> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-10-26-091627.png" alt="" width="500px" /></p> <h3 id="id-124-简化部署">1.2.4 简化部署</h3> <p>上百万行代码的单块系统,修改一句代码也得重新部署整个应用。这种部署频率低,风险高。微服务架构中,各微服务可独立部署,出错后容易快速回滚。</p> <h3 id="id-125-与组织结构相匹配">1.2.5 与组织结构相匹配</h3> <h3 id="id-126-可组合性">1.2.6 可组合性</h3> <p>微服务架构中,人们可以通过不同的方式使用同一个功能。系统会开放很多细粒度的接口给外部调用,可以使用不同的方式构造应用。而整体化应用程序只能提供一个非常粗粒度的接缝供外部使用。</p> <h3 id="id-127-对可替代性的优化">1.2.7 对可替代性的优化</h3> <p>庞大的系统不敢轻易替代,微服务很容易重写。</p> <h2 id="id-13-面向服务的架构">1.3 面向服务的架构</h2> <p>SOA(Service-Oriented Architecture)是一种<strong>设计方法</strong>,其中包含多个服务,而服务之间通过配合最终会提供一系列功能。一个服务通常以独立的形式存在于操作系统进程中。服务之间通过网络调用,而非采用进程内调用的方式进行通信。<br /> <strong>微服务架构是SOA的一种特定方法。</strong></p> <h2 id="id-14-其它分解技术">1.4 其它分解技术</h2> <h3 id="id-141-共享库">1.4.1 共享库</h3> <p>缺点:</p> <ul> <li>无法选择异构技术</li> <li>除非是动态链接库,否则每次库更新都得重新部署</li> </ul> <h3 id="id-142-模块">1.4.2 模块</h3> <h2 id="id-15-没有银弹">1.5 没有银弹</h2> <p>微服务不是免费的午餐,你需要面对所有分布式系统需要面对的复杂性。</p> <h1 id="id-第三章-如何建模服务">第三章 如何建模服务</h1> <h2 id="id-32-什么样的服务是好服务">3.2 什么样的服务是好服务</h2> <p>松耦合、高内聚</p> <h3 id="id-321-松耦合">3.2.1 松耦合</h3> <p><strong>能够独立修改及部署单个服务而不需要修改系统的其它部分。</strong><br /> 如何避免紧耦合:尽可能少的知道与之协作的服务的信息。限制两个服务之间不同调用形式的数量,过度的通信可能导致紧耦合。</p> <h3 id="id-322-高内聚">3.2.2 高内聚</h3> <p>把相关的行为聚集在一起,把不相关的行为放在别处。<br /> 为的是,<strong>如果你要修改某个功能,那么最好只在一个地方修改</strong>。这样可以提高发布效率,降低修改风险。</p> <h2 id="id-33-限界上下文">3.3 限界上下文</h2> <p>见《领域驱动设计》</p> <h1 id="id-第四章-集成">第四章 集成</h1> <h2 id="id-43-共享数据库">4.3 共享数据库</h2> <p>如果其它服务想从一个服务获取信息,可以直接访问数据库。</p> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-10-31-065242.png" alt="" width="500px" /></p> <h2 id="id-44-同步与异步">4.4 同步与异步</h2> <h2 id="id-45-编排与协同">4.5 编排与协同</h2> <h2 id="id-46-远程过程调用">4.6 远程过程调用</h2> 2017-10-27T00:00:00+00:00 https://ysmull.cn/micro_service https://ysmull.cn/micro_service 谈谈 HashMap 实现中的若干数学问题 <h2 id="id-1-为什么长度必须是-2-的倍数">1. 为什么长度必须是 2 的倍数</h2> <pre><code class="language-java">/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 &lt;&lt; 4; // aka 16 </code></pre> <p>答:因为某一个结点散列后的 index 是根据 <code>hash &amp; (len - 1)</code> 计算得到的。如果 <code>len</code> 为 2 的整数倍,那么其二进制表示为:</p> <pre><code class="language-text">len : ...0001000...000 len - 1 : ...0000111...111 </code></pre> <p>那么一定有 <code>index = hash &amp; (len - 1) &lt;= len - 1</code>,且如果 hash 是均匀分布的,那么 index 也是均匀分布的。</p> <h2 id="id-2-为什么-treeify_threshold-定为-8">2. 为什么 TREEIFY_THRESHOLD 定为 8</h2> <pre><code class="language-java">/** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ static final int TREEIFY_THRESHOLD = 8; </code></pre> <p>其实源码开头的注释里提到了,但是解释的不是很清楚。这里我试着来讲一下原因,先给出一个问题:</p> <blockquote> <p>有 n 个抽屉,随机的往这些抽屉丢 m 个苹果,问某一个抽屉有 k 个苹果的概率。</p> </blockquote> <p>设 X 为某个抽屉里的苹果数,即 X 是一个 m 重伯努利实验成功的次数,X 的分布列为<br /> <span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>P</mi><mo stretchy="false">(</mo><mi>X</mi><mo>=</mo><mi>k</mi><mo stretchy="false">)</mo><mo>=</mo><mrow><mo fence="true">(</mo><mfrac linethickness="0px"><mi>m</mi><mi>k</mi></mfrac><mo fence="true">)</mo></mrow><msup><mi>p</mi><mi>k</mi></msup><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>p</mi><msup><mo stretchy="false">)</mo><mrow><mi>m</mi><mo>−</mo><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">P(X=k) = \binom{m}{k}p^k(1-p)^{m-k}</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 mathdefault" style="margin-right:0.13889em;">P</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.40003em;vertical-align:-0.95003em;"></span><span class="mord"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1075599999999999em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.03148em;">k</span></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mord"><span class="mord mathdefault">p</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.149108em;vertical-align:-0.25em;"></span><span class="mord mathdefault">p</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">m</span><span class="mbin mtight">−</span><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span></span></span></span></span><br /> 其中 <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>p</mi><mo>=</mo><mfrac><mn>1</mn><mi>n</mi></mfrac></mrow><annotation encoding="application/x-tex">p = \frac{1}{n}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">p</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.190108em;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.845108em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">n</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 mtight">1</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>,这个分布列为<strong>二项分布</strong>,记为 <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>X</mi><mo>∼</mo><mi>b</mi><mo stretchy="false">(</mo><mi>m</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">X\sim b(m,p)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∼</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault">b</span><span class="mopen">(</span><span class="mord mathdefault">m</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault">p</span><span class="mclose">)</span></span></span></span>,我们有 <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>E</mi><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo><mo>=</mo><mi>m</mi><mi>p</mi></mrow><annotation encoding="application/x-tex">E(X) = mp</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 mathdefault" style="margin-right:0.05764em;">E</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">m</span><span class="mord mathdefault">p</span></span></span></span><br /> 当 m 很大的时候,我们可以用泊松分布做近似,设 <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>λ</mi><mo>=</mo><mi>E</mi><mo stretchy="false">(</mo><mi>X</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\lambda = E(X)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathdefault">λ</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.05764em;">E</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mclose">)</span></span></span></span>,则<br /> <span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mrow><mo fence="true">(</mo><mfrac linethickness="0px"><mi>m</mi><mi>k</mi></mfrac><mo fence="true">)</mo></mrow><msup><mi>p</mi><mi>k</mi></msup><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>p</mi><msup><mo stretchy="false">)</mo><mrow><mi>m</mi><mo>−</mo><mi>k</mi></mrow></msup><mo>≈</mo><mfrac><msup><mi>λ</mi><mi>k</mi></msup><mrow><mi>k</mi><mo stretchy="false">!</mo></mrow></mfrac><msup><mi>e</mi><mrow><mo>−</mo><mi>λ</mi></mrow></msup></mrow><annotation encoding="application/x-tex">\binom{m}{k}p^k(1-p)^{m-k} \approx \frac{\lambda^k}{k!}e^{-\lambda}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.40003em;vertical-align:-0.95003em;"></span><span class="mord"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1075599999999999em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.03148em;">k</span></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">m</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mord"><span class="mord mathdefault">p</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.149108em;vertical-align:-0.25em;"></span><span class="mord mathdefault">p</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">m</span><span class="mbin mtight">−</span><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.212108em;vertical-align:-0.686em;"></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:1.526108em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mclose">!</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.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault">λ</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.849108em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</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.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="mord mathdefault">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathdefault mtight">λ</span></span></span></span></span></span></span></span></span></span></span></span></span><br /> 有了以上结论,我们将情景换为 <em>capacity 为 n 的 HashMap 往里面插入 n/2 个元素</em>,假设 hash 是均匀分布的,设某个 bin 里元素的个数为 X,则<br /> <span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>X</mi><mo>∼</mo><mi>b</mi><mo stretchy="false">(</mo><mfrac><mi>n</mi><mn>2</mn></mfrac><mo separator="true">,</mo><mfrac><mn>1</mn><mi>n</mi></mfrac><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">X\sim b(\frac{n}{2}, \frac{1}{n})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">∼</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.00744em;vertical-align:-0.686em;"></span><span class="mord mathdefault">b</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:1.10756em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</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.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></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:1.32144em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">n</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.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span></span></span></span></span><br /> 使用泊松分布近似,取<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>λ</mi><mo>=</mo><mfrac><mi>n</mi><mn>2</mn></mfrac><mo>×</mo><mfrac><mn>1</mn><mi>n</mi></mfrac><mo>=</mo><mn>0.5</mn></mrow><annotation encoding="application/x-tex">\lambda = \frac{n}{2} \times \frac{1}{n} = 0.5</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathdefault">λ</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.040392em;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.695392em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</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 mathdefault mtight">n</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="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.190108em;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.845108em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">n</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 mtight">1</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="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">0</span><span class="mord">.</span><span class="mord">5</span></span></span></span>,计算结果在源码的注释里已经给出:</p> <table> <thead> <tr> <th style="text-align: left">k</th> <th style="text-align: left">P(X=k)</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">0</td> <td style="text-align: left">0.60653066</td> </tr> <tr> <td style="text-align: left">1</td> <td style="text-align: left">0.30326533</td> </tr> <tr> <td style="text-align: left">2</td> <td style="text-align: left">0.07581633</td> </tr> <tr> <td style="text-align: left">3</td> <td style="text-align: left">0.01263606</td> </tr> <tr> <td style="text-align: left">4</td> <td style="text-align: left">0.00157952</td> </tr> <tr> <td style="text-align: left">6</td> <td style="text-align: left">0.00001316</td> </tr> <tr> <td style="text-align: left">7</td> <td style="text-align: left">0.00000094</td> </tr> <tr> <td style="text-align: left">8</td> <td style="text-align: left">0.00000006</td> </tr> </tbody> </table> <p>可以发现,当 k=8 的时候,概率已经小于百万分之一了。</p> 2017-05-09T09:07:15+00:00 https://ysmull.cn/JCF/HashMap.html https://ysmull.cn/JCF/HashMap.html HashSet 源码分析 <ul id="markdown-toc"> <li><a href="#id-hashset-构造" id="markdown-toc-id-hashset-构造">HashSet 构造</a></li> <li><a href="#id-hashsetadd-调用链分析" id="markdown-toc-id-hashsetadd-调用链分析">HashSet#add 调用链分析</a></li> <li><a href="#id-hashsetequals-调用链分析" id="markdown-toc-id-hashsetequals-调用链分析">HashSet#equals 调用链分析</a></li> <li><a href="#id-hashsetremove-调用链分析" id="markdown-toc-id-hashsetremove-调用链分析">HashSet#remove 调用链分析</a></li> <li><a href="#id-总结" id="markdown-toc-id-总结">总结</a></li> </ul> <h2 id="id-hashset-构造">HashSet 构造</h2> <pre><code class="language-java">private transient HashMap&lt;E,Object&gt; map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); /** * Constructs a new, empty set; the backing &lt;tt&gt;HashMap&lt;/tt&gt; instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap&lt;&gt;(); } public HashSet(Collection&lt;? extends E&gt; c) { map = new HashMap&lt;&gt;(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap&lt;&gt;(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap&lt;&gt;(initialCapacity); } /** * Constructs a new, empty linked hash set. (This package private * constructor is only used by LinkedHashSet.) The backing * HashMap instance is a LinkedHashMap with the specified initial * capacity and the specified load factor. */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap&lt;&gt;(initialCapacity, loadFactor); } </code></pre> <p>可以看到 HashSet 包裹了一个 HashMap,这里的 <code>PRESENT</code> 是一个空的对象,作为这个 HashMap 的 Value。注意到,HashMap 的默认初始容量为 16,默认负载因子为 0.75。</p> <h2 id="id-hashsetadd-调用链分析">HashSet#add 调用链分析</h2> <pre><code class="language-java">//1.HashSet#add() public boolean add(E e) { return map.put(e, PRESENT)==null; } </code></pre> <pre><code class="language-java">//HashMap#hash static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h &gt;&gt;&gt; 16); } //2.HashMap#put public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } </code></pre> <pre><code class="language-java">//3.HashMap#putVal final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) &amp; hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node&lt;K,V&gt; e; K k; if (p.hash == hash &amp;&amp; ((k = p.key) == key || (key != null &amp;&amp; key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount &gt;= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash &amp;&amp; ((k = e.key) == key || (key != null &amp;&amp; key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size &gt; threshold) resize(); afterNodeInsertion(evict); return null; } </code></pre> <p>调用链为:</p> <ol> <li><em>HashSet#add</em></li> <li><em>HashMap#put</em> ( –&gt; 使用到 <em>HashMap#hash</em> –&gt; 使用到 <em>Object#hashCode</em>)</li> <li><em>HashMap#putVal</em> ( –&gt; 使用到了 <em>Object#equals</em>)</li> </ol> <h2 id="id-hashsetequals-调用链分析">HashSet#equals 调用链分析</h2> <pre><code class="language-java">//1.AbstractSet#equals public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Set)) return false; Collection&lt;?&gt; c = (Collection&lt;?&gt;) o; if (c.size() != size()) return false; try { return containsAll(c); } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } } </code></pre> <pre><code class="language-java">//2.AbstractCollection#containsAll public boolean containsAll(Collection&lt;?&gt; c) { for (Object e : c) if (!contains(e)) return false; return true; } </code></pre> <pre><code class="language-java">//3.HashSet#contains public boolean contains(Object o) { return map.containsKey(o); } </code></pre> <pre><code class="language-java">//4.HashMap#containsKey public boolean containsKey(Object key) { return getNode(hash(key), key) != null; } </code></pre> <pre><code class="language-java">//5.HashMap#getNode final Node&lt;K,V&gt; getNode(int hash, Object key) { Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; first, e; int n; K k; if ((tab = table) != null &amp;&amp; (n = tab.length) &gt; 0 &amp;&amp; (first = tab[(n - 1) &amp; hash]) != null) { if (first.hash == hash &amp;&amp; ((k = first.key) == key || (key != null &amp;&amp; key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode&lt;K,V&gt;)first).getTreeNode(hash, key); do { if (e.hash == hash &amp;&amp; ((k = e.key) == key || (key != null &amp;&amp; key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } </code></pre> <p>上面依次调用了:</p> <ol> <li><em>AbstractSet#equals</em></li> <li><em>AbstractCollection#containsAll</em></li> <li><em>HashSet#contains</em></li> <li><em>HashMap#containsKey</em> ( –&gt; 使用到 <em>HashMap#hash</em> –&gt; 使用到 <em>Object#hashCode</em>)</li> <li><em>HashMap#getNode</em> ( –&gt; 使用到了 <em>Object#equals</em>)</li> </ol> <p>由于 HashSet 其实是把 element 存储在 HashMap 的 key 上,<em>HashMap#getNode</em> 根据 key 来找结点,如果不为 <code>null</code>,则证明 HashMap 包含这个元素,也就是 HashSet 包含这个元素。所以对两个 HashMap 进行 equals 操作时,被包装类型必须重写 <code>equals()</code> 和 <code>hashCode()</code> 方法,否则会导致<br /> <em>HashSet#contains</em> 判断失效。</p> <h2 id="id-hashsetremove-调用链分析">HashSet#remove 调用链分析</h2> <pre><code class="language-java">//1.HashSet#remove public boolean remove(Object o) { return map.remove(o)==PRESENT; } </code></pre> <pre><code class="language-java">//2.HashMap#remove /** * Removes the mapping for the specified key from this map if present. * * @param key key whose mapping is to be removed from the map * @return the previous value associated with &lt;tt&gt;key&lt;/tt&gt;, or * &lt;tt&gt;null&lt;/tt&gt; if there was no mapping for &lt;tt&gt;key&lt;/tt&gt;. * (A &lt;tt&gt;null&lt;/tt&gt; return can also indicate that the map * previously associated &lt;tt&gt;null&lt;/tt&gt; with &lt;tt&gt;key&lt;/tt&gt;.) */ public V remove(Object key) { Node&lt;K,V&gt; e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } </code></pre> <pre><code class="language-java">//3.HashMap#removeNode final Node&lt;K,V&gt; removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, index; if ((tab = table) != null &amp;&amp; (n = tab.length) &gt; 0 &amp;&amp; (p = tab[index = (n - 1) &amp; hash]) != null) { Node&lt;K,V&gt; node = null, e; K k; V v; if (p.hash == hash &amp;&amp; ((k = p.key) == key || (key != null &amp;&amp; key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode&lt;K,V&gt;)p).getTreeNode(hash, key); else { do { if (e.hash == hash &amp;&amp; ((k = e.key) == key || (key != null &amp;&amp; key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null &amp;&amp; (!matchValue || (v = node.value) == value || (value != null &amp;&amp; value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode&lt;K,V&gt;)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; } </code></pre> <p>调用依次为:</p> <ol> <li><em>HashSet#remove</em></li> <li><em>HashMap#remove</em></li> <li><em>HashMap#removeNode</em></li> </ol> <p>看上面 <em>HashMap#remove</em> 的注释说的:<br /> “调用这个方法会删除键值为 key 的元素并且返回该 key 的 value。如果返回 <code>null</code> 代表不存在这个 key”。然而马上括号里面又说返回 <code>null</code>也有可能是这个 key 对应的 value 就是 <code>null</code>。这说明 HashMap 的 value 是可以为 <code>null</code> 的。(其实 key 也可以为 <code>null</code>)</p> <h2 id="id-总结">总结</h2> <p>1.现在回过头来看《阿里巴巴 Java 开发手册》编程规约的集合处理部分第一条:</p> <blockquote> <p>1.【强制】关于 hashCode 和 equals 的处理,遵循如下规则:</p> <p>1) 只要重写equals,就必须重写hashCode。</p> <p>2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的 对象必须重写这两个方法。</p> <p>3) 如果自定义对象做为Map的键,那么必须重写hashCode和equals。</p> <p>说明:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象 作为 key 来使用。</p> </blockquote> <p>2.你会发现 <em>HashMap#putVal</em> 方法里面怎么有 <code>putTreeVal()</code> 和 <code>treeifyBin()</code> 这样的带 tree 字眼的方法。这其实是 Java8 相对 Java7 的<a href="http://docs.oracle.com/javase/8/docs/technotes/guides/collections/changes8.html">升级</a>,对于 HashMap、LinkedHashMap、ConcurrentHashMap 中存在大量冲突的位置,将用一颗平衡二叉树取代链表,可以看到代码里面定义了一些常量和操作:</p> <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>TREEIFY_THRESHOLD</code>=8</td> <td style="text-align: center"><em>HashMap#treeifyBin</em></td> </tr> <tr> <td style="text-align: center"><code>UNTREEIFY_THRESHOLD</code>=6</td> <td style="text-align: center"><em>TreeNode#untreeify</em></td> </tr> <tr> <td style="text-align: center"><code>MIN_TREEIFY_CAPACITY</code>=64</td> <td style="text-align: center"><em>HashMap#resize</em></td> </tr> </tbody> </table> <p>详见:</p> <ul> <li>OpenJDK/JDK7 :<a href="http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/util/HashMap.java">classes/java/util/HashMap.java</a></li> <li>OpenJDK/JDK8 :<a href="http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashMap.java">classes/java/util/HashMap.java</a></li> </ul> <p>3.这一版本同时去掉了 JDK7 对 HashMap 增加的一个<a href="http://docs.oracle.com/javase/8/docs/technotes/guides/collections/changes7.html">hash function</a>,设置 <em>jdk.map.althashing.threshold</em> 后,当 Map 的 size 超过这个设置的值,对于 key 类型为 String 的 Map,将会采用一个特别设计的 hash function,默认不启用。<br /> 需要重点提醒的是,不论是 JDK7 采用 <code>alternative hash function</code> 或者是 JDK8 把 Map 的链表优化成平衡树,均可能导致某些情况下遍历其元素的顺序产生变化。因此我们的代码 <strong>不应该依赖于对Map遍历的顺序</strong>。</p> 2017-04-19T17:18:44+00:00 https://ysmull.cn/JCF/HashSet.html https://ysmull.cn/JCF/HashSet.html JCF -- Overview <ul id="markdown-toc"> <li><a href="#id-collection-consists" id="markdown-toc-id-collection-consists">Collection Consists</a></li> <li><a href="#id-collection-interfaces" id="markdown-toc-id-collection-interfaces">Collection Interfaces</a></li> <li><a href="#id-collection-implementations" id="markdown-toc-id-collection-implementations">Collection Implementations</a></li> <li><a href="#id-concurrent-collections" id="markdown-toc-id-concurrent-collections">Concurrent Collections</a> <ul> <li><a href="#id-interface" id="markdown-toc-id-interface">Interface</a></li> <li><a href="#id-implementations" id="markdown-toc-id-implementations">Implementations</a></li> </ul> </li> </ul> <h2 id="id-collection-consists">Collection Consists</h2> <ul> <li><strong>Collection interfaces</strong>: 描绘了不同的集合类型,比如<code>sets</code>、<code>lists</code>、<code>maps</code>。这些接口建立了集合框架的基础。</li> <li><strong>General-purpose implementations</strong>: 对集合接口的通用实现。</li> <li><strong>Legacy implementations</strong>: 很早版本的集合类 Vector 和 Hashtable 也实现了新的接口。</li> <li><strong>Special-purpose implementations</strong>: 一些专用的实现。这些实现拥有非标准化的性能特性、使用限制和行为。</li> <li><strong>Concurrent implementations</strong>: 为高并发场景而设计的实现。</li> <li><strong>Wrapper implementations</strong>: 为其它实现增加了功能,比如同步。</li> <li><strong>Convenience implementations</strong>: 一组简化版高性能实现。</li> <li><strong>Abstract implementations</strong>: 一些加速某些实现构建的实现。</li> <li><strong>Algorithms</strong>: 在集合上运行的一些有用的静态方法,比如给列表排序。</li> <li><strong>Infrastructure</strong>: 对集合提供重要支持的接口</li> <li><strong>Array Utilities</strong>: 为数组提供了使用的函数。确切的说不是集合框架的一部分,但是是和集合框架同事加进Java平台的。</li> </ul> <h2 id="id-collection-interfaces">Collection Interfaces</h2> <p>java.util.Collection 的后代:</p> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-04-19-collection_interfaces.png?imageView2/1/q/100" alt="" /></p> <p>java.util.Map 的后代:</p> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-04-19-map_interfaces.png?imageView2/1/q/100" alt="graph" /></p> <h2 id="id-collection-implementations">Collection Implementations</h2> <table> <thead> <tr> <th style="text-align: left">Interface</th> <th style="text-align: center">Hash Table</th> <th style="text-align: center">Resizable Array</th> <th style="text-align: center">Balanced Tree</th> <th style="text-align: center">Linked List</th> <th style="text-align: center">Hash Table + Linked List</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">Set</td> <td style="text-align: center"><a href="/JCF/HashSet.html">HashSet</a></td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/TreeSet.html">TreeSet</a></td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/LinkedHashSet.html">LinkedHashSet</a></td> </tr> <tr> <td style="text-align: left">List</td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/ArrayList.html">ArrayList</a></td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/LinkedList.html">LinkedList</a></td> <td style="text-align: center"> </td> </tr> <tr> <td style="text-align: left">Deque</td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/ArrayDeque.html">ArrayDeque</a></td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/LinkedList.html">LinkedList</a></td> <td style="text-align: center"> </td> </tr> <tr> <td style="text-align: left">Map</td> <td style="text-align: center"><a href="/JCF/HashMap.html">HashMap</a></td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/TreeMap.html">TreeMap</a></td> <td style="text-align: center"> </td> <td style="text-align: center"><a href="/JCF/LinkedHashMap.html">LinkedHashMap</a></td> </tr> </tbody> </table> <h2 id="id-concurrent-collections">Concurrent Collections</h2> <h3 id="id-interface">Interface</h3> <ul> <li>BlockingQueue</li> <li>TransferQueue</li> <li>BlockingDeque</li> <li>ConcurrentMap</li> <li>ConcurrentNavigableMap</li> </ul> <h3 id="id-implementations">Implementations</h3> <ul> <li>LinkedBlockingQueue</li> <li>ArrayBlockingQueue</li> <li>PriorityBlockingQueue</li> <li>DelayQueue</li> <li>SynchronousQueue</li> <li>LinkedBlockingDeque</li> <li>LinkedTransferQueue</li> <li>CopyOnWriteArrayList</li> <li>CopyOnWriteArraySet</li> <li>ConcurrentSkipListSet</li> <li>ConcurrentHashMap</li> <li>ConcurrentSkipListMap</li> </ul> 2017-04-19T14:30:10+00:00 https://ysmull.cn/JCF/Overview.html https://ysmull.cn/JCF/Overview.html Selenium2讲解---至媳妇儿 <ul id="markdown-toc"> <li><a href="#id-什么是webdriver" id="markdown-toc-id-什么是webdriver">什么是WebDriver</a></li> <li><a href="#id-如何通过-webdriver-让浏览器做事情" id="markdown-toc-id-如何通过-webdriver-让浏览器做事情">如何通过 WebDriver 让浏览器做事情</a></li> <li><a href="#id-附wire-协议图一张" id="markdown-toc-id-附wire-协议图一张">附:Wire 协议图一张</a></li> </ul> <p>关键词:<strong>webdriver</strong>、<strong>wire协议</strong>、<strong>Http调用</strong>。</p> <h2 id="id-什么是webdriver">什么是WebDriver</h2> <blockquote> <p>当Selenium2.x 提出了WebDriver的概念之后,它提供了完全另外的一种方式与浏览器交互。<strong>那就是把浏览器原生的API封装成一套Selenium WebDriver API</strong>,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)。由于使用的是浏览器原生的API,速度大大提高,而且调用的稳定性交给了浏览器厂商本身,显然是更加科学。然而带来的一些副作用就是,不同的浏览器厂商,对Web元素的操作和呈现多少会有一些差异,这就直接导致了 <strong>Selenium WebDriver要分浏览器厂商不同而提供不同的实现</strong>。例如Firefox就有专门的FirefoxDriver,Chrome就有专门的ChromeDriver等等。</p> </blockquote> <p>总结:</p> <ol> <li><strong>WebDriver就是把浏览器原生的API(接口)封装成了统一的Selenium WebDriver API</strong></li> <li><strong>不同浏览器需要使用不同的driver(驱动程序)</strong></li> </ol> <h2 id="id-如何通过-webdriver-让浏览器做事情">如何通过 WebDriver 让浏览器做事情</h2> <blockquote> <p>在我们new一个webdriver过程中selenium会检测本地浏览器组件是否存在,版本是否匹配。接着会 <strong>启动一个WebService</strong>,这套Web Service使用了Selenium自己设计定义的协议,名字叫做 <strong>The WebDriver Wire Protocol</strong>。,这套协议几乎可以操作浏览器的任何操作,Wire协议是通用的,也就是说不管是FirefoxDriver还是ChromeDriver,启动之后都会在某一个端口启动基于这套协议的WebService。<br /> 例如FirefoxDriver初始化成功之后,默认会从<code>http://localhost:7055</code>开始,而ChromeDriver则大概是<code>http://localhost:46350</code>。<br /> 接下来,我们 <strong>调用WebDriver的任何API,实际上是给Web Service发送HTTP请求</strong>。在我们的HTTP 请求的body中,会 <strong>以Wire协议规定的JSON格式的字符串</strong> 来告诉Selenium我们希望浏览器接下来做什么事情。</p> </blockquote> <p>总结:</p> <ol> <li><strong>Webdriver启动一个webservice(web服务)</strong></li> <li><strong>根据Wire协议给Webdriver发送HTTP请求</strong> <ul> <li>request和response都以Wire协议规定的<strong>JSON格式</strong>的字符串通信</li> </ul> </li> <li><strong>webdriver接收到请求后再去操纵浏览器</strong></li> </ol> <h2 id="id-附wire-协议图一张">附:Wire 协议图一张</h2> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-04-13-114724.jpg" alt="" /></p> 2017-04-13T20:57:02+00:00 https://ysmull.cn/blog/Selenium.html https://ysmull.cn/blog/Selenium.html JMeter讲解---至媳妇儿 <ul id="markdown-toc"> <li><a href="#id-1如何录请求" id="markdown-toc-id-1如何录请求">1.如何录请求</a></li> <li><a href="#id-2如何进行性能测试" id="markdown-toc-id-2如何进行性能测试">2.如何进行性能测试</a> <ul> <li><a href="#id-21-如何使用java创建线程" id="markdown-toc-id-21-如何使用java创建线程">2.1 如何使用Java创建线程</a></li> <li><a href="#id-22-如何使用java发送一个http请求" id="markdown-toc-id-22-如何使用java发送一个http请求">2.2 如何使用Java发送一个HTTP请求</a></li> <li><a href="#id-23-如何使用java并发发送请求" id="markdown-toc-id-23-如何使用java并发发送请求">2.3 如何使用Java并发发送请求</a></li> </ul> </li> </ul> <h2 id="id-1如何录请求">1.如何录请求</h2> <p>先解释什么叫代理服务器,代理服务器是一个程序,运行在特定的IP地址和端口号上。</p> <ol> <li>无代理服务器:HTTP请求直接发送出去。</li> </ol> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-04-13-120507.jpg" alt="" /></p> <ol> <li>有代理服务器:HTTP请求先通过代理服务器,代理服务器再转发出去。</li> </ol> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-04-13-120831.jpg" alt="" /></p> <p>JMeter和Charles会创建一个HTTP代理服务器(HTTP Proxy Server),拦截所有从操作系统上发出的HTTP请求并记录,这就是录请求的原理。</p> <h2 id="id-2如何进行性能测试">2.如何进行性能测试</h2> <p>JMeter通过线程组来模拟真实用户对Web服务器的访问压力,创建许多个线程来并发的给目的地址发送HTTP请求。</p> <h3 id="id-21-如何使用java创建线程">2.1 如何使用Java创建线程</h3> <pre><code class="language-java">public class TestThread { public static void main(String[] args) { for (int i = 0; i &lt; 10; i++) { MyThread thread = new MyThread(i); thread.start(); } } } class MyThread extends Thread { public int a; public MyThread(int a) { this.a = a; } @Override public void run() { System.out.println(a); } } </code></pre> <p>运行结果:</p> <pre><code>0 3 4 2 1 6 5 7 8 9 </code></pre> <h3 id="id-22-如何使用java发送一个http请求">2.2 如何使用Java发送一个HTTP请求</h3> <pre><code class="language-java">public class MockHTTP { public static void get(String url) { try { URL target = new URL(url); HttpURLConnection connection = (HttpURLConnection) target.openConnection(); InputStream is = connection.getInputStream(); int len = connection.getContentLength(); byte[] b = new byte[len]; is.read(b, 0, len); System.out.println(new String(b, StandardCharsets.UTF_8)); } catch (java.io.IOException e) { e.printStackTrace(); } } public static void main(String[] args) { get("http://www.baidu.com"); } } </code></pre> <p>结果:</p> <pre><code class="language-html">&lt;!DOCTYPE html&gt; &lt;!--STATUS OK--&gt;&lt;html&gt; &lt;head&gt;&lt;meta http-equiv=content-type content=text/html;charset=utf-8&gt;&lt;meta http-equiv=X-UA-Compatible content=IE=Edge&gt;&lt;meta content=always name=referrer&gt;&lt;link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css&gt;&lt;title&gt;百度一下,你就知道&lt;/title&gt;&lt;/head&gt; &lt;body link=#0000cc&gt; &lt;div id=wrapper&gt; &lt;div id=head&gt; &lt;div class=head_wrapper&gt; &lt;div class=s_form&gt; &lt;div class=s_form_wrapper&gt; &lt;div id=lg&gt; &lt;img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129&gt; &lt;/div&gt; &lt;form id=form name=f action=//www.baidu.com/s class=fm&gt; &lt;input type=hidden name=bdorz_come value=1&gt; &lt;input type=hidden name=ie value=utf-8&gt; &lt;input type=hidden name=f value=8&gt; &lt;input type=hidden name=rsv_bp value=1&gt; &lt;input type=hidden name=rsv_idx value=1&gt; &lt;input type=hidden name=tn value=baidu&gt;&lt;span class="bg s_ipt_wr"&gt;&lt;input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus&gt;&lt;/span&gt;&lt;span class="bg s_btn_wr"&gt;&lt;input type=submit id=su value=百度一下 class="bg s_btn"&gt;&lt;/span&gt; &lt;/form&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=u1&gt; &lt;a href=http://news.baidu.com name=tj_trnews class=mnav&gt;新闻&lt;/a&gt; &lt;a href=http://www.hao123.com name=tj_trhao123 class=mnav&gt;hao123&lt;/a&gt; &lt;a href=http://map.baidu.com name=tj_trmap class=mnav&gt;地图&lt;/a&gt; &lt;a href=http://v.baidu.com name=tj_trvideo class=mnav&gt;视频&lt;/a&gt; &lt;a href=http://tieba.baidu.com name=tj_trtieba class=mnav&gt;贴吧&lt;/a&gt; &lt;noscript&gt; &lt;a href=http://www.baidu.com/bdorz/login.gif?login&amp;amp;tpl=mn&amp;amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb&gt;登录&lt;/a&gt; &lt;/noscript&gt; &lt;script&gt;document.write('&lt;a href="http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&amp;")+ "bdorz_come=1")+ '" name="tj_login" class="lb"&gt;登录&lt;/a&gt;');&lt;/script&gt; &lt;a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;"&gt;更多产品&lt;/a&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div id=ftCon&gt; &lt;div id=ftConw&gt; &lt;p id=lh&gt; &lt;a href=http://home.baidu.com&gt;关于百度&lt;/a&gt; &lt;a href=http://ir.baidu.com&gt;About Baidu&lt;/a&gt; &lt;/p&gt; &lt;p id=cp&gt;&amp;copy;2017&amp;nbsp;Baidu&amp;nbsp;&lt;a href=http://www.baidu.com/duty/&gt;使用百度前必读&lt;/a&gt;&amp;nbsp; &lt;a href=http://jianyi.baidu.com/ class=cp-feedback&gt;意见反馈&lt;/a&gt;&amp;nbsp;京ICP证030173号&amp;nbsp; &lt;img src=//www.baidu.com/img/gs.gif&gt; &lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <h3 id="id-23-如何使用java并发发送请求">2.3 如何使用Java并发发送请求</h3> <pre><code class="language-java">public class MockHTTPThreads { public static void main(String[] args) { for (int i = 0; i &lt; 100; i++) { MyThread thread = new MyThread(); thread.start(); } } } class MyThread extends Thread { public void get(String url) { try { URL target = new URL(url); HttpURLConnection connection = (HttpURLConnection) target.openConnection(); InputStream is = connection.getInputStream(); int len = connection.getContentLength(); byte[] b = new byte[len]; is.read(b, 0, len); System.out.println(new String(b, StandardCharsets.UTF_8)); } catch (java.io.IOException e) { e.printStackTrace(); } } @Override public void run() { get("http://www.baidu.com"); } } </code></pre> <p>结果:</p> <pre><code>100条百度首页的html。 </code></pre> 2017-04-13T20:52:55+00:00 https://ysmull.cn/blog/Jmeter.html https://ysmull.cn/blog/Jmeter.html Singleton Pattern <ul id="markdown-toc"> <li><a href="#id-intent" id="markdown-toc-id-intent">Intent</a></li> <li><a href="#id-implements" id="markdown-toc-id-implements">Implements</a> <ul> <li><a href="#id-eager-initialization" id="markdown-toc-id-eager-initialization">Eager initialization</a></li> <li><a href="#id-lazy-initialization-i" id="markdown-toc-id-lazy-initialization-i">Lazy initialization I</a></li> <li><a href="#id-lazy-initialization-ii" id="markdown-toc-id-lazy-initialization-ii">Lazy initialization II</a></li> <li><a href="#id-lazy-initialization-iii" id="markdown-toc-id-lazy-initialization-iii">Lazy initialization III</a></li> <li><a href="#id-lazy-initialization-iv" id="markdown-toc-id-lazy-initialization-iv">Lazy initialization IV</a></li> <li><a href="#id-lazy-initialization-v" id="markdown-toc-id-lazy-initialization-v">Lazy initialization V</a></li> <li><a href="#id-enum-singleton" id="markdown-toc-id-enum-singleton">Enum Singleton</a></li> </ul> </li> <li><a href="#id-reference" id="markdown-toc-id-reference">Reference</a></li> </ul> <h2 id="id-intent">Intent</h2> <blockquote> <p>Ensure a class has only one instance, and provide a global point of access to it.</p> <p>确保一个类只有一个实例,并提供一个全局访问点。<a href="https://en.wikipedia.org/wiki/Singleton_pattern" title="Singleton pattern">[1]</a></p> </blockquote> <h2 id="id-implements">Implements</h2> <h3 id="id-eager-initialization">Eager initialization</h3> <pre><code class="language-java">public class Singleton { public static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } </code></pre> <p>说明: 构造函数声明为private是为了防止用new创建类的实例。</p> <h3 id="id-lazy-initialization-i">Lazy initialization I</h3> <p>只有第一次调用<code>getInstance()</code>方法才会创建实例。</p> <pre><code class="language-java">public final class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } </code></pre> <h3 id="id-lazy-initialization-ii">Lazy initialization II</h3> <p><a href="#id-lazy-initialization-i"><em>Lazy initialization I</em></a> 的实现在多线程环境下可能会产生多个实例。比如线程A判断<code>instance == null</code>后在执行<code>new Singleton()</code>之前,线程B也在判断<code>instance == null</code>,这样两个线程调用<code>getInstance()</code>返回的是不同的实例。解决办法是加一个重量级锁,保证同一时刻只有一个线程能进入<code>getInstance()</code>方法。</p> <pre><code class="language-java">public final class Singleton { private static Singleton instance = null; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } </code></pre> <h3 id="id-lazy-initialization-iii">Lazy initialization III</h3> <p><a href="#id-lazy-initialization-ii"><em>Lazy initialization II</em></a> 的实现虽然解决了可能创建多个实例的问题,但是当instance已经创建完毕后,之后线程每一次调用<code>getInstance()</code>获取单例时都需要同步,这会带来性能问题。为了减少同步,我们可以使用<code>double-checked locking</code>来减少同步代码块的规模。</p> <pre><code class="language-java">public final class Singleton { private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // a synchronized(Singleton.class) { // b if (instance == null) { // c instance = new Singleton(); } } } return instance; } } </code></pre> <p>说明:</p> <ol> <li>我上面写的这种 <em>dobule-checked locking</em> <a href="https://en.wikipedia.org/wiki/Double-checked_locking" title="Double-checked locking">[3]</a>只适用于java 5+</li> <li>假设多个线程同时调用<code>getInstance()</code>方法,首先在 <em>a</em> 处判断instance是否为null,假设大家都判断为null,然后执行到 <em>b</em> 处,只有一个线程能进入同步代码块执行 <em>b</em> 里面的代码,这个线程创建完instance实例离开同步区后,其它线程就可以一个一个的进入同步代码块了,由于volaile保证了<code>instance</code>的<strong>可见性</strong>,当其它线程执行到 <em>c</em> 时,会发现instance不为null了,就不会创建新实例。</li> </ol> <h3 id="id-lazy-initialization-iv">Lazy initialization IV</h3> <p><a href="#id-lazy-initialization-iii"><em>Lazy initialization III</em></a>已经没什么问题了,但是性能还可以再提高一些。我们可以减少对volatile变量的访问。</p> <pre><code class="language-java">public final class Singleton { private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { Singleton result = instance; if (result == null) { synchronized(Singleton.class) { result = instance; if (result == null) { instance = result = new Singleton(); } } } return result; } } </code></pre> <p>引入result中间变量,是因为大多数时候,instance已经被实例化了,这样代码对instance的访问就只有一次,《effectiv java》的作者说在他的电脑上这样做相对不引入临时变量,提高了25%的性能。</p> <h3 id="id-lazy-initialization-v">Lazy initialization V</h3> <p>使用 <em>initialize-on-demand holder class</em> <a href="https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom" title="Initialization-on-demand holder idiom">[2]</a></p> <pre><code class="language-java">public class Singleton { private Singleton() {} public static Singleton getInstance() { return LazyHolder.instance; } private static class LazyHolder { private static Singleton instance = new Singleton(); } } </code></pre> <p>补充说明:</p> <ol> <li>如果我们要延迟加载的是instance filed,考虑使用 <em>dobule-checked locking</em></li> <li>如果我们要延迟加载的是static filed,考虑使用 <em>initialize-on-demand holder class</em></li> </ol> <h3 id="id-enum-singleton">Enum Singleton</h3> <p>上面的单利模式的实现可能会被反射或者序列化攻击,我们使用enum singleton</p> <pre><code class="language-java">public enum Singleton { INSTANCE; } </code></pre> <p>当我们第一次调用<code>Singleton.INSTANCE</code>的时候,Singleton就会被加载和初始化,翻译一下就是:</p> <pre><code class="language-java">public final class Singleton { public final static Singleton INSTANCE = new Singleton(); } </code></pre> <p>这个方法被<a href="https://raw.githubusercontent.com/IMCG/books/master/books/Effective%20Java%2C%202nd%20Edition.pdf" title="Effective Java, 2nd Edition.pdf">[4]</a>的作者认为是单例模式最佳实践。</p> <h2 id="id-reference">Reference</h2> 2017-04-12T10:48:25+00:00 https://ysmull.cn/blog/singleton_pattern.html https://ysmull.cn/blog/singleton_pattern.html Prototype Pattern <ul id="markdown-toc"> <li><a href="#id-quote" id="markdown-toc-id-quote">Quote</a></li> <li><a href="#id-participants" id="markdown-toc-id-participants">Participants</a></li> <li><a href="#id-practice" id="markdown-toc-id-practice">Practice</a></li> <li><a href="#id-attention" id="markdown-toc-id-attention">Attention</a></li> <li><a href="#id-reference" id="markdown-toc-id-reference">Reference</a></li> </ul> <h2 id="id-quote">Quote</h2> <ul> <li> <blockquote> <p>Prototype design pattern is used in scenarios where application needs to create a number of instances of a class, which has almost same state or differs very little.<a href="http://howtodoinjava.com/design-patterns/creational/prototype-design-pattern-in-java" title="howtodoinjava: prototype-design-pattern-in-java">[2]</a></p> </blockquote> </li> <li> <blockquote> <p>Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.<a href="https://sourcemaking.com/design_patterns/prototype" title="sourcemaking: prototype">[1]</a></p> </blockquote> </li> <li> <blockquote> <p>当创建给定类的实例很昂贵或很复杂时使用原型模式。—《Head First设计模式》</p> </blockquote> </li> </ul> <h2 id="id-participants">Participants</h2> <ul> <li><strong>Prototype</strong> : the prototype of actual object.</li> <li><strong>Prototype registry</strong> : This is used as registry service to have all prototypes accessible using simple string parameters.</li> <li><strong>Client</strong> : Client will be responsible for using registry service to access prototype instances.</li> </ul> <h2 id="id-practice">Practice</h2> <p>定义一个细胞Prototype,<code>split()</code>方法表示出分裂(clone)出一个新细胞。</p> <pre><code class="language-java">interface Cell extends Cloneable { Cell split() throws CloneNotSupportedException; } </code></pre> <p>分别定义动物细胞和植物细胞</p> <pre><code class="language-java">public class AnimalCell implements Cell { @Override public AnimalCell split() throws CloneNotSupportedException { return (AnimalCell) super.clone(); } } public class PlantCell implements Cell { @Override public PlantCell split() throws CloneNotSupportedException { return (PlantCell) super.clone(); } } </code></pre> <p>写一个CellRegestry,可以根据不同的细胞类型返回对应的新细胞实例。(有种简单工厂的味道吧?)</p> <pre><code class="language-java">public class CellRegestry { public enum CellType { ANIMAL, PLANT } private static Map&lt;CellType, Cell&gt; prototypes = new HashMap&lt;&gt;(); static { prototypes.put(CellType.ANIMAL, new AnimalCell()); prototypes.put(CellType.PLANT, new PlantCell()); } public static Cell getCell(CellType type) throws CloneNotSupportedException { // 先根据type获取对应实例,然后clone之 return prototypes.get(type).split(); } } </code></pre> <p>Client使用:</p> <pre><code class="language-java">public class Client { public static void main(String[] args) { try { AnimalCell cell1 = (AnimalCell) CellRegestry.getNewCell(CellType.ANIMAL); PlantCell cell2 = (PlantCell) CellRegestry.getNewCell(CellType.PLANT); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } </code></pre> <h2 id="id-attention">Attention</h2> <ol> <li>Cloneable接口是个<strong>标记接口</strong>(tagging/marker interface),它没有方法。标记接口的唯一目的是可以用instanceof进行类型检查。</li> <li>使用 Prototype Pattern 的其中一个原因是因为 clone 比 new 要快,但其实这已经<strong>过时</strong>了,参考<a href="http://stackoverflow.com/questions/2427317/java-prototype-pattern-new-vs-clone-vs-class-newinstance" title="Java 'Prototype' pattern - new vs clone vs class.newInstance">[3]</a></li> <li>可以使用<em>工厂方法模式</em>或者<em>复制构造函数</em>来替代该模式<a href="http://stackoverflow.com/questions/3707612/cloning-vs-instantiating-a-new-class" title="cloning vs instantiating a new class">[4]</a>。</li> <li>需要注意<code>深拷贝</code>和<code>浅拷贝</code>的问题。</li> </ol> <h2 id="id-reference">Reference</h2> 2017-04-11T20:05:15+00:00 https://ysmull.cn/blog/prototype_pattern.html https://ysmull.cn/blog/prototype_pattern.html Builder Pattern <ul id="markdown-toc"> <li><a href="#id-definition" id="markdown-toc-id-definition">Definition</a></li> <li><a href="#id-participants" id="markdown-toc-id-participants">Participants</a></li> <li><a href="#id-description" id="markdown-toc-id-description">Description</a></li> <li><a href="#id-practice" id="markdown-toc-id-practice">Practice</a> <ul> <li><a href="#id-ordinary-version" id="markdown-toc-id-ordinary-version">Ordinary Version</a></li> <li><a href="#id-generic-version" id="markdown-toc-id-generic-version">Generic Version</a></li> </ul> </li> <li><a href="#id-reference" id="markdown-toc-id-reference">Reference</a></li> </ul> <h2 id="id-definition">Definition</h2> <blockquote> <p>The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.</p> </blockquote> <h2 id="id-participants">Participants</h2> <ul> <li><strong>Product</strong> 表示被构造的复杂对象</li> <li><strong>Builder</strong> 指定 <code>Product</code> 各个部件的创建过程的接口(或抽象类)</li> <li><strong>ConcreteBuilder</strong> 实现<code>Builder</code></li> <li><strong>Director</strong> 使用Builder的实例实际创建一个<code>Product</code></li> </ul> <h2 id="id-description">Description</h2> <ol> <li><code>Builder</code>处理<code>Director</code>的请求。</li> <li><code>Client</code>从<code>Director</code>获取<code>Product</code>。</li> <li><code>Builder</code> 和 <code>ConcreteBuilder</code> 都不应该有部件的具体业务数据,它们只负责抽象的创建过程。具体的数据只出现在<code>Director</code>。</li> <li><code>Builder</code> 的组件方法的返回类型设定为Builder类本身可以实现 <strong><em>fluent interface</em></strong></li> </ol> <h2 id="id-practice">Practice</h2> <h3 id="id-ordinary-version">Ordinary Version</h3> <pre><code class="language-java">// Product @Data // using Lombok class Car { private int wheels; private String color; } </code></pre> <pre><code class="language-java">// Builder interface CarBuilder { CarBuilder setWheels(final int wheels); CarBuilder setColor(final String color); Car build(); } class CarBuilderImpl implements CarBuilder { private Car car; public CarBuilderImpl() { car = new Car(); } @Override public CarBuilder setWheels(final int wheels) { car.setWheels(wheels); return this; } @Override public CarBuilder setColor(final String color) { car.setColor(color); return this; } @Override public Car build() { return car; } } </code></pre> <pre><code class="language-java">// Director public class CarBuildDirector { private CarBuilder builder; public CarBuildDirector(final CarBuilder builder) { this.builder = builder; } public Car construct() { return builder.setWheels(4) .setColor("Red") .build(); } public static void main(final String[] arguments) { CarBuilder builder = new CarBuilderImpl(); CarBuildDirector carBuildDirector = new CarBuildDirector(builder); System.out.println(carBuildDirector.construct()); } } </code></pre> <h3 id="id-generic-version">Generic Version</h3> <p>我们可以使用Java8实现一个泛型的Builder,很强大!</p> <pre><code class="language-java">public class GenericBuilder&lt;T&gt; { private final Supplier&lt;T&gt; instantiator; private List&lt;Consumer&lt;T&gt;&gt; instanceModifiers = new ArrayList&lt;&gt;(); public GenericBuilder(Supplier&lt;T&gt; instantiator) { this.instantiator = instantiator; } public static &lt;T&gt; GenericBuilder&lt;T&gt; of(Supplier&lt;T&gt; instantiator) { return new GenericBuilder&lt;T&gt;(instantiator); } public &lt;U&gt; GenericBuilder&lt;T&gt; with(BiConsumer&lt;T, U&gt; consumer, U value) { Consumer&lt;T&gt; c = instance -&gt; consumer.accept(instance, value); instanceModifiers.add(c); return this; } public T build() { T value = instantiator.get(); instanceModifiers.forEach(modifier -&gt; modifier.accept(value)); instanceModifiers.clear(); return value; } public static void main(String[] args) { Car car = GenericBuilder.of(Car::new) .with(Car::setColors, "red") .with(Car::setWheels, 5) .build(); } } </code></pre> <h2 id="id-reference">Reference</h2> <ol> <li><a href="https://en.wikipedia.org/wiki/Builder_pattern#Java" title="a">wikipedia builder pattern</a></li> <li><a href="http://stackoverflow.com/questions/31754786/how-to-implement-the-builder-pattern-in-java-8" title="b">how-to-implement-the-builder-pattern-in-java-8</a></li> </ol> 2017-04-10T20:05:15+00:00 https://ysmull.cn/blog/builder_pattern.html https://ysmull.cn/blog/builder_pattern.html Sql_basic_tutorial <blockquote> <p>看这本书的目的是温故标准 SQL 基础知识。</p> </blockquote> <h3 id="id-p14-sql-语句及其种类"><code>p14</code> SQL 语句及其种类</h3> <ul> <li>DDL:(Data Definition Language) <ul> <li><code>CREATE</code></li> <li><code>DROP</code></li> <li><code>ALTER</code></li> </ul> </li> <li>DML:(Data Manipulation Language) <ul> <li><code>SELECT</code></li> <li><code>INSERT</code></li> <li><code>UPDATE</code></li> <li><code>DELETE</code></li> </ul> </li> <li>DCL:(Data Control Language) <ul> <li><code>COMMIT</code></li> <li><code>ROLLBACK</code></li> <li><code>GRANT</code></li> <li><code>REVOKE</code></li> </ul> </li> </ul> <h3 id="id-p15-p16-sql-的基本书写规则"><code>p15-p16</code> SQL 的基本书写规则</h3> <ul> <li>SQL 语句要以分号结尾</li> <li>SQL 语句不区分关键字的大小写</li> <li>常数书写方式固定: <ul> <li>字符串用单引号括起来</li> <li>数字直接写,不使用任何标记符号</li> <li>日期格式有多种(‘26 Jan 2010’或者’10/01/26’或者’2010-01-06’)</li> </ul> </li> <li>单词之间需要用半角空格或者换行符来分隔</li> </ul> <h3 id="id-p21-命名规则"><code>p21</code> 命名规则</h3> <ol> <li><strong>只能使用半角英文字母、数字、下划线作为数据库、表和列的名称</strong>。例如,不能讲列明 a_b 改为 a-b。标准 SQL 不支持连字符、$、#、?作为名称使用。</li> <li><strong>名称只能以半角英文字母开头</strong></li> </ol> 2017-04-07T00:00:00+00:00 https://ysmull.cn/SQL_Basic_tutorial https://ysmull.cn/SQL_Basic_tutorial Abstract Factory <h3 id="id-定义">定义</h3> <blockquote> <p><strong>提供一个接口,用来创建相关或依赖对象的家族,而无需指定它们具体的类。</strong></p> </blockquote> <h3 id="id-实践">实践</h3> <p>抽象工厂模式其实就是对工厂方法模式的升级版。工厂方法模式的那个工厂接口或者抽象类里面只包含一个工厂方法。而抽象工厂方法的工厂接口或者抽象类里,包含有多个工厂方法,可以返回不同类型的产品。这里我们来举一个例子,首先定义一个水果抽象工厂:</p> <pre><code class="language-java">public interface FruitFactory { Apple getApple(); Pear getPear(); } </code></pre> <p>可以看到,这个抽象工厂里面有两个工厂方法,返回的是不同种类的产品,但是这两种产品具有相关性(都是水果)。然后我们实现这个接口,跟工厂方法模式的想法一样,为的是在子类中实例化产品</p> <pre><code class="language-java">public class Factory1 implements FruitFactory { @Override public Apple getApple() { return new Apple1(); } @Override public Pear getPear() { return new Pear1(); } } public class Factory2 implements FruitFactory { @Override public Apple getApple() { return new Apple2(); } @Override public Pear getPear() { return new Pear2(); } } </code></pre> <p>Factory1和Factory2分别返回的是不同的Apple和Pear。这基本上就是抽象工厂模式了。</p> <p>当需要增加一类产品比如Banana时:</p> <ol> <li>新增加Banana接口和产品类,这是正常的也是必须的。</li> <li>在FruitFactory接口里增加一个getBanana的方法。</li> <li>在Factory1和Factory2中实现getBanana方法。</li> </ol> <p>可以看到,当我们增加一类产品,需要修改三个文件,这不符合对修改封闭的原则。那抽象工厂方法的优点是什么呢?就是我们只要一开始指定<strong>具体</strong>使用哪一个工厂,后面的代码将与具体产品解耦,都是面向接口的。</p> <h3 id="id-改进">改进</h3> <p>我们可以使用带反射的简单工厂模式来改进抽象工厂模式。</p> <pre><code class="language-java">public class FruitFactory { private static final String prefix = "me.ysmull.factory.abstractFactory3.product."; public Apple getApple(String className) { try { Class clazz = Class.forName(prefix + className); return (Apple) clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); return null; } } public Pear getPear(String className) { try { Class clazz = Class.forName(prefix + className); return (Pear) clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); return null; } } } </code></pre> <p>可以看到,每一个工厂方法都是一个使用反射的简单工厂。这样当我们需要新增加一类产品的时候,除去新建的类与接口外,我们只需要在FruitFactory里增加一个加单工厂即可。如果只是增加某一类产品的新品种,那么不需要有代码上的修改。</p> 2017-04-03T11:37:26+00:00 https://ysmull.cn/blog/abstract_factory_pattern.html https://ysmull.cn/blog/abstract_factory_pattern.html Factory Method <h3 id="id-定义">定义</h3> <blockquote> <p><strong>定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行.</strong></p> </blockquote> <h3 id="id-实践">实践</h3> <p>工厂方法模式需要使用继承。在《大话数据结构》里使用的是继承一个只含有工厂方法的接口。《Head First 设计模式》里是继承一个抽象类,这个类不仅包括未实现的抽象工厂方法,而且实现了操纵产品的方法。这个抽象类就是产品的使用者。我们使用后者来讲述这个设计模式。</p> <p>首先,Product同<a href="/blog/simple_factory_pattern.html">simple factory pattern</a>。<br /> 然后定义一个抽象的基类,其中<code>protected abstract Product getProduct(String type)</code>就是<strong>工厂方法</strong>,它将在子类被实现。</p> <pre><code class="language-java">public abstract class AbstractCreator { public void operation() { //这里可以算是 client 的代码了 Product product = getProduct(); product.process(); } // factory method protected abstract Product getProduct(); } </code></pre> <p>有几种产品就创建几个ConcreteCreator类</p> <pre><code class="language-java">public class ConcreteCreator1 extends AbstractCreator { @Override protected Product getProduct() { return new ConcreteProduct1(); } } public class ConcreteCreator2 extends AbstractCreator { @Override protected Product getProduct() { return new ConcreteProduct2(); } } ... </code></pre> <p>使用方法:</p> <pre><code class="language-java">AbstractCreator creator1 = new ConcreteCreator1(); creator1.operation(); AbstractCreator creator2 = new ConcreteCreator2(); creator2.operation(); </code></pre> <p>当需要增加一种产品的时候,新建一个类继承AbstractCreator,实现工厂方法getProduct即可。工厂方法模式客服了简单工厂模式违背「开放-封闭」原则的缺点。<br /> 我们来看一下,当增加一个产品的时候,我们需要做哪些事情:</p> <ol> <li>添加一个产品类——&gt;这个是无法避免的,我们对扩展开放。</li> <li>如果工厂方法是在某个抽象类中,新建一个类继承该抽象类,覆盖工厂方法,返回新产品。</li> <li>如果工厂方法在接口IFactory中,那么就要新建一个工厂实现IFactory来返回该产品。</li> <li>在client使用时,得new一个新的类来产生新产品。</li> </ol> <hr /> <p>工厂方法也可以参数化,变得像一个简单工厂</p> <pre><code class="language-java">public abstract class AbstractCreator { public void operation(String type) { Product product = getProduct(type); product.process(); } // factory method protected abstract Product getProduct(String type); } public class ConcreteCreator extends AbstractCreator { @Override protected Product getProduct(String type) { if (type.equals("1")) { return new ConcreteProduct1(); } else if (type.equals("2")) { return new ConcreteProduct2(); } else { return null; } } } </code></pre> <p>使用方法:</p> <pre><code class="language-java">AbstractCreator creator = new ConcreteCreator(); creator.operation("1"); creator.operation("2"); </code></pre> 2017-04-02T23:16:28+00:00 https://ysmull.cn/blog/factory_mathod_pattern.html https://ysmull.cn/blog/factory_mathod_pattern.html Simple Factory <p>首先说一个被叫做<strong>简单工厂模式</strong>的编程技巧,有说这个其实不算一个设计模式的。</p> <p>准备好<strong>产品</strong></p> <pre><code class="language-java">public interface Product { void process(); } </code></pre> <pre><code class="language-java">public class ConcreteProduct1 implements Product { @Override public void process() { System.out.println("product1 is processing..."); } } ... </code></pre> <p><img src="http://onk1k9bha.bkt.clouddn.com/2017-04-02-133144.jpg" alt="" /><br /> 然后准备<strong>工厂</strong>,<code>SimpleProductFactory</code>就是个<strong>简单工厂</strong>,为了描述方便对模式的描述,type为String类型。</p> <pre><code class="language-java">public class SimpleProductFactory { public Product getProduct(String type) { if (type.equals("1")) { return new ConcreteProduct1(); } else if (type.equals("2")) { return new ConcreteProduct2(); } else if (type.equals("3")) { return new ConcreteProduct3(); } else if (type.equals("4")) { return new ConcreteProduct4(); } else { return null; } } } </code></pre> <p>client使用工厂</p> <pre><code class="language-java">SimpleProductFactory factory = new SimpleProductFactory(); factory.getProduct("1").process(); factory.getProduct("2").process(); factory.getProduct("3").process(); factory.getProduct("4").process(); </code></pre> <p>当需要增加一个ConcreteProduct5时:</p> <ol> <li>首先要新添加这个产品类,当然,这个是无法避免的。</li> <li>然后在工厂的实现里增加一个分支判断,来返回对应产品。</li> <li>在client使用时,只需要修改传给工厂方法的参数即可获取新的产品。</li> </ol> <p>上面第二点可以通过反射来避免,事实上,当我们的代码中有简单工厂模式的时候,我们都应该考虑使用反射来避免分支判断:</p> <pre><code class="language-java">public Product getProduct(String className) { try { Class clazz = Class.forName(classPrefix + className); return (Product)clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); return null; } } </code></pre> <p>client使用工厂</p> <pre><code class="language-java">SimpleProductFactory factory = new SimpleProductFactory(); factory.getProduct("ConcreteProduct5").process(); </code></pre> 2017-04-02T21:22:11+00:00 https://ysmull.cn/blog/simple_factory_pattern.html https://ysmull.cn/blog/simple_factory_pattern.html Minimum Window Substring <p><a href="https://leetcode.com/problems/minimum-window-substring/#/description">原题链接</a></p> <h1 id="id-题目">题目</h1> <blockquote> <p>Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).<br /> For example,<br /> S = “ADOBECODEBANC”<br /> T = “ABC”</p> </blockquote> <h1 id="id-解法">解法</h1> <p>这道题目,我一开始用动态规划做,感觉有点难哦,只能看答案。<br /> 讨论区有个人给了一个解决一类字符串问题的<a href="https://discuss.leetcode.com/topic/30941/here-is-a-10-line-template-that-can-solve-most-substring-problems">模板</a>:</p> <blockquote> <p>For most substring problem, we are given a string and need to find a substring of it which satisfy some restrictions. A general way is to use a hashmap assisted with two pointers. The template is given below.</p> </blockquote> <pre><code class="language-c">int findSubstring(string s) { vector&lt;int&gt; map(128,0); int counter; // check whether the substring is valid int begin=0, end=0; //two pointers, one point to tail and one head int d; //the length of substring for() { /* initialize the hash map here */ } while(end&lt;s.size()){ if(map[s[end++]]-- ?){ /* modify counter here */ } while(/* counter condition */){ /* update d here if finding minimum*/ //increase begin to make it invalid/valid again if(map[s[begin++]]++ ?){ /*modify counter here*/ } } /* update d here if finding maximum*/ } return d; } </code></pre> <p>这个模板暗示的算法是,维护两个指针start和end分别指向子串的起始位置和结束位置,end向后遍历,当满足子串的性质之后,<br /> 向后移动start破坏该性质并寻找下一个满足性质的位置。</p> <p>用这个思想可以解决<a href="/blog/leetcode-3">Longest Substring Without Repeating Characters</a></p> <h1 id="id-代码">代码</h1> <pre><code class="language-c">string minWindow(string s, string t) { int ascii[256]; memset(ascii, 0, 256 * sizeof(int)); int count = 0; for (int i = 0; i &lt; t.length(); i++) { ascii[t[i]]++; count++; } int start = 0, end = 0; int minLen = 999999, minStart = 0; while (end &lt; s.length()) { if (ascii[s[end]]-- &gt; 0) { count--; } while (count == 0) { if (end - start + 1 &lt; minLen) { minStart = start; minLen = end - start + 1; } if (++ascii[s[start++]] &gt; 0) { count++; } // if (start &gt; end) break; } end++; } if (minLen == 999999 ) return ""; return s.substr(minStart, minLen); } </code></pre> 2017-03-30T21:56:46+00:00 https://ysmull.cn/blog/leetcode-76.html https://ysmull.cn/blog/leetcode-76.html Longest Valid Parentheses <p><a href="https://leetcode.com/problems/longest-valid-parentheses/#/description">原题链接</a></p> <h1 id="id-题目">题目</h1> <p>求最长的有效括号的长度。题目对有效括号的定义没有说清楚,我以为是连续若干个<code>()</code>,实际上有效括号P的定义如下:</p> <pre><code class="language-text">1. () is P 2. PP is P 3. (P) is P </code></pre> <h1 id="id-求解">求解</h1> <p>这道题分类在动态规划下面,我尝试做了一下没有做出来。然后看到讨论区的动态规划解法,觉得有点难想到。但是另一个使用栈的解法比较有意思。</p> <h2 id="id-使用栈解决">使用栈解决</h2> <p>依次把括号的index入栈,如果遇到'('就push,如果遇到')'且栈顶是'('就pop,这样到最后,能匹配上的括号全部都出栈了,<br /> 在栈里剩下的括号是没有匹配到的。这里仔细看一下栈里面剩下的括号,可以发现有一个特点,它们是字符串里所有的<strong>连续有效括号的分隔点</strong>,<br /> 每两个分割点之间一定是<strong>有效括号</strong>,我们遍历栈里剩下的元素,求出所有的有效括号的长度即可。</p> <pre><code class="language-text"> str: )(()(())))()() len:14 stack: ↑ ↑ 分隔点: 0 9 </code></pre> <pre><code class="language-c">int longestValidParentheses(string s) { stack&lt;int&gt; stack; int len = s.length(); for (int i = 0; i &lt; len; i ++) { if (s[i] == '(') { stack.push(i); continue; } if (!stack.empty() &amp;&amp; s[stack.top()] == '(') { stack.pop(); } else { stack.push(i); } } if (stack.empty()) return len; else { int maxNum = -1; int end = len; while(!stack.empty()) { int start = stack.top(); stack.pop(); maxNum = max(maxNum, end - start - 1); end = start; } maxNum = max(maxNum, end);// 这里容易漏掉,最后还要比较一次 return maxNum; } } </code></pre> <h2 id="id-使用动态规划">使用动态规划</h2> <p>思路来源自讨论区,不是很好想。</p> <p>定义dp[i]是以第i个元素结尾的最长有效括号。</p> <pre><code class="language-text"> ┏-0 if str[i] == '(' dp[i] = ┠-dp[i-2] + 2 if str[i] == ')' &amp;&amp; str[i-1] == '(' ┗-dp[i-1] + 2 + dp[i-dp[i-1]-2] if str[i] == ')' &amp;&amp; str[i-1] == ')' </code></pre> <p><img src="https://ww2.sinaimg.cn/large/006tNc79ly1fe4z0xq33sj31bw094my5.jpg" alt="" /><br /> <a href="https://discuss.leetcode.com/topic/2426/my-dp-o-n-solution-without-using-stack">代码</a></p> 2017-03-30T15:58:26+00:00 https://ysmull.cn/blog/leetcode-32.html https://ysmull.cn/blog/leetcode-32.html Longest Increasing Subsequence <p><a href="https://leetcode.com/problems/longest-increasing-subsequence/#/description">原题链接</a></p> <h1 id="id-题目描述">题目描述</h1> <p>最长增长子列(LIS)</p> <h1 id="id-分析">分析</h1> <p>典型动态规划问题,设dp[i]为以第i个数结尾的最长增长子列的长度,则:</p> <p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mrow><mi mathvariant="normal">d</mi><mi mathvariant="normal">p</mi></mrow><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>=</mo><munder><mo><mi>max</mi><mo>⁡</mo></mo><munder><mo><mn>0</mn><mo>≤</mo><mi>k</mi><mo>&lt;</mo><mi>i</mi></mo><mrow><mi mathvariant="normal">A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>&gt;</mo><mi mathvariant="normal">A</mi><mo stretchy="false">[</mo><mi>k</mi><mo stretchy="false">]</mo></mrow></munder></munder><mrow><mo fence="true">{</mo><mrow><mi mathvariant="normal">d</mi><mi mathvariant="normal">p</mi></mrow><mo stretchy="false">[</mo><mi>k</mi><mo stretchy="false">]</mo><mo>+</mo><mn>1</mn><mo fence="true">}</mo></mrow></mrow><annotation encoding="application/x-tex"> \mathrm{dp}[i] = \max_{\mathop{0\leq k &lt; i}\limits_{\mathrm{A}[i] &gt; \mathrm{A}[k]}}\left \{\mathrm{dp}[k]+1 \right\} </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"><span class="mord mathrm">d</span><span class="mord mathrm">p</span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.313587em;vertical-align:-1.563587em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.43056000000000005em;"><span style="top:-2.047892em;margin-left:0em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mop op-limits mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944399999999998em;"><span style="top:-1.8623157142857143em;margin-left:0em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathrm mtight">A</span></span><span class="mopen mtight">[</span><span class="mord mathdefault mtight">i</span><span class="mclose mtight">]</span><span class="mrel mtight">&gt;</span><span class="mord mtight"><span class="mord mathrm mtight">A</span></span><span class="mopen mtight">[</span><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</span><span class="mclose mtight">]</span></span></span></span><span style="top:-2.6999999999999997em;"><span class="pstrut" style="height:2.7em;"></span><span><span class="mop mtight"><span class="mord mtight">0</span><span class="mrel mtight">≤</span><span class="mord mathdefault mtight" style="margin-right:0.03148em;">k</span><span class="mrel mtight">&lt;</span><span class="mord mathdefault mtight">i</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.1592557142857145em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span><span class="mop">max</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.563587em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">{</span><span class="mord"><span class="mord mathrm">d</span><span class="mord mathrm">p</span></span><span class="mopen">[</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span><span class="mclose delimcenter" style="top:0em;">}</span></span></span></span></span></span></p> <p>初始时dp所有元素为1</p> <h1 id="id-实现">实现</h1> <pre><code class="language-c">int lengthOfLIS(int* nums, int len) { if (len == 0) return 0; int *dp = (int*)malloc(len * sizeof(int)); for (int i = 0; i &lt; len; i++) dp[i] = 1; int maxSubLen = 1; for (int i = 1; i &lt; len; i++) { for (int k = 0; k &lt; i; k++) { if (nums[i] &gt; nums[k]) { dp[i] = max(dp[i], dp[k] + 1); if (dp[i] &gt;= maxSubLen) { maxSubLen = dp[i]; } } } } return maxSubLen; } </code></pre> 2017-03-30T13:09:24+00:00 https://ysmull.cn/blog/leetcode-300.html https://ysmull.cn/blog/leetcode-300.html Longest Palindromic Substring <p><a href="https://leetcode.com/problems/longest-palindromic-substring/#/description">原题链接</a></p> <h1 id="id-题目描述">题目描述</h1> <p>求字符串的最长回文子串。</p> <h1 id="id-动归解法">动归解法</h1> <p>之前写了两题动归的题,想练习一下动态规划,现在时间是 2017-03-27 02:18:52 ,走了很多弯路,折腾了一个小时,终于独立搞出了状态转移方程,目前dp对我来说还是挺艰难的…</p> <pre><code class="language-text">定义s[i,j]标志着以i开头j结尾的子串是否是回文子串 s[i,j] (i &lt;= j) = 1 if i == j = 1 if i == j-1 &amp;&amp; a[i] == a[j] = 0 if i == j-1 &amp;&amp; a[i] != a[j] = 0 if s[i+1, j-1] == 0 = 0 if s[i+1, j-1] == 1 &amp;&amp; a[i] != a[j] = 1 if s[i+1, j-1] == 1 &amp;&amp; a[i] == a[j] </code></pre> <p>化简 =&gt;</p> <pre><code class="language-text">s[i,j] (i &lt;= j) = 1 if i == j = a[i] == a[j] if i == j-1 = s[i+1, j-1] &amp;&amp; (a[i] == a[j]) </code></pre> <h1 id="id-代码">代码</h1> <pre><code class="language-c">string longestPalindrome(string s) { int dp[1000][1000]; memset(dp, -1, 1000000 * sizeof(int)); int maxLen = 1, maxi = 0; for (int i = s.length() - 1; i &gt;= 0; i--) { for (int j = s.length() - 1; j &gt;= i; j--) { if (i == j) { dp[i][j] = 1; } else if (i == j - 1) { if (dp[i][j] = (s[i] == s[j])) { if (j - i + 1 &gt; maxLen) { maxi = i; maxLen = j - i + 1; } } } else if (dp[i][j] = dp[i + 1][j - 1] &amp;&amp; (s[i] == s[j])) { if (j - i + 1 &gt; maxLen) { maxi = i; maxLen = j - i + 1; } } } } return s.substr(maxi, maxLen); } </code></pre> <p>再提供一个其它解法的代码(<a href="https://discuss.leetcode.com/topic/23498/very-simple-clean-java-solution">来源</a>)</p> <pre><code class="language-java">public class Solution { private int lo, maxLen; public String longestPalindrome(String s) { int len = s.length(); if (len &lt; 2) return s; for (int i = 0; i &lt; len - 1; i++) { extendPalindrome(s, i, i); //assume odd length, try to extend Palindrome as possible extendPalindrome(s, i, i + 1); //assume even length. } return s.substring(lo, lo + maxLen); } private void extendPalindrome(String s, int j, int k) { while (j &gt;= 0 &amp;&amp; k &lt; s.length() &amp;&amp; s.charAt(j) == s.charAt(k)) { j--; k++; } if (maxLen &lt; k - j - 1) { lo = j + 1; maxLen = k - j - 1; } } } </code></pre> 2017-03-27T02:17:47+00:00 https://ysmull.cn/blog/leetcode-5.html https://ysmull.cn/blog/leetcode-5.html Maximum Subarray <p><a href="https://leetcode.com/problems/maximum-subarray/#/description">原题目链接</a></p> <h2 id="id-题目描述">题目描述</h2> <p>寻找数列的最大子列和。</p> <h2 id="id-分析">分析</h2> <p>动态规划问题,见<a href="/blog/leetcode-3">leetcode-3</a>的讲解。</p> <p>定义s[i]是以第i个元素结尾的最大子列和。</p> <pre><code class="language-text"> nums[i] + s[i-1] ,if s[i-1] &gt;= 0 s[i] ={ nums[i] ,if s[i-1] &lt; 0 </code></pre> <h2 id="id-代码">代码</h2> <pre><code class="language-c">int maxSubArray(vector&lt;int&gt;&amp; nums) { int curSum = nums[0]; int maxSum = curSum; for (int i = 1; i &lt; nums.size(); i++) { curSum = nums[i] + (curSum &gt; 0 ? curSum : 0); if (curSum &gt; maxSum) maxSum = curSum; } return maxSum; } </code></pre> 2017-03-25T23:13:46+00:00 https://ysmull.cn/blog/leetcode-53.html https://ysmull.cn/blog/leetcode-53.html Longest Substring Without Repeating Characters <p><a href="https://leetcode.com/problems/longest-substring-without-repeating-characters/#/description">原题连接</a></p> <h1 id="id-引子">引子</h1> <p>这道题可以用暴力的方法,遍历枚举所有的子串,如果子串没有重复元素,则更新max,但是时间复杂度很高,最后超时了。看到讨论区的算法:</p> <pre><code class="language-c++">int lengthOfLongestSubstring(string s) { map&lt;char, int&gt; charMap; int j = 0; int maxLen = 0; for (int i = 0; i &lt; s.size(); i++) { if (charMap.count(s[i]) != 0) { j = max(j, charMap[s[i]]+1); } charMap[s[i]] = i; maxLen = max(maxLen, i-j+1); } return maxLen; } </code></pre> <p>作者解释:</p> <blockquote> <p>the basic idea is, keep a hashmap which stores the characters in string as keys and their positions as values, and keep two pointers which define the max substring. move the right pointer to scan through the string , and meanwhile update the hashmap. If the character is already in the hashmap, then move the left pointer to the right of the same character last found. Note that the two pointers can only move forward.</p> </blockquote> <p>该作者只解释了这个算法是如何操作的,但却没有说明为什么算法是「正确」的,我晚上看了一下,不是很懂算法的正确性如何证明。早上起来继续想,终于想通了,尝试把这个算法用我这种小白能够理解的语句来进行描述和分析如下。</p> <h1 id="id-解决方法">解决方法</h1> <p>定义s[i]为以位置i的字符结尾的最大不重复子串,则:</p> <pre><code>1.如果str[i]不在s[i-1]里 s[i] = s[i-1] ++ str[i] (++为连接符) 2.否则 s[i] = 以s[i-1]中str[i]的后一个位置起始,以位置i的字符为结束的子串 </code></pre> <p>怎么想到的呢,我们这样来思考。从数组的第一个元素为子串的起始位置和结束位置,设j指向开始位置,i指向结束位置,那么当i在遍历的过程中,在遇到重复的元素之前,子串是一直增长的;当i=k的时候,如果遇到元素s[k]与子串s[j,k-1]中的元素有重复,这个时候将j移动到子串s[j,k-1]里重复的元素的下一个位置,则子串s[j,i]仍然是以位置i的字符结束的最大子串。也就是说,只要新考察的那个字符是重复的,就修改当前子串的起始位置为上一次该重复字符在子串中出现位置的下一位,这种修改保证了新子串一定是以该重复字符为结尾的最大子串。<strong>因为我弱,所以才啰嗦这么多:)</strong></p> <p><br /><br /> 那么我自己就可以实现这个算法了呀:</p> <pre><code class="language-c">int lengthOfLongestSubstring(string s) { int start = 0, end = 0, maxLen = 0, ascii[256];//ascii最多256个字符 memset(ascii, -1, 256 * sizeof(int));//初始化为-1是为了防止第2个元素就重复了 for (end = 0; end &lt; s.length(); end++) { if (ascii[s[end]] &gt;= start) start = ascii[s[end]] + 1; if (end - start + 1 &gt; maxLen) maxLen = end - start + 1; ascii[s[end]] = end; //记录最后一次s[end]出现的位置 } return maxLen; } </code></pre> <h1 id="id-另一个解决方案">另一个解决方案</h1> <p>见<a href="/blog/leetcode-76">Minimum Window Substring</a></p> <pre><code class="language-c">int lengthOfLongestSubstring(string s) { int ascii[256]; memset(ascii, 0, 256 * sizeof(int)); int start = 0, end = 0, count = 0, maxCount = 0; while (end &lt; s.length()) { ascii[s[end]]++; count++; while (ascii[s[end]] &gt; 1) { // 如果发现重复元素 ascii[s[start++]]--; // start后移直到无重复元素 count--; } if (count &gt; maxCount) maxCount = count; end++; } return maxCount; } </code></pre> <h1 id="id-附">附</h1> <p>一开始不理解,做的算法运行状态模拟如下:</p> <pre><code>-------------- str: abcadaebf j: ↑ i: ↑ -------------- str: abcadaebf j: ↑ i: ↑ -------------- str: abcadaebf j: ↑ i: ↑ -------------- str: abcadaebf 更新j j: ↑ i: ↑ -------------- str: abcadaebf j: ↑ i: ↑ -------------- str: abcadaebf 更新j j: ↑ i: ↑ -------------- str: abcadaebf j: ↑ i: ↑ -------------- str: abcadaebf j: ↑ i: ↑ -------------- str: abcadaebf j: ↑ i: ↑ </code></pre> 2017-03-25T23:13:46+00:00 https://ysmull.cn/blog/leetcode-3.html https://ysmull.cn/blog/leetcode-3.html Total Hamming Distance <p><a href="https://leetcode.com/problems/total-hamming-distance/#/description">题目连接</a></p> <h2 id="id-题目描述">题目描述</h2> <p>给一组数,计算所有两两之间汉明距离的和。</p> <h2 id="id-分析">分析</h2> <ol> <li>遍历数组,计算 n(n-1)/2 次累加。但是这样做,时间复杂度比较高,最后超时了。</li> <li>对于数组中所有数字的同一位来说,如果有 p 个数在这一位是 1,q 个数在这一位是 0,那么这一位对总汉明距离的贡献量为 p*q,因为满足这一位的汉明距离为1的组合有 p*q 对。<a href="https://discuss.leetcode.com/topic/72099/share-my-o-n-c-bitwise-solution-with-thinking-process-and-explanation">思路来源</a></li> </ol> <h2 id="id-代码">代码</h2> <pre><code class="language-c">int totalHammingDistance(int* nums, int numsSize) { int sum = 0; while (true) { int zeroNum = 0; int p0 = 0, p1 = 0; for (int i = 0; i &lt; numsSize; i++) { if (nums[i] == 0) zeroNum++; nums[i] % 2 == 0 ? p0++ : p1++; nums[i] &gt;&gt;= 1; } sum += p0*p1; if (zeroNum == numsSize) return sum; } } </code></pre> 2017-03-22T15:04:50+00:00 https://ysmull.cn/blog/leetcode-477.html https://ysmull.cn/blog/leetcode-477.html Hamming Distance <p><a href="https://leetcode.com/problems/hamming-distance/#/description">原题目链接</a></p> <h2 id="id-题目描述">题目描述</h2> <p>计算两个32位整数的<a href="https://en.wikipedia.org/wiki/Hamming_distance">Hamming distance</a></p> <pre><code class="language-text">1 : (0 0 0 1) 4 : (0 1 0 0) ↑ ↑ </code></pre> <h2 id="id-分析">分析</h2> <p>问题的关键是统计一个数的二进制表示中有多少个1</p> <ol> <li>遍历异或所得数的每一位,统计1的个数,判断最低位为是否是1可以用 <code>n % 2</code> 或者用 <code>n &amp; 1</code>。 <pre><code class="language-c">int hammingDistance(int a, int b) { int sum = 0; for (int i = 0; i &lt; 32; i++) { sum += (a &amp; 1) ^ (b &amp; 1); a &gt;&gt;= 1; b &gt;&gt;= 1; } return sum; } </code></pre> </li> <li>使用<code>n &amp; (n-1)</code>可以消去最后一位1,判断消到0一共消了多少次。 <pre><code class="language-c">int hammingDistance(int a, int b) { int count = 0; int n = a ^ b; while(n) { n = n &amp; (n-1); count++; } return count; } </code></pre> </li> <li>参考 Java 里的 Integer.bitCount 方法 <pre><code class="language-java">public static int bitCount(int i) { i = i - ((i &gt;&gt;&gt; 1) &amp; 0x55555555); i = (i &amp; 0x33333333) + ((i &gt;&gt;&gt; 2) &amp; 0x33333333); i = (i + (i &gt;&gt;&gt; 4)) &amp; 0x0f0f0f0f; i = i + (i &gt;&gt;&gt; 8); i = i + (i &gt;&gt;&gt; 16); return i &amp; 0x3f; } </code></pre> </li> </ol> 2017-03-22T15:04:06+00:00 https://ysmull.cn/blog/leetcode-461.html https://ysmull.cn/blog/leetcode-461.html Reverse Linked List <p><a href="https://leetcode.com/problems/reverse-linked-list/#/description">原题目地址</a></p> <h1 id="id-题目描述">题目描述</h1> <p>反转一个单链表</p> <h1 id="id-分析">分析</h1> <h2 id="id-方法一">方法一</h2> <p>递归版的写法,参考stackoverflow上面的<a href="http://stackoverflow.com/questions/354875/reversing-a-linked-list-in-java-recursively?page=1&amp;tab=votes#tab-top">这个回答</a>,很有启发。</p> <h2 id="id-方法二">方法二</h2> <p>维护两个指针,指针rHead指向反后的链表的头结点,lHead指向的是待反转连标点头结点。</p> <table> <thead> <tr> <th style="text-align: right">左边链表</th> <th style="text-align: left">右边链表</th> </tr> </thead> <tbody> <tr> <td style="text-align: right">null</td> <td style="text-align: left">a-&gt;b-&gt;c-&gt;null</td> </tr> <tr> <td style="text-align: right">null&lt;-a</td> <td style="text-align: left">b-&gt;c-&gt;null</td> </tr> <tr> <td style="text-align: right">null&lt;-a&lt;-b</td> <td style="text-align: left">c-&gt;null</td> </tr> <tr> <td style="text-align: right">null&lt;-a&lt;-b&lt;-c</td> <td style="text-align: left">null</td> </tr> </tbody> </table> <h1 id="id-代码">代码</h1> <h2 id="id-递归版本">递归版本</h2> <pre><code class="language-c">struct ListNode* reverseList(struct ListNode* head) { if (head == NULL) return NULL; if (head-&gt;next == NULL) return head; struct ListNode* second = head-&gt;next; head-&gt;next = NULL; struct ListNode* reversedList = reverseList(second); second-&gt;next = head; return reversedList; } </code></pre> <h2 id="id-haskell版">haskell版</h2> <p>学习上面的思想后用<code>haskell</code>实现了一个反转列表函数,<a href="http://stackoverflow.com/questions/3543399/implement-reverse-in-haskell-that-runs-in-linear-time">虽然效率比较低</a></p> <pre><code class="language-haskell">reverse1 [] = [] reverse1 (x:[]) = [x] reverse1 (x:xs) = reverse1 xs ++ [xs] </code></pre> <p>实际上haskell自己是这么实现的</p> <pre><code class="language-haskell">reverse :: [a] -&gt; [a] #ifdef USE_REPORT_PRELUDE reverse = foldl (flip (:)) [] #else reverse l = rev l [] where rev [] a = a rev (x:xs) a = rev xs (x:a) #endif </code></pre> <h2 id="id-两个指针版本">两个指针版本</h2> <pre><code class="language-c">struct ListNode* reverseList(struct ListNode* head) { struct ListNode *lHead = NULL; struct ListNode *rHead = head; while (rHead != NULL) { struct ListNode *ptr = rHead-&gt;next; rHead-&gt;next = lHead; lHead = rHead; rHead = ptr; } return lHead; } </code></pre> 2017-03-21T14:40:47+00:00 https://ysmull.cn/blog/leetcode-206.html https://ysmull.cn/blog/leetcode-206.html Add Two Numbers II <p><a href="https://leetcode.com/problems/add-two-numbers-ii/#/description">原题目地址</a></p> <h2 id="id-题目描述">题目描述</h2> <p>与<a href="/blog/leetcode-2">Add Two Numbers</a>的不同在于,链表<code>1-&gt;2-&gt;3-&gt;null</code>代表的是数123</p> <h2 id="id-分析">分析</h2> <p>一开始我是先把链表转换<code>long long</code>类型的数进行相加再把结果转换成链表。不过这样无法处理特别长的数。<br /> 然后我使用<a href="http://ysmull.me/blog/leetcode-206">Reverse Linked List</a>先把链表翻转,再使用,最后<a href="/blog/leetcode-2">Add Two Numbers</a>的算法求加法,最后翻转相加得到的链表并返回。</p> <p>看到<a href="https://discuss.leetcode.com/topic/65279/easy-o-n-java-solution-using-stack">论坛</a>里面有人使用了栈,通过出栈的方式来从低位到高位进行相加和进位。</p> <h2 id="id-代码">代码</h2> <p>略</p> 2017-03-21T14:40:46+00:00 https://ysmull.cn/blog/leetcode-445.html https://ysmull.cn/blog/leetcode-445.html Add Two Numbers <p><a href="https://leetcode.com/problems/add-two-numbers/#/description">原题目地址</a></p> <h1 id="id-题目描述">题目描述</h1> <p>每一个链表代表一个数,比如 <code>2-&gt;3-&gt;4-&gt;null</code> 代表的就是 <code>432</code>。现在输入是两个链表,代表两个数,输出是这两个数的和对应的链表。</p> <h1 id="id-分析">分析</h1> <p>这是我的第一篇算法记录,不能为了做题而做题,而应该多思考我们能够从一道题中获得什么东西。<br /> 这道题思路上比较简单,就是从链表的第一个结点开始,计算相加和进位。但是有一个编程技巧可以简化我们代码的表达。</p> <pre><code class="language-c">int val1 = (p1 == NULL) ? 0 : p1-&gt;val; int val2 = (p2 == NULL) ? 0 : p2-&gt;val; </code></pre> <p>当其中一个链走到头但另一个链没有走到头的时候,另一个链表的节点在下一次循环中,值为0。如果不采用这种方式,那么我们可能要分别判断如果p1到头了该怎么做,p2到头了改怎么做等等。</p> <p>总结一下:</p> <ol> <li>当有多个数据结构需要并行操作互运算时,为了防止体量的不同而导致逻辑处理变复杂,可考虑采用这种方式来清洁代码逻辑。</li> <li>不带头结点的单链表建表套路。</li> </ol> <h1 id="id-代码">代码</h1> <pre><code class="language-c">struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) { struct ListNode *p1 = l1; struct ListNode *p2 = l2; struct ListNode *head = NULL; struct ListNode *ptr = NULL; int carry = 0; while (p1 != NULL || p2 != NULL || carry) { int val1 = (p1 == NULL) ? 0 : p1-&gt;val; int val2 = (p2 == NULL) ? 0 : p2-&gt;val; int newVal = val1 + val2 + carry; carry = newVal / 10; newVal %= 10; if (head == NULL) { head = (struct ListNode *)malloc(sizeof(struct ListNode)); head-&gt;val = newVal; head-&gt;next = NULL; ptr = head; } else { struct ListNode *newNode = (struct ListNode *)malloc(sizeof(struct ListNode)); newNode-&gt;val = newVal; newNode-&gt;next = NULL; ptr-&gt;next = newNode; ptr = newNode; } if (p1 != NULL) { p1 = p1-&gt;next; } if (p2 != NULL) { p2 = p2-&gt;next; } } return head; } </code></pre> 2017-03-21T14:40:45+00:00 https://ysmull.cn/blog/leetcode-2.html https://ysmull.cn/blog/leetcode-2.html Matlab C++ 混合编程笔记 <ul id="markdown-toc"> <li><a href="#id-1-直接调用cc生成的dll" id="markdown-toc-id-1-直接调用cc生成的dll">1. 直接调用C/C++生成的DLL</a> <ul> <li><a href="#id-11-用vs生成dll" id="markdown-toc-id-11-用vs生成dll">1.1 用VS生成dll</a></li> <li><a href="#id-12-在matlab中调用dll中的函数" id="markdown-toc-id-12-在matlab中调用dll中的函数">1.2 在matlab中调用dll中的函数</a></li> <li><a href="#id-13-常见错误与注意事项" id="markdown-toc-id-13-常见错误与注意事项">1.3 常见错误与注意事项</a></li> </ul> </li> <li><a href="#id-2-使用mex编译c代码" id="markdown-toc-id-2-使用mex编译c代码">2. 使用mex编译C++代码</a></li> <li><a href="#id-3-参考文献" id="markdown-toc-id-3-参考文献">3. 参考文献</a></li> </ul> <h2 id="id-1-直接调用cc生成的dll">1. 直接调用C/C++生成的DLL</h2> <h3 id="id-11-用vs生成dll">1.1 用VS生成dll</h3> <p>新建一个win32 dll工程,工程名为test,如果你的MATLAB和系统都是64位的,这里要修改平台参数到64位。<br /> 例如要导出<code>add()</code>函数,那么头文件里就要写:</p> <pre><code class="language-c">//main.h extern "C" double __declspec(dllexport)add(double x, double y); </code></pre> <p>注意<code>extern "C"</code>不能掉,表明它按照C的编译和连接规约来编译和连接,你可以认为这里的C++函数将要给C调用,因为下面讲到的MATLAB的<code>loadlibrary()</code>函数只能识别C格式的DLL。<br /> 源文件内容为:</p> <pre><code class="language-c">//main.cpp #include "main.h" double add(double a, double b) { return a + b; } </code></pre> <p>这里的函数返回类型必须是<strong>数值类型</strong>。<br /> 例如编写返回字符的函数 <em>rep_str</em></p> <pre><code class="language-c">char rep_str(char s) { return s; } </code></pre> <p>在Matlab中调用返回错误:</p> <pre><code class="language-matlab">&gt;&gt; calllib('test','rep_str','a') Error using calllib Array must be numeric or logical. </code></pre> <h3 id="id-12-在matlab中调用dll中的函数">1.2 在matlab中调用dll中的函数</h3> <p>将生成的test.dll与这里的<em>main.h</em>头文件放在同一个目录下,并把头文件中的<code>extern "C"</code>删除,因为C语言中没有这种写法,而MATLAB以为自己在调用C的DLL。<br /> 在matlab中使用<code>loadlibrary()</code>函数载入dll,使用<code>libfunctions()</code>函数查看该dll中包含的函数。<br /> 运行结果:</p> <pre><code class="language-matlab">&gt;&gt;loadlibrary('test.dll', 'main.h') &gt;&gt;libfunctions('test') Functions in library test: add rep_str </code></pre> <p>使用<code>calllib()</code>函数调用dll中的函数:</p> <pre><code class="language-matlab">&gt;&gt; calllib('test', 'add', 8, 13) ans = 21 </code></pre> <h3 id="id-13-常见错误与注意事项">1.3 常见错误与注意事项</h3> <ol> <li>参数个数必须匹配 <pre><code class="language-matlab">&gt;&gt; calllib('test', 'add', 8) Error using calllib No method with matching signature. </code></pre> </li> <li>参数必须是<strong>数值类型</strong>或<strong>逻辑类型</strong> <pre><code class="language-matlab">&gt;&gt; calllib('test', 'add', 'a', 'b') Error using calllib Array must be numeric or logical. </code></pre> </li> <li>且必须是<strong>标量</strong> <pre><code class="language-matlab">&gt;&gt; calllib('test', 'add', [1,2], [3,4]) Error using calllib Parameter must be scalar. </code></pre> </li> <li> <p>dll文件不能在某个磁盘的根目录,如<code>"F:\"</code></p> </li> <li>头文件的编写最好统采用如下形式: <pre><code class="language-c++">#ifdef __cplusplus extern "C" { #endif //exported functions... extern Type1 func1(...); extern Type2 func2(...); ... #ifdef __cplusplus } #endif </code></pre> <p>这样就不需要二次修改头文件了。</p> </li> </ol> <h2 id="id-2-使用mex编译c代码">2. 使用mex编译C++代码</h2> <p>不细讲了,见参考文献3,讲解了如何编写 <em>mexFunction</em> 。</p> <h2 id="id-3-参考文献">3. 参考文献</h2> <ol> <li><a href="http://songpengfei.iteye.com/blog/1100239">c++中的 extern “C”</a></li> <li><a href="http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html">C++项目中的extern “C” {}</a></li> <li><a href="http://www.cnblogs.com/lidabo/archive/2012/08/24/2654148.html">Matlab与C/C++混合编程接口及应用</a></li> </ol> 2015-07-28T00:00:00+00:00 https://ysmull.cn/blog/matlab_cpp.html https://ysmull.cn/blog/matlab_cpp.html 基本TCP套接字编程 <ul id="markdown-toc"> <li><a href="#id-1-connect函数" id="markdown-toc-id-1-connect函数">1. connect()函数</a> <ul> <li><a href="#id-11-出错情况" id="markdown-toc-id-11-出错情况">1.1 出错情况</a></li> <li><a href="#id-12-注意" id="markdown-toc-id-12-注意">1.2 注意</a></li> </ul> </li> <li><a href="#id-2-bind函数" id="markdown-toc-id-2-bind函数">2. bind()函数</a></li> <li><a href="#id-3-listen函数" id="markdown-toc-id-3-listen函数">3. listen()函数</a></li> <li><a href="#id-4-accept函数" id="markdown-toc-id-4-accept函数">4. accept()函数</a></li> <li><a href="#id-5-close函数" id="markdown-toc-id-5-close函数">5. close()函数</a></li> </ul> <h2 id="id-1-connect函数">1. connect()函数</h2> <pre><code class="language-c++">#include &lt;sys/socket.h&gt; int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);//成功返回0,出错返回-1 </code></pre> <h3 id="id-11-出错情况">1.1 出错情况</h3> <ol> <li>TCP客户没有收到SYN分节响应(超时机制),返回<code>ETIMEDOUT</code>错误。</li> <li>目标端口没有进程在等待连接,客户收到RST(复位)分节,返回<code>ECONNREFUSED</code>错误(硬错误)。 <blockquote> <p><strong>RST分节产生的条件</strong></p> <ol> <li>端口上没有正在监听的服务器</li> <li>TCP想取消一个连接</li> <li>收到一个不存在的连接的分节</li> </ol> </blockquote> </li> <li>TCP客户发送的SYN分节在某路由上引发“不可达”(destination unreachable)<a href="http://baike.baidu.com/view/30564.htm">ICMP</a>错误(软错误)(超时机制),则返回<code>EHOSTUNREACH</code>或<code>ENETUNREACH</code>错误。额外两种情况也触发该错误:本地系统转发表无到达远程系统的路径;<code>connect()</code>不等待就返回。</li> </ol> <h3 id="id-12-注意">1.2 注意</h3> <ul> <li>connect函数导致socket由CLOSED状态转移到SYN_SENT状态,若成功,再转移到ESTABLISHED状态。</li> <li>connect失败后导致套接字不可再用,不能对该套接字再次调用<code>connect()</code>需要<code>close()</code>。</li> </ul> <hr /> <h2 id="id-2-bind函数">2. bind()函数</h2> <pre><code>#include &lt;sys/socket.h&gt; int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);//成功返回0,出错返回-1 </code></pre> <p>给套接字一个地址(把IP地址捆绑到套接字上),套接字是<strong>主体</strong>,地址和端口是<strong>客体</strong>。</p> <ul> <li>若未调用<code>bind()</code>就调用了<code>connect()</code>或<code>listen()</code>,内核就要为相应的套接字选择一个临时端口。</li> <li>端口号为0则内核选择端口,IP地址为通配地址(<code>INADDR_ANY</code>),则内核选择IP地址。</li> <li>常见错误:<code>EADDRINUSE</code>(地址已使用)</li> </ul> <hr /> <h2 id="id-3-listen函数">3. listen()函数</h2> <pre><code>#include &lt;sys/socket.h&gt; int listen(int sockfd, int backlog);//成功返回0,出错返回-1。 </code></pre> <p>仅由TCP服务器调用,做两件事:</p> <ol> <li>将<code>socket()</code>创建的<strong>主动套接字</strong>转为<strong>被动套接字</strong>,导致socket由CLOSED状态转移到LISTEN状态。</li> <li>规定内核为相应套接字排队的最大连接数。<br /> <img src="//ww2.sinaimg.cn/mw690/49d9625fjw1eud84l3ieqj20pq0dq0ty.jpg" alt="TCP为监听套接字维护的两个队列" /></li> </ol> <p><img src="//ww2.sinaimg.cn/mw690/49d9625fgw1eud80l3nplj20nf09gmy2.jpg" alt="TCP三路握手和监听套接字的两个队列" /></p> <p>当客户SYN到达时,如果队列已满,TCP则忽略该分节,不发送RST,因为队满只是暂时的。</p> <hr /> <h2 id="id-4-accept函数">4. accept()函数</h2> <pre><code>#include &lt;sys/socket.h&gt; int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)//成功返回非负描述符,出错返回-1。 由服务器调用,用于从已完成连接队列对头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠(阻塞)。 </code></pre> <ul class="task-list"> <li>函数中的第一个参数 <em>sockfd</em> 为<strong>监听套接字</strong>,函数返回的是<strong>已连接套接字</strong>。</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />暂时不清楚 <em>cliaddr</em> (客户进程的协议地址)和 <em>addrlen</em> 与 <code>getsockname()</code> 或 <code>getpeername()</code> 得到的地址的区别。</li> </ul> <hr /> <h2 id="id-5-close函数">5. close()函数</h2> <pre><code>#include &lt;unistd.h&gt; int close(int sockfd);//成功返回0,出错返回-1。 </code></pre> <p>通常的unix close函数,可以用来关闭套接字,终止TCP连接。<br /> 关闭过程如下图:</p> <p><img src="//ww1.sinaimg.cn/mw690/49d9625fgw1eudc276a99j20h909amxw.jpg" alt="" /></p> <p>客户端或服务器都可以主动关闭。</p> <hr /> <blockquote> <p><img src="//ww3.sinaimg.cn/mw690/49d9625fjw1eudaw2wlcbj20mr0p1mza.jpg" alt="TCP为监听套接字维护的两个队列" /></p> </blockquote> 2015-07-24T00:00:00+00:00 https://ysmull.cn/blog/unp1.html https://ysmull.cn/blog/unp1.html 哪些数字没有出现 <blockquote> <p>给定数组A,大小为n,数组元素为1到n的数字,不过有的数字出现了多次,有的数字没有出现。请给出算法和程序,统计哪些数字没有出现,哪些数字出现了多少次。能够在O(n)的时间复杂度,O(1)的空间复杂度要求下完成么?</p> </blockquote> <h2 id="id-初步分析">初步分析</h2> <p>假如没有空间复杂度的限制,很简单地,我们可以想到的开一个空数组B,遍历一遍数组B,执行<code>B[A[i]]++</code>操作即可知道数组A中哪些元素没有,哪些元素出现了多次。可现在空间复杂度要求为O(1),这意味着我们只能在原数组上进行数据操作。</p> <h2 id="id-遇到困难">遇到困难</h2> <p>既然是在原数组上进行操作,如果我们能够让数组A的第A[i]个位置的值加一,那么加一处理后的A’减去原来的A就得到结果,可这个算法需要额外的存储空间A’来存储原来的A,况且我们也不方便在遍历时对<code>A[A[i]]</code>进行操作,因为<strong>下标</strong> A[i]的值在遍历过程中可能发生改变,因而变得不可预知.</p> <h2 id="id-解决方案1">解决方案1</h2> <p><a href="http://weibo.com/1670029795">@HanruiGao</a>给出了如下解答.<br /> 我们对数组进行三次遍历(数组下标从1到n)</p> <pre><code class="language-text">A[i] *= (n+1); A[A[i]/(n+1)]++; 输出A[i]%(n+1)即得结果. </code></pre> <p>第一次遍历,对A[i]进行预处理,为了表述方便,我们记这个新的数组为<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>A</mi><mo>∗</mo></msup></mrow><annotation encoding="application/x-tex">A^*</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.688696em;vertical-align:0em;"></span><span class="mord"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.688696em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span></span></span></span>,即<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>A</mi><mo>∗</mo></msup><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>=</mo><mo stretchy="false">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">A^*[i]=(n+1)A[i]</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"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.688696em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathdefault">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span></span></span></span><br /> 这样在第二次遍历时,<strong>下标</strong> <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mstyle displaystyle="true" scriptlevel="0"><mfrac><mrow><msup><mi>A</mi><mo>∗</mo></msup><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo></mrow><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></mfrac></mstyle></mrow><annotation encoding="application/x-tex">\dfrac{A^*[i]}{n+1}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.19633em;vertical-align:-0.7693300000000001em;"></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:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</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.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.688696em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.7693300000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>的值一定会是原来的A[i],因为对于任意的<br /> <span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mn>1</mn><mo>≤</mo><msub><mi>k</mi><mi>i</mi></msub><mo>≤</mo><mi>n</mi></mrow><annotation encoding="application/x-tex">1\leq k_i\leq n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.78041em;vertical-align:-0.13597em;"></span><span class="mord">1</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathdefault">n</span></span></span></span></span><br /> 我们都有<br /> <span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>=</mo><mo fence="false">⌊</mo><mfrac><mrow><msup><mi>A</mi><mo>∗</mo></msup><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>+</mo><msub><mi>k</mi><mi>i</mi></msub></mrow><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></mfrac><mo fence="false">⌋</mo></mrow><annotation encoding="application/x-tex"> A[i]=\Big\lfloor\frac{A^*[i]+k_i}{n+1}\Big\rfloor </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 mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.19633em;vertical-align:-0.7693300000000001em;"></span><span class="mord"><span class="delimsizing size2">⌊</span></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:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</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.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.688696em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.7693300000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="delimsizing size2">⌋</span></span></span></span></span></span></p> <p>这就是第一步要乘以n+1的原因了(事实上这里的n+1取n即可,因为k_i取不到n,当然取任意大于n的整数都行).通过这种<strong>先乘后除</strong>的方法,我们就解决了刚才分析过程中下标动态变化的问题.<br /> 第二次遍历结束后,此时<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>A</mi><mo>∗</mo></msup><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>=</mo><mo stretchy="false">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>+</mo><msub><mi>k</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">A^*[i]=(n+1)A[i]+k_i</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"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.688696em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathdefault">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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></span></span>,这里的<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msub><mi>k</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">k_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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></span></span>就是i出现的次数,我们不需要额外的存储空间来得到<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msub><mi>k</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">k_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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></span></span>,而是直接对第二次遍历结束后的<span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>A</mi><mo>∗</mo></msup></mrow><annotation encoding="application/x-tex">A^*</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.688696em;vertical-align:0em;"></span><span class="mord"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.688696em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span></span></span></span>求模,</p> <p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mtable rowspacing="0.24999999999999992em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><msup><mi>A</mi><mo>∗</mo></msup><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mo stretchy="false">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>+</mo><msub><mi>k</mi><mi>i</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><msub><mi>k</mi><mi>i</mi></msub><mspace></mspace><mspace width="1em"></mspace><mrow><mi mathvariant="normal">m</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><mtext> </mtext><mtext> </mtext><mo stretchy="false">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex"> \begin{aligned} A^*[i] &amp;= (n+1)A[i]+k_i \\ &amp;= k_i \mod (n+1) \end{aligned} </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.0000000000000004em;vertical-align:-1.2500000000000002em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.7500000000000002em;"><span style="top:-3.91em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.738696em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mbin mtight">∗</span></span></span></span></span></span></span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2500000000000002em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.7500000000000002em;"><span style="top:-3.91em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mopen">(</span><span class="mord mathdefault">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mord mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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="mspace allowbreak"></span><span class="mspace" style="margin-right:1em;"></span><span class="mord"><span class="mord"><span class="mord mathrm">m</span><span class="mord mathrm">o</span><span class="mord mathrm">d</span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mopen">(</span><span class="mord mathdefault">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2500000000000002em;"><span></span></span></span></span></span></span></span></span></span></span></span></p> <p>就能够得到每一个元素出现的次数了.</p> <h2 id="id-解决方案2">解决方案2</h2> <p>刚才我们通过先乘以一个大整数再除以它的方法来维护数组的下标,现在我们介绍另一种方法。<br /> 取一个整数M(M&gt;n),对数组进行两次遍历:</p> <pre><code>A[A[i]%M] += M; 输出A[i]/M. </code></pre> <p>第一次遍历,所有的A[i]变成了 <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>+</mo><msub><mi>k</mi><mi>i</mi></msub><mo>×</mo><mi>M</mi></mrow><annotation encoding="application/x-tex">A[i]+k_i\times 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 mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault" style="margin-right:0.10903em;">M</span></span></span></span> ,其中A[i]&lt;M,这里的k_i就是我们统计的频数。并且在修改A[i]的过程中,我们通过取模运算仍然保证了下标 <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mi mathvariant="normal">%</mi><mi>M</mi></mrow><annotation encoding="application/x-tex">A[i]\%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 mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mord">%</span><span class="mord mathdefault" style="margin-right:0.10903em;">M</span></span></span></span> 就是改变之前的<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">A[i]</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 mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span></span></span></span>;第二次遍历用除把它提取出来:<br /> <span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><msup><mi>A</mi><mo mathvariant="normal">′</mo></msup><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>=</mo><mo fence="false">⌊</mo><mfrac><mrow><mi>A</mi><mo stretchy="false">[</mo><mi>i</mi><mo stretchy="false">]</mo><mo>+</mo><msub><mi>k</mi><mi>i</mi></msub><mi>M</mi></mrow><mi>M</mi></mfrac><mo fence="false">⌋</mo><mo>=</mo><msub><mi>k</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">A&#x27;[i]=\Big\lfloor\frac{A[i]+k_iM}{M}\Big\rfloor=k_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.051892em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathdefault">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.113em;vertical-align:-0.686em;"></span><span class="mord"><span class="delimsizing size2">⌊</span></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:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.10903em;">M</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.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">A</span><span class="mopen">[</span><span class="mord mathdefault">i</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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="mord mathdefault" style="margin-right:0.10903em;">M</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="delimsizing size2">⌋</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.84444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathdefault" 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.31166399999999994em;"><span style="top:-2.5500000000000003em;margin-left:-0.03148em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">i</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></span></span></span></p> 2013-10-31T00:00:00+00:00 https://ysmull.cn/blog/array1.html https://ysmull.cn/blog/array1.html