<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Java语言 | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/category/proglanguage/javadev/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Wed, 07 May 2014 03:28:00 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>面向GC的Java编程</title>
		<link>https://coolshell.cn/articles/11541.html</link>
					<comments>https://coolshell.cn/articles/11541.html#comments</comments>
		
		<dc:creator><![CDATA[王 晨纯]]></dc:creator>
		<pubDate>Wed, 07 May 2014 03:24:38 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11541</guid>

					<description><![CDATA[<p>（感谢网友 @Hesey小纯纯 投稿  博客 &#124;　原文链接） Java程序员在编码过程中通常不需要考虑内存问题，JVM经过高度优化的GC机制大部分情况下都能够很...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11541.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11541.html">面向GC的Java编程</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（感谢网友 <a href="http://weibo.com/tbmujian" target="_blank">@Hesey小纯纯</a> 投稿  <a href="http://blog.hesey.net/" target="_blank">博客</a> |　<a href="http://blog.hesey.net/2014/05/gc-oriented-java-programming.html" target="_blank">原文链接</a>）</strong></p>
<p>Java程序员在编码过程中通常不需要考虑内存问题，JVM经过高度优化的GC机制大部分情况下都能够很好地处理堆(Heap)的清理问题。以至于许多Java程序员认为，我只需要关心何时创建对象，而回收对象，就交给GC来做吧！甚至有人说，如果在编程过程中频繁考虑内存问题，是一种退化，这些事情应该交给编译器，交给虚拟机来解决。</p>
<p>这话其实也没有太大问题，的确，大部分场景下关心内存、GC的问题，显得有点“杞人忧天”了，高老爷说过：</p>
<p style="padding-left: 30px;">过早优化是万恶之源。</p>
<p>但另一方面，<strong>什么才是“过早优化”？</strong></p>
<p style="padding-left: 30px;">If we could do things right for the first time, why not?</p>
<p>事实上<strong>JVM的内存模型</strong>( <a href="http://www.cs.umd.edu/~pugh/java/memoryModel/" target="_blank">JMM</a> )理应是Java程序员的基础知识，处理过几次JVM线上内存问题之后就会很明显感受到，很多系统问题，都是内存问题。</p>
<p>对JVM内存结构感兴趣的同学可以看下 <a href="http://blog.hesey.net/2011/04/introduction-to-java-virtual-machine.html" target="_blank">浅析Java虚拟机结构与机制</a> 这篇文章，本文就不再赘述了，本文也并不关注具体的GC算法，相关的文章汗牛充栋，随时可查。</p>
<p>另外，不要指望GC优化的这些技巧，可以对应用性能有成倍的提高，特别是对I/O密集型的应用，或是实际落在YoungGC上的优化，可能效果只是帮你减少那么一点YoungGC的频率。</p>
<p>但我认为，<strong>优秀程序员的价值，不在于其所掌握的几招屠龙之术，而是在细节中见真著</strong>，就像前面说的，<strong>如果我们可以一次把事情做对，并且做好，在允许的范围内尽可能追求卓越，为什么不去做呢？</strong><span id="more-11541"></span></p>
<h4>一、GC分代的基本假设</h4>
<p>大部分GC算法，都将堆内存做分代(Generation)处理，但是为什么要分代呢，又为什么不叫内存分区、分段，而要用面向时间、年龄的“代”来表示不同的内存区域？</p>
<p>GC分代的<strong>基本假设</strong>是：</p>
<p style="padding-left: 30px;"><strong>绝大部分对象的生命周期都非常短暂，存活时间短。</strong></p>
<p>而这些短命的对象，恰恰是GC算法需要首先关注的。所以在大部分的GC中，YoungGC（也称作MinorGC）占了绝大部分，对于负载不高的应用，可能跑了数个月都不会发生FullGC。</p>
<p>基于这个前提，在编码过程中，我们应该<strong>尽可能地缩短对象的生命周期</strong>。在过去，分配对象是一个比较重的操作，所以有些程序员会尽可能地减少new对象的次数，尝试减小堆的分配开销，减少内存碎片。</p>
<p>但是，短命对象的创建在JVM中比我们想象的性能更好，所以，不要吝啬new关键字，大胆地去new吧。</p>
<p>当然前提是不做无谓的创建，对象创建的速率越高，那么GC也会越快被触发。</p>
<p>结论：</p>
<ul>
<li>分配小对象的开销分享小，不要吝啬去创建。</li>
<li>GC最喜欢这种小而短命的对象。</li>
<li>让对象的生命周期尽可能短，例如在方法体内创建，使其能尽快地在YoungGC中被回收，不会晋升(romote)到年老代(Old Generation)。</li>
</ul>
<h4>二、对象分配的优化</h4>
<p>基于大部分对象都是小而短命，并且不存在多线程的数据竞争。这些小对象的分配，会优先在线程私有的<strong> TLAB</strong> 中分配，TLAB中创建的对象，不存在锁甚至是CAS的开销。</p>
<p>TLAB占用的空间在Eden Generation。</p>
<p>当对象比较大，TLAB的空间不足以放下，而JVM又认为当前线程占用的TLAB剩余空间还足够时，就会直接在Eden Generation上分配，此时是存在并发竞争的，所以会有CAS的开销，但也还好。</p>
<p>当对象大到Eden Generation放不下时，JVM只能尝试去Old Generation分配，这种情况需要尽可能避免，因为一旦在Old Generation分配，这个对象就只能被Old Generation的GC或是FullGC回收了。</p>
<h4>三、不可变对象的好处</h4>
<p>GC算法在扫描存活对象时通常需要从ROOT节点开始，扫描所有存活对象的引用，构建出对象图。</p>
<p>不可变对象对GC的优化，主要体现在Old Generation中。</p>
<p>可以想象一下，如果存在Old Generation的对象引用了Young Generation的对象，那么在每次YoungGC的过程中，就必须考虑到这种情况。</p>
<p>Hotspot JVM为了提高YoungGC的性能，避免每次YoungGC都扫描Old Generation中的对象引用，采用了 <strong>卡表(Card Table) </strong>的方式。</p>
<p>简单来说，当Old Generation中的对象发生对Young Generation中的对象产生新的引用关系或释放引用时，都会在卡表中响应的标记上标记为脏(dirty)，而YoungGC时，只需要扫描这些dirty的项就可以了。</p>
<p>可变对象对其它对象的引用关系可能会频繁变化，并且有可能在运行过程中持有越来越多的引用，特别是容器。这些都会导致对应的卡表项被频繁标记为dirty。</p>
<p>而不可变对象的引用关系非常稳定，在扫描卡表时就不会扫到它们对应的项了。</p>
<p>注意，这里的不可变对象，不是指仅仅自身引用不可变的final对象，而是真正的<strong><span style="color: #ff0000;">Immutable Objects</span></strong>。</p>
<h4>四、引用置为null的传说</h4>
<p>早期的很多Java资料中都会提到在方法体中将一个变量置为null能够优化GC的性能，类似下面的代码：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">List&lt;String&gt; list = new ArrayList&lt;String&gt;();
// some code
list = null; // help GC
</pre>
<p>事实上这种做法对GC的帮助微乎其微，有时候反而会导致代码混乱。</p>
<p>我记得几年前 @rednaxelafx 在HLL VM小组中详细论述过这个问题，原帖我没找到，结论基本就是：</p>
<ul>
<li>在一个非常大的方法体内，对一个较大的对象，将其引用置为null，某种程度上可以帮助GC。</li>
<li>大部分情况下，这种行为都没有任何好处。</li>
</ul>
<p>所以，还是早点放弃这种“优化”方式吧。</p>
<p>GC比我们想象的更聪明。</p>
<h4>五、手动档的GC</h4>
<p>在很多Java资料上都有下面两个奇技淫巧：</p>
<ul>
<li>通过<strong>Thread.yield()</strong>让出CPU资源给其它线程。</li>
<li>通过<strong>System.gc()</strong>触发GC。</li>
</ul>
<p>事实上JVM从不保证这两件事，而System.gc()在JVM启动参数中如果允许显式GC，则会<strong>触发FullGC</strong>，对于响应敏感的应用来说，几乎等同于自杀。</p>
<p>So，让我们牢记两点：</p>
<ul>
<li>Never use Thread.yield()。</li>
<li>Never use System.gc()。除非你真的需要回收Native Memory。</li>
</ul>
<p>第二点有个Native Memory的例外，如果你在以下场景：</p>
<ul>
<li>使用了NIO或者NIO框架（Mina/Netty）</li>
<li>使用了DirectByteBuffer分配字节缓冲区</li>
<li>使用了MappedByteBuffer做内存映射</li>
</ul>
<p>由于<strong>Native Memory只能通过FullGC（或是CMS GC）回收</strong>，所以除非你非常清楚这时真的有必要，否则不要轻易调用System.gc()，且行且珍惜。</p>
<p>另外为了防止某些框架中的System.gc调用（例如NIO框架、Java RMI），建议在启动参数中加上-XX:+DisableExplicitGC来禁用显式GC。</p>
<p>这个参数有个巨大的坑，如果你禁用了System.gc()，那么上面的3种场景下的内存就无法回收，可能造成OOM，如果你使用了CMS GC，那么可以用这个参数替代：-XX:+ExplicitGCInvokesConcurrent。</p>
<p>关于System.gc()，可以参考 @bluedavy 的几篇文章：</p>
<ul>
<li><a href="http://hellojava.info/?p=56" target="_blank">CMS GC会不会回收Direct ByteBuffer的内存</a></li>
<li><a href="http://hellojava.info/?p=323" target="_blank">说说在Java启动参数上我犯的错</a></li>
<li><a href="http://hellojava.info/?p=319" target="_blank">java.lang.OutOfMemoryError:Map failed</a></li>
</ul>
<p>&nbsp;</p>
<h4>六、指定容器初始化大小</h4>
<p>Java容器的一个特点就是可以动态扩展，所以通常我们都不会去考虑初始大小的设置，不够了反正会自动扩容呗。</p>
<p>但是扩容不意味着没有代价，甚至是很高的代价。</p>
<p>例如一些基于数组的数据结构，例如StringBuilder、StringBuffer、ArrayList、HashMap等等，在扩容的时候都需要做ArrayCopy，对于不断增长的结构来说，经过若干次扩容，会存在大量无用的老数组，而回收这些数组的压力，全都会加在GC身上。</p>
<p>这些容器的构造函数中通常都有一个可以指定大小的参数，如果对于某些大小可以预估的容器，建议加上这个参数。</p>
<p>可是因为容器的扩容并不是等到容器满了才扩容，而是有一定的比例，例如HashMap的扩容阈值和负载因子(loadFactor)相关。</p>
<p>Google Guava框架对于容器的初始容量提供了非常便捷的工具方法，例如：</p>
<p>[code lang=&#8221;java&#8221;]Lists.newArrayListWithCapacity(initialArraySize);</p>
<p>Lists.newArrayListWithExpectedSize(estimatedSize);</p>
<p>Sets.newHashSetWithExpectedSize(expectedSize);</p>
<p>Maps.newHashMapWithExpectedSize(expectedSize);<br />
[/code]</p>
<p>这样我们只要传入预估的大小即可，容量的计算就交给Guava来做吧。</p>
<p><strong>反例</strong>：如果采用默认无参构造函数，创建一个ArrayList，不断增加元素直到OOM，那么在此过程中会导致：</p>
<ul>
<li>多次数组扩容，重新分配更大空间的数组</li>
<li>多次数组拷贝</li>
<li>内存碎片</li>
</ul>
<h4>七、对象池</h4>
<p>为了减少对象分配开销，提高性能，可能有人会采取对象池的方式来缓存对象集合，作为复用的手段。</p>
<p>但是对象池中的对象由于在运行期长期存活，大部分会晋升到Old Generation，因此无法通过YoungGC回收。</p>
<p>并且通常……没有什么效果。</p>
<p>对于对象本身：</p>
<ul>
<li>如果对象很小，那么分配的开销本来就小，对象池只会增加代码复杂度。</li>
<li>如果对象比较大，那么晋升到Old Generation后，对GC的压力就更大了。</li>
</ul>
<p>从线程安全的角度考虑，通常池都是会被并发访问的，那么你就需要处理好同步的问题，这又是一个大坑，并且<strong>同步带来的开销，未必比你重新创建一个对象小</strong>。</p>
<p>对于对象池，唯一合适的场景就是<strong>当池中的每个对象的创建开销很大</strong>时，缓存复用才有意义，例如每次new都会创建一个连接，或是依赖一次RPC。</p>
<p>比如说：</p>
<ul>
<li>线程池</li>
<li>数据库连接池</li>
<li>TCP连接池</li>
</ul>
<p>即使你真的需要实现一个对象池，也请使用成熟的开源框架，例如Apache Commons Pool。</p>
<p>另外，使用JDK的ThreadPoolExecutor作为线程池，不要重复造轮子，除非当你看过AQS的源码后认为你可以写得比Doug Lea更好。</p>
<h4>八、对象作用域</h4>
<p>尽可能缩小对象的作用域，即生命周期。</p>
<ul>
<li>如果可以在方法内声明的局部变量，就不要声明为实例变量。</li>
<li>除非你的对象是单例的或不变的，否则尽可能少地声明static变量。</li>
</ul>
<h4>九、各类引用</h4>
<p>java.lang.ref.Reference有几个子类，用于处理和GC相关的引用。JVM的引用类型简单来说有几种：</p>
<ul>
<li>Strong Reference，最常见的引用</li>
<li>Weak Reference，当没有指向它的强引用时会被GC回收</li>
<li>Soft Reference，只当临近OOM时才会被GC回收</li>
<li>Phantom Reference，主要用于识别对象被GC的时机，通常用于做一些清理工作</li>
</ul>
<p>当你需要实现一个缓存时，可以考虑优先使用WeakHashMap，而不是HashMap，当然，更好的选择是使用框架，例如Guava Cache。</p>
<p>最后，再次提醒，以上的这些未必可以对代码有多少性能上的提升，但是熟悉这些方法，是为了帮助我们写出更卓越的代码，和GC更好地合作。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/2631.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/8.jpg" alt="五大基于JVM的脚本语言" width="150" height="150" /></a><a href="https://coolshell.cn/articles/2631.html" class="wp_rp_title">五大基于JVM的脚本语言</a></li><li ><a href="https://coolshell.cn/articles/1252.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/5.jpg" alt="G1新型垃圾回收器一瞥" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1252.html" class="wp_rp_title">G1新型垃圾回收器一瞥</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-150x150.jpg" alt="Java中的CopyOnWrite容器" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_title">Java中的CopyOnWrite容器</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11541.html">面向GC的Java编程</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11541.html/feed</wfw:commentRss>
			<slash:comments>46</slash:comments>
		
		
			</item>
		<item>
		<title>从LongAdder看更高效的无锁实现</title>
		<link>https://coolshell.cn/articles/11454.html</link>
					<comments>https://coolshell.cn/articles/11454.html#comments</comments>
		
		<dc:creator><![CDATA[liuinsect]]></dc:creator>
		<pubDate>Thu, 17 Apr 2014 15:11:40 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[AtomicLong]]></category>
		<category><![CDATA[cas]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[LongAdder]]></category>
		<category><![CDATA[Performance]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11454</guid>

					<description><![CDATA[<p>（感谢 @jd刘锟洋 投稿，更多文章参看他的博客：码梦为生） 原文链接：《比AtomicLong还高效的LongAdder 源码解析》 接触到AtomicLon...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11454.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11454.html">从LongAdder看更高效的无锁实现</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（感谢 <a href="http://weibo.com/liuinsect" target="_blank">@jd刘锟洋</a> 投稿，更多文章参看他的博客：<a href="http://www.liuinsect.com/" target="_blank">码梦为生</a>）</strong></p>
<p><strong>原文链接</strong>：《<a href="http://www.liuinsect.com/2014/04/15/%E6%AF%94atomiclong%E8%BF%98%E9%AB%98%E6%95%88%E7%9A%84longadder-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/" target="_blank">比AtomicLong还高效的LongAdder 源码解析</a>》</p>
<p>接触到AtomicLong的原因是在看guava的LoadingCache相关代码时，关于LoadingCache，其实思路也非常简单清晰：用模板模式解决了缓存不命中时获取数据的逻辑，这个思路我早前也正好在项目中使用到。</p>
<p>言归正传，为什么说LongAdder引起了我的注意，原因有二：</p>
<ol>
<li>作者是Doug lea ，地位实在举足轻重。</li>
<li>他说这个比AtomicLong高效。</li>
</ol>
<p>我们知道，AtomicLong已经是非常好的解决方案了，涉及并发的地方都是使用CAS操作，在硬件层次上去做 compare and set操作。效率非常高。</p>
<p>因此，我决定研究下，为什么LongAdder比AtomicLong高效。</p>
<p>首先，看LongAdder的继承树：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-209 aligncenter" alt="la1" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la1.png" width="431" height="104" /></p>
<p>继承自Striped64，这个类包装了一些很重要的内部类和操作。稍候会看到。</p>
<p><span id="more-11454"></span></p>
<p><strong>正式开始前，强调下，我们知道，AtomicLong的实现方式是内部有个value 变量，当多线程并发自增，自减时，均通过CAS 指令从机器指令级别操作保证并发的原子性。</strong></p>
<p>再看看LongAdder的方法：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-210 aligncenter" alt="la2" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la2.png" width="472" height="436" /><br />
怪不得可以和AtomicLong作比较，连API都这么像。我们随便挑一个API入手分析，这个API通了，其他API都大同小异，因此，我选择了add这个方法。事实上,其他API也都依赖这个方法。</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-211 aligncenter" alt="la3" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la3.png" width="701" height="281" /><br />
LongAdder中包含了一个Cell 数组，Cell是Striped64的一个内部类，顾名思义，Cell 代表了一个最小单元，这个单元有什么用，稍候会说道。先看定义：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-212 aligncenter" alt="la4" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la4.png" width="686" height="649" /><br />
Cell内部有一个非常重要的value变量，并且提供了一个CAS更新其值的方法。</p>
<p>回到add方法：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-211 aligncenter" alt="la3" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la3.png" width="701" height="281" /></p>
<p>这里，我有个疑问，AtomicLong已经使用CAS指令，非常高效了（比起各种锁），LongAdder如果还是用CAS指令更新值，怎么可能比AtomicLong高效了？ 何况内部还这么多判断！！！</p>
<p>这是我开始时最大的疑问，所以，我猜想，难道有比CAS指令更高效的方式出现了？ 带着这个疑问，继续。</p>
<p>第一if 判断，第一次调用的时候cells数组肯定为null,因此，进入casBase方法：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-213 aligncenter" alt="la5" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la5.png" width="772" height="81" /><br />
原子更新base没啥好说的，如果更新成功，本地调用开始返回，否则进入分支内部。</p>
<p>什么时候会更新失败？ 没错，并发的时候，好戏开始了，AtomicLong的处理方式是死循环尝试更新，直到成功才返回，而LongAdder则是进入这个分支。</p>
<p>分支内部，通过一个Threadlocal变量threadHashCode 获取一个HashCode对象，该HashCode对象依然是Striped64类的内部类，看定义：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-214 aligncenter" alt="la6" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la6.png" width="734" height="203" /><br />
有个code变量，保存了一个非0的随机数随机值。</p>
<p>回到add方法：</p>
<p><img decoding="async" loading="lazy" class="alignnone size-full wp-image-211 aligncenter" alt="la3" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la3.png" width="701" height="281" /></p>
<p>拿到该线程相关的HashCode对象后，获取它的code变量，as[(n-1)&amp;h] 这句话相当于对h取模，只不过比起取模，因为是 与 的运算所以效率更高。</p>
<p>计算出一个在Cells 数组中当先线程的HashCode对应的 索引位置，并将该位置的Cell 对象拿出来用CAS更新它的value值。</p>
<p>当然，如果as 为null 并且更新失败，才会进入retryUpdate方法。</p>
<p>看到这里我想应该有很多人明白为什么LongAdder会比AtomicLong更高效了，没错，唯一会制约AtomicLong高效的原因是高并发，高并发意味着CAS的失败几率更高， 重试次数更多，越多线程重试，CAS失败几率又越高，变成恶性循环，AtomicLong效率降低。 那怎么解决？<strong> LongAdder给了我们一个非常容易想到的解决方案：减少并发，将单一value的更新压力分担到多个value中去，降低单个value的 “热度”，分段更新！！！</strong></p>
<p>这样，线程数再多也会分担到多个value上去更新，只需要增加value就可以降低 value的 “热度”  AtomicLong中的 恶性循环不就解决了吗？ cells 就是这个 “段” cell中的value 就是存放更新值的， 这样，<strong>当我需要总数时，把cells 中的value都累加一下不就可以了么！！</strong></p>
<p><strong>当然，聪明之处远远不仅仅这里，在看看add方法中的代码，casBase方法可不可以不要，直接分段更新,上来就计算 索引位置，然后更新value？</strong></p>
<p>答案是不好，不是不行，因为，casBase操作等价于AtomicLong中的CAS操作，要知道，LongAdder这样的处理方式是有坏处的，分段操作必然带来空间上的浪费，可以空间换时间，但是，<strong>能不换就不换，看空间时间都节约~！</strong> 所以，<strong>casBase操作保证了在低并发时，不会立即进入分支做分段更新操作</strong>，因为低并发时，casBase操作基本都会成功，只有并发高到一定程度了，才会进入分支，所以，Doug Lea对该类的说明是：<strong> 低并发时LongAdder和AtomicLong性能差不多，高并发时LongAdder更高效！</strong></p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class=" wp-image-215 aligncenter" alt="la7" src="http://www.liuinsect.com/wp-content/uploads/2014/04/la7.png" width="750" height="331" /></p>
<p>但是，Doung Lea 还是没这么简单，聪明之处还没有结束&#8230;&#8230;</p>
<p>如此，retryUpdate中做了什么事，也基本略知一二了，因为cell中的value都更新失败(说明该索引到这个cell的线程也很多，并发也很高时) 或者cells数组为空时才会调用retryUpdate,</p>
<p>因此，<strong>retryUpdate里面应该会做两件事：</strong></p>
<ol>
<li><strong>扩容，将cells数组扩大</strong>，降低每个cell的并发量，同样，这也意味着cells数组的rehash动作。</li>
<li> <strong>给空的cells变量赋一个新的Cell数组</strong>。</li>
</ol>
<p>是不是这样呢？ 继续看代码：</p>
<p>代码比较长，变成文本看看，为了方便大家看if else 分支，对应的  { } 我用相同的颜色标注出来。可以看到，这个时候Doug Lea才愿意使用死循环保证更新成功~！</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
  final void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
        int h = hc.code;
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null &amp;&amp; (n = as.length) &gt; 0) {// 分支1
                if ((a = as[(n - 1) &amp; h]) == null) {
                    if (busy == 0) {            // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (busy == 0 &amp;&amp; casBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &amp;&amp;
                                        (m = rs.length) &gt; 0 &amp;&amp;
                                        rs[j = (m - 1) &amp; h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                busy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (a.cas(v = a.value, fn(v, x)))
                    break;
                else if (n &gt;= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (busy == 0 &amp;&amp; casBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n &lt;&lt; 1];
                            for (int i = 0; i &lt; n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        busy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h ^= h &lt;&lt; 13;                   // Rehash  h ^= h &gt;&gt;&gt; 17;
                h ^= h &lt;&lt; 5;
            }
            else if (busy == 0 &amp;&amp; cells == as &amp;&amp; casBusy()) {//分支2
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h &amp; 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    busy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, fn(v, x)))
                break;                          // Fall back on using base
        }
        hc.code = h;                            // Record index for next time
    }

</pre>
<p>分支2中，为cells为空的情况，需要new 一个Cell数组。</p>
<p>分支1分支中，略复杂一点点：</p>
<p>注意，几个分支中都提到了busy这个方法，这个可以理解为一个CAS实现的锁，只有在需要更新cells数组的时候才会更新该值为1，如果更新失败，则说明当前有线程在更新cells数组，当前线程需要等待。重试。</p>
<p>回到分支1中，这里首先判断当前cells数组中的索引位置的cell元素是否为空，如果为空，则添加一个cell到数组中。</p>
<p>否则更新 标示冲突的标志位wasUncontended 为 true ，重试。</p>
<p>否则，再次更新cell中的value,如果失败，重试。</p>
<p>。。。。。。。一系列的判断后<span style="line-height: 1.5em;">，如果还是失败，下下下策，reHash,直接将cells数组扩容一倍，并更新当前线程的hash值，保证下次更新能尽可能成功。</span></p>
<p><strong>可以看到，LongAdder确实用了很多心思减少并发量，并且，每一步都是在”没有更好的办法“的时候才会选择更大开销的操作，从而尽可能的用最最简单的办法去完成操作。追求简单，但是绝对不粗暴。</strong></p>
<p>———————<strong>陈皓注————————</strong></p>
<p>最后留给大家思考的两个问题：</p>
<p style="padding-left: 30px;">1）是不是AtomicLong可以被废了？</p>
<p style="padding-left: 30px;">2）如果cell被创建后，原来的casBase就不走了，会不会性能更差？</p>
<p>———————liuinsect<strong>注————————</strong></p>
<p>昨天和左耳朵耗子简单讨论了下，发现左耳朵耗子,耗哥对读者思维的引导还是非常不错的，在第一次发现这个类后，对里面的实现又提出了更多的问题，引导大家思考，值得学习。</p>
<p>我们 发现的问题有这么几个（包括以上的问题），自己简单总结下，欢迎大家讨论：</p>
<p>1. jdk 1.7中是不是有这个类？<br />
我确认后，结果如下：    jdk-7u51 版本上还没有  但是jdk-8u20版本上已经有了。代码基本一样 ，增加了对double类型的支持和删除了一些冗余的代码。有兴趣的同学可以去下载下JDK 1.8看看</p>
<p>2. base有没有参与汇总？<br />
base在调用intValue等方法的时候是会汇总的：</p>
<p><a href="http://www.liuinsect.com/wp-content/uploads/2014/04/LA101.bmp"><img decoding="async" alt="LA10" src="http://www.liuinsect.com/wp-content/uploads/2014/04/LA101.bmp" /></a></p>
<p>3. 如果cell被创建后，原来的casBase就不走了，会不会性能更差？ base的顺序可不可以调换?<br />
<span style="line-height: 1.5em;">    刚开始我想可不可以调换add方法中的判断顺序，比如，先做casBase的判断？ 仔细思考后认为还是 不调换可能更好，调换后每次都要CAS一下，在高并发时，失败几率非常高，并且是恶性循环，比起一次判断，后者的开销明显小很多，还没有副作用（上一个问题，base变量在sum时base是会被统计的，并不会丢掉base的值）。因此，不调换可能会更好。</span></p>
<p>4. AtomicLong可不可以废掉？<br />
我的想法是可以废掉了，因为，虽然LongAdder在空间上占用略大，但是，它的性能已经足以说明一切了,无论是从节约空的角度还是执行效率上，AtomicLong基本没有优势了，具体看这个测试（感谢<a id="commentauthor-1431785" href="http://lianming.info/" rel="external nofollow">Lemon</a>的回复）:http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/</p>
<p style="padding-left: 30px;">
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="http://coolshell.cn/articles/8239.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/09/lock_free_bicycle-150x150.jpg" alt="无锁队列的实现" width="150" height="150" /></a><a href="http://coolshell.cn/articles/8239.html" class="wp_rp_title">无锁队列的实现</a></li><li ><a href="http://coolshell.cn/articles/9703.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3-150x150.jpg" alt="无锁HashMap的原理与实现" width="150" height="150" /></a><a href="http://coolshell.cn/articles/9703.html" class="wp_rp_title">无锁HashMap的原理与实现</a></li><li ><a href="http://coolshell.cn/articles/9169.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/Disruptor-150x150.png" alt="并发框架Disruptor译文" width="150" height="150" /></a><a href="http://coolshell.cn/articles/9169.html" class="wp_rp_title">并发框架Disruptor译文</a></li><li ><a href="http://coolshell.cn/articles/9606.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/race_condition-150x150.jpg" alt="疫苗：Java HashMap的死循环" width="150" height="150" /></a><a href="http://coolshell.cn/articles/9606.html" class="wp_rp_title">疫苗：Java HashMap的死循环</a></li><li ><a href="http://coolshell.cn/articles/6424.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/16.jpg" alt="Hash Collision DoS 问题" width="150" height="150" /></a><a href="http://coolshell.cn/articles/6424.html" class="wp_rp_title">Hash Collision DoS 问题</a></li><li ><a href="http://coolshell.cn/articles/11175.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-150x150.jpg" alt="Java中的CopyOnWrite容器" width="150" height="150" /></a><a href="http://coolshell.cn/articles/11175.html" class="wp_rp_title">Java中的CopyOnWrite容器</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11454.html">从LongAdder看更高效的无锁实现</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11454.html/feed</wfw:commentRss>
			<slash:comments>35</slash:comments>
		
		
			</item>
		<item>
		<title>Java中的CopyOnWrite容器</title>
		<link>https://coolshell.cn/articles/11175.html</link>
					<comments>https://coolshell.cn/articles/11175.html#comments</comments>
		
		<dc:creator><![CDATA[方 腾飞]]></dc:creator>
		<pubDate>Fri, 07 Mar 2014 00:26:31 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[Copy-On-Write]]></category>
		<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11175</guid>

					<description><![CDATA[<p>感谢 清英 同学的投稿 Copy-On-Write简称COW，是一种用于程序设计中的优化策略。其基本思路是，从一开始大家都在共享同一个内容，当某个人想要修改这个...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11175.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11175.html">Java中的CopyOnWrite容器</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-11219" alt="" src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-300x222.jpg" width="300" height="222" /><strong>感谢 <a href="http://ifeve.com" target="_blank">清英</a> 同学的投稿</strong></p>
<p>Copy-On-Write简称COW，是一种用于程序设计中的优化策略。其基本思路是，从一开始大家都在共享同一个内容，当某个人想要修改这个内容的时候，才会真正把内容Copy出去形成一个新的内容然后再改，这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用，可以在非常多的并发场景中使用到。</p>
<h4>什么是CopyOnWrite容器</h4>
<p>CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候，不直接往当前容器添加，而是先将当前容器进行Copy，复制出一个新的容器，然后新的容器里添加元素，添加完元素之后，再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读，而不需要加锁，因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想，读和写不同的容器。</p>
<p><span id="more-11175"></span></p>
<h4>CopyOnWriteArrayList的实现原理</h4>
<p>在使用CopyOnWriteArrayList之前，我们先阅读其源码了解下它是如何实现的。以下代码是向ArrayList里添加元素，可以发现在添加的时候是需要加锁的，否则多线程写的时候会Copy出N个副本出来。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public boolean add(T e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {

        Object[] elements = getArray();

        int len = elements.length;
        // 复制出新数组

        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 把新元素添加到新数组里

        newElements[len] = e;
        // 把原数组引用指向新数组

        setArray(newElements);

        return true;

    } finally {

        lock.unlock();

    }

}

final void setArray(Object[] a) {
    array = a;
}
</pre>
<p>读的时候不需要加锁，如果读的时候有多个线程正在向ArrayList添加数据，读还是会读到旧的数据，因为写的时候不会锁住旧的ArrayList。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public E get(int index) {
    return get(getArray(), index);
}
</pre>
<p>JDK中并没有提供CopyOnWriteMap，我们可以参考CopyOnWriteArrayList来实现一个，基本代码如下：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">

import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class CopyOnWriteMap&lt;K, V&gt; implements Map&lt;K, V&gt;, Cloneable {
    private volatile Map&lt;K, V&gt; internalMap;

    public CopyOnWriteMap() {
        internalMap = new HashMap&lt;K, V&gt;();
    }

    public V put(K key, V value) {

        synchronized (this) {
            Map&lt;K, V&gt; newMap = new HashMap&lt;K, V&gt;(internalMap);
            V val = newMap.put(key, value);
            internalMap = newMap;
            return val;
        }
    }

    public V get(Object key) {
        return internalMap.get(key);
    }

    public void putAll(Map&lt;? extends K, ? extends V&gt; newData) {
        synchronized (this) {
            Map&lt;K, V&gt; newMap = new HashMap&lt;K, V&gt;(internalMap);
            newMap.putAll(newData);
            internalMap = newMap;
        }
    }
}
</pre>
<p>实现很简单，只要了解了CopyOnWrite机制，我们可以实现各种CopyOnWrite容器，并且在不同的应用场景中使用。</p>
<h4>CopyOnWrite的应用场景</h4>
<p>CopyOnWrite并发容器用于读多写少的并发场景。比如白名单，黑名单，商品类目的访问和更新场景，假如我们有一个搜索网站，用户在这个网站的搜索框中，输入关键字搜索内容，但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中，黑名单每天晚上更新一次。当用户搜索时，会检查当前关键字在不在黑名单当中，如果在，则提示不能搜索。实现代码如下：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
package com.ifeve.book;

import java.util.Map;

import com.ifeve.book.forkjoin.CopyOnWriteMap;

/**
 * 黑名单服务
 *
 * @author fangtengfei
 *
 */
public class BlackListServiceImpl {

    private static CopyOnWriteMap&lt;String, Boolean&gt; blackListMap = new CopyOnWriteMap&lt;String, Boolean&gt;(
            1000);

    public static boolean isBlackList(String id) {
        return blackListMap.get(id) == null ? false : true;
    }

    public static void addBlackList(String id) {
        blackListMap.put(id, Boolean.TRUE);
    }

    /**
     * 批量添加黑名单
     *
     * @param ids
     */
    public static void addBlackList(Map&lt;String,Boolean&gt; ids) {
        blackListMap.putAll(ids);
    }

}
</pre>
<p>代码很简单，但是使用CopyOnWriteMap需要注意两件事情：</p>
<p>1. 减少扩容开销。根据实际需要，初始化CopyOnWriteMap的大小，避免写时CopyOnWriteMap扩容的开销。</p>
<p>2. 使用批量添加。因为每次添加，容器每次都会进行复制，所以减少添加次数，可以减少容器的复制次数。如使用上面代码里的addBlackList方法。</p>
<h4>CopyOnWrite的缺点</h4>
<p>CopyOnWrite容器有很多优点，但是同时也存在两个问题，即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。</p>
<p><strong>内存占用问题</strong>。因为CopyOnWrite的写时复制机制，所以在进行写操作的时候，内存里会同时驻扎两个对象的内存，旧的对象和新写入的对象（注意:在复制的时候只是复制容器里的引用，只是在写的时候会创建新对象添加到新容器里，而旧容器的对象还在使用，所以有两份对象内存）。如果这些对象占用的内存比较大，比如说200M左右，那么再写入100M数据进去，内存就会占用300M，那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象，造成了每晚15秒的Full GC，应用响应时间也随之变长。</p>
<p>针对内存占用问题，可以通过压缩容器中的元素的方法来减少大对象的内存消耗，比如，如果元素全是10进制的数字，可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器，而使用其他的并发容器，如<a href="http://ifeve.com/concurrenthashmap/" target="_blank">ConcurrentHashMap</a>。</p>
<p><strong>数据一致性问题</strong>。CopyOnWrite容器只能保证数据的最终一致性，不能保证数据的实时一致性。所以如果你希望写入的的数据，马上能读到，请不要使用CopyOnWrite容器。</p>
<p>关于C++的STL中，曾经也有过Copy-On-Write的玩法，参见陈皓的《<a href="http://blog.csdn.net/haoel/article/details/24058" target="_blank">C++ STL String类中的Copy-On-Write</a>》，后来，因为有很多线程安全上的事，就被去掉了。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3-150x150.jpg" alt="无锁HashMap的原理与实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_title">无锁HashMap的原理与实现</a></li><li ><a href="https://coolshell.cn/articles/9606.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/race_condition-150x150.jpg" alt="疫苗：Java HashMap的死循环" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9606.html" class="wp_rp_title">疫苗：Java HashMap的死循环</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11175.html">Java中的CopyOnWrite容器</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11175.html/feed</wfw:commentRss>
			<slash:comments>38</slash:comments>
		
		
			</item>
		<item>
		<title>无锁HashMap的原理与实现</title>
		<link>https://coolshell.cn/articles/9703.html</link>
					<comments>https://coolshell.cn/articles/9703.html#comments</comments>
		
		<dc:creator><![CDATA[onetwogoo]]></dc:creator>
		<pubDate>Thu, 30 May 2013 13:31:20 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Hash]]></category>
		<category><![CDATA[HashMap]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[并发]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9703</guid>

					<description><![CDATA[<p> (本文由onetwogoo投稿) 在《疫苗：Java HashMap的死循环》中，我们看到，java.util.HashMap并不能直接应用于多线程环境。对于...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9703.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9703.html">无锁HashMap的原理与实现</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong> (本文由<a href="https://github.com/onetwogoo" rel="author">onetwogoo</a>投稿)</strong></p>
<p>在《<a title="疫苗：Java HashMap的死循环" href="https://coolshell.cn/articles/9606.html" target="_blank">疫苗：Java HashMap的死循环</a>》中，我们看到，java.util.HashMap并不能直接应用于多线程环境。对于多线程环境中应用HashMap，主要有以下几种选择：</p>
<ol>
<li><span style="line-height: 13px;">使用线程安全的java.util.Hashtable作为替代。</span></li>
<li>使用java.util.Collections.synchronizedMap方法，将已有的HashMap对象包装为线程安全的。</li>
<li>使用java.util.concurrent.ConcurrentHashMap类作为替代，它具有非常好的性能。</li>
</ol>
<p>而以上几种方法在实现的具体细节上，都或多或少地用到了互斥锁。互斥锁会造成线程阻塞，降低运行效率，并有可能产生死锁、优先级翻转等一系列问题。</p>
<p>CAS(Compare And Swap)是一种底层硬件提供的功能，它可以将判断并更改一个值的操作原子化。关于CAS的一些应用，《<a title="无锁队列的实现" href="https://coolshell.cn/articles/8239.html" target="_blank">无锁队列的实现</a>》一文中有很详细的介绍。</p>
<h4>Java中的原子操作</h4>
<p>在java.util.concurrent.atomic包中，Java为我们提供了很多方便的原子类型，它们底层完全基于CAS操作。</p>
<p>例如我们希望实现一个全局公用的计数器，那么可以：</p>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">private AtomicInteger counter = new AtomicInteger(3);

public void addCounter() {
    for (;;) {
        int oldValue = counter.get();
        int newValue = oldValue + 1;
        if (counter.compareAndSet(oldValue, newValue))
            return;
    }
}</pre>
<p><span id="more-9703"></span></p>
<p>其中，compareAndSet方法会检查counter现有的值是否为oldValue，如果是，则将其设置为新值newValue，操作成功并返回true；否则操作失败并返回false。</p>
<p>当计算counter新值时，若其他线程将counter的值改变，compareAndSwap就会失败。此时我们只需在外面加一层循环，不断尝试这个过程，那么最终一定会成功将counter值+1。（其实AtomicInteger已经为常用的+1/-1操作定义了incrementAndGet与decrementAndGet方法，以后我们只需简单调用它即可）</p>
<p>除了AtomicInteger外，java.util.concurrent.atomic包还提供了AtomicReference和AtomicReferenceArray类型，它们分别代表原子性的引用和原子性的引用数组（引用的数组）。</p>
<h4>无锁链表的实现</h4>
<p>在实现无锁HashMap之前，让我们先来看一下比较简单的无锁链表的实现方法。</p>
<p>以插入操作为例：</p>
<ol>
<li><span style="line-height: 13px;">首先我们需要找到待插入位置前面的节点A和后面的节点B。</span></li>
<li><span style="line-height: 13px;">然后新建一个节点C，并使其next指针指向节点B。（见图1）</span></li>
<li>最后使节点A的next指针指向节点C。（见图2）</li>
</ol>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-9743" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3.jpg" width="600" height="479" srcset="https://coolshell.cn/wp-content/uploads/2013/05/图1-3.jpg 600w, https://coolshell.cn/wp-content/uploads/2013/05/图1-3-300x240.jpg 300w, https://coolshell.cn/wp-content/uploads/2013/05/图1-3-338x270.jpg 338w" sizes="(max-width: 600px) 100vw, 600px" /></p>
<p>但在操作中途，有可能其他线程在A与B直接也插入了一些节点（假设为D），如果我们不做任何判断，可能造成其他线程插入节点的丢失。（见图3）我们可以利用CAS操作，在为节点A的next指针赋值时，判断其是否仍然指向B，如果节点A的next指针发生了变化则重试整个插入操作。大致代码如下：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">private void listInsert(Node head, Node c) {
    for (;;) {
        Node a = findInsertionPlace(head), b = a.next.get();
        c.next.set(b);
        if (a.next.compareAndSwap(b,c))
            return;
    }
}</pre>
<p>(Node类的next字段为AtomicReference&lt;Node&gt;类型，即指向Node类型的原子性引用)</p>
<p>无锁链表的查找操作与普通链表没有区别。而其删除操作，则需要找到待删除节点前方的节点A和后方的节点B，利用CAS操作验证并更新节点A的next指针，使其指向节点B。</p>
<h4>无锁HashMap的难点与突破</h4>
<p>HashMap主要有<strong>插入</strong>、<strong>删除</strong>、<strong>查找</strong>以及<strong>ReHash</strong>四种基本操作。一个典型的HashMap实现，会用到一个数组，数组的每项元素为一个节点的链表。对于此链表，我们可以利用上文提到的操作方法，执行插入、删除以及查找操作，但对于ReHash操作则比较困难。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-9744" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/图4.jpg" width="648" height="265" srcset="https://coolshell.cn/wp-content/uploads/2013/05/图4.jpg 648w, https://coolshell.cn/wp-content/uploads/2013/05/图4-300x122.jpg 300w" sizes="(max-width: 648px) 100vw, 648px" /></p>
<p>如图4，在ReHash过程中，一个典型的操作是遍历旧表中的每个节点，计算其在新表中的位置，然后将其移动至新表中。期间我们需要操纵3次指针：</p>
<ol>
<li>将A的next指针指向D</li>
<li>将B的next指针指向C</li>
<li>将C的next指针指向E</li>
</ol>
<p>而这三次指针操作必须同时完成，才能保证移动操作的原子性。但我们不难看出，CAS操作每次只能保证<strong>一个</strong>变量的值被原子性地验证并更新，无法满足同时验证并更新三个指针的需求。</p>
<p>于是我们不妨换一个思路，既然移动节点的操作如此困难，我们可以使所有节点始终保持有序状态，从而避免了移动操作。在典型的HashMap实现中，数组的长度始终保持为2<sup>i</sup>，而从Hash值映射为数组下标的过程，只是简单地对数组长度执行取模运算（即仅保留Hash二进制的后i位）。当ReHash时，数组长度加倍变为2<sup>i+1</sup>，旧数组第j项链表中的每个节点，要么移动到新数组中第j项，要么移动到新数组中第j+2<sup>i</sup>项，而它们的唯一区别在于Hash值第i+1位的不同（第i+1位为0则仍为第j项，否则为第j+2<sup>i</sup>项）。</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-9745" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/图5-6.jpg" width="690" height="297" srcset="https://coolshell.cn/wp-content/uploads/2013/05/图5-6.jpg 863w, https://coolshell.cn/wp-content/uploads/2013/05/图5-6-300x128.jpg 300w" sizes="(max-width: 690px) 100vw, 690px" /></p>
<p>如图5，我们将所有节点按照Hash值的翻转位序（如1101-&gt;1011）由小到大排列。当数组大小为8时，2、18在一个组内；3、11、27在另一个组内。每组的开始，插入一个哨兵节点，以方便后续操作。为了使哨兵节点正确排在组的最前方，我们将正常节点Hash的最高位（翻转后变为最低位）置为1，而哨兵节点不设置这一位。</p>
<p>当数组扩容至16时（见图6），第二组分裂为一个只含3的组和一个含有11、27的组，但节点之间的相对顺序并未改变。这样在ReHash时，我们就不需要移动节点了。</p>
<h4>实现细节</h4>
<p>由于扩容时数组的复制会占用大量的时间，这里我们采用了将整个数组分块，懒惰建立的方法。这样，当访问到某下标时，仅需判断此下标所在块是否已建立完毕（如果没有则建立）。</p>
<p>另外定义size为当前已使用的下标范围，其初始值为2，数组扩容时仅需将size加倍即可；定义count代表目前HashMap中包含的总节点个数（不算哨兵节点）。</p>
<p>初始时，数组中除第0项外，所有项都为null。第0项指向一个仅有一个哨兵节点的链表，代表整条链的起点。初始时全貌见图7，其中浅绿色代表当前未使用的下标范围，虚线箭头代表逻辑上存在，但实际未建立的块。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-9746" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/图7.jpg" width="446" height="282" srcset="https://coolshell.cn/wp-content/uploads/2013/05/图7.jpg 446w, https://coolshell.cn/wp-content/uploads/2013/05/图7-300x189.jpg 300w" sizes="(max-width: 446px) 100vw, 446px" /></p>
<h5>初始化下标操作</h5>
<p>数组中为null的项都认为处于未初始化状态，初始化某个下标即代表建立其对应的哨兵节点。初始化是递归进行的，即若其父下标未初始化，则先初始化其父下标。（一个下标的父下标是其移除最高二进制位后得到的下标）大致代码如下：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">private void initializeBucket(int bucketIdx) {
    int parentIdx = bucketIdx ^ Integer.highestOneBit(bucketIdx);
    if (getBucket(parentIdx) == null)
        initializeBucket(parentIdx);

    Node dummy = new Node();
    dummy.hash = Integer.reverse(bucketIdx);
    dummy.next = new AtomicReference&amp;lt;&amp;gt;();

    setBucket(bucketIdx, listInsert(getBucket(parentIdx), dummy));
}</pre>
<p>其中getBucket即封装过的获取数组某下标内容的方法，setBucket同理。listInsert将从指定位置开始查找适合插入的位置插入给定的节点，若链表中已存在hash相同的节点则返回那个已存在的节点；否则返回新插入的节点。</p>
<h5>插入操作</h5>
<ul>
<li>首先用HashMap的size对键的hashCode取模，得到应插入的数组下标。</li>
<li>然后判断该下标处是否为null，如果为null则初始化此下标。</li>
<li>构造一个新的节点，并插入到适当位置，注意节点中的hash值应为原hashCode经过位翻转并将最低位置1之后的值。</li>
<li>将节点个数计数器加1，若加1后节点过多，则仅需将size改为size*2，代表对数组扩容（ReHash）。</li>
</ul>
<h5>查找操作</h5>
<ul>
<li>找出待查找节点在数组中的下标。</li>
<li>判断该下标处是否为null，如果为null则返回查找失败。</li>
<li>从相应位置进入链表，顺次寻找，直至找出待查找节点或超出本组节点范围。</li>
</ul>
<h5>删除操作</h5>
<ul>
<li><span style="line-height: 13px;">找出应删除节点在数组中的下标。</span></li>
<li>判断该下标处是否为null，如果为null则初始化此下标。</li>
<li>找到待删除节点，并从链表中删除。（注意由于哨兵节点的存在，任何正常元素只被其唯一的前驱节点所引用，不存在被前驱节点与数组中指针同时引用的情况，从而不会出现需要同时修改多个指针的情况）</li>
<li>将节点个数计数器减1。</li>
</ul>
<h4>参考文献</h4>
<p><a title="《Split-Ordered Lists: Lock-Free Extensible Hash Tables》" href="http://www.cs.ucf.edu/~dcm/Teaching/COT4810-Spring2011/Literature/SplitOrderedLists.pdf" target="_blank">《Split-Ordered Lists: Lock-Free Extensible Hash Tables》</a></p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9606.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/race_condition-150x150.jpg" alt="疫苗：Java HashMap的死循环" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9606.html" class="wp_rp_title">疫苗：Java HashMap的死循环</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/9169.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/Disruptor-150x150.png" alt="并发框架Disruptor译文" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9169.html" class="wp_rp_title">并发框架Disruptor译文</a></li><li ><a href="https://coolshell.cn/articles/6424.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="Hash Collision DoS 问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6424.html" class="wp_rp_title">Hash Collision DoS 问题</a></li><li ><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/05/etcd-150x150.png" alt="ETCD的内存问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_title">ETCD的内存问题</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9703.html">无锁HashMap的原理与实现</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9703.html/feed</wfw:commentRss>
			<slash:comments>35</slash:comments>
		
		
			</item>
		<item>
		<title>疫苗：Java HashMap的死循环</title>
		<link>https://coolshell.cn/articles/9606.html</link>
					<comments>https://coolshell.cn/articles/9606.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 10 May 2013 00:12:12 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Hash]]></category>
		<category><![CDATA[HashMap]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[并发]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9606</guid>

					<description><![CDATA[<p>在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障，并且这个事发生了很多次，原因是在Java语言在并发情况下使用HashMap造成Race Condi...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9606.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9606.html">疫苗：Java HashMap的死循环</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="size-medium wp-image-9618 alignright" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/race_condition-300x190.jpg" width="300" height="190" srcset="https://coolshell.cn/wp-content/uploads/2013/05/race_condition-300x190.jpg 300w, https://coolshell.cn/wp-content/uploads/2013/05/race_condition-426x270.jpg 426w, https://coolshell.cn/wp-content/uploads/2013/05/race_condition.jpg 549w" sizes="(max-width: 300px) 100vw, 300px" />在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障，并且这个事发生了很多次，原因是在Java语言在并发情况下使用HashMap造成Race Condition，从而导致死循环。这个事情我4、5年前也经历过，本来觉得没什么好写的，因为Java的HashMap是非线程安全的，所以在并发下必然出现问题。但是，我发现近几年，很多人都经历过这个事（在网上查“HashMap Infinite Loop”可以看到很多人都在说这个事）所以，觉得这个是个普遍问题，需要写篇疫苗文章说一下这个事，并且给大家看看一个完美的“Race Condition”是怎么形成的。</p>
<h4>问题的症状</h4>
<p>从前我们的Java代码因为一些原因使用了HashMap这个东西，但是当时的程序是单线程的，一切都没有问题。后来，我们的程序性能有问题，所以需要变成多线程的，于是，变成多线程后到了线上，发现程序经常占了100%的CPU，查看堆栈，你会发现程序都Hang在了HashMap.get()这个方法上了，重启程序后问题消失。但是过段时间又会来。而且，这个问题在测试环境里可能很难重现。</p>
<p>我们简单的看一下我们自己的代码，我们就知道HashMap被多个线程操作。而Java的文档说HashMap是非线程安全的，应该用ConcurrentHashMap。</p>
<p>但是在这里我们可以来研究一下原因。</p>
<p><span id="more-9606"></span></p>
<h4>Hash表数据结构</h4>
<p>我需要简单地说一下HashMap这个经典的数据结构。</p>
<p>HashMap通常会用一个指针数组（假设为table[]）来做分散所有的key，当一个key被加入时，会通过Hash算法通过key算出这个数组的下标i，然后就把这个&lt;key, value&gt;插到table[i]中，如果有两个不同的key被算在了同一个i，那么就叫冲突，又叫碰撞，这样会在table[i]上形成一个链表。</p>
<p>我们知道，如果table[]的尺寸很小，比如只有2个，如果要放进10个keys的话，那么碰撞非常频繁，于是一个O(1)的查找算法，就变成了链表遍历，性能变成了O(n)，这是Hash表的缺陷（可参看《<a title="Hash Collision DoS 问题" href="https://coolshell.cn/articles/6424.html" target="_blank" rel="bookmark">Hash Collision DoS 问题</a>》）。</p>
<p>所以，Hash表的尺寸和容量非常的重要。一般来说，Hash表这个容器当有数据要插入时，都会检查容量有没有超过设定的thredhold，如果超过，需要增大Hash表的尺寸，但是这样一来，整个Hash表里的无素都需要被重算一遍。这叫rehash，这个成本相当的大。</p>
<p>相信大家对这个基础知识已经很熟悉了。</p>
<h4>HashMap的rehash源代码</h4>
<p>下面，我们来看一下Java的HashMap的源代码。</p>
<p>Put一个Key,Value对到Hash表中：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW" data-enlighter-highlight="19">public V put(K key, V value)
{
    ......
    //算Hash值
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    //如果该key已被插入，则替换掉旧的value （链接操作）
    for (Entry&lt;K,V&gt; e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    //该key不存在，需要增加一个结点
    addEntry(hash, key, value, i);
    return null;
}</pre>
<p>检查容量是否超标</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW" data-enlighter-highlight="7">void addEntry(int hash, K key, V value, int bucketIndex)
{
    Entry&lt;K,V&gt; e = table[bucketIndex];
    table[bucketIndex] = new Entry&lt;K,V&gt;(hash, key, value, e);
    //查看当前的size是否超过了我们设定的阈值threshold，如果超过，需要resize
    if (size++ &gt;= threshold)
        resize(2 * table.length);
} </pre>
<p>新建一个更大尺寸的hash表，然后把数据从老的Hash表中迁移到新的Hash表中。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW" data-enlighter-highlight="9">void resize(int newCapacity)
{
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    ......
    //创建一个新的Hash Table
    Entry[] newTable = new Entry[newCapacity];
    //将Old Hash Table上的数据迁移到New Hash Table上
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}</pre>
<p>迁移的源代码，注意高亮处：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW" data-enlighter-highlight="12,14,15,16">void transfer(Entry[] newTable)
{
    Entry[] src = table;
    int newCapacity = newTable.length;
    //下面这段代码的意思是：
    //  从OldTable里摘一个元素出来，然后放到NewTable中
    for (int j = 0; j &lt; src.length; j++) {
        Entry&lt;K,V&gt; e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry&lt;K,V&gt; next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
} </pre>
<p>好了，这个代码算是比较正常的。而且没有什么问题。</p>
<h4>正常的ReHash的过程</h4>
<p>画了个图做了个演示。</p>
<ul>
<li>我假设了我们的hash算法就是简单的用key mod 一下表的大小（也就是数组的长度）。</li>
</ul>
<ul>
<li>最上面的是old hash 表，其中的Hash表的size=2, 所以key = 3, 7, 5，在mod 2以后都冲突在table[1]这里了。</li>
</ul>
<ul>
<li>接下来的三个步骤是Hash表 resize成4，然后所有的&lt;key,value&gt; 重新rehash的过程</li>
</ul>
<p style="text-align: center;"><img decoding="async" class="aligncenter  wp-image-9607" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/HashMap01.jpg" /></p>
<h4>并发下的Rehash</h4>
<p><strong>1）假设我们有两个线程。</strong>我用红色和浅蓝色标注了一下。</p>
<p>我们再回头看一下我们的 transfer代码中的这个细节：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW" data-enlighter-highlight="2">do {
    Entry&lt;K,V&gt; next = e.next; // &lt;--假设线程一执行到这里就被调度挂起了
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);</pre>
<p>而我们的线程二执行完成了。于是我们有下面的这个样子。</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/HashMap02.jpg" width="616" height="434" /></p>
<p>注意，<strong>因为Thread1的 e 指向了key(3)，而next指向了key(7)，其在线程二rehash后，指向了线程二重组后的链表</strong>。我们可以看到链表的顺序被反转后。</p>
<p><strong>2）线程一被调度回来执行。</strong></p>
<ul>
<li><strong>先是执行 newTalbe[i] = e;</strong></li>
<li><strong>然后是e = next，导致了e指向了key(7)，</strong></li>
<li><strong>而下一次循环的next = e.next导致了next指向了key(3)</strong></li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/HashMap03.jpg" width="591" height="376" /></p>
<p><strong>3）一切安好。</strong></p>
<p>线程一接着工作。<strong>把key(7)摘下来，放到newTable[i]的第一个，然后把e和next往下移</strong>。</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/HashMap04.jpg" width="627" height="411" /></p>
<p><strong>4）环形链接出现。</strong></p>
<p><strong>e.next = newTable[i] 导致  key(3).next 指向了 key(7)</strong></p>
<p><strong>注意：此时的key(7).next 已经指向了key(3)， 环形链表就这样出现了。</strong></p>
<p style="text-align: left;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/05/HashMap05.jpg" width="623" height="395" /></p>
<p style="text-align: left;"><strong>于是，当我们的线程一调用到，HashTable.get(11)时，悲剧就出现了——Infinite Loop。</strong></p>
<h4 style="text-align: left;">其它</h4>
<p>有人把这个问题报给了Sun，不过Sun不认为这个是一个问题。因为HashMap本来就不支持并发。要并发就用ConcurrentHashmap</p>
<p><a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457" target="_blank">http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457</a></p>
<p>我在这里把这个事情记录下来，只是为了让大家了解并体会一下并发环境下的危险。</p>
<p>参考：<a href="http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html" rel="nofollow">http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html</a></p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3-150x150.jpg" alt="无锁HashMap的原理与实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_title">无锁HashMap的原理与实现</a></li><li ><a href="https://coolshell.cn/articles/6424.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="Hash Collision DoS 问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6424.html" class="wp_rp_title">Hash Collision DoS 问题</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9606.html">疫苗：Java HashMap的死循环</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9606.html/feed</wfw:commentRss>
			<slash:comments>181</slash:comments>
		
		
			</item>
		<item>
		<title>实例分析Java Class的文件结构</title>
		<link>https://coolshell.cn/articles/9229.html</link>
					<comments>https://coolshell.cn/articles/9229.html#comments</comments>
		
		<dc:creator><![CDATA[tiger.zhou]]></dc:creator>
		<pubDate>Tue, 05 Mar 2013 15:28:51 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9229</guid>

					<description><![CDATA[<p>【感谢网友 @Krq_Tiger 投稿】 今天把之前在Evernote中的笔记重新整理了一下，发上来供对java class 文件结构的有兴趣的同学参考一下。 ...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9229.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9229.html">实例分析Java Class的文件结构</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>【感谢网友 @<a title="Krq_Tiger" href="http://weibo.com/xmuzyq" target="_blank">Krq_Tiger</a> 投稿】</strong></p>
<p>今天把之前在Evernote中的笔记重新整理了一下，发上来供对java class 文件结构的有兴趣的同学参考一下。</p>
<p>学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号，说“一次编写，到处运行”，其实说到无关性，Java平台还有另外一个无关 性那就是语言无关性，要实现语言无关性，那么Java体系中的class的文件结构或者说是字节码就显得相当重要了，其实Java从刚开始的时候就有两套 规范，一个是Java语言规范，另外一个是Java虚拟机规范，Java语言规范只是规定了Java语言相关的约束以及规则，而虚拟机规范则才是真正从跨 平台的角度去设计的。今天我们就以一个实际的例子来看看，到底Java中一个Class文件对应的字节码应该是什么样子。 这篇文章将首先总体上阐述一下Class到底由哪些内容构成，然后再用一个实际的Java类入手去分析class的文件结构。</p>
<p>在继续之前，我们首先需要明确如下几点：</p>
<p style="padding-left: 30px;">1）Class文件是有8个字节为基础的字节流构成的，这些字节流之间都严格按照规定的顺序排列，并且字节之间不存在任何空隙，对于超过8个字节的数据，将按 照Big-Endian的顺序存储的，也就是说高位字节存储在低的地址上面，而低位字节存储到高地址上面，其实这也是class文件要跨平台的关键，因为 PowerPC架构的处理采用Big-Endian的存储顺序，而x86系列的处理器则采用Little-Endian的存储顺序，因此为了Class文 件在各中处理器架构下保持统一的存储顺序，虚拟机规范必须对起进行统一。</p>
<p style="padding-left: 30px;">2） Class文件结构采用类似C语言的结构体来存储数据的，主要有两类数据项，无符号数和表，无符号数用来表述数字，索引引用以及字符串等，比如 u1,u2,u4,u8分别代表1个字节，2个字节，4个字节，8个字节的无符号数，而表是有多个无符号数以及其它的表组成的复合结构。可能大家看到这里 对无符号数和表到底是上面也不是很清楚，不过不要紧，等下面实例的时候，我会再以实例来解释。</p>
<p>明确了上面的两点以后，我们接下来后来看看Class文件中按照严格的顺序排列的字节流都具体包含些什么数据：</p>
<p><span id="more-9229"></span></p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" title="点击查看原始大小图片" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/1.png" width="700" height="399" /></p>
<p style="text-align: center;">（上图来自The Java Virtual Machine Specification Java SE 7 Edition)</p>
<p>在看上图的时候，有一点我们需要注意，比如cp_info，cp_info表示常量池，上图中用 constant_pool[constant_pool_count-1]的方式来表示常量池有constant_pool_count-1个常量，它 这里是采用数组的表现形式，但是大家不要误以为所有的常量池的常量长度都是一样的，其实这个地方只是为了方便描述采用了数组的方式，但是这里并不像编程语 言那里，一个int型的数组，每个int长度都一样。明确了这一点以后，我们在回过头来看看上图中每一项都具体代表了什么含义。</p>
<p>1）u4 magic 表示魔数，并且魔数占用了4个字节，魔数到底是做什么的呢？它其实就是表示一下这个文件的类型是一个Class文件，而不是一张JPG图片，或者AVI的电影。而Class文件对应的魔数是0xCAFEBABE.</p>
<p>2）u2 minor_version 表示Class文件的次版本号，并且此版本号是u2类型的无符号数表示。</p>
<p>3） u2 major_version 表示Class文件的主版本号，并且主版本号是u2类型的无符号数表示。major_version和minor_version主要用来表示当前的虚拟 机是否接受当前这种版本的Class文件。不同版本的Java编译器编译的Class文件对应的版本是不一样的。高版本的虚拟机支持低版本的编译器编译的 Class文件结构。比如Java SE 6.0对应的虚拟机支持Java SE 5.0的编译器编译的Class文件结构，反之则不行。</p>
<p>4） u2 constant_pool_count 表示常量池的数量。这里我们需要重点来说一下常量池是什么东西，请大家不要与Jvm内存模型中的运行时常量池混淆了，Class文件中常量池主要存储了字 面量以及符号引用，其中字面量主要包括字符串，final常量的值或者某个属性的初始值等等，而符号引用主要存储类和接口的全限定名称，字段的名称以及描 述符，方法的名称以及描述符，这里名称可能大家都容易理解，至于描述符的概念，放到下面说字段表以及方法表的时候再说。另外大家都知道Jvm的内存模型中 有堆，栈，方法区，程序计数器构成，而方法区中又存在一块区域叫运行时常量池，运行时常量池中存放的东西其实也就是编译器长生的各种字面量以及符号引用， 只不过运行时常量池具有动态性，它可以在运行的时候向其中增加其它的常量进去，最具代表性的就是String的intern方法。</p>
<p>5）cp_info 表示常量池，这里面就存在了上面说的各种各样的字面量和符号引用。放到常量池的中数据项在The Java Virtual Machine Specification Java SE 7 Edition 中一共有14个常量，每一种常量都是一个表，并且每种常量都用一个公共的部分tag来表示是哪种类型的常量。</p>
<p>下面分别简单描述一下具体细节等到后面的实例 中我们再细化。</p>
<ul>
<li>CONSTANT_Utf8_info      tag标志位为1,   UTF-8编码的字符串</li>
<li>CONSTANT_Integer_info  tag标志位为3， 整形字面量</li>
<li>CONSTANT_Float_info     tag标志位为4， 浮点型字面量</li>
<li>CONSTANT_Long_info     tag标志位为5， 长整形字面量</li>
<li>CONSTANT_Double_info  tag标志位为6， 双精度字面量</li>
<li>CONSTANT_Class_info    tag标志位为7， 类或接口的符号引用</li>
<li>CONSTANT_String_info    tag标志位为8，字符串类型的字面量</li>
<li>CONSTANT_Fieldref_info  tag标志位为9,  字段的符号引用</li>
<li>CONSTANT_Methodref_info  tag标志位为10，类中方法的符号引用</li>
<li>CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用</li>
<li>CONSTANT_NameAndType_info tag 标志位为12，字段和方法的名称以及类型的符号引用</li>
</ul>
<p style="text-align: center;">6） u2 access_flags 表示类或者接口的访问信息，具体如下图所示：<br />
<img decoding="async" loading="lazy" class="aligncenter" title="点击查看原始大小图片" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/2.png" width="700" height="421" /></p>
<p>7）u2 this_class 表示类的常量池索引，指向常量池中CONSTANT_Class_info的常量</p>
<p>8）u2 super_class 表示超类的索引，指向常量池中CONSTANT_Class_info的常量</p>
<p>9）u2 interface_counts 表示接口的数量</p>
<p>10）u2 interface[interface_counts]表示接口表，它里面每一项都指向常量池中CONSTANT_Class_info常量</p>
<p>11）u2 fields_count 表示类的实例变量和类变量的数量</p>
<p>12） field_info fields[fields_count]表示字段表的信息，其中字段表的结构如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/3.png" width="581" height="159" /></p>
<p>上图中access_flags表示字段的访问表示，比如字段是public,private，protect 等，name_index表示字段名 称，指向常量池中类型是CONSTANT_UTF8_info的常量，descriptor_index表示字段的描述符，它也指向常量池中类型为 CONSTANT_UTF8_info的常量，attributes_count表示字段表中的属性表的数量，而属性表是则是一种用与描述字段，方法以及 类的属性的可扩展的结构，不同版本的Java虚拟机所支持的属性表的数量是不同的。</p>
<p>13） u2 methods_count表示方法表的数量</p>
<p>14）method_info 表示方法表，方法表的具体结构如下图所示：</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/4.png" width="550" height="157" /><br />
其中access_flags表示方法的访问表示，name_index表示名称的索引，descriptor_index表示方法的描述 符，attributes_count以及attribute_info类似字段表中的属性表，只不过字段表和方法表中属性表中的属性是不同的，比如方法 表中就Code属性，表示方法的代码，而字段表中就没有Code属性。其中具体Class中到底有多少种属性，等到Class文件结构中的属性表的时候再 说说。</p>
<p>15） attribute_count表示属性表的数量，说到属性表，我们需要明确以下几点：</p>
<ul>
<li>属性表存在于Class文件结构的最后，字段表，方法表以及Code属性中，也就是说属性表中也可以存在属性表</li>
<li>属性表的长度是不固定的，不同的属性，属性表的长度是不同的</li>
</ul>
<p>上面说完了Class文件结构中每一项的构成以后，我们以一个实际的例子来解释以下上面所说的内容。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">package com.ejushang.TestClass;

public class TestClass implements Super{

private static final int staticVar = 0;

private int instanceVar=0;

public int instanceMethod(int param){
 return param+1;
 }

}

interface Super{ }</pre>
<p>通过jdk1.6.0_37的javac 编译后的TestClass.java对应的TestClass.class的二进制结构如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/5.png" width="658" height="400" /></p>
<p>下面我们就根据前面所说的Class的文件结构来解析以下上图中字节流。</p>
<p><strong>1）魔数</strong><br />
从Class的文件结构我们知道，刚开始的4个字节是魔数，上图中从地址00000000h-00000003h的内容就是魔数，从上图可知Class的文件的魔数是0xCAFEBABE。</p>
<p><strong> 2）主次版本号</strong><br />
接下来的4个字节是主次版本号，有上图可知从00000004h-00000005h对应的是0x0000,因此Class的minor_version 为0x0000,从00000006h-00000007h对应的内容为0x0032,因此Class文件的major_version版本为 0x0032,这正好就是jdk1.6.0不带target参数编译后的Class对应的主次版本。</p>
<p><strong> 3）常量池的数量</strong><br />
接下来的2个字节从00000008h-00000009h表示常量池的数量，由上图可以知道其值为0x0018，十进制为24个,但是对于常量池的数量 需要明确一点，常量池的数量是constant_pool_count-1，为什么减一，是因为索引0表示class中的数据项不引用任何常量池中的常 量。</p>
<p><strong> 4）常量池</strong><br />
我们上面说了常量池中有不同类型的常量，下面就来看看TestClass.class的第一个常量，我们知道每个常量都有一个u1类型的tag标识来表示 常量的类型，上图中0000000ah处的内容为0x0A，转换成二级制是10，有上面的关于常量类型的描述可知tag为10的常量是Constant_Methodref_info,而Constant_Methodref_info的结够如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/6.png" width="342" height="120" /></p>
<p>其中class_index指向常量池中类型为CONSTANT_Class_info的常量，从TestClass的二进制文件结构中可以看出 class_index的值为0x0004（地址为0000000bh-0000000ch)，也就是说指向第四个常量。</p>
<p>name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0x0013，表示指向常量池中的第19个常量。</p>
<p>接下来又可以通过同样的方法来找到常量池中的所有常量。不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap -verbose TestClass 即可得到所有常量池中的常量，截图如下：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/7.png" width="689" height="372" /></p>
<p>从上图我们可以清楚的看到，TestClass中常量池有24个常量，不要忘记了第0个常量，因为第0个常量被用来表示 Class中的数据项不引用任何常量池中的常量。从上面的分析中我们得知TestClass的第一个常量表示方法，其中class_index指向的第四 个常量为java/lang/Object，name_and_type_index指向的第19个常量值为&lt;init&gt;:()V,从这里可 以看出第一个表示方法的常量表示的是java编译器生成的实例构造器方法。通过同样的方法可以分析常量池的其它常量。OK，分析完常量池，我们接下来再分 析下access_flags。<br />
<strong>5）u2 access_flags</strong> 表示类或者接口方面的访问信息，比如Class表示的是类还是接口，是否为public,static，final等。具体访问标示的含义之前已经说过 了，下面我们就来看看TestClass的访问标示。Class的访问标示是从0000010dh-0000010e，期值为0x0021，根据前面说的 各种访问标示的标志位，我们可以知道：0x0021=0x0001|0x0020 也即ACC_PUBLIC 和 ACC_SUPER为真，其中ACC_PUBLIC大家好理解，ACC_SUPER是jdk1.2之后编译的类都会带有的标志。</p>
<p><strong>6）u2 this_class</strong> 表示类的索引值，用来表示类的全限定名称，类的索引值如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/8.png" width="671" height="396" /></p>
<p>从上图可以清楚到看到，类索引值为0x0003，对应常量池的第三个常量，通过javap的结果，我们知道第三个常量为 CONSTANT_Class_info类型的常量，通过它可以知道类的全限定名称为：com/ejushang/TestClass /TestClass</p>
<p><strong> 7）u2 super_class</strong> 表示当前类的父类的索引值，索引值所指向的常量池中类型为CONSTANT_Class_info的常量，父类的索引值如下图所示，其值为0x0004, 查看常量池的第四个常量，可知TestClass的父类的全限定名称为：java/lang/Object</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/9.png" width="670" height="395" /></p>
<p><strong>8）interfaces_count和  interfaces[interfaces_count]</strong>表示接口数量以及具体的每一个接口，TestClass的接口数量以及接口如下图所示，其中 0x0001表示接口数量为1，而0x0005表示接口在常量池的索引值，找到常量池的第五个常量，其类型为CONSTANT_Class_info，其 值为：com/ejushang/TestClass/Super</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/10.png" width="674" height="400" /></p>
<p style="text-align: center;"><strong>9）fields_count 和 field_info</strong>, fields_count表示类中field_info表的数量，而field_info表示类的实例变量和类变量，这里需要注意的是 field_info不包含从父类继承过来的字段，field_info的结构如下图所示：<br />
<img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/11.png" width="581" height="159" /></p>
<p style="text-align: center;">其中access_flags表示字段的访问标示，比如public,private,protected，static,final等，access_flags的取值如下图所示：<br />
<img decoding="async" loading="lazy" class="aligncenter" title="点击查看原始大小图片" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/12.png" width="700" height="484" /></p>
<p style="text-align: left;">其中name_index 和 descriptor_index都是常量池的索引值，分别表示字段的名称和字段的描述符，字段的名称容易理解，但是字段的描述符如何理解呢？其实在JVM 规范中，对于字段的描述符规定如下图所示：<br />
<img decoding="async" loading="lazy" class="aligncenter" title="点击查看原始大小图片" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/13.png" width="700" height="408" /><br />
其中大家需要关注一下上图最后一行，它表示的是对一维数组的描述符，对于String[][]的描述符将是[[ Ljava/lang/String,而对于int[][]的描述符为[[I。接下来的attributes_count以及 attribute_info分别表示属性表的数量以及属性表。下面我们还是以上面的TestClass为例，来看看TestClass的字段表吧。</p>
<p>首先我们来看一下字段的数量，TestClass的字段的数量如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/14.png" width="669" height="395" /></p>
<p>从上图中可以看出TestClass有两个字段，查看TestClass的源代码可知，确实也只有两个字段，接下来我们看看第一个字段，我们知道第一个字段应该为private int staticVar,它在Class文件中的二进制表示如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/15.png" width="669" height="402" /><br />
其中0x001A表示访问标示，通过查看access_flags表可知，其为ACC_PRIVATE,ACC_STATIC,ACC_FINAL,接下 来0x0006和0x0007分别表示常量池中第6和第7个常量，通过查看常量池可知，其值分别为：staticVar和I，其中staticVar为字 段名称，而I为字段的描述符，通过上面对描述符的解释，I所描述的是int类型的变量，接下来0x0001表示staticVar这个字段表中的属性表的 数量，从上图可以staticVar字段对应的属性表有1个，0x0008表示常量池中的第8个常量，查看常量池可以得知此属性为 ConstantValue属性，而ConstantValue属性的格式如下图所示：<br />
<img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/16.png" width="351" height="140" /></p>
<p>其中attribute_name_index表述属性名的常量池索引，本例中为ConstantValue，而ConstantValue的 attribute_length固定长度为2，而constantValue_index表示常量池中的引用，本例中，其中为0x0009，查看第9个 常量可以知道，它表示一个类型为CONSTANT_Integer_info的常量，其值为0。</p>
<p>上面说完了private static final int staticVar=0，下面我们接着说一下TestClass的private int instanceVar=0,在本例中对instanceVar的二进制表示如下图所示：</p>
<p style="text-align: left;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/17.png" width="680" height="397" /><br />
其中0x0002表示访问标示为ACC_PRIVATE,0x000A表示字段的名称，它指向常量池中的第10个常量，查看常量池可以知道字段名称为 instanceVar，而0x0007表示字段的描述符，它指向常量池中的第7个常量，查看常量池可以知道第7个常量为I，表示类型为 instanceVar的类型为I，最后0x0000表示属性表的数量为0.</p>
<p><strong> 10）methods_count 和 method_info</strong> ，其中methods_count表示方法的数量，而method_info表示的方法表，其中方法表的结构如下图所示：</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/18.png" width="550" height="157" /></p>
<p style="text-align: left;">从上图可以看出method_info和field_info的结构是很类似的，方法表的access_flag的所有标志位以及取值如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" title="点击查看原始大小图片" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/19.png" width="700" height="484" /></p>
<p>其中name_index和descriptor_index表示的是方法的名称和描述符，他们分别是指向常量池的索引。这里需要结解释一下方法的描述 符，方法的描述符的结构为：（参数列表）返回值，比如public int instanceMethod(int param)的描述符为：（I）I，表示带有一个int类型参数且返回值也为int类型的方法，接下来就是属性数量以及属性表了，方法表和字段表虽然都有 属性数量和属性表，但是他们里面所包含的属性是不同。接下来我们就以TestClass来看一下方法表的二进制表示。首先来看一下方法表数量，截图如下：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/20.png" width="665" height="394" /><br />
从上图可以看出方法表的数量为0x0002表示有两个方法，接下来我们来分析第一个方法，我们首先来看一下TestClass的第一个方法的access_flag，name_index,descriptor_index，截图如下：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/21.png" width="671" height="396" /><br />
从上图可以知道access_flags为0x0001，从上面对access_flags标志位的描述，可知方法的access_flags的取值为 ACC_PUBLIC,name_index为0x000B，查看常量池中的第11个常量，知道方法的名称为&lt;init&gt;，0x000C表示 descriptor_index表示常量池中的第12常量，其值为()V,表示&lt;init&gt;方法没有参数和返回值，其实这是编译器自动生成 的实例构造器方法。接下来的0x0001表示&lt;init&gt;方法的方法表有1个属性，属性截图如下：<br />
<img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/22.png" width="679" height="396" /><br />
从上图可以看出0x000D对应的常量池中的常量为Code,表示的方法的Code属性，所以到这里大家应该明白方法的那些代码是存储在Class文件方法表中的属性表中的Code属性中。接下来我们在分析一下Code属性，Code属性的结构如下图所示：<br />
<img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/23.png" width="607" height="344" /></p>
<p>其中attribute_name_index指向常量池中值为Code的常量，attribute_length的长度表示Code属性表的长度（这里 需要注意的时候长度不包括attribute_name_index和attribute_length的6个字节的长度）。</p>
<p>max_stack表示最大栈深度，虚拟机在运行时根据这个值来分配栈帧中操作数的深度，而max_locals代表了局部变量表的存储空间。</p>
<p>max_locals的单位为slot，slot是虚拟机为局部变量分配内存的最小单元，在运行时，对于不超过32位类型的数据类型，比如 byte,char,int等占用1个slot，而double和Long这种64位的数据类型则需要分配2个slot，另外max_locals的值并 不是所有局部变量所需要的内存数量之和，因为slot是可以重用的，当局部变量超过了它的作用域以后，局部变量所占用的slot就会被重用。</p>
<p>code_length代表了字节码指令的数量，而code表示的时候字节码指令，从上图可以知道code的类型为u1,一个u1类型的取值为0x00-0xFF,对应的十进制为0-255，目前虚拟机规范已经定义了200多条指令。</p>
<p>exception_table_length以及exception_table分别代表方法对应的异常信息。</p>
<p>attributes_count和attribute_info分别表示了Code属性中的属性数量和属性表，从这里可以看出Class的文件结构中，属性表是很灵活的，它可以存在于Class文件，方法表，字段表以及Code属性中。</p>
<p>接下来我们继续以上面的例子来分析一下，从上面init方法的Code属性的截图中可以看出，属性表的长度为0x00000026,max_stack的 值为0x0002,max_locals的取值为0x0001,code_length的长度为0x0000000A，那么00000149h- 00000152h为字节码，接下来exception_table_length的长度为0x0000，而attribute_count的值为 0x0001，00000157h-00000158h的值为0x000E,它表示常量池中属性的名称，查看常量池得知第14个常量的值为 LineNumberTable，LineNumberTable用于描述java源代码的行号和字节码行号的对应关系，它不是运行时必需的属性，如果通 过-g:none的编译器参数来取消生成这项信息的话，最大的影响就是异常发生的时候，堆栈中不能显示出出错的行号，调试的时候也不能按照源代码来设置断 点，接下来我们再看一下LineNumberTable的结构如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/24.png" width="566" height="184" /></p>
<p>其中attribute_name_index上面已经提到过，表示常量池的索引，attribute_length表示属性长度，而start_pc和 line_number分表表示字节码的行号和源代码的行号。本例中LineNumberTable属性的字节流如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/25.png" width="675" height="395" /></p>
<p>上面分析完了TestClass的第一个方法，通过同样的方式我们可以分析出TestClass的第二个方法，截图如下：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/26.png" width="671" height="395" /></p>
<p>其中access_flags为0x0001,name_index为0x000F,descriptor_index为0x0010，通过查看常量池可 以知道此方法为public int instanceMethod(int param)方法。通过和上面类似的方法我们可以知道instanceMethod的Code属性为下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/27.png" width="670" height="397" /></p>
<p>最后我们来分析一下，Class文件的属性，从00000191h-00000199h为Class文件中的属性表，其中0x0011表示属性的名称，查看常量池可以知道属性名称为SourceFile，我们再来看看SourceFile的结构如下图所示：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/28.png" width="338" height="118" /></p>
<p>其中attribute_length为属性的长度，sourcefile_index指向常量池中值为源代码文件名称的常量，在本例中SourceFile属性截图如下：</p>
<p style="text-align: left;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/03/29.png" width="681" height="395" /><br />
其中attribute_length为0x00000002表示长度为2个字节，而soucefile_index的值为0x0012,查看常量池的第18个常量可以知道源代码文件的名称为TestClass.java</p>
<p>最后，希望对技术感兴趣的朋友多交流。个人微博：（<a href="http://weibo.com/xmuzyq" target="_blank">http://weibo.com/xmuzyq</a>)</p>
<div id="xunlei_com_thunder_helper_plugin_d462f475-c18e-46be-bd10-327458d045bd">(全文完)</div>
<p><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-150x150.jpg" alt="Java中的CopyOnWrite容器" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_title">Java中的CopyOnWrite容器</a></li><li ><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3-150x150.jpg" alt="无锁HashMap的原理与实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_title">无锁HashMap的原理与实现</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9229.html">实例分析Java Class的文件结构</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9229.html/feed</wfw:commentRss>
			<slash:comments>48</slash:comments>
		
		
			</item>
		<item>
		<title>并发框架Disruptor译文</title>
		<link>https://coolshell.cn/articles/9169.html</link>
					<comments>https://coolshell.cn/articles/9169.html#comments</comments>
		
		<dc:creator><![CDATA[方 腾飞]]></dc:creator>
		<pubDate>Thu, 28 Feb 2013 12:13:46 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[Disruptor]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[lmax]]></category>
		<category><![CDATA[Performance]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9169</guid>

					<description><![CDATA[<p>（感谢同事方腾飞投递本文） Martin Fowler在自己网站上写了一篇LMAX架构的文章，在文章中他介绍了LMAX是一种新型零售金融交易平台，它能够以很低的...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9169.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9169.html">并发框架Disruptor译文</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（感谢同事<a href="http://ifeve.com" target="_blank">方腾飞</a>投递本文）</strong></p>
<p><img decoding="async" loading="lazy" class="alignright size-medium wp-image-9188" alt="" src="https://coolshell.cn/wp-content/uploads/2013/02/Disruptor-300x144.png" width="300" height="144" />Martin Fowler在自己网站上写了一篇<a href="http://ifeve.com/lmax" target="_blank">LMAX架构</a>的文章，在文章中他介绍了LMAX是一种新型零售金融交易平台，它能够以很低的延迟产生大量交易。这个系统是建立在JVM平台上，其核心是一个业务逻辑处理器，它能够在一个线程里每秒处理6百万订单。业务逻辑处理器完全是运行在内存中，使用事件源驱动方式。业务逻辑处理器的核心是Disruptor。</p>
<p>Disruptor它是一个开源的并发框架，并获得<a href="http://www.java.net/dukeschoice" target="_blank">2011 Duke’s </a>程序框架创新奖，能够在无锁的情况下实现网络的Queue并发操作。本文是<a href="https://code.google.com/p/disruptor/wiki/BlogsAndArticles" target="_blank">Disruptor官网</a>中发布的文章的译文（<a href="http://lmax-exchange.github.com/disruptor/" target="_blank">现在被移到了GitHub</a>）。</p>
<h4><strong><span style="color: #008000">剖析Disruptor:为什么会这么快</span></strong></h4>
<ul>
<li><a href="http://ifeve.com/locks-are-bad/" target="_blank">剖析Disruptor:为什么会这么快？(一)锁的缺点</a></li>
</ul>
<ul>
<li><a title="剖析Disruptor:为什么会这么快？（二）神奇的缓存行填充" href="http://ifeve.com/disruptor-cacheline-padding/" target="_blank">剖析Disruptor:为什么会这么快？(二)神奇的缓存行填充</a></li>
</ul>
<ul>
<li><a title="伪共享(False Sharing)" href="http://ifeve.com/falsesharing/" target="_blank">剖析Disruptor:为什么会这么快？(三)伪共享</a></li>
</ul>
<ul>
<li><a title="剖析Disruptor:为什么会这么快？(四)揭秘内存屏障" href="http://ifeve.com/disruptor-memory-barrier/" target="_blank">剖析Disruptor:为什么会这么快？(四)揭秘内存屏障</a></li>
</ul>
<h4><span style="color: #008000">Disruptor如何工作和使用</span></h4>
<ul>
<li><a title="剖析Disruptor:为什么会这么快？（一）Ringbuffer的特别之处" href="http://ifeve.com/dissecting-disruptor-whats-so-special/" target="_blank">如何使用Disruptor（一）Ringbuffer的特别之处</a></li>
</ul>
<ul>
<li><a title="如何使用Disruptor（二）如何从Ringbuffer读取" href="http://ifeve.com/dissecting_the_disruptor_how_doi_read_from_the_ring_buffer/" target="_blank">如何使用Disruptor（二）如何从Ringbuffer读取</a></li>
</ul>
<ul>
<li><a title="如何使用 Disruptor（三）写入 Ringbuffer" href="http://ifeve.com/disruptor-writing-ringbuffer/" target="_blank">如何使用Disruptor（三）写入Ringbuffer</a></li>
</ul>
<p><span id="more-9169"></span></p>
<ul>
<li><a title="Disruptor(无锁并发框架)-发布" href="http://ifeve.com/the-disruptor-lock-free-publishing/" target="_blank">Disruptor(无锁并发框架)-发布</a></li>
</ul>
<ul>
<li><a title="LMAX Disruptor——一个高性能、低延迟且简单的框架" href="http://ifeve.com/disruptor-dsl/" target="_blank" rel="nofollow">LMAX Disruptor——一个高性能、低延迟且简单的框架</a></li>
</ul>
<ul>
<li><a title="Disruptor Wizard已死，Disruptor Wizard永存！" href="http://ifeve.com/disruptor-wizard/" target="_blank" rel="nofollow">Disruptor Wizard已死，Disruptor Wizard永存！</a></li>
</ul>
<ul>
<li><a title="Disruptor 2.0更新摘要" href="http://ifeve.com/disruptor-2-change/" target="_blank">Disruptor 2.0更新摘要</a></li>
</ul>
<ul>
<li><a title="线程间共享数据无需竞争" href="http://ifeve.com/sharing-data-among-threads-without-contention/" target="_blank">线程间共享数据不需要竞争</a></li>
</ul>
<h4><span style="color: #008000">Disruptor的应用</span></h4>
<ul>
<li><a title="LMAX架构" href="http://ifeve.com/lmax/" target="_blank">LMAX的架构</a></li>
</ul>
<ul>
<li><a title="通过Axon和Disruptor处理1M tps" href="http://ifeve.com/axon/" target="_blank">通过Axon和Disruptor处理1M tps</a></li>
</ul>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3-150x150.jpg" alt="无锁HashMap的原理与实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_title">无锁HashMap的原理与实现</a></li><li ><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/05/etcd-150x150.png" alt="ETCD的内存问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_title">ETCD的内存问题</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17381.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/PerfTest-150x150.png" alt="性能测试应该怎么做？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17381.html" class="wp_rp_title">性能测试应该怎么做？</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9169.html">并发框架Disruptor译文</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9169.html/feed</wfw:commentRss>
			<slash:comments>38</slash:comments>
		
		
			</item>
		<item>
		<title>如此理解面向对象编程</title>
		<link>https://coolshell.cn/articles/8745.html</link>
					<comments>https://coolshell.cn/articles/8745.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 13 Dec 2012 00:19:28 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[轶事趣闻]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[OOP]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=8745</guid>

					<description><![CDATA[<p>从Rob Pike 的 Google+上的一个推看到了一篇叫《Understanding Object Oriented Programming》的文章，我先把...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/8745.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/8745.html">如此理解面向对象编程</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>从Rob Pike 的 Google+上的一个推看到了一篇叫《<a href="http://www.csis.pace.edu/~bergin/patterns/ppoop.html" target="_blank">Understanding Object Oriented Programming</a>》的文章，我先把这篇文章简述一下，然后再说说老牌黑客Rob Pike的评论。</p>
<p>先看这篇教程是怎么来讲述OOP的。它先给了下面这个问题，这个问题需要输出一段关于操作系统的文字：假设Unix很不错，Windows很差。</p>
<p>这个把下面这段代码描述成是<strong>Hacker Solution</strong>。（这帮人觉得下面这叫黑客？我估计这帮人真是没看过C语言的代码）</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">public class PrintOS
{
	public static void main(final String[] args)
	{
		String osName = System.getProperty(&quot;os.name&quot;) ;
		if (osName.equals(&quot;SunOS&quot;) || osName.equals(&quot;Linux&quot;))
		{
			System.out.println(&quot;This is a UNIX box and therefore good.&quot;) ;
		}
		else if (osName.equals(&quot;Windows NT&quot;) || osName.equals(&quot;Windows 95&quot;))
		{
			System.out.println(&quot;This is a Windows box and therefore bad.&quot;) ;
		}
		else
		{
			System.out.println(&quot;This is not a box.&quot;) ;
		}
	}
}</pre>
<p>然后开始用面向对象的编程方式一步一步地进化这个代码。</p>
<p>先是以过程化的思路来重构之。</p>
<p><span id="more-8745"></span></p>
<h4>过程化的方案</h4>
<pre data-enlighter-language="java" class="EnlighterJSRAW">public class PrintOS
{
	private static String unixBox()
	{
		return &quot;This is a UNIX box and therefore good.&quot; ;
	}
	private static String windowsBox()
  	{
		return &quot;This is a Windows box and therefore bad.&quot; ;
	}
	private static String defaultBox()
	{
		return &quot;This is not a box.&quot; ;
	}
	private static String getTheString(final String osName)
	{
		if (osName.equals(&quot;SunOS&quot;) || osName.equals(&quot;Linux&quot;))
		{
			return unixBox() ;
		}
		else if (osName.equals(&quot;Windows NT&quot;) ||osName.equals(&quot;Windows 95&quot;))
		{
			return windowsBox() ;
		}
		else
		{
			return defaultBox() ;
		}
  	}
	public static void main(final String[] args)
	{
		System.out.println(getTheString(System.getProperty(&quot;os.name&quot;))) ;
	}
}</pre>
<p>然后是一个幼稚的面向对象的思路。</p>
<h4>幼稚的面向对象编程</h4>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class PrintOS
{
	public static void main(final String[] args)
  	{
		System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ;
 	}
}</pre>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class OSDiscriminator // Factory Pattern
{
	private static BoxSpecifier theBoxSpecifier = null ;
  	public static BoxSpecifier getBoxSpecifier()
	{
		if (theBoxSpecifier == null)
		{
			String osName = System.getProperty(&quot;os.name&quot;) ;
 			if (osName.equals(&quot;SunOS&quot;) || osName.equals(&quot;Linux&quot;))
 			{
				theBoxSpecifier = new UNIXBox() ;
			}
			else if (osName.equals(&quot;Windows NT&quot;) || osName.equals(&quot;Windows 95&quot;))
			{
				theBoxSpecifier = new WindowsBox() ;
			}
			else
			{
				theBoxSpecifier = new DefaultBox () ;
			}
		}
		return theBoxSpecifier ;
	}
}</pre>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public interface BoxSpecifier
{
	String getStatement() ;
}</pre>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class DefaultBox implements BoxSpecifier
{
	public String getStatement()
	{
		return &quot;This is not a box.&quot; ;
  	}
}</pre>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class UNIXBox implements BoxSpecifier
{
	public String getStatement()
	{
		return &quot;This is a UNIX box and therefore good.&quot; ;
  	}
}</pre>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class WindowsBox implements BoxSpecifier
{
  	public String getStatement()
	{
		return &quot;This is a Windows box and therefore bad.&quot; ;
	}
}</pre>
<p>他们觉得上面这段代码没有消除if语句，他们说这叫代码的“logic bottleneck”（逻辑瓶颈），因为如果你要增加一个操作系统的判断的话，你不但要加个类，还要改那段if-else的语句。</p>
<p>所以，他们整出一个叫Sophisticated的面向对象的解决方案。</p>
<h4>OO大师的方案</h4>
<p>注意其中的Design Pattern</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class PrintOS
{
  	public static void main(final String[] args)
  	{
		System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ;
  	}
}</pre>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class OSDiscriminator // Factory Pattern
{
  	private static java.util.HashMap storage = new java.util.HashMap() ;

 	public static BoxSpecifier getBoxSpecifier()
	{
		BoxSpecifier value = (BoxSpecifier)storage.get(System.getProperty(&quot;os.name&quot;)) ;
		if (value == null)
			return DefaultBox.value ;
		return value ;
 	}
  	public static void register(final String key, final BoxSpecifier value)
  	{
		storage.put(key, value) ; // Should guard against null keys, actually.
  	}
  	static
  	{
		WindowsBox.register() ;
  		UNIXBox.register() ;
  		MacBox.register() ;
  	}
}</pre>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public interface BoxSpecifier
{
  	String getStatement() ;
}</pre>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class DefaultBox implements BoxSpecifier // Singleton Pattern
{
	public static final DefaultBox value = new DefaultBox () ;
	private DefaultBox() { }
	public String getStatement()
	{
		return &quot;This is not a box.&quot; ;
	}
}</pre>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class UNIXBox implements BoxSpecifier // Singleton Pattern
{
 	public static final UNIXBox value = new UNIXBox() ;
	private UNIXBox() { }
	public  String getStatement()
   	{
		return &quot;This is a UNIX box and therefore good.&quot; ;
 	}
  	public static final void register()
  	{
		OSDiscriminator.register(&quot;SunOS&quot;, value) ;
  		OSDiscriminator.register(&quot;Linux&quot;, value) ;
 	}
}</pre>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class WindowsBox implements BoxSpecifier  // Singleton Pattern
{
	public  static final WindowsBox value = new WindowsBox() ;
	private WindowsBox() { }
	public String getStatement()
	{
		return &quot;This is a Windows box and therefore bad.&quot; ;
  	}
  	public static final void register()
  	{
		OSDiscriminator.register(&quot;Windows NT&quot;, value) ;
  		OSDiscriminator.register(&quot;Windows 95&quot;, value) ;
	}
}</pre>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class MacBox implements BoxSpecifier // Singleton Pattern
{
 	public static final MacBox value = new MacBox() ;
	private MacBox() { }
	public  String getStatement()
   	{
		return &quot;This is a Macintosh box and therefore far superior.&quot; ;
 	}
  	public static final void register()
  	{
		OSDiscriminator.register(&quot;Mac OS&quot;, value) ;
 	}
}</pre>
<p>作者还非常的意地说，他加了一个“Mac OS”的东西。<strong>老实说，当我看到最后这段OO大师搞出来的代码，我快要吐了</strong>。我瞬间想到了两件事：一个是以前酷壳上的《<a style="line-height: 13px;" title="面向对象是个骗局？！" href="https://coolshell.cn/articles/3036.html" target="_blank">面向对象是个骗局</a>》和 《<a style="line-height: 13px;" title="各种流行的编程风格" href="https://coolshell.cn/articles/2058.html" target="_blank">各种流行的编程方式</a>》中说的“设计模式驱动编程”，另一个我想到了那些被敏捷洗过脑的程序员和咨询师，也是这种德行。</p>
<p>于是我去看了一下第一作者<a href="http://csis.pace.edu/~bergin/" target="_blank">Joseph Bergin的主页</a>，这个Ph.D是果然刚刚完成了一本关于敏捷和模式的书。</p>
<h4>Rob Pike的评论</h4>
<p>（Rob Pike是当年在Bell lab里和Ken一起搞Unix的主儿，后来和Ken开发了UTF-8，现在还和Ken一起搞Go语言。注：不要以为Ken和Dennis是基友，其实他们才是真正的老基友！）</p>
<p>Rob Pike在他的<a href="https://plus.google.com/101960720994009339267/posts/hoJdanihKwb" target="_blank">Google+的这贴</a>里评论到这篇文章——</p>
<p>他并不确认这篇文章是不是搞笑？但是他觉得这些个写这篇文章是很认真的。他说他要评论这篇文章是因为他们是一名Hacker，至少这个词出现在这篇文章的术语中。</p>
<p>他说，这个程序根本就不需要什么Object，只需要一张小小的配置表格，里面配置了对应的操作系统和你想输出的文本。这不就完了。这么简单的设计，非常容易地扩展，他们那个所谓的Hack Solution完全就是笨拙的代码。后面那些所谓的代码进化相当疯狂和愚蠢的，这个完全误导了对编程的认知。</p>
<p>然后，他还说，<strong>他觉得这些OO的狂热份子非常害怕数据，他们喜欢用多层的类的关系来完成一个本来只需要检索三行数据表的工作</strong>。他说他曾经听说有人在他的工作种用各种OO的东西来替换While循环。（我听说中国Thoughtworks那帮搞敏捷的人的确喜欢用Object来替换所有的if-else语句，他们甚至还喜欢把函数的行数限制在10行以内）</p>
<p>他还给了一个链接<a href="http://prog21.dadgum.com/156.html">http://prog21.dadgum.com/156.html</a>，你可以读一读。最后他说，<strong>OOP的本质就是——对数据和与之关联的行为进行编程</strong>。便就算是这样也不完全对，因为：</p>
<p style="text-align: center;"><strong>Sometimes data is just data and functions are just functions.</strong></p>
<h4>我的理解</h4>
<p>我觉得，这篇文章的例子举得太差了，差得感觉就像是OO的高级黑。面向对象编程注重的是：<strong>1）数据和其行为的打包封装，2）程序的接口和实现的解耦</strong>。你那怕，举一个多个开关和多个电器的例子，不然就像STL中，一个排序算法对多个不同容器的例子，都比这个例子要好得多得多。老实说，Java SDK里太多这样的东西了。</p>
<p>我以前给一些公司讲一些设计模式的培训课，我一再提到，<strong>那23个经典的设计模式和OO半毛钱关系没有</strong>，只不过人家用OO来实现罢了。<strong>设计模式就三个准则：1）中意于组合而不是继承，2）依赖于接口而不是实现，3）高内聚，低耦合。你看，这完全就是Unix的设计准则</strong>。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="一些软件设计的原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_title">一些软件设计的原则</a></li><li ><a href="https://coolshell.cn/articles/3036.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/19.jpg" alt="面向对象是个骗局？！" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3036.html" class="wp_rp_title">面向对象是个骗局？！</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/8990.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/linus_pointer_to_pointer-150x150.jpg" alt="Linus：利用二级指针删除单向链表" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8990.html" class="wp_rp_title">Linus：利用二级指针删除单向链表</a></li><li ><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/kiss-150x150.png" alt="从面向对象的设计模式看软件设计" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_title">从面向对象的设计模式看软件设计</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/8745.html">如此理解面向对象编程</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/8745.html/feed</wfw:commentRss>
			<slash:comments>185</slash:comments>
		
		
			</item>
		<item>
		<title>Resin服务器getResource揭秘</title>
		<link>https://coolshell.cn/articles/6335.html</link>
					<comments>https://coolshell.cn/articles/6335.html#comments</comments>
		
		<dc:creator><![CDATA[liuxiaori]]></dc:creator>
		<pubDate>Thu, 05 Jan 2012 00:28:59 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[ClassLoader]]></category>
		<category><![CDATA[getResource]]></category>
		<category><![CDATA[getResourceAsStream]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Resin]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6335</guid>

					<description><![CDATA[<p>（感谢网友 liuxiaori 继续分享其经历）这样的详细的图文并茂的文章让我很佩服！ 前言 接上文“由一个问题到Resin ClassLoader的学习”，本...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6335.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6335.html">Resin服务器getResource揭秘</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（<span style="color: #cc0000;">感谢网友 liuxiaori 继续分享其经历</span>）这样的详细的图文并茂的文章让我很佩服！</strong></p>
<h4>前言</h4>
<p>接上文“<a title="由一个问题到 Resin ClassLoader 的学习" href="https://coolshell.cn/articles/6112.html" target="_blank">由一个问题到Resin ClassLoader的学习</a>”，本文将以this.getClass().getResource(&#8220;/&#8221;).getPath()和this.getClass().getResourceAsStream(&#8220;/a.txt&#8221;)为例，一步步解析加载的过程。</p>
<h4>调试环境</h4>
<ol>
<li>下载resin3.0.23的源码(<a href="http://www.caucho.com/download/resin-3.0.23-src.zip">http://www.caucho.com/download/resin-3.0.23-src.zip</a>)。</li>
<li>部署到myeclipse中，有错误，本人忽略了。Resin可运行。</li>
<li>将EhCacheTestAnnotation部署到resin3.0.23中。</li>
<li>调试this.getClass().getResource(&#8220;/&#8221;).getPath()。</li>
</ol>
<p>问题来了，无论如何也模拟不出来&lt;compiling-loader&gt;所造成的影响，一直输出：/D:/work_other/project/resin-3.0.23/bin/ 。无奈之下，采用了这种方式：使用两个eclipse，一个使用发布版本的，部署EhCacheTestAnnotation进行调试；另外一个部署resin3.0.23源码，调试到哪里对照看源码。</p>
<h4>开始</h4>
<h5>1) this.getClass().getResource(&#8220;/&#8221;).getPath()</h5>
<p>本次调试涉及的所有类加载器为：</p>
<blockquote><p>EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]</p>
<p>EnvironmentClassLoader$7806641[host:http://localhost:8787]</p>
<p>EnvironmentClassLoader$22459270[servlet-server:]</p>
<p>sun.misc.Launcher$AppClassLoader@7259da</p>
<p>sun.misc.Launcher$ExtClassLoader@16930e2</p></blockquote>
<p>首先进入Class的getResource(String name)方法，如下图：</p>
<p><span id="more-6335"></span></p>
<figure id="attachment_6390" aria-describedby="caption-attachment-6390" style="width: 553px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6390" title="图片1" src="https://coolshell.cn/wp-content/uploads/2012/01/图片1.png" alt="图片1" width="553" height="182" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片1.png 553w, https://coolshell.cn/wp-content/uploads/2012/01/图片1-300x99.png 300w" sizes="(max-width: 553px) 100vw, 553px" /><figcaption id="caption-attachment-6390" class="wp-caption-text">图1</figcaption></figure>
<p>最后委托给ClassLoader的getResource方法。那么这个ClassLoader是哪个呢？一看下图便知：</p>
<figure id="attachment_6391" aria-describedby="caption-attachment-6391" style="width: 553px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6391" title="图片2" src="https://coolshell.cn/wp-content/uploads/2012/01/图片2.png" alt="图片2" width="553" height="85" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片2.png 553w, https://coolshell.cn/wp-content/uploads/2012/01/图片2-300x46.png 300w" sizes="(max-width: 553px) 100vw, 553px" /><figcaption id="caption-attachment-6391" class="wp-caption-text">图2</figcaption></figure>
<p>是DynamicClassLoader的getResource方法，原理上文已述。</p>
<p>最终会委托给sun.misc.Launcher$ExtClassLoader@16930e2类加载器的getResource方法，返回null，然后开始回溯。</p>
<p>还记得吗？当java.net.URLClassLoader分支的ClassLoader的getResource方法返回值为null后，就要遍历嵌入DynamicClassLoader中的Resin的Loader(即_loaders集合)。</p>
<p>当然回溯到EnvironmentClassLoader$22459270[servlet-server:]中，那么它中_loaders这个集合中的Loader又有哪些呢？</p>
<p>以图为证，当天确实回溯到该ClassLoader，而且开始准备遍历_loaders集合。</p>
<figure id="attachment_6392" aria-describedby="caption-attachment-6392" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6392" title="图3" src="https://coolshell.cn/wp-content/uploads/2012/01/图片3.png" alt="图3" width="554" height="74" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片3.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片3-300x40.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6392" class="wp-caption-text">图3</figcaption></figure>
<p>DynamicClassLoader的1306行，没问题，resin3.0.23源码截图为证：</p>
<figure id="attachment_6393" aria-describedby="caption-attachment-6393" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6393" title="图4" src="https://coolshell.cn/wp-content/uploads/2012/01/图片4.png" alt="图4" width="554" height="236" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片4.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片4-300x127.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6393" class="wp-caption-text">图4</figcaption></figure>
<p>不做多余解释，那么“servlet-server”这个ClassLoader中的_loaders集合中都放了一些什么呢？</p>
<figure id="attachment_6394" aria-describedby="caption-attachment-6394" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6394" title="图5" src="https://coolshell.cn/wp-content/uploads/2012/01/图片5.png" alt="图5" width="554" height="121" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片5.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片5-300x65.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6394" class="wp-caption-text">图5</figcaption></figure>
<p>存放了两个TreeLoader(Loader的子类)，然未找到结果，返回null。继续回溯。</p>
<p>这次轮到遍历EnvironmentClassLoader$7806641[host:http://localhost:8787]的_loaders。下图为证：</p>
<figure id="attachment_6395" aria-describedby="caption-attachment-6395" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6395" title="图6" src="https://coolshell.cn/wp-content/uploads/2012/01/图片6.png" alt="图6" width="554" height="170" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片6.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片6-300x92.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6395" class="wp-caption-text">图6</figcaption></figure>
<p>_loaders中的内容如下图：</p>
<figure id="attachment_6396" aria-describedby="caption-attachment-6396" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6396" title="图7" src="https://coolshell.cn/wp-content/uploads/2012/01/图片7.png" alt="图7" width="554" height="193" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片7.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片7-300x104.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6396" class="wp-caption-text">图7</figcaption></figure>
<p>比较长，我贴出来：</p>
<blockquote><p>[CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes], LibraryLoader[com.caucho.config.types.FileSetType@fb6763], CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes], LibraryLoader[com.caucho.config.types.FileSetType@140b8fd], CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes], LibraryLoader[com.caucho.config.types.FileSetType@30fc1f]]</p></blockquote>
<p>注意到了吧，主角来了。那仔细调试下把。爆料一下：CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes]就是主角。</p>
<figure id="attachment_6397" aria-describedby="caption-attachment-6397" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6397" title="图8" src="https://coolshell.cn/wp-content/uploads/2012/01/图片8.png" alt="图8" width="554" height="251" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片8.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片8-300x135.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6397" class="wp-caption-text">图8</figcaption></figure>
<p>看到了吧，遍历时，当前的Loader为CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes]，而且url可是不为null了哦。再贴一张，看看url的值到底是什么！</p>
<figure id="attachment_6400" aria-describedby="caption-attachment-6400" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6400" title="图9" src="https://coolshell.cn/wp-content/uploads/2012/01/图片9.png" alt="图9" width="554" height="250" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片9.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片9-300x135.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6400" class="wp-caption-text">图9</figcaption></figure>
<p>嗯，不用多做解释了吧。</p>
<p>最后看看程序输出是否吻合，如下图：</p>
<figure id="attachment_6401" aria-describedby="caption-attachment-6401" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6401" title="图10" src="https://coolshell.cn/wp-content/uploads/2012/01/图片10.png" alt="图10" width="554" height="81" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片10.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片10-300x43.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6401" class="wp-caption-text">图10</figcaption></figure>
<p>然后修改resin.conf中的&lt;compiling-loader&gt;将其注释掉，看看程序结果会不会是我们期望的：/D:/work/resin-3.0.23/webapps/EhCacheTestAnnotation/WEB-INF/classes/。拭目以待。</p>
<figure id="attachment_6402" aria-describedby="caption-attachment-6402" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6402" title="图11" src="https://coolshell.cn/wp-content/uploads/2012/01/图片11.png" alt="图11" width="554" height="106" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片11.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片11-300x57.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6402" class="wp-caption-text">图11</figcaption></figure>
<p>为节省篇幅，一下只关注关键位置。</p>
<p>首先调试到EnvironmentClassLoader$7806641[host:http://localhost:8787]，我们需要停下来一下。</p>
<figure id="attachment_6403" aria-describedby="caption-attachment-6403" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6403" title="图12" src="https://coolshell.cn/wp-content/uploads/2012/01/图片12.png" alt="图12" width="554" height="154" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片12.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片12-300x83.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6403" class="wp-caption-text">图12</figcaption></figure>
<p>再看一下_loaders的值。</p>
<figure id="attachment_6404" aria-describedby="caption-attachment-6404" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6404" title="图13" src="https://coolshell.cn/wp-content/uploads/2012/01/图片13.png" alt="图13" width="554" height="157" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片13.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片13-300x85.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6404" class="wp-caption-text">图13</figcaption></figure>
<p>贴一个详细的：</p>
<blockquote><p>[LibraryLoader[com.caucho.config.types.FileSetType@1299f7e], LibraryLoader[com.caucho.config.types.FileSetType@1a631cc], LibraryLoader[com.caucho.config.types.FileSetType@f6398]]</p></blockquote>
<p>对比一下，在注释掉&lt;compiling-loader&gt;后，loaders中是没有CompilingClassLoader实例的。</p>
<p>继续，下面就轮到EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]这个ClassLoader了，会是什么样子呢？</p>
<figure id="attachment_6405" aria-describedby="caption-attachment-6405" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6405" title="图14" src="https://coolshell.cn/wp-content/uploads/2012/01/图片14.png" alt="图14" width="554" height="154" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片14.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片14-300x83.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6405" class="wp-caption-text">图14</figcaption></figure>
<p>进入该ClassLoader时，url值依旧为null，那_loaders会有变化吗？如下图：</p>
<figure id="attachment_6406" aria-describedby="caption-attachment-6406" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6406" title="图15" src="https://coolshell.cn/wp-content/uploads/2012/01/图片15.png" alt="图15" width="554" height="149" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片15.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片15-300x80.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6406" class="wp-caption-text">图15</figcaption></figure>
<p>继续遍历_loaders。</p>
<figure id="attachment_6407" aria-describedby="caption-attachment-6407" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6407" title="图16" src="https://coolshell.cn/wp-content/uploads/2012/01/图片16.png" alt="图16" width="554" height="211" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片16.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片16-300x114.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6407" class="wp-caption-text">图16</figcaption></figure>
<p>到这里就结束了，url在EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]中被加载。</p>
<h5>1) this.getClass().getResourceAsStream(&#8220;/a.txt&#8221;)</h5>
<p>getResourceAsStream(String name)方法也是采用双亲委派的方式。在前一篇文章中提出“getResourceAsStream可是将获取路径委托给getResource，&lt;compiling-loader&gt;却没有对getResourceAsStream产生影响”</p>
<p>ClassLoader中getResourceAsStream源码也确实是委托为getResource了，可是为什么呢？</p>
<p>getResourceAsStream(String name)方法。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public InputStream getResourceAsStream(String name) {
    URL url = getResource(name);
    try {
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}
</pre>
<p>其实不难解释，JVM中ClassLoader的getResourceAsStream(&#8220;/a.txt&#8221;)返回了null，然后开始回溯，与getResource方法的原理一致，直到某个ClassLoader及其子类或者Loader及其子类找到了&#8221;/a.txt&#8221;，并以流的形式返回，当然谁都没找到就返回null。</p>
<p>捡重点的说。</p>
<p>调试到sun.misc.Launcher$AppClassLoader@18d107f，即ClassLoader的子类，情形如下图：</p>
<figure id="attachment_6408" aria-describedby="caption-attachment-6408" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6408" title="图17" src="https://coolshell.cn/wp-content/uploads/2012/01/图片17.png" alt="图17" width="554" height="249" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片17.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片17-300x134.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6408" class="wp-caption-text">图17</figcaption></figure>
<p>看见getResource(name)喽，按F5进去看个究竟。如下图，其parent为：sun.misc.Launcher$ExtClassLoader@360be0，其返回null。</p>
<figure id="attachment_6398" aria-describedby="caption-attachment-6398" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6398" title="图18" src="https://coolshell.cn/wp-content/uploads/2012/01/图片18.png" alt="图18" width="554" height="278" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片18.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片18-300x150.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6398" class="wp-caption-text">图18</figcaption></figure>
<p>开始回溯到：EnvironmentClassLoader$1497769[servlet-server:]，与getResource方法一致，开始遍历_loaders集合。</p>
<p>这样就可以解释为何&lt;compiling-loader&gt;没有影响到getResourceAsStream了。因为资源(这里是/a.txt)，就不是由AppClassLoader和ExtClassLoader加载的，而是由DynamicClassLoader或者其内部的_loaders集合完成的加载。或者更确切的说是由CompilingClassLoader获取到的URL，再转换成InputStream。</p>
<p><span style="color: #ff0000;"><strong>&lt;comiling-loader&gt;其实对getResourceAsStream还是有点影响的，如果配置中配置了&lt;comiling-loader&gt;，并且&lt;comiling-loader&gt;配置的路径下，与实际项目的指定路径下，都放置了同名资源，则会先加载&lt;comiling-loader&gt;配置路径下的资源。</strong></span></p>
<p>比如，下图所示：</p>
<figure id="attachment_6399" aria-describedby="caption-attachment-6399" style="width: 554px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-6399" title="图19" src="https://coolshell.cn/wp-content/uploads/2012/01/图片19.png" alt="图19" width="554" height="266" srcset="https://coolshell.cn/wp-content/uploads/2012/01/图片19.png 554w, https://coolshell.cn/wp-content/uploads/2012/01/图片19-300x144.png 300w" sizes="(max-width: 554px) 100vw, 554px" /><figcaption id="caption-attachment-6399" class="wp-caption-text">图19</figcaption></figure>
<p>&lt;compiling-loader&gt;配置的路径为：&lt;compiling-loader path=&#8221;webapps/WEB-INF/classes&#8221;/&gt;</p>
<p>在加载&#8221;/a.txt&#8221;时，优先加载webapps/WEB-INF/classes/a.txt。</p>
<h4>总结</h4>
<ol>
<li>&lt;compiling-loader&gt;如被注释掉，则只会在EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]中的_loaders中被初始化，否则会在EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]和EnvironmentClassLoader$7806641[host:http://localhost:8787两个类加载器各自的_loaders集合中被初始化。(通过调试this.getClass().getResource(&#8220;/test&#8221;).getPath()验证)</li>
<li>&lt;compiling-loader&gt;未注释掉，&#8221;/&#8221;(根路径)由EnvironmentClassLoader$7806641[host:http://localhost:8787]加载，注释掉后由EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]加载。</li>
<li>EnvironmentClassLoader$7806641[host:http://localhost:8787]为Resin server的类加载器实例，EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]为Web应用程序的类加载器实例。他们都属于java.net.URLClassLoader的实例。</li>
<li>&lt;compiling-loader&gt;某种程度上对getResourceAsStream方法有影响。</li>
</ol>
<p>现在&lt;compiling-loader&gt;如何影响getResource(&#8220;/&#8221;)，以及getResourceAsStream“不”被影响全部真相大白。</p>
<p><span style="color: #ff0000;">注：&lt;compiling-loader&gt;只对获取根路径产生影响，也就是参数为&#8221;/&#8221;。比如加载&#8221;/test/Path.class&#8221;不会产生影响。</span></p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/6112.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/12/resin01-150x150.png" alt="由一个问题到 Resin ClassLoader 的学习" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6112.html" class="wp_rp_title">由一个问题到 Resin ClassLoader 的学习</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-150x150.jpg" alt="Java中的CopyOnWrite容器" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_title">Java中的CopyOnWrite容器</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6335.html">Resin服务器getResource揭秘</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6335.html/feed</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title>由一个问题到 Resin ClassLoader 的学习</title>
		<link>https://coolshell.cn/articles/6112.html</link>
					<comments>https://coolshell.cn/articles/6112.html#comments</comments>
		
		<dc:creator><![CDATA[liuxiaori]]></dc:creator>
		<pubDate>Wed, 28 Dec 2011 04:22:55 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[ClassLoader]]></category>
		<category><![CDATA[getResource]]></category>
		<category><![CDATA[getResourceAsStream]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Resin]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6112</guid>

					<description><![CDATA[<p>（感谢网友 liuxiaori 分享其经历） 背景 某日临近下班，一个同事欲取任何类中获取项目绝对路径，不通过Request方式获取，可是始终获取不到预想的路径...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6112.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6112.html">由一个问题到 Resin ClassLoader 的学习</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（<span style="color: #cc0000;">感谢网友 liuxiaori 分享其经历</span>）<br />
</strong></p>
<h4>背景</h4>
<p>某日临近下班，一个同事欲取任何类中获取项目绝对路径，不通过Request方式获取，可是始终获取不到预想的路径。于是晚上回家google了一下，误以为是System.getProperty(&#8220;java.class.path&#8221;)-未实际进行测试，早上来和同事沟通，提出了使用这个内置方法，结果人家早已验证过，该方法是打印出CLASSPATH环境变量的值。</p>
<p>于是乎，继续google，找到了Class的getResource与getResourceAsStream两个方法。这两个方法会委托给ClassLoader对应的同名方法。以为这样就可以搞定(实际上确实可以搞定)，但验证过程中却发生了奇怪的事情。</p>
<p>软件环境：Windows XP、Resin 3、Tomcat6.0、Myeclipse、JDK1.5</p>
<h4>发展</h4>
<p>我的验证思路是这样的：</p>
<ol>
<li>定义一个Servlet，然后在该Servlet中调用Path类的getPath方法，getPath方法返回工程classpath的绝对路径，显示在jsp中。</li>
<li>另外在Path类中，通过Class的getResourceAsStream读取当前工程classpath路径中的a.txt文件，写入到getResource路径下的b.txt。</li>
</ol>
<p>由于时间匆忙，代码没有好好去组织。大致能看出上述两个功能，很简单不做解释。</p>
<p><span id="more-6112"></span></p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class Path {
    public String getPath() throws IOException
    {
        InputStream is = this.getClass().getResourceAsStream(&quot;/a.txt&quot;);
        File file = new File(Path.class.getResource(&quot;/&quot;).getPath()+&quot;/b.txt&quot;);
        OutputStream os = new FileOutputStream(file);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        while ((bytesRead = is.read(buffer, 0, 8192)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        os.close();
        is.close();
        return this.getClass().getResource(&quot;/&quot;).getPath();
    }
}
</pre>
<p>&nbsp;</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public class PathServlet extends HttpServlet {
    private static final long serialVersionUID = 4443655831011903288L;
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        Path path = new Path();
        request.setAttribute(&quot;path&quot;, path.getPath());
        PrintWriter out = response.getWriter();
        out.println(&quot;Class.getResource(&#039;/&#039;).getPath():&quot; + path.getPath());
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        doGet(request, response);
    }
}</pre>
<p>在此之前使用main函数测试Path.class.getResource(&#8220;/&#8221;).getPath()打印出预想的路径为：/D:/work/project/EhCacheTestAnnotation/WebRoot/WEB-INF/classes/</p>
<p>于是将WEB应用部署到Resin下，运行定义好的Servlet，出乎意料的结果是：/D:/work/resin-3.0.23/webapps/WEB-INF/classes/ 。特别奇怪，怎么会丢掉项目名称：EhCacheTestAnnotation呢？</p>
<p>还有一点值得注意，getPath方法中使用getResourceAsStream(&#8220;/a.txt&#8221;)却正常的读到了位于下图的a.txt。<br />
<img decoding="async" loading="lazy" class="aligncenter size-full wp-image-6280" title="resin01" src="https://coolshell.cn/wp-content/uploads/2011/12/resin01.png" alt="" width="547" height="154" srcset="https://coolshell.cn/wp-content/uploads/2011/12/resin01.png 547w, https://coolshell.cn/wp-content/uploads/2011/12/resin01-300x84.png 300w" sizes="(max-width: 547px) 100vw, 547px" /></p>
<p style="text-align: center;">然后写到了如下图的b.txt中。代码中是这样实现的：File file = new File(Path.class.getResource(&#8220;/&#8221;).getPath()+&#8221;/b.txt&#8221;);本意是想在a.txt文件目录下入b.txt。结果却和料想的不一样。<br />
<img decoding="async" loading="lazy" class="aligncenter" src="https://coolshell.cn/wp-content/uploads/2011/12/resin02.png" alt="" width="430" height="119" /></p>
<p>请注意，区别还是丢掉了项目名称。</p>
<p>写的比较乱，稍微总结下：</p>
<p>程序中使用ClassLoader的两个方法：getResourceAsStream和getResource。但是事实证明在WEB应用的场景下却得到了不同的结果。大家别误会啊，看名字他们两个方法肯定不一样，这个我知道，但是getResourceAsStream总会获取指定路径下的文件吧，示例中的参数为&#8221;/a.txt&#8221;，正确读取“/D:/work/resin-3.0.23/webapps/EhCacheTestAnnotation/WEB-INF/classes/ ”下的a.txt，可是将文件写到getResource方法的getPath返回路径的b.txt文件。两个位置的差别在项目名称(EhCacheTestAnnotation)。</p>
<p>这样我暂且得出一个结论：通过getResourceAsStream和getResource两个方法获取的路径是不同的。但是为什么呢？</p>
<p>于是查看了ClassLoader的源码，贴出getResource和getResourceAsStream的源码。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public URL getResource(String name) {
    URL url;
    if (parent != null) {
        url = parent.getResource(name);
    } else {
        url = getBootstrapResource(name);
    }

    if (url == null) {
        url = findResource(name);
    }
    return url;
}

public InputStream getResourceAsStream(String name) {
    URL url = getResource(name);
    try {
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}</pre>
<p>从代码中看，getResourceAsStream将获取URL委托给了getResource方法。天啊，这是怎么回事儿？由此我彻底迷茫了，百思不得其解。</p>
<p>但是没有因此就放弃，继续回想了一遍整个过程：</p>
<ol>
<li>在main函数中，测试getResource与getResourceAsStream是完全相同的，正确的。</li>
<li>将其部署到Resin下，导致了getResource与getResourceAsStream获取的路径不一致。</li>
</ol>
<p>一个闪光点，是不是与web容器有关啊，于是换成Tomcat6.0。OMG，“奇迹”出现了，真的是这样子啊，换成Tomcat就一样了啊！和预想的一致。</p>
<p style="text-align: center;">在Tomcat下运行结果如下图：<br />
<img decoding="async" loading="lazy" class="aligncenter" src="https://coolshell.cn/wp-content/uploads/2011/12/resin03.png" alt="" width="554" height="107" /></p>
<p>对，这就是我想要的。</p>
<p>因此我对Resin产生了厌恶感，之前也因为在Resin下程序报错，在Tomcat下正常运行而纠结了好久。记得看《松本行弘的程序世界》中对C++中的多继承是这样评价的(大概意思)：多重继承带来的负面影响多数是由于使用不当造成的。是不是因为对Resin使用不得当才使得和Tomcat下得到不同的结果。</p>
<p>最终，在查阅Resin配置文件resin.conf时候在&lt;host-default&gt;标签下发现了这样一段：</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;class-loader&gt;
&lt;compiling-loader path=&quot;webapps/WEB-INF/classes&quot;/&gt;
&lt;library-loader path=&quot;webapps/WEB-INF/lib&quot;/&gt;
&lt;/class-loader&gt;</pre>
<p>其中的compiling-loader很可能与之有关，遂将其注释掉，一切正常。担心是错觉，于是将compiling-loader的path属性改成：webapps/WEB-INF/classes1，然后运行pathServlet，b.txt位置如下图：</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" src="https://coolshell.cn/wp-content/uploads/2011/12/resin04.png" alt="" width="483" height="93" /></p>
<p>确实与compiling-loader有关。</p>
<h4>结论</h4>
<p>终于通过将&lt;class-loader&gt;标签注释掉，同样可以在Resin中获取“预想”的路径。验证了的确是使用Resin的人出了问题。</p>
<h4>疑问</h4>
<div>
<p>但是没有这样就结束，我继续对getResource的源码进行了跟进，由于能力有限，没有弄清楚getResource的原理。</p>
<p>最终留下了两个疑问：</p>
<p>1、如果追踪到getResource方法的最底层(也许是JVM层面)，它实现的原理是什么？</p>
<p>2、为何Resin中&lt;class-loader&gt;的配置会对getResource产生影响，但是对getResourceAsStream毫无影响(getResourceAsStream可是将获取路径委托给getResource的啊)。还是这里我理解或者使用错误了？</p>
<p>本来文章到这里就结束了，本来是想问问牛人的，但是这个问题引起了很多的好奇心，于是我又花了一两周做了下面的调查。</p>
<h4>Resin中类加载器</h4>
<p>在我了解的<span style="font-family: 'Times New Roman';">ClassLoader</span><span style="font-family: 宋体;">是在</span>com.caucho.loader包下，结构请看下图：<br />
<img decoding="async" loading="lazy" class="aligncenter" src="https://coolshell.cn/wp-content/uploads/2011/12/resin05.png" alt="图1" width="390" height="810" /><br />
图1<br />
<a href="https://coolshell.cn/wp-content/uploads/2011/12/resin06.png"><img decoding="async" loading="lazy" class="aligncenter" src="https://coolshell.cn/wp-content/uploads/2011/12/resin06.png" alt="图2 （点击看大图）" width="677" height="354" /></a><br />
图2</p>
<p>从上面两幅图中可以看出，图1是与Jdk有关联的，继承自java.net.URLClassLoader。DynamicClassLoader的注释是这样的：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
/**
* Class	loader which checks for changes in class files and automatically
* picks up new jars.
*
* DynamicClassLoaders can be chained creating one virtual class loader.
* From the perspective of the JDK, it&#039;s all one classloader.  Internally,
* the class loader chain searches like a classpath.
*/
</pre>
<p>EnvironmentClassLoader又继承了DynamicClassLoader<span style="font-family: 宋体;">。</span></p>
<p>图<span style="font-family: 'Times New Roman';">2</span><span style="font-family: 宋体;">应该是</span><span style="font-family: 'Times New Roman';">Resin</span><span style="font-family: 宋体;">本身的</span><span style="font-family: 'Times New Roman';">ClassLoader</span><span style="font-family: 宋体;">，其中</span><span style="font-family: 'Times New Roman';">Loader</span><span style="font-family: 宋体;">是一个抽象类，包含了各种子类类加载器。</span></p>
<p>从两幅图中是看不出<span style="font-family: 'Times New Roman';">Resin</span><span style="font-family: 宋体;">自身的</span><span style="font-family: 'Times New Roman';">Loader</span><span style="font-family: 宋体;">体系与继承自</span><span style="font-family: 'Times New Roman';">JVM</span><span style="font-family: 宋体;">的类加载器存在关系，那是不是他们就不存在某种关联呢？其实不是这样子的。请看下面</span><span style="font-family: 'Times New Roman';">DynamicClassLoader</span><span style="font-family: 宋体;">源码的片段：</span></p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// List of resource loaders
private ArrayList _loaders = new ArrayList();
private JarLoader _jarLoader;
private PathLoader _pathLoader;
</pre>
<p>清楚了吧，这两个Loader<span style="font-family: 宋体;">分支通过组合的方式协作。</span></p>
<h4>类加载器顺序</h4>
<p>既然<span style="font-family: 'Times New Roman';">Resin</span><span style="font-family: 宋体;">标准的</span><span style="font-family: 'Times New Roman';">Loader</span><span style="font-family: 宋体;">及其子类以组合的方式嵌入到</span><span style="font-family: 'Times New Roman';">DynamicClassLoader</span><span style="font-family: 宋体;">中，那么在加载一个“资源”时，</span><span style="font-family: 'Times New Roman';">Loader</span><span style="font-family: 宋体;">分支和</span><span style="font-family: 'Times New Roman';">java.net.URLClassLoader</span><span style="font-family: 宋体;">分支的先后顺序是什么样子的呢？</span></p>
<p>首先使用下面这段代码，将类加载器名称打印到控制台：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
ClassLoader loader = PathServlet.class.getClassLoader();
while (loader != null) {
    System.out.println(loader.toString());
    loader = loader.getParent();
}
</pre>
<p>输出的结果为：</p>
<blockquote><p><em>EnvironmentClassLoader[web-app:http://localhost:8080/Test]</em></p>
<p><em><em>EnvironmentClassLoader[web-app:http://localhost:8080]</em></em></p>
<p><em>EnvironmentClassLoader[cluster ]</em></p>
<p><em><em>EnvironmentClassLoader[]</em></em></p>
<p><em>sun.misc.Launcher$AppClassLoader@cac268</em></p>
<p><em>sun.misc.Launcher$ExtClassLoader@1a16869</em></p></blockquote>
<p>额，没有任何一个Resin<span style="font-family: 宋体;">的</span><span style="font-family: 'Courier New';">Loader</span><span style="font-family: 宋体;">被打印出来啊，对头，有就错了。下面就让我们看看</span><span style="font-family: 'Courier New';">DynamicClassLoader</span><span style="font-family: 宋体;">中</span><span style="font-family: 'Courier New';">getResource</span><span style="font-family: 宋体;">的源码来解答。</span></p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
/**
* Gets the named resource
*
* @param name name of the resource
*/

public URL getResource(String name)
{
    if (_resourceCache == null) {
        long expireInterval = getDependencyCheckInterval();
        _resourceCache = new TimedCache(256, expireInterval);
    }

    URL url = _resourceCache.get(name);
    if (url == NULL_URL)
        return null;
    else if (url != null)
        return url;

    boolean isNormalJdkOrder = isNormalJdkOrder(name);

    if (isNormalJdkOrder) {
    url = getParentResource(name);
    if (url != null)
        return url;
    }

    ArrayList loaders = _loaders;
    for (int i = 0; loaders != null &amp;&amp; i &lt; loaders.size(); i++) {
        Loader loader = loaders.get(i);
        url = loader.getResource(name);

        if (url != null) {
            _resourceCache.put(name, url);
            return url;
        }

    }

    if (! isNormalJdkOrder) {
        url = getParentResource(name);
        if (url != null)
            return url;
    }

    _resourceCache.put(name, NULL_URL);
    return null;
}

</pre>
<p>代码不难懂，我画了一张流程图，不规范，凑合看下。<br />
<img decoding="async" loading="lazy" class="aligncenter size-full wp-image-6286" title="resin07" src="https://coolshell.cn/wp-content/uploads/2011/12/resin07.png" alt="" width="326" height="601" srcset="https://coolshell.cn/wp-content/uploads/2011/12/resin07.png 326w, https://coolshell.cn/wp-content/uploads/2011/12/resin07-162x300.png 162w" sizes="(max-width: 326px) 100vw, 326px" /></p>
<h4>总结</h4>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
boolean isNormalJdkOrder = isNormalJdkOrder(name);
</pre>
<p>这行代码控制着Resin<span style="font-family: 宋体;">类加载的顺序，如果是常规的类加载顺序</span><span style="font-family: 'Courier New';">(</span><span style="font-family: 宋体;">向上代理，原文：</span>Returns true if the class loader should use the normal order, i.e. looking at the parents first.)<span style="font-family: 宋体;">，则先</span>url = getParentResource(name)，后遍历_loaders。否则是按照先遍历_loaders<span style="font-family: 宋体;">再</span>url = getParentResource(name)向上代理。</p>
<p>在我的调试经历中，一直都是先向上代理，后遍历_loaders<span style="font-family: 宋体;">的顺序，未遇到第二种方式。</span></p>
<p>文字对先向上代理，后遍历的顺序做点儿说明：</p>
<ol>
<li>首先使用“最上层”的<em>sun.misc.Launcher$ExtClassLoader@1a16869</em>加载name<span style="font-family: 宋体;">资源，如果找到就返回</span><span style="font-family: 'Courier New';">URL</span><span style="font-family: 宋体;">否则返回</span><span style="font-family: 'Courier New';">null</span></li>
<li>程序返回到<em>sun.misc.Launcher$AppClassLoader@cac268</em>，首先判断父类加载器返回的<span style="font-family: 'Courier New';">url</span><span style="font-family: 宋体;">是否为</span><span style="font-family: 'Courier New';">null</span><span style="font-family: 宋体;">，如果不为</span><span style="font-family: 'Courier New';">null</span><span style="font-family: 宋体;">则返回</span><span style="font-family: 'Courier New';">url</span><span style="font-family: 宋体;">，返回</span><span style="font-family: 'Courier New';">null</span><span style="font-family: 宋体;">。</span></li>
<li><span style="font-family: 宋体;"><em><em><em>EnvironmentClassLoader[]</em></em></em></span></li>
<li>程序返回到<em>EnvironmentClassLoader[cluster ]</em>的getParentResource<span style="font-family: 宋体;">，再返回到</span><span style="font-family: 'Courier New';">getResource</span><span style="font-family: 宋体;">，如果</span><span style="font-family: 'Courier New';">url</span><span style="font-family: 宋体;">不为</span><span style="font-family: 'Courier New';">null</span><span style="font-family: 宋体;">，则直接返回，否则遍历</span>ArrayList&lt;Loader&gt; loaders = _loaders;从各个loader<span style="font-family: 宋体;">中加载</span><span style="font-family: 'Courier New';">name</span><span style="font-family: 宋体;">，如果加载成功，即不为</span><span style="font-family: 'Courier New';">null</span><span style="font-family: 宋体;">，则返回，否则继续遍历，直至遍历完成。</span></li>
<li><em><em>EnvironmentClassLoader[web-app:http://localhost:8080]</em></em>同4</li>
<li><em>EnvironmentClassLoader[web-app:http://localhost:8080/Test]</em>同4</li>
</ol>
<p>OK<span style="font-family: 宋体;">，完事儿，后续还有，准备好好写几篇。</span></p>
<p>本文同时发布于：</p>
<ul>
<li><a href="http://www.oschina.net/question/129471_34225">http://www.oschina.net/question/129471_34225</a>。</li>
<li><a href="http://www.oschina.net/question/129471_35231#AnchorAnswer143898">http://www.oschina.net/question/129471_35231#AnchorAnswer143898</a></li>
</ul>
</div>
<p style="text-align: left;">（全文完）</p>
<p><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/6335.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/01/图片1-150x150.png" alt="Resin服务器getResource揭秘" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6335.html" class="wp_rp_title">Resin服务器getResource揭秘</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-150x150.jpg" alt="Java中的CopyOnWrite容器" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_title">Java中的CopyOnWrite容器</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6112.html">由一个问题到 Resin ClassLoader 的学习</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6112.html/feed</wfw:commentRss>
			<slash:comments>30</slash:comments>
		
		
			</item>
		<item>
		<title>一些有意思的算法代码</title>
		<link>https://coolshell.cn/articles/6010.html</link>
					<comments>https://coolshell.cn/articles/6010.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Tue, 29 Nov 2011 03:11:07 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Java语言]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[技术读物]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[算法]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6010</guid>

					<description><![CDATA[<p>Keith Schwarz是一个斯坦福大学计算机科学系的讲师。他对编程充满了热情。他的主页上他自己正在实现各种各样的有意思的算法和数据结构，http://www...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6010.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6010.html">一些有意思的算法代码</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>Keith Schwarz是一个斯坦福大学计算机科学系的讲师。他对编程充满了热情。他的主页上他自己正在实现各种各样的有意思的算法和数据结构，<a href="http://www.keithschwarz.com/interesting/">http://www.keithschwarz.com/interesting/</a>， 目前这个网页上有88个（见下面的列表），但这位大哥要干135个，你可以看看他的<a href="http://www.keithschwarz.com/interesting/" target="_blank">To-Do List</a>。</p>
<p>从这个列表上，我们可以看到，他从去年7月份就在自己实现这些东西了，我把他实现的这些算法转过来，</p>
<ul>
<li>一方面我们可以学习一下这些算法和代码，因为很多东西对我来说都比较新，我以前<a href="https://coolshell.cn/articles/2583.html" target="_blank">列举过一些经典的算法</a>，<a title="链接：算法和数据结构词典" href="https://coolshell.cn/articles/1499.html" rel="bookmark">算法和数据结构词典</a>，还有<a title="链接：可视化的数据结构和算法" href="https://coolshell.cn/articles/4671.html" rel="bookmark">可视化的数据结构和算法</a>， 不过感觉都没有这个全。</li>
</ul>
<ul>
<li>另一方面我希望这个事可以影响到一些正在学习编程的人。看看别人是怎么学习编程的，希望对你有借鉴作用。</li>
</ul>
<table width="100%" border="0" cellspacing="0" cellpadding="6">
<thead>
<tr>
<th>Name</th>
<th>Link</th>
<th>Date Added</th>
<th>Language</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Binomial Heap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=binomial-heap">(link)</a></td>
<td>7‑24‑2010</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Binomial_heap">binomial heap</a> data structure for use as a priority queue.</td>
</tr>
<tr>
<td>Bounded Priority Queue</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=bounded-pqueue">(link)</a></td>
<td>7‑24‑2010</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Priority_queue">priority queue</a> with a fixed upper limit to its size..</td>
</tr>
<tr>
<td>Matrix</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=matrix">(link)</a></td>
<td>7‑24‑2010</td>
<td>C++</td>
<td>A collection of classes for manipulating <a href="http://en.wikipedia.org/wiki/Matrix_%28mathematics%29">matrices</a>.</td>
</tr>
<tr>
<td>VList</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=vlist">(link)</a></td>
<td>8‑16‑2010</td>
<td>Java</td>
<td>An implementation of the <tt>List</tt> abstraction backed by a <a href="http://en.wikipedia.org/wiki/VList">VList</a>.</td>
</tr>
<tr>
<td>Function Wrapper</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=function">(link)</a></td>
<td>8‑16‑2010</td>
<td>C++</td>
<td>A C++ wrapper class around unary functions.</td>
</tr>
<tr>
<td>String</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=string">(link)</a></td>
<td>8‑17‑2010</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/String_(computer_science)">string</a> abstraction that uses the small string optimization.</td>
</tr>
</tbody>
</table>
<p><span id="more-6010"></span></p>
<table>
<tbody>
<tr>
<td>nstream</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=nstream">(link)</a></td>
<td>8‑31‑2010</td>
<td>C++</td>
<td>An stream class that sends and receives data over a network.</td>
</tr>
<tr>
<td>Snake</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=snake">(link)</a></td>
<td>8‑31‑2010</td>
<td>C++</td>
<td>An implementation of the game <a href="http://en.wikipedia.org/wiki/Snake_(video_game)"><em>Snake</em></a> with a rudimentary AI.</td>
</tr>
<tr>
<td>Mergesort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=mergesort">(link)</a></td>
<td>9‑14‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Mergesort">mergesort</a> algorithm.</td>
</tr>
<tr>
<td>Next Permutation</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=next-permutation">(link)</a></td>
<td>10‑6‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://www.cplusplus.com/reference/algorithm/next_permutation/"><tt>next_permutation</tt></a> STL algorithm.</td>
</tr>
<tr>
<td>Interval Heap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=interval-heap">(link)</a></td>
<td>10‑17‑2010</td>
<td>Java</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Double-ended_priority_queue">double-ended priority queue</a> using an <a href="http://www.mhhe.com/engcs/compsci/sahni/enrich/c9/interval.pdf">interval heap</a>.</td>
</tr>
<tr>
<td>Linear-Time Selection</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=median-of-medians">(link)</a></td>
<td>10‑18‑2010</td>
<td>C++</td>
<td>A deterministic, linear-time <a href="http://en.wikipedia.org/wiki/Selection_algorithm">selection algorithm</a> using the <a href="http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm">median-of-medians</a> algorithm.</td>
</tr>
<tr>
<td>Heapsort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=heapsort">(link)</a></td>
<td>10‑18‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Heapsort">heapsort</a> algorithm.</td>
</tr>
<tr>
<td>Union-Find</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=union-find">(link)</a></td>
<td>10‑19‑2010</td>
<td>Java</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Disjoint-set_data_structure">disjoint-set data structure</a> using a disjoint set forest.</td>
</tr>
<tr>
<td>Radix Sort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=radix-sort">(link)</a></td>
<td>10‑19‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Radix_sort">radix sort</a> algorithm.</td>
</tr>
<tr>
<td>Rational</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=rational">(link)</a></td>
<td>10‑23‑2010</td>
<td>C++</td>
<td>A data structure representing a <a href="http://en.wikipedia.org/wiki/Rational_number">rational number</a>.</td>
</tr>
<tr>
<td>DPLL</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=dpll">(link)</a></td>
<td>10‑23‑2010</td>
<td>Haskell</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/DPLL_algorithm">DPLL algorithm</a> for solving <a href="http://en.wikipedia.org/wiki/Boolean_satisfiability_problem">CNF-SAT</a>.</td>
</tr>
<tr>
<td>Smoothsort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=smoothsort">(link)</a></td>
<td>10‑27‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://www.keithschwarz.com/smoothsort/">smoothsort algorithm</a>, an adaptive heapsort variant.</td>
</tr>
<tr>
<td>Extendible Array</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=extendible-array">(link)</a></td>
<td>10‑28‑2010</td>
<td>Java</td>
<td>A <a href="http://en.wikipedia.org/wiki/Dynamic_array">dynamic array</a> class with O(1) worst-case runtime lookup and append.</td>
</tr>
<tr>
<td>In-Place Merge</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=inplace-merge">(link)</a></td>
<td>10‑29‑2010</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Merge_algorithm">merge algorithm</a> that runs <a href="http://en.wikipedia.org/wiki/In-place_algorithm">in-place</a>.</td>
</tr>
<tr>
<td>Random Shuffle</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=random-shuffle">(link)</a></td>
<td>10‑29‑2010</td>
<td>C++</td>
<td>An algorithm for generating a <a href="http://en.wikipedia.org/wiki/Random_permutation">random permutation</a> of a set of elements.</td>
</tr>
<tr>
<td>Random Sample</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=random-sample">(link)</a></td>
<td>10‑29‑2010</td>
<td>C++</td>
<td>An O(n) time, O(1) space algorithm for randomly choosing k elements out of a stream with uniform probability.</td>
</tr>
<tr>
<td>Natural Mergesort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=natural-mergesort">(link)</a></td>
<td>10‑30‑2010</td>
<td>C++</td>
<td>An implementation of <a href="http://www.algorithmist.com/index.php/Merge_sort#Natural_mergesort">natural mergesort</a>, an <a href="http://en.wikipedia.org/wiki/Adaptive_sort">adaptive</a> variant of <a href="http://en.wikipedia.org/wiki/Merge_sort">mergesort</a>.</td>
</tr>
<tr>
<td>Interpolation Search</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=interpolation-search">(link)</a></td>
<td>10‑31‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Interpolation_search">interpolation search</a> algorithm.</td>
</tr>
<tr>
<td>Introsort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=introsort">(link)</a></td>
<td>10‑31‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Introsort">introsort</a> algorithm, a fast hybrid of <a href="http://en.wikipedia.org/wiki/Quicksort">quicksort</a>, <a href="http://en.wikipedia.org/wiki/Heapsort">heapsort</a>, and<a href="http://en.wikipedia.org/wiki/Insertion_sort">insertion sort</a>.</td>
</tr>
<tr>
<td>Hashed Array Tree</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=hashed-array-tree">(link)</a></td>
<td>11‑3‑2010</td>
<td>Java</td>
<td>An implementation of a dynamic array backed by a <a href="http://en.wikipedia.org/wiki/Hashed_array_tree">hashed array tree</a>.</td>
</tr>
<tr>
<td>Recurrence Solver</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=recurrence">(link)</a></td>
<td>11‑13‑2010</td>
<td>C++</td>
<td>A fast algorithm for generating terms of a sequence defined by a <a href="http://en.wikipedia.org/wiki/Recurrence_relation#Linear_homogeneous_recurrence_relations_with_constant_coefficients">linear recurrence relation</a>.</td>
</tr>
<tr>
<td>Fibonacci Heap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=fibonacci-heap">(link)</a></td>
<td>11‑15‑2010</td>
<td>Java</td>
<td>An implementation of a priority queue backed by a <a href="http://en.wikipedia.org/wiki/Fibonacci_heap">Fibonacci heap</a>.</td>
</tr>
<tr>
<td>Dijkstra&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=dijkstra">(link)</a></td>
<td>11‑16‑2010</td>
<td>Java</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Dijkstra's_algorithm">Dijkstra&#8217;s algorithm</a> for single-source shortest paths.</td>
</tr>
<tr>
<td>Prim&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=prim">(link)</a></td>
<td>11‑17‑2010</td>
<td>Java</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Prim's_algorithm">Prim&#8217;s algorithm</a> for computing <a href="http://en.wikipedia.org/wiki/Minimum_spanning_tree">minimum spanning trees</a>.</td>
</tr>
<tr>
<td>Kruskal&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=kruskal">(link)</a></td>
<td>11‑17‑2010</td>
<td>Java</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Kruskal's_algorithm">Kruskal&#8217;s algorithm</a> for computing <a href="http://en.wikipedia.org/wiki/Minimum_spanning_tree">minimum spanning trees</a>.</td>
</tr>
<tr>
<td>Majority Element</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=majority-element">(link)</a></td>
<td>11‑17‑2010</td>
<td>C++</td>
<td>A fast, linear-time algorithm for finding the <a href="http://www.cs.utexas.edu/~moore/best-ideas/mjrty/">majority element</a> of a data set.</td>
</tr>
<tr>
<td>Haar Transform</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=haar">(link)</a></td>
<td>11‑17‑2010</td>
<td>C++</td>
<td>A set of functions to decompose a sequence of values into a sum of <a href="http://en.wikipedia.org/wiki/Haar_wavelet">Haar wavelets</a>.</td>
</tr>
<tr>
<td>Argmax</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=argmax">(link)</a></td>
<td>11‑19‑2010</td>
<td>C++</td>
<td>A pair of functions to compute the <a href="http://en.wikipedia.org/wiki/Arg_max">arg min or max</a> of a function on some range.</td>
</tr>
<tr>
<td>Derivative</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=derivative">(link)</a></td>
<td>11‑19‑2010</td>
<td>C++</td>
<td>A <a href="http://en.wikipedia.org/wiki/Function_object">function object</a> that approximates the <a href="http://en.wikipedia.org/wiki/Derivative">derivative</a> of a function.</td>
</tr>
<tr>
<td>Levenshtein Distance</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=levenshtein">(link)</a></td>
<td>11‑19‑2010</td>
<td>C++</td>
<td>An algorithm for computing the <a href="http://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance</a> between two sequences.</td>
</tr>
<tr>
<td>Skiplist</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=skiplist">(link)</a></td>
<td>11‑20‑2010</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Skip_list">skip list</a>, a randomized data structure for maintaining a sorted collection.</td>
</tr>
<tr>
<td>van Emde Boas Tree</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=van-emde-boas-tree">(link)</a></td>
<td>11‑26‑2010</td>
<td>C++</td>
<td>An implementation of a sorted associative array backed by a <a href="http://en.wikipedia.org/wiki/Van_Emde_Boas_tree">van Emde Boas tree</a>.</td>
</tr>
<tr>
<td>Cuckoo HashMap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=cuckoo-hashmap">(link)</a></td>
<td>11‑27‑2010</td>
<td>Java</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Hash_table">hash table</a> using <a href="http://en.wikipedia.org/wiki/Cuckoo_hashing">cuckoo hashing</a>.</td>
</tr>
<tr>
<td>Needleman-Wunsch Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=needleman-wunsch">(link)</a></td>
<td>11‑28‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm">Needleman-Wunsch</a> algorithm for optimal string alignment.</td>
</tr>
<tr>
<td>Treap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=treap">(link)</a></td>
<td>11‑28‑2010</td>
<td>C++</td>
<td>An implementation of a sorted associative array backed by a <a href="http://en.wikipedia.org/wiki/Treap">treap</a>.</td>
</tr>
<tr>
<td>Floyd-Warshall Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=floyd-warshall">(link)</a></td>
<td>12‑10‑2010</td>
<td>Java</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Floyd-Warshall_algorithm">Floyd-Warshall algorithm</a> for all-pairs shortest paths in a graph.</td>
</tr>
<tr>
<td>Power Iteration</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=power-iteration">(link)</a></td>
<td>12‑10‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Power_iteration">power iteration</a> algorithm for finding dominant eigenvectors.</td>
</tr>
<tr>
<td>Edmonds&#8217;s Matching Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=edmonds-matching">(link)</a></td>
<td>12‑15‑2010</td>
<td>Java</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Edmonds's_matching_algorithm">Edmonds&#8217;s matching algorithm</a> for finding <a href="http://en.wikipedia.org/wiki/Matching_(graph_theory)#Maximum_matchings">maximum matchings</a> in undirected graphs.</td>
</tr>
<tr>
<td>Kosaraju&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=kosaraju">(link)</a></td>
<td>12‑15‑2010</td>
<td>Java</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Kosaraju's_algorithm">Kosaraju&#8217;s algorithm</a> algorithm for finding <a href="http://en.wikipedia.org/wiki/Strongly_connected_component">strongly connected components</a> of a directed graph.</td>
</tr>
<tr>
<td>2-SAT</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=2sat">(link)</a></td>
<td>12‑15‑2010</td>
<td>Java</td>
<td>A linear-time algorithm for solving <a href="http://en.wikipedia.org/wiki/2-satisfiability">2-SAT</a>.</td>
</tr>
<tr>
<td>Bellman-Ford Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=bellman-ford">(link)</a></td>
<td>12‑17‑2010</td>
<td>Java</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm">Bellman-Ford</a> algorithm for single-source shortest paths.</td>
</tr>
<tr>
<td>Topological Sort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=topological-sort">(link)</a></td>
<td>12‑17‑2010</td>
<td>Java</td>
<td>An algorithm for computing a <a href="http://en.wikipedia.org/wiki/Topological_sorting">topological sort</a> of a directed acyclic graph.</td>
</tr>
<tr>
<td>Graham Scan</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=graham-scan">(link)</a></td>
<td>12‑19‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Graham_scan">Graham scan</a> for finding convex hulls in 2D space.</td>
</tr>
<tr>
<td>Bipartite Testing</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=bipartite-verify">(link)</a></td>
<td>12‑19‑2010</td>
<td>Java</td>
<td>A linear-time algorithm for checking whether a directed graph is <a href="http://en.wikipedia.org/wiki/Bipartite_graph">bipartite</a>.</td>
</tr>
<tr>
<td>Johnson&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=johnson">(link)</a></td>
<td>12‑19‑2010</td>
<td>Java</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Johnson's_algorithm">Johnson&#8217;s algorithm</a> for all-pairs shortest paths.</td>
</tr>
<tr>
<td>Strassen Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=strassen">(link)</a></td>
<td>12‑20‑2010</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Strassen_algorithm">Strassen algorithm</a> for fast matrix multiplication.</td>
</tr>
<tr>
<td>Cartesian Tree Sort</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=cartesian-tree-sort">(link)</a></td>
<td>12‑21‑2010</td>
<td>C++</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Cartesian_tree#Application_in_sorting">Cartesian tree sort</a>, an adaptive, out-of-place heapsort variant.</td>
</tr>
<tr>
<td>Ford-Fulkerson Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=ford-fulkerson">(link)</a></td>
<td>12‑21‑2010</td>
<td>Java</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm">Ford-Fulkerson</a> maximum-flow algorithm.</td>
</tr>
<tr>
<td>Scaling Ford-Fulkerson</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=ford-fulkerson-scaling">(link)</a></td>
<td>12‑22‑2010</td>
<td>Java</td>
<td>An modification of the <a href="http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm">Ford-Fulkerson</a> maximum-flow algorithm that uses scaling to achieve polynomial time..</td>
</tr>
<tr>
<td>Splay Tree</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=splay-tree">(link)</a></td>
<td>12‑27‑2010</td>
<td>C++</td>
<td>An implementation of a sorted associative array backed by a <a href="http://en.wikipedia.org/wiki/Splay_tree">splay tree</a>.</td>
</tr>
<tr>
<td>Ternary Search Tree</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=ternary-search-tree">(link)</a></td>
<td>12‑28‑2010</td>
<td>C++</td>
<td>An implementation of a sorted set of strings backed by a <a href="http://en.wikipedia.org/wiki/Ternary_search_tree">ternary search tree</a>.</td>
</tr>
<tr>
<td>Ring Buffer</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=ring-buffer">(link)</a></td>
<td>12‑30‑2010</td>
<td>Java</td>
<td>An implementation of a FIFO queue using a <a href="http://en.wikipedia.org/wiki/Circular_buffer">ring buffer</a>.</td>
</tr>
<tr>
<td>AVL Tree</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=avl-tree">(link)</a></td>
<td>12‑30‑2010</td>
<td>C++</td>
<td>A sorted associative container backed by an <a href="http://en.wikipedia.org/wiki/AVL_tree">AVL tree</a>.</td>
</tr>
<tr>
<td>Rabin-Karp Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=rabin-karp">(link)</a></td>
<td>1‑1‑2011</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_string_search_algorithm">Rabin-Karp algorithm</a> for string matching.</td>
</tr>
<tr>
<td>RPN Evaluator</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=rpn-evaluate">(link)</a></td>
<td>1‑18‑2011</td>
<td>C++ / strain</td>
<td>A library to tokenize and evaluate simple arithmetic expressions in <a href="http://en.wikipedia.org/wiki/Reverse_Polish_notation">reverse Polish notation</a>.</td>
</tr>
<tr>
<td>Shunting-Yard Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=shunting-yard">(link)</a></td>
<td>1‑18‑2011</td>
<td>C++ / strain</td>
<td>An implementation of Dijkstra&#8217;s <a href="http://en.wikipedia.org/wiki/Shunting-yard_algorithm">shunting-yard algorithm</a> for converting infix expressions to reverse-Polish notation.</td>
</tr>
<tr>
<td>Skew Binomial Heap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=skew-binomial-heap">(link)</a></td>
<td>1‑20‑2011</td>
<td>C++</td>
<td>An implementation of a priority queue backed by a <a href="http://en.wikipedia.org/wiki/Skew_binomial_heap">skew binomial heap</a>.</td>
</tr>
<tr>
<td>2/3 Heap</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=two-three-heap">(link)</a></td>
<td>3‑1‑2011</td>
<td>C++</td>
<td>An implementation of a priority queue whose branching factor alternates at different levels to maximize performance.</td>
</tr>
<tr>
<td>Zeckendorf Logarithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=zeckendorf-logarithm">(link)</a></td>
<td>3‑10‑2011</td>
<td>C++</td>
<td>An algorithm based on <a href="http://en.wikipedia.org/wiki/Zeckendorf's_theorem">Zeckendorf representations</a> that efficiently computes logarithms.</td>
</tr>
<tr>
<td>Factoradic Permutations</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=factoradic-permutation">(link)</a></td>
<td>3‑17‑2011</td>
<td>C++</td>
<td>A set of algorithms for generating <a href="http://en.wikipedia.org/wiki/Permutation">permutations</a> using the <a href="http://en.wikipedia.org/wiki/Factorial_number_system">factoradic number system</a>.</td>
</tr>
<tr>
<td>Binary Cyclic Subsets</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=binary-subset">(link)</a></td>
<td>3‑20‑2011</td>
<td>C++</td>
<td>A set of algorithms for generating <a href="http://en.wikipedia.org/wiki/Subset">subsets</a> in <a href="http://en.wikipedia.org/wiki/Lexicographical_order">lexicographical order</a> using <a href="http://www.keithschwarz.com/binary-subsets">binary numbers and cyclic shifts</a>.</td>
</tr>
<tr>
<td>Fibonacci Iterator</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=fibonacci-iterator">(link)</a></td>
<td>3‑22‑2011</td>
<td>C++</td>
<td>An STL-style iterator for iterating over the <a href="http://en.wikipedia.org/wiki/Fibonacci_number">Fibonacci numbers</a>.</td>
</tr>
<tr>
<td>Fibonacci Search</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=fibonacci-search">(link)</a></td>
<td>3‑22‑2011</td>
<td>C++</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Fibonacci_search_technique">Fibonacci search</a> algorithm.</td>
</tr>
<tr>
<td>Euclid&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=euclid">(link)</a></td>
<td>4‑18‑2011</td>
<td>Haskell</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Euclidean_algorithm">Euclid&#8217;s algorithm</a> and applications to <a href="http://en.wikipedia.org/wiki/Continued_fraction">continued fractions</a> and <a href="http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm">the extended Euclidean algorithm</a>.</td>
</tr>
<tr>
<td>Find Duplicate</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=find-duplicate">(link)</a></td>
<td>4‑18‑2011</td>
<td>Python</td>
<td>An algorithm to find a repeated element in an array using <a href="http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare">Floyd&#8217;s cycle-finding algorithm</a>.</td>
</tr>
<tr>
<td>Permutation Generator</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=permutation-generator">(link)</a></td>
<td>4‑19‑2011</td>
<td>Python</td>
<td>A <a href="http://en.wikipedia.org/wiki/Generator_(computer_programming)">generator</a> for producing all <a href="http://en.wikipedia.org/wiki/Permutation">permutations</a> of a list of elements.</td>
</tr>
<tr>
<td>Matrix Find</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=matrix-find">(link)</a></td>
<td>4‑19‑2011</td>
<td>Python</td>
<td>A solution to the classic interview question of searching a sorted matrix for a particular value.</td>
</tr>
<tr>
<td>Binary GCD</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=binary-gcd">(link)</a></td>
<td>4‑23‑2011</td>
<td>Scheme</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Binary_GCD_algorithm">binary GCD algorithm</a> for computing greatest common divisors of nonnegative integers.</td>
</tr>
<tr>
<td>Knuth-Morris-Pratt Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=knuth-morris-pratt">(link)</a></td>
<td>5‑3‑2011</td>
<td>Python</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm">Knuth-Morris-Pratt algorithm</a> for fast string matching.</td>
</tr>
<tr>
<td>Kadane&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=kadane">(link)</a></td>
<td>5‑7‑2011</td>
<td>C++</td>
<td>An implementation of Kadane&#8217;s algorithm for solving the <a href="http://en.wikipedia.org/wiki/Maximum_subarray_problem">maximum-weight subarray problem</a>.</td>
</tr>
<tr>
<td>Karatsuba&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=karatsuba">(link)</a></td>
<td>8‑15‑2011</td>
<td>Python</td>
<td>An implementation of <a href="http://en.wikipedia.org/wiki/Karatsuba_algorithm">Karatsuba&#8217;s algorithm</a> for fast integer multiplication.</td>
</tr>
<tr>
<td>Min-Stack</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=min-stack">(link)</a></td>
<td>8‑15‑2011</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Stack_(data_structure)">LIFO stack</a> that supports O(1) push, pop, and find-minimum.</td>
</tr>
<tr>
<td>Random Bag</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=random-bag">(link)</a></td>
<td>8‑15‑2011</td>
<td>Python</td>
<td>A data structure that supports insertion and removal of a uniformly-random element.</td>
</tr>
<tr>
<td>Min-Queue</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=min-queue">(link)</a></td>
<td>8‑15‑2011</td>
<td>C++</td>
<td>An implementation of a <a href="http://en.wikipedia.org/wiki/Queue_(data_structure)">FIFO queue</a> that supports O(1) push, pop, and find-minimum.</td>
</tr>
<tr>
<td>Lights-Out Solver</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=lights-out">(link)</a></td>
<td>8‑29‑2011</td>
<td>C++</td>
<td>A solver for the game <a href="http://en.wikipedia.org/wiki/Lights_Out_(game)">Lights Out</a> using <a href="http://en.wikipedia.org/wiki/Gaussian_elimination">Gaussian elimination</a> over <a href="http://en.wikipedia.org/wiki/GF(2)">GF(2)</a>.</td>
</tr>
<tr>
<td>Maximum Single-Sell Profit</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=single-sell-profit">(link)</a></td>
<td>11‑9‑2011</td>
<td>Python</td>
<td>Four algorithms for the <a href="http://stackoverflow.com/q/7086464/501557">maximum single-sell profit problem</a>, each showing off a different algorithmic technique.</td>
</tr>
<tr>
<td>Generalized Kadane&#8217;s Algorithm</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=generalized-kadane">(link)</a></td>
<td>11‑10‑2011</td>
<td>C++</td>
<td>A generalization of <a href="http://en.wikipedia.org/wiki/Maximum_subarray_problem">Kadane&#8217;s algorithm</a> for solving the maximum subarray problem subject to a <a href="http://stackoverflow.com/q/7861387/501557">length restriction</a>.</td>
</tr>
<tr>
<td>Longest Range</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=longest-range">(link)</a></td>
<td>11‑19‑2011</td>
<td>Java</td>
<td>An algorithm for solving the <a href="http://stackoverflow.com/q/5415305/501557">longest contiguous range</a> problem.</td>
</tr>
<tr>
<td>Egyptian Fractions</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=egyptian-fraction">(link)</a></td>
<td>11‑20‑2011</td>
<td>Python</td>
<td>An implementation of the <a href="http://en.wikipedia.org/wiki/Greedy_algorithm_for_Egyptian_fractions">greedy algorithm</a> for finding <a href="http://en.wikipedia.org/wiki/Egyptian_fraction">Egyptian fractions</a>.</td>
</tr>
<tr>
<td>LL(1) Parser Generator</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=ll1">(link)</a></td>
<td>11‑21‑2011</td>
<td>Java</td>
<td>An <a href="http://en.wikipedia.org/wiki/LL_parser">LL(1) parser generator</a>.</td>
</tr>
<tr>
<td>LR(0) Parser Generator</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=lr0">(link)</a></td>
<td>11‑23‑2011</td>
<td>Java</td>
<td>An <a href="http://en.wikipedia.org/wiki/LR_parser">LR(0) parser generator</a>.</td>
</tr>
<tr>
<td>Word Ladders</td>
<td><a href="http://www.keithschwarz.com/interesting/code/?dir=word-ladder">(link)</a></td>
<td>11‑27‑2011</td>
<td>JavaScript</td>
<td>A program for finding <a href="http://en.wikipedia.org/wiki/Word_ladder">word ladders</a> between two words.</td>
</tr>
</tbody>
</table>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/29.jpg" alt="Leetcode 编程训练" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_title">Leetcode 编程训练</a></li><li ><a href="https://coolshell.cn/articles/4220.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/0.jpg" alt="一些有意思的文章和资源" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4220.html" class="wp_rp_title">一些有意思的文章和资源</a></li><li ><a href="https://coolshell.cn/articles/9886.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/8.jpg" alt="二叉树迭代器算法" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9886.html" class="wp_rp_title">二叉树迭代器算法</a></li><li ><a href="https://coolshell.cn/articles/8239.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/09/lock_free_bicycle-150x150.jpg" alt="无锁队列的实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8239.html" class="wp_rp_title">无锁队列的实现</a></li><li ><a href="https://coolshell.cn/articles/4671.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/11.jpg" alt="可视化的数据结构和算法" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4671.html" class="wp_rp_title">可视化的数据结构和算法</a></li><li ><a href="https://coolshell.cn/articles/3933.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/23.jpg" alt="可视化的排序过程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3933.html" class="wp_rp_title">可视化的排序过程</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6010.html">一些有意思的算法代码</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6010.html/feed</wfw:commentRss>
			<slash:comments>45</slash:comments>
		
		
			</item>
		<item>
		<title>Eclipse开发Android应用程序入门:重装上阵</title>
		<link>https://coolshell.cn/articles/4334.html</link>
					<comments>https://coolshell.cn/articles/4334.html#comments</comments>
		
		<dc:creator><![CDATA[Neo]]></dc:creator>
		<pubDate>Fri, 08 Apr 2011 00:30:09 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Eclipse]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=4334</guid>

					<description><![CDATA[<p>翻译:赵锟 原文：http://www.smashingmagazine.com/2011/03/28/get-started-developing-for-a...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/4334.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/4334.html">Eclipse开发Android应用程序入门:重装上阵</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>翻译:赵锟</strong><br />
原文：<a href="http://www.smashingmagazine.com/2011/03/28/get-started-developing-for-android-with-eclipse-reloaded/">http://www.smashingmagazine.com/2011/03/28/get-started-developing-for-android-with-eclipse-reloaded/</a></p>
<p>在我们教程系列的<a href="https://coolshell.cn/articles/4270.html">第一部分</a>中，我们使用Android和Eclipse开发了一个简单的饮茶计时器的应用程序。在第二部分，我们将继续开发这个程序，并给它增加一些其他的额外的功能。在开发的过程中，我们将给你介绍更多重要而强大的Android SDK特性，包括持久化数据存储，Activity和Intent，和共享用户首选项（译者注：类似于windows 的注册表的一种机制）。</p>
<p>跟着本教程，你需要上一篇教程中的代码，如果你想直接使用代码，你可以使用如下的指令从<a href="http://github.com/cblunt/BrewClock">GitHub</a>上check out出tutorial_par_1标记的代码：</p>
<p><img decoding="async" loading="lazy" width="793" height="564" src="https://coolshell.cn/wp-content/uploads/2011/04/1_starting_point_full.jpg" alt="" title="1_starting_point_full"  class="aligncenter size-full wp-image-4362" srcset="https://coolshell.cn/wp-content/uploads/2011/04/1_starting_point_full.jpg 793w, https://coolshell.cn/wp-content/uploads/2011/04/1_starting_point_full-300x213.jpg 300w, https://coolshell.cn/wp-content/uploads/2011/04/1_starting_point_full-768x546.jpg 768w, https://coolshell.cn/wp-content/uploads/2011/04/1_starting_point_full-380x270.jpg 380w" sizes="(max-width: 793px) 100vw, 793px" /><br />
[code]<br />
$ git clone git://github.com/cblunt/BrewClock.git<br />
$ cd BrewClock<br />
$ git checkout tutorial_part_1<br />
[/code]</p>
<p>在GitHub中检出了代码后，你需要将代码倒入到Eclipse中的项目中：</p>
<ol>
<li>运行      Eclipse 选择 <em>File → Import…</em></li>
<li>在导入窗口, 选择 <em>“Existing Projects into Workspace”</em>并点击<em> “Next.”</em></li>
<li>在下一屏，点击 <em>“Browse,”</em>选择你从GitHub上clone出的代码目录。</li>
<li>点击“Finish” 将项目导入到Eclipse中。</li>
</ol>
<p><span id="more-4334"></span><br />
在导入项目到Eclipse之后，你有可能会看到有如下的警告信息：<br />
[code]<br />
Android required .class compatibility set to 5.0.<br />
Please fix project properties.<br />
[/code]<br />
如果有这种情况，右键点击“Project Explorer ”中新导入的BrewClock项目，并选择 “Fix Project Properties,” 并重启Eclipse。</p>
<h3>数据持久化入门</h3>
<p>当前,BrewClock 让用户为他们泡的茶设置一个定时器。这个非常棒的一个工作，但是如果对于不同的茶使用同一个泡茶时间的结果会怎样呢，是不每种茶都应该有自己的一个泡茶时间呢？如果这样，那岂不是所有的用户都需要记下每一类茶所需要泡的时间！这不是一个很好的用户体验。因此，在这篇教程中，我将新增一个功能来为用户每种不同的茶叶存放一个泡茶时间，并当用户想泡茶的时候，可以从茶叶列表中进行选择。</p>
<p>为了实现这个目的，我们得利用Android的丰富的数据持久化的API。Android提供了几种方式来存储数据，本文将要覆盖其中的两种方式。第一种，使用SQLite数据库引擎来为我们存储数据。</p>
<p>SQLite 是一种流行的轻量级SQL数据库引擎，它将数据存在单个文件中。SQLite经常用于桌面或在那些运行不能运行客户端-服务器SQL引擎（例如MySQL或PostgreSQL)的嵌入式的应用上。</p>
<p>每个安装在Android上的应用都可以保存和使用多个SQLite数据库文件（由数据存储容量决定），这些数据由系统自动地进行管理。应用程序的数据是私有并且不能被其他的应用程序所访问。（数据可以通过ContentProvider(译者注：内容提供者类)类进行共享，但是我们不会在本教程中覆盖关于内容提供者的内容）。当数据应用程序被更新时，数据库文件就进行持久化，当应用程序被删除时，数据库文家就被删除。</p>
<p>我们在BrewClock应用使用SQLite数据来维护我们的茶叶列表和泡茶所需要的时间。下面是我们我们将使用的数据表的一个总体介绍。</p>
<p>[code]<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />
| Table: teas                         |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+<br />
| Column     | Description            |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+<br />
| _ID        | integer, autoincrement |<br />
| name       | text, not null         |<br />
| brew_time  | integer, not null      |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;+<br />
[/code]</p>
<p>如果以前你使用过SQL，你应该熟悉这些内容。数据表有三个字段，一个唯一标示（_ID），茶叶名称(name)和泡茶时间(brew_time)字段。我们将使用Android提供给我们的API在应用中建立数据表。系统将负责在正确的位置为我们的创建数据库文件。</p>
<h4>抽象数据库</h4>
<p>为了确保数据库的代码容易被维护，我们用一个单独的类TeaData来抽象所有处理数据库创建，插入，和查询的代码。如果你熟悉模型-试图-控制(译者注：MVC)方法的话，这个你也应该熟悉。所有数据库代码与我们的BrewClockActitvity类隔离开来。Actitvity可以初始化一个新的TeaData实例（这个实例将连接数据库）并完成它所需要的工作。以这种方式工作保证了我们可以方便的更改我们所使用的数据库而不用修改其他那些和数据库不相关部分的代码。</p>
<p>通过菜单File → New → Class.在BrewClock项目中创建一个TeaData的新类。确保TeaData扩展于android.database.sqlite.SQLiteOpenHelper 类，并选中“Constructors from superclass”复选框。<br />
<img decoding="async" src="https://coolshell.cn/wp-content/uploads/2011/04/2_create_teadata_class1.jpg" alt="" title="1_starting_point_full"  class="aligncenter size-full wp-image-4362" /></p>
<p>TeaData 类将为你自动地处理SQLite数据库的创建和版本。我们需要增加一些方法来作为其他代码到数据库的接口。</p>
<p>增加两个常量来存储数据库的名字和版本,增加表名和表中列名。我们使用Android提供的常类BaseColumns._ID来做为表的唯一id列：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/TeaData.java
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.provider.BaseColumns;

public class TeaData extends SQLiteOpenHelper {
  private static final String DATABASE_NAME = &quot;teas.db&quot;;
  private static final int DATABASE_VERSION = 1;

  public static final String TABLE_NAME = &quot;teas&quot;;

  public static final String _ID = BaseColumns._ID;
  public static final String NAME = &quot;name&quot;;
  public static final String BREW_TIME = &quot;brew_time&quot;;

  // …
}
</pre>
<p>为TeaData增加一个构造方法，以数据库名称合版本号为参数调用其父类的构造方法。Android将会自动地打开数据库（如果数据库不存在就自动创建它）。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/TeaData.java
public TeaData(Context context) {
  super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
</pre>
<p>我们需要重载onCreate方法，并执行一个SQL 串执行创建数据库表的操作。Android将会在数据库文件第一次被创建时调用这个方法。</p>
<p>在启动过程中，Android检查数据库的版本是否我们传入的版本一致。如果版本发生了改变，Android将会调用onUpgrade方法，在这个方法总，你可以编写修改数据库结构的业务逻辑。在本教程中，我们将让Android删除数据库并重建数据库。</p>
<p>在onCreate和onUpgrade中增加如下的代码:</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/TeaData.java
@Override
public void onCreate(SQLiteDatabase db) {
  // CREATE TABLE teas (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, brew_time INTEGER);
  String sql =
    &quot;CREATE TABLE &quot; + TABLE_NAME + &quot; (&quot;
      + _ID + &quot; INTEGER PRIMARY KEY AUTOINCREMENT, &quot;
      + NAME + &quot; TEXT NOT NULL, &quot;
      + BREW_TIME + &quot; INTEGER&quot;
      + &quot;);&quot;;

  db.execSQL(sql);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  db.execSQL(&quot;DROP TABLE IF EXISTS &quot; + TABLE_NAME);
  onCreate(db);
}

</pre>
<p>下一步，我们需要新增代码让我们方便地在数据库中新增茶叶记录。我们新增一个带茶叶名称和泡茶时间的方法来负责插入记录。Android为了尽量避免开发者使用SQL语句，提供了一堆类来处理向数据库中查入记录。首先，我们创建一个ContentValues集合，并将相关的值插入到这个集合中去。</p>
<p>对于ContentValues集合，我们只要简单地提供一个列名和值来插入就行了。Android负责创建和运行正确的SQL。使用Android的数据类确保了你能写出安全，跨平台的数据库操作代码。</p>
<p>Add a new method, insert(), to the TeaData class:</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/TeaData.java
public void insert(String name, int brewTime) {
  SQLiteDatabase db = getWritableDatabase();

  ContentValues values = new ContentValues();
  values.put(NAME, name);
  values.put(BREW_TIME, brewTime);

  db.insertOrThrow(TABLE_NAME, null, values);
}

</pre>
<h4>查询数据</h4>
<p>我们应用程序具有了在数据库中保存数据的能力后，我们同样也需要一种方式将数据取回来。Android提供了游标Cursor接口来完成这件工作。一个游标代表了针对数据库运行一个SQL返回的结果集，游标在这个结果集中维护了一个指针来指向结果集中的一行。这个指针可以向前，向后移动，并返回每一列的值，下面我们用图形来帮助你理解游标:</p>
<p>SQL 查询: SELECT * from teas LIMIT 3;<br />
[code]<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+</p>
<p>|  _ID  |  name       |  brew_time  |</p>
<p>+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+</p>
<p>|    1  |  Earl Grey  |          3  |</p>
<p>|    2  |  Green      |          1  | &lt;= Cursor</p>
<p>|    3  |  Assam      |          5  |</p>
<p>+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+<br />
[/code]</p>
<p>在这个例子中，游标指向了结果集中的第二条记录（绿茶）。我们可以通过调用cursor.moveToPrevious()方法，将游标向前移动，让它指向第一行（Earl Grey），或者调用moveToNext向前移动指向Assam。要取到游标所指向记录的茶叶的名称，我们只要调用cursor.getString(1)，1代表我们向提取数据列的下标（注意下标识从0开始的，1代表第二列，依次类推）。</p>
<p>在了解游标后，我们增加一个创建游标对象并返回数据库中所有的茶叶信息。在TeaData中增加all方法：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/TeaData.java
public Cursor all(Activity activity) {
  String[] from = { _ID, NAME, BREW_TIME };
  String order = NAME;

  SQLiteDatabase db = getReadableDatabase();
  Cursor cursor = db.query(TABLE_NAME, from, null, null, null, null, order);
  activity.startManagingCursor(cursor);

  return cursor;
}
</pre>
<p>因为这个方法乍一看有点古怪，所以让我们先来关心一下这个方法的一些细节。我们没有使用SQL的查询语句，而是使用了Android提供的数据库接口方法。</p>
<p>第一，我们需要告诉Android，我们所关心的列的信息。我们创建了一个字符串数组，数组中存放这TeaData中列的标示信息。我们还设置了我名们期望的结果集按照哪一个列进行排序的列名。</p>
<p>第二，我们使用getReadalbeDatabase()创建了一个到数据库的只读连接，并调用query方法告诉Android我们希望用query方法运行一个查询。query()方法有很多的参数，Android在内部将这些参数转化为一个查询语句。此外，Android的抽象层保证了即使底层数据储存机制发生了变化，我们的应用程序代码也能正确的工作。</p>
<p>由于我们只要返回表中的所有记录，所以我们没有在方法中使用到链接join，过滤filter和分组group（例如：在SQL中的WHERE，JOIN，和GROUP BY）。from和order变量告诉查询数据库需要返回那些列和提取数据时按什么列进行排序。我们使用SQLiteDatabase.query()作为和数据库的人机交互接口。</p>
<p>最后，我们让Activity（在本例中，我们的BrewClockActivity）来管理游标。通常，游标需要人工刷新内容，因此当我们增加一个新茶信息到数据库中时，我们就需要刷新我们的游标。每当我们的应用被挂起和恢复的时候，通过调用startManagingCursor()让Android来帮我们重建结果集。</p>
<p>在TeaData类中增加count方法:</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/TeaData.java
  public long count() {
    SQLiteDatabase db = getReadableDatabase();
    return DatabaseUtils.queryNumEntries(db, TABLE_NAME);
  }

</pre>
<p>保存TeaData类，使用修正没有import 的类(Source → Organize Imports)，在完成我们的数据类后，下一步我们将着手修改我们BrewClock的人机界面。</p>
<h4>修改BrewClock用户界面，允许进行茶叶选择</h4>
<p>持久化茶和泡茶的时间的目的是让用能快速的选择他们所钟爱的预设置的茶。为了完成这个功能，我们需要再BrewClock的主界面上增加一个Spinner（类似于桌面上弹出菜单），生成一个来自于TeaData的茶列表。</p>
<p>和前面的教程一样，我们使用了Eclipse的布局器编辑器在BrewClock的主界面布局XML文件中增加Spinner。在LinearLayout元素下面增加下面这些代码（大约在24行）。如果你打开了可视化的布局编辑器后，你可以点击窗口下面的地&#8221;Code View&#8221;进行切换。</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- /res/layout/main.xml --&gt;

&lt;!-- Tea Selection --&gt;
&lt;LinearLayout
  android:orientation=&quot;vertical&quot;
  android:layout_width=&quot;fill_parent&quot;
  android:layout_height=&quot;wrap_content&quot;&gt;

  &lt;Spinner
    android:id=&quot;@+id/tea_spinner&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;wrap_content&quot; /&gt;

&lt;/LinearLayout&gt;
</pre>
<p>在BrewClockActivity类里面,增加一个成员变量指向Spinner，通过使用findViewById连接界面上的控件：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
protected Spinner teaSpinner;
protected TeaData teaData;

// …

public void onCreate(Bundle savedInstanceState) {
  // …
  teaData = new TeaData(this);
  teaSpinner = (Spinner) findViewById(R.id.tea_spinner);
}
</pre>
<p>运行你的程序以确保新的界面正确地生效。你应该在泡茶计数器下看见一个空白的弹出式菜单（或者是Spinner)。如果点击spinner，Android将显示一个弹出式的菜单并为你提供选择列表。在这时，菜单的内容因该是空的，现在让我们来绑定Spinner和我们的茶叶数据库。</p>
<p><img decoding="async" loading="lazy" width="500" height="356" src="https://coolshell.cn/wp-content/uploads/2011/04/3_blank_spinner.jpg" alt="" title="3_blank_spinner"  class="aligncenter size-full wp-image-4364" srcset="https://coolshell.cn/wp-content/uploads/2011/04/3_blank_spinner.jpg 500w, https://coolshell.cn/wp-content/uploads/2011/04/3_blank_spinner-300x213.jpg 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<h4>数据绑定</h4>
<p>当Android从数据库中查询数据时，它将会返回一个游标Cursor对象。Cursor代表了来自数据库的结果集，并可以移动游标来提取结果中的数据。使用一类Android提供的称为“适配器Adapter”的类，我们很容易将这个结果集绑定到Spinner上。适配器完成了提取数据库结果集中的数据并在界面上显示这些数据等这些复杂而困难工作。</p>
<p>在我们的TeaData.all()方法中已经可以返回一个带有tea表内容的游标，使用这个游标，我们所需要做的工作就是创建一个SimpleCursor适配器来绑定我们的teaSpinner，Android会负责处理将数据显示在spinner的列表中。</p>
<p>通过创建一个SimpleCursorAdapter类来连接Spinner与teaData.all()返回的游标：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// com/example/brewclock/BrewClockActivity.java

public void onCreate(Bundle savedInstanceState) {
  // …
  Cursor cursor = teaData.all(this);

  SimpleCursorAdapter teaCursorAdapter = new SimpleCursorAdapter(
    this,
    android.R.layout.simple_spinner_item,
    cursor,
    new String[] { TeaData.NAME },
    new int[] { android.R.id.text1 }
  );

  teaSpinner.setAdapter(teaCursorAdapter);
  teaCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}

</pre>
<p>注意，我们使用了Android内建的android.R对象。这个对象提供了你的应用程序中的默认资源，例如视图和布局。在我们的代码中，我们使用了android.R.layout.simple_spinner_item，它是简单的文本标签布局。</p>
<p>如果你再次运行的应用程序，你将会看到spinner中仍然是空的！虽然我们已经连接了我们的数据库，但是由于数据库中没有任何记录，所以我们任何看到了空列表。</p>
<p>我们通过在构造方法中增加一些默认记录来让用户可以选择所需要的茶叶，为了避免重复记录，我们只有在数据库中记录为0的情况才增加默认记录。在本教程的代码中，我们使用前面增加的count()来检查数据库中表记录是否为空。</p>
<p>增加当数据库中表为空的默认记录代码。把这些代码增加从数据库提取茶叶数据的前面（译者注：上一段的代码前）。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// com/example/brewclock/BrewClockActivity.java
public void onCreate(Bundle savedInstanceState) {
  // …

  // Add some default tea data! (Adjust to your preference :)
  if(teaData.count() == 0) {
    teaData.insert(&quot;Earl Grey&quot;, 3);
    teaData.insert(&quot;Assam&quot;, 3);
    teaData.insert(&quot;Jasmine Green&quot;, 1);
    teaData.insert(&quot;Darjeeling&quot;, 2);
  }

  // Code from the previous step:
  Cursor cursor = teaData.all(this);

  // …
}

</pre>
<p>现在再次运行你的应用程序。你将会发现茶叶Spinner有了一条选择。点击Spinner让你可以从数据库选择你要的茶叶。</p>
<p><img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2011/04/4_populated_spinner.jpg" alt="" title="4_populated_spinner" width="500" height="356" class="aligncenter size-full wp-image-4365" srcset="https://coolshell.cn/wp-content/uploads/2011/04/4_populated_spinner.jpg 500w, https://coolshell.cn/wp-content/uploads/2011/04/4_populated_spinner-300x213.jpg 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p>恭喜你！你已经成功关联了你的界面和代码。这是任何软件开发过程中一个非常重要的方面。正如你所看见的，Android将这一步简化的非常容易，但是功能有是非常的NB。使用游标和适配器，你可以将数据源（丛简单的字符串数组到复杂的数据库查询）绑定到任何类型的视图：spinner或列表，设置是类似iTunes cover-flow gallery!</p>
<p>虽然现在已经可以开始泡茶了，但是我们工作还远没有结束。当你从Spinner选择了不同的茶，这个选择却不会发生任何作用。我们需要根据用户所选茶叶的种类取更新我们的泡茶时间。</p>
<h4>读取选中茶叶数据并更新泡茶时间</h4>
<p>为了能读取用户从数据库中选择茶叶的数据，我们必须增加一个针对此事件的监听器。类似于处理按钮点击事件的OnClickListener监听器一样，我们将实现一个OnItemSelectedListener。当用户从视图中做出一个选择的事件将触发这个监听器，例如从我们的Spinner。</p>
<p>在BrewClockActivity中增加需要实现的接口OnItemSelectedListener。并增加其响应的处理方法onItemSelected()和onNothingSelected()：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
public class BrewClockActivity extends Activity implements OnClickListener, OnItemSelectedListener {
  // …
  public void onItemSelected(AdapterView&lt;?&gt; spinner, View view, int position, long id) {
    if(spinner == teaSpinner) {
      // Update the brew time with the selected tea’s brewtime
      Cursor cursor = (Cursor) spinner.getSelectedItem();
      setBrewTime(cursor.getInt(2));
    }
  }

  public void onNothingSelected(AdapterView&lt;?&gt; adapterView) {
    // Do nothing
  }
}

</pre>
<p>在这里我们要检查是触发的spinner此事件是不是BrewClock的teaSpinner。如果是，我们将提取代表选中记录的游标对象。这些都是由关联teaData和Spinner的SimpleCursorAdapter来提供我们完成的。Android知道哪个查询产生的Spinner数据，也知道用户选择的哪个数据。Android使用游标来返回数据库的一行记录，也代表了用户所选择的茶叶数据。</p>
<p>Cursor的getInt()方法带了一个我们想提取的列的下标为参数。在我们的teaData.all()方法中创建游标的时候，我们读取的列是_ID,NAME和BREW_TIME。假设我们在teaSpinner中选择的是Jasmine Tea，那么将返回我们所选数据所对应的数据库记录。</p>
<p>然后我们再通过传递参数2来选择此记录的第二列的整型值。这个值提供给setBrewTime()方法。这个方法用于更新界面上的泡茶时间。</p>
<p>最后，我们需要告诉teaSpinner BrewClockActivity正在监听OnItemSelected事件。在BrewClockActivity的onCreate方法中增加下面的代码：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
public void onCreate() {
  // …
  teaSpinner.setOnItemSelectedListener(this);
}
</pre>
<p>大功告成！再次运行你的程序，并从Spinner选择不同的茶叶。每次你所选的茶叶它所对应的泡茶时间都回显示对应的界面上。我们余下的代码中已经可以处理从当前时间开始递减计数。所以在有预先设置的茶叶种类下，我们已经可以完成我们所想要的功能。</p>
<p>你当然可以，回到之前的代码中去增加一些茶叶种类你满足你的口味。但是如果你发布BrewClock程序到Android Market，每当有人向增加新的茶叶数据到数据库中，我就需要去手动的取更新数据中的内容并重新发布它；这样所有的人就必须去更新它，并且所有的人都有一个同样的列表。这听起来非常的不灵活，因此我们还有很多的工作需要完成！</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/5_default_teas.jpg" alt="" title="3_blank_spinner"  class="aligncenter size-full wp-image-4364"/></p>
<p>如果用户自己有方法新增茶叶种类到数据库里面，将会非常的不错的做法。因此我们将在下一章继续。。。</p>
<h3>Activity 介绍</h3>
<p>和你应用程序中每个屏幕关联的代码就是Activity。每次当你从一屏切换到另外一屏，Android就会创建一个新的Activity。在真实世界中，虽然一个应用程序经常由多个屏幕/Activity构成，Andriod却将每个屏幕看作独立的个体。多个Activity工作在一起形成一种关联的体验，这是因为Android让你非常容易地在屏幕/Activity之间传递数据。</p>
<p>在本节最后，你将为你的应用程序新增一个新的Activity（AddTeaActivity）并将它注册到Android系统中。你还需要从最初的BrewClockActivity传递数据到新的Activity中。</p>
<p>首先，我们需要给用户一种方式切换到新的Activity上。我们将使用选项菜单来完成之一步。</p>
<h4>选项菜单</h4>
<p>当用户他们的设备上的“Menu”按键时，选项菜单以弹出菜单的形式出现。Android负责菜单的自动创建和显示；你只需要告诉Android，菜单显示什么内容和当用户点击菜单时该做什么就行。</p>
<p>然而,最好不要在代码中硬编码菜单的标题，我们可以使用Android的字符串资源。字符串资源是一个独立的文件，在这个文件中你可以维护所有用于用户阅读的字符串和标签资源，并可以在代码调用它们。这就意味着当你在未来需要修改字符串时，你只要修改这一处地方即可。.</p>
<p>在project explorer中导航到“res/values”下，你将会看到string.xml文件已经存在。这个是你再创建新项目的时候由Eclipse创建的，这文件存放着在整个应用程序我们将要使用的字符串。</p>
<p>双击打开<em>strings.xml</em> ,通过窗口底部的选项页切换到XML 视图。</p>
<p>在&lt;resources&gt;…&lt;/resources&gt; 元素中增加下面的内容:</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">

&lt;!-- res/values/strings.xml --&gt;
  &lt;resources&gt;
    &lt;!-- … --&gt;
    &lt;string name=&quot;add_tea_label&quot;&gt;Add Tea&lt;/string&gt;
  &lt;/resources&gt;


</pre>
<p>我们在这里定义了一个字符串，add_tea_label和它关联的文本，我们可以在整个程序代码中通过add_tea_label来使用其关联的文本。如果标签因为某个原因需要修改，我们只需要在这个文件修改这一个地方就能完成整个程序的修改。</p>
<p>下一步，让我们创建一个新文件完成选项菜单的定义，如果字符串和布局一样，菜单也使用XML来定义。因此我们将在Eclipse中川建一个新的XML文件：</p>
<p>通过选择File → New → Other, 并选择“Android XML File.”在Eclipse中创建一个新的XML文件。</p>
<p>选择资源的类型为 “Menu”，保存文件名为main.xml。Eclipse将为你自动的创建一个目录<em>res/menu</em>, 来存放你的菜单文件。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/7_new_menu_xml.jpg"></img></p>
<p>打开<em>res/menus/main.xml</em> 文件, 通过窗口底部的“main.xml”选项页来切换到XML视图。</p>
<p>增加菜单项， add_tea。</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- res/values/strings.xml --&gt;
  &lt;resources&gt;
    &lt;!-- … --&gt;
    &lt;string name=&quot;add_tea_label&quot;&gt;Add Tea&lt;/string&gt;
  &lt;/resources&gt;
</pre>
<p>注意android:title 属性被设置为@string/add_tea_label。这告诉Android在我们的strings.xml文件中查找add_tea_label并返回相关联的标签内容。在本列中我们的菜单项的标签时“Add Tea”。</p>
<p>下一步，我们将告诉我们的Activity，当用户点击设备上的“memu”按键时来显示这个选项菜单。</p>
<p>返回<em>BrewClockActivity.java</em>代码, 重载onCreateOptionsMenu 方法,这个方法告诉Android 当用户点击“Menu”按键时，装载我们的菜单：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.main, menu);

  return true;
}

</pre>
<p>当用户点击他设备上的“Menu”按键时，Android将调用onCreateOptionsMenu。在这个方法中，我们创建了一个MenuInflater, 这个对象将从你的应用程序包中装载你的菜单资源。就如同按钮和文本域组成你的应用程序布局一样，main.xml资源也是通过全局对象R来生效的，因此我们将此对象提交给MenuInflater对象。</p>
<p>为了测试菜单，保存并在模拟器中并运行应用程序。当程序运行起来使，点击“Menu”按键，你将会看到一个弹出式的菜单显示了一个“Add Tea”选项。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/8_add_teas_options_menu.jpg"></img></p>
<p>如果你点击“Add Tea”选项，Android自动地检测到点击并关闭菜单。在后台，Android将会提醒应用程序选项已经被点击。</p>
<h4>处理菜单点击</h4>
<p>当用户点击 “Add Tea” 菜单选项，我们想要显示一个新的Activity以便我们能进入增加新茶叶种类的界面。通过选择File → New → Class来创建一个的Activiy。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/9_new_activity_settings.jpg"></img></p>
<p>将新类命名为 AddTeaActivity,并确保它继承于android.app.Activity类。这个类也放在com.example.brewclock包中:</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
package com.example.brewclock;

import android.app.Activity;
import android.os.Bundle;

public class AddTeaActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }
}

</pre>
<p>上面样例中的空白Activity将不会完成任何工作。但是通过它，我们已经可以完成选项菜单的功能。</p>
<p>在BrewClockActivity增加一个重载方法onOptionsItemSelected 。当用户点击菜单项时，这个方法被Android调用。 (注意点击的MenuItem为它的接收参数：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
@Override
public boolean onOptionsItemSelected(MenuItem item) {
  switch(item.getItemId()) {
    case R.id.add_tea:
      Intent intent = new Intent(this, AddTeaActivity.class);
      startActivity(intent);
      return true;

    default:
      return super.onOptionsItemSelected(item);
  }
}


</pre>
<p>通过上面的代码，我们告诉Android，当“Add Tea”被点击的时候，我们将要创建一个的Activity；在本教程中，就是AddTeaActivity。然而，不要直接创建这个类的实例，注意我们使用了Intent。Intent有着Android框架的强大特性；他们将Activity绑定在一起来组成应用程序，并允许在他们之间相互传递数据。</p>
<p>Intent的优点甚至让你的应用程序可以使用用户安装的其他的应用程序。例如，当用户要从图库里面显示一张图片，Android自动地给显一个对话框来让用户选择应用程序来显示图片。任何注册为可以处理图片显示的应用程序都会出现在这个对话框的列表中。</p>
<p>Intent功能强大而复杂的主体, 因此它值得你从官方的文档<a href="http://developer.android.com/guide/topics/intents/intents-filters.html">official Android SDK documentation</a>中仔细研究。</p>
<p>让我们运行我们的应用程序，以测试我们的“Add Tea”屏幕。</p>
<p>运行你的项目，按下Menu按键，并点击 “Add Tea.”。</p>
<p>不如你预期的，你并没有看到 “Add Tea” Activity，出现在你面前的是一个Android开发者经常看到的对话框：</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/10_crash.jpg"></img></p>
<p>虽然我们创建了一个Intent并告诉Android启动我们的AddTeaActivity Activity, 由于我们没有将这个Activity注册到Android系统中，我们的应用程序最终还是crash掉了。系统不知道从哪里去找到我们试图运行的Activity（应该还记得Intent可以启动安装在设备上的任何Activity吧）。让我们在应用程序的mainfest文件来注册这些Acitivity。</p>
<p>打开应用的manifest文件，在Eclipse中的AndroidManifest.xml。通过窗口底部的“AndroidManifest.xml”选项页切换到xml视图</p>
<p>应用程序的mainfest文件是保存你应用程序全局设置和信息的地方。你将会看见里面已经有一个.BrewClockActivity 的Activity声明，并且这个Activity在程序运行的时候启动。</p>
<p>在&lt;application&gt;中, 增加一个 &lt;activity&gt; 节点，描述为“Add Tea”的 Activity. 使用我们早先在strings.xml声明的 add_tea_label字符串作为这个Activity的标题：</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- AndroidManifest.xml --&gt;
&lt;application …&gt;
  …
  &lt;activity android:name=&quot;.AddTeaActivity&quot; android:label=&quot;@string/add_tea_label&quot; /&gt;
&lt;/application&gt;
</pre>
<p>在你再次运行BrewClock保存这个manifest文件。这一次，当你打开菜单并点击“Add Tea,”时Android将会启动AddTeaActivity。按下back按键返回主屏幕。</p>
<p>完成了Activity的关联，下一步我们将要开发一个增加新茶的界面！</p>
<h3>开发茶叶编辑器界面</h3>
<p>开发一个增加茶叶界面和上一个教程中开发的BrewClock主界面是非常相似的。首先要创建一个布局文件，然后在按照下面的讲解添加适合的XML内容。</p>
<p>和主界面开发所有不同的是，你可以使用Android最近改进的Eclipse布局编辑器来开界面。创建一个新的XML文件来定义你的布局。从菜单File → New然后选择 “Android XML File,” 选择 “Layout”类型。并将文件命令为<em>add_tea.xml</em>。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/11_new_layout_xml.jpg"></img></p>
<p>用下面的布局内容替换<em>add_tea.xml</em> 文件的内容：</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- res/layouts/add_tea.xml --&gt;
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout
  xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
  android:layout_width=&quot;fill_parent&quot;
  android:layout_height=&quot;fill_parent&quot;
  android:orientation=&quot;vertical&quot;
  android:padding=&quot;10dip&quot;&gt;

  &lt;TextView
    android:text=&quot;@string/tea_name_label&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;wrap_content&quot; /&gt;

  &lt;EditText
    android:id=&quot;@+id/tea_name&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;wrap_content&quot;/&gt;

  &lt;TextView
    android:text=&quot;@string/brew_time_label&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;/&gt;

  &lt;SeekBar
    android:id=&quot;@+id/brew_time_seekbar&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:progress=&quot;2&quot;
    android:max=&quot;9&quot; /&gt;

  &lt;TextView
    android:id=&quot;@+id/brew_time_value&quot;
    android:text=&quot;3 m&quot;
    android:textSize=&quot;20dip&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:gravity=&quot;center_horizontal&quot; /&gt;
&lt;/LinearLayout&gt;

</pre>
<p>为了这个界面上使用的字符串，我们同样也需要在<em>strings.xml</em> 中增加一些新的内容：</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- res/values/strings.xml --&gt;
&lt;resources&gt;
  &lt;!-- … --&gt;
  &lt;string name=&quot;tea_name_label&quot;&gt;Tea Name&lt;/string&gt;

  &lt;string name=&quot;brew_time_label&quot;&gt;Brew Time&lt;/string&gt;
&lt;/resources&gt;

</pre>
<p>在这个布局中，我们加了一个新的界面控件类型，SeekBar。这个控件可以让用户通过从左向右拖拉一个指示器thumb，非常容易的指定泡茶时间。这个值得范围从0到android:max。</p>
<p>在这个界面中，我们使用刻度是0到9，意思是从1分钟到10分钟（泡0分钟茶等于是浪费好茶）。第一，我们需要确保AddTeaActivity能正确地加载我们的界面:</p>
<p>在Activity的onCreate()方法中增加下面的代码用于加载和显示add_tea布局文件：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.add_tea);
}

</pre>
<p>现在通过运行项目来测试你的应用程序，按下“Menu”按键，并点击“Add Tea”菜单。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/12_add_tea_interface.jpg"/></p>
<p>你将从“Add Tea”屏幕上看到你的新界面。你可以在文本域中输入文字和从左到右拖动SeekBar。但是由于我们没有增加相关代码，这个界面并没有实现什么具体的功能。</p>
<p>在AddTeaActivity中增加下面这些属性，并关联到我们界面上元素：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
public class AddTeaActivity {
  // …

  /** Properties **/
  protected EditText teaName;
  protected SeekBar brewTimeSeekBar;
  protected TextView brewTimeLabel;

  // …

</pre>
<p>下一步,关联属性和你的界面：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
public void onCreate(Bundle savedInstanceState) {
  // …
  // Connect interface elements to properties
  teaName = (EditText) findViewById(R.id.tea_name);
  brewTimeSeekBar = (SeekBar) findViewById(R.id.brew_time_seekbar);
  brewTimeLabel = (TextView) findViewById(R.id.brew_time_value);
}

</pre>
<p>界面非常的简单，我们只要增加相应SeekBar 改变事件的监听器。当用户从左到右移动SeekBar指示器时，我们的应用程序需要读出新值并更新SeekBar之下泡茶时间标签的内容。我们将使用一个监听器来检测SeekBar何时改变的：</p>
<p>在AddTeaActivity类声明中增加实现 onSeekBarChangedListener接口，并添加所必要的方法：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
public class AddTeaActivity
extends Activity
implements OnSeekBarChangeListener {
  // …

  public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    // TODO Detect change in progress
  }

  public void onStartTrackingTouch(SeekBar seekBar) {}

  public void onStopTrackingTouch(SeekBar seekBar) {}
}

</pre>
<p>我们唯一感兴趣的事件时onProgressChanged，因此我们需要在这个方法内增加代码更新泡茶时间标签的内容为SeekBar选中的值。之前我们说过SeekBar的刻度是0到9，因此我们需要将SeekBar的加1的值来显示给用户才有意义。</p>
<p>在<em>AddTeaActivity.java</em>代码中增加如下的onProgressChanged()代码：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  if(seekBar == brewTimeSeekBar) {
    // Update the brew time label with the chosen value.
    brewTimeLabel.setText((progress + 1) + &quot; m&quot;);
  }
}

</pre>
<p>在AddTeaActivity的onCreate方法中设置监听器：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
public void onCreate(Bundle savedInstanceState) {
  // …

  // Setup Listeners
  brewTimeSeekBar.setOnSeekBarChangeListener(this);
}

</pre>
<p>现在运行你的程序，并拖动SeekBar,泡茶时间标签的内容将会同步更新为正确地值：</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/13_seekbar.jpg"></img></p>
<h4>保存新增茶叶</h4>
<p>完成了增加茶叶界面之后,剩下的工作就是让用户可以将他们新增的茶叶保存到数据库中.我们将会对界面上输入数据增加一点校验,以避免茶叶名为空的数据被保存到数据库中！</p>
<p>在编辑器中打开<em>strings.xml</em> 增加一些我们在应用程序将要使用到的新标签。</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- res/values/strings.xml --&gt;
&lt;string name=&quot;save_tea_label&quot;&gt;Save Tea&lt;/string&gt;
&lt;string name=&quot;invalid_tea_title&quot;&gt;Tea could not be saved.&lt;/string&gt;

&lt;string name=&quot;invalid_tea_no_name&quot;&gt;Enter a name for your tea.&lt;/string&gt;


</pre>
<p>如同前面的那样，我们需要为AddTeaActivity创建一个新的选项菜单来让用户可以执行保存茶叶的指令：</p>
<p>在<em>res/menus</em> 目录，通过选择File → New 并选 Other → Android XML 文件来创建一个新的 <em>add_tea.xml</em> XML文件, 记住资源类型为“Menu”。</p>
<p>增加保存茶叶的菜单项：</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">

&lt;!-- res/menus/add_tea.xml --&gt;
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
  &lt;item android:title=&quot;@string/save_tea_label&quot; android:id=&quot;@+id/save_tea&quot; /&gt;
&lt;/menu&gt;


</pre>
<p>返回 AddTeaActivity 代码中,类似你在BrewClockActivity中一样，增加重载方法onCreateOptionsMenu 和onOptionsItemSelected。唯一的区别是这次你提供的MenuInflater的资源文件名是<em>add_tea.xml</em> ：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.add_tea, menu);

  return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
  switch(item.getItemId()) {
    case R.id.save_tea:
      saveTea();

    default:
      return super.onOptionsItemSelected(item);
  }
}

</pre>
<p>下一步, 增加新方法, saveTea(), 来保存茶叶信息。saveTea 首先从界面上读取茶叶的名称和用户所选的泡茶时间，如果这些输入数据都能通过验证，就将这些数据保存到数据库中：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
public boolean saveTea() {
  // Read values from the interface
  String teaNameText = teaName.getText().toString();
  int brewTimeValue = brewTimeSeekBar.getProgress() + 1;

  // Validate a name has been entered for the tea
  if(teaNameText.length() &lt; 2) {
    AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    dialog.setTitle(R.string.invalid_tea_title);
    dialog.setMessage(R.string.invalid_tea_no_name);
    dialog.show();

    return false;
  }

  // The tea is valid, so connect to the tea database and insert the tea
  TeaData teaData = new TeaData(this);
  teaData.insert(teaNameText, brewTimeValue);
  teaData.close();

  return true;
}


</pre>
<p>大段的代码，让我们过一遍这段代码的逻辑。</p>
<p>首先，我们从文本框中读取茶叶名称，从SeekBar读取泡茶时间（记着读的时间要加1以保证时间在1到10分钟之内）。下一步，我们验证茶叶名大于等于2个字符（这是非常简单的验证，如果想做更复杂的验证，那么就使用正则表达式吧）。</p>
<p>如果茶叶名称非法，我们需要让用户知道。我们使用Android提供的工具类，AlertDialog.Biulder类，这个类给我们提供了一个快捷创建和显示模态窗口的方法。在设置完标题和错误信息后，通过调用show方法来显示对话框。这个对话框是模态的modal，因此用户只有按下back按键，这个对话框才会关闭。在这时，我们不想保存任何数据，所以我们的方法返回了false。</p>
<p>如果茶名称合法，我们通过TeaData类创建一个到茶叶数据库的临时连接。这里又一次的显示出把数据库访问抽象成一个独立文件的好处：你可以从任何地方完成对数据库（译者注：其实应该是对TeaData 类）的访问。</p>
<p>当调用完teaData.insert() 来增加记录到数据库后，我们不再需要数据库连接，因此在我们返回成功前，我们关闭了连接。</p>
<p>在模拟器中运行你的程序，按下“Menu”按键，点击屏幕上的“Add Tea”。试图通过在此按下“Menu”和点击屏幕的 “Save Tea.”来保存空茶叶名的茶叶数据。由于是没有茶叶名，一条错误消息将出现在你的面前：</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/14_invalid_tea.jpg"/></p>
<p>下一步，试着键入你的茶叶名，并选择合适的泡茶时间，再次从菜单选择 “Save Tea” 。这一次，你将不在看到错误的消息。事实上，你什么都看消息不到。</p>
<h4>改进用户体验</h4>
<p>这样做不是一个很好的用户体验，用户不能知道他的茶叶是否已经成功地保存了。事实上，用户只有从“Add Tea”界面返回，去茶叶列表中查看这一个办法来检查他的是否成功的被保存。这样的做法不好，让用户知道他们的茶叶数据被成功地保存会是更好的一种方式。在茶叶数据被成功保存后，让我们在屏幕上显示一条成功信息。</p>
<p>我们要一条被动的非模态化的信息，因此AlertDialog这次就不能满足我们的需求了。下面我们将要使用另外一个Android的非常流行的特性，Toast。</p>
<p>Toast 在接近屏幕的下方显示一条消息，但是并不会终止用户的操作。Toast经常用于做非重要的的提醒和状态更新。.</p>
<p>在<em>strings.xml</em> 资源文件中新增一个字符串。注意字符串中的%s。我们在下一步中将保存的茶叶名字结合到这个字符串来显示信息。</p>
<pre data-enlighter-language="xml" class="EnlighterJSRAW">
&lt;!-- res/values/strings.xml --&gt;
&lt;string name=&quot;save_tea_success&quot;&gt;%s tea has been saved.&lt;/string&gt;
</pre>
<p>注意，在onOptionsItemSelected 代码中进行修改，当saveTea返回真时，创建并显示一条弹出式的Toast。第二参数getString()用来连接茶叶名称到Toast信息中。最后，我们需要将茶叶名称清楚，以便用户可以快速增加更多的新茶。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/AddTeaActivity.java
// …
switch(item.getItemId()) {
 case R.id.save_tea:
   if(saveTea()) {
     Toast.makeText(this, getString(R.string.save_tea_success, teaName.getText().toString()), Toast.LENGTH_SHORT).show();
     teaName.setText(&quot;&quot;);
   }
// …
</pre>
<p>现在，重新运行应用程序，并增加和保存一些新茶叶。你将会看到弹出式的Toast并让你知道你的茶叶信息已经被保存成功。getString()方法用于连接存在XML文件中的String和茶叶名称，并将%s替换成茶叶的名称。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/16_valid_save.jpg"></img></p>
<p>按下“Back”按键，返回应用程序的主屏幕，点击茶叶spinner。你新增的在数据库中的茶叶已近可以显示在spinner的选项中！</p>
<h3>用户首选项</h3>
<p>现在BrewClock已经完成了所有的功能。用户可以增加他们喜爱的茶叶和各自不同的泡茶时间到数据库中，并且他们可以快速的从选择他们并开始泡上一杯新茶。任何新增的茶叶信息都被保存在数据库中，因此，即使你退出你的程序，这些茶叶信息在你下次启动程序时仍然可以从spinner列表中找到。</p>
<p>当你重启BrewClock的时候，有一件事你必须注意，就是泡茶计数被清为了0。这使得跟踪我们每天喝了多少茶（一条重要的数据）变得困难。作为最后一个练习，让我们将泡茶计数保存在我们设备上。</p>
<p>我们将不通过增加茶叶数据库的表来完成这个功能，我们将使用Android的“共享首选项Shared Preferences”，一个Android提供给你应用程序用于存储简单数据的数据库（字符串，数字，等等）。例如，优秀的最高分和用户首选项等（译者注：非常类似Windows下的注册表）。</p>
<p>我们首先在<em>BrewClockActivity.java</em> 中增加一堆常量。这些常量用于存放你的共享首选项的名称。我们将使用键的名称来访问泡茶计数。Android负责保存和持久化我们的共享首选项文件。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">

// src/com/example/brewclock/BrewClockActivity.java

protected static final String SHARED_PREFS_NAME = &quot;brew_count_preferences&quot;;

protected static final String BREW_COUNT_SHARED_PREF = &quot;brew_count&quot;;

</pre>
<p>下一步，为了我们能在用户首选项中读写泡茶计数，而不是直接的依赖于代码中的初始值，我们将在代码中做一些修改。在BrewClockActivity 的 onCreate 方法中我们将就该setBrewCount附件的代码：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
public void onCreate() {
  // … 

  // Set the initial brew values
  SharedPreferences sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
  brewCount = sharedPreferences.getInt(BREW_COUNT_SHARED_PREF, 0);
  setBrewCount(brewCount);

  // …
}

</pre>
<p>这里我们将以使用SharedPreference来获取应用程序的共享首选项的实例，并希望得到brew_count键值的值（通过我们之前定义的BREW_COUNT_SHARED_PREF常量来标示）。如果值能获取，这个值将返回给应用程序，如果没有我们使用getInt的第二参数作为默认值返回（在教程中为0）。</p>
<p>现在我们取得存储的泡茶计数值，我们需要确保每当泡茶计数更新的时候，这个值能写回到共享首选项中。</p>
<p>BrewClockActivity的setBrewCount中增加下面的代码：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
// src/com/example/brewclock/BrewClockActivity.java
 public void setBrewCount(int count) {
   brewCount = count;
   brewCountLabel.setText(String.valueOf(brewCount));

   // Update the brewCount and write the value to the shared preferences.
   SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE).edit();
   editor.putInt(BREW_COUNT_SHARED_PREF, brewCount);
   editor.commit();
 }


</pre>
<p>共享首选项不能直接地保存。我们需要使用Android的SharedPreferences.Editor类。调用SharedPreferences的edit方法，返回一个editor实例，这个实例用来保存我们的首选项值。我们只要调用editor实例的commit方法就可以将值保存到共享首选项中。</p>
<p>我们应用程序的所有代码都已完成，现在让我们测试一下我们的程序！</p>
<p>在模拟器中运行应用程序，定一个泡茶时间（这真是一个良好的借口去泡一杯你自己爱喝的茶哦）并退出应用程序，试着运行模拟器上的安装的其他应用程序确保BrewClock被终止。记住，除非这个应用程序已经不在内存中，否则Android不会终止一个Activity。</p>
<p>当你下一次运行你的应用程序时，你将看见之前的泡茶计数已经被维护了。</p>
<h3>总结</h3>
<p>恭喜!你已经完成了这个应用的程序的所有开发工作,并使用了Android　SDK中的数个核心组件。在本教程中，你从中学到了：</p>
<ul>
<li>创建一个简单的SQLite数据库，并保存你的数据；</li>
<li>使用Android的数据库类和编写客户化类抽象数据访问；</li>
<li>在你的应用程序中增加选项菜单。；</li>
<li>在你应用程序中创建并注册新Activity并使用Intent将他们绑定成一组界面；</li>
<li>使用内建的“共享首选项”数据库来保存和提取简单用户数据。</li>
</ul>
<p>无论你要开发神马样类型的应用程序，数据存储和持久化是一个重要的主题。从工具程序和业务工具到3-D游戏，几乎每个应用程序都需要使用到Android提供的数据工具类。</p>
<p><img decoding="async" src=" https://coolshell.cn/wp-content/uploads/2011/04/17_brew_up.jpg"/></p>
<h4>Activities</h4>
<p>虽然BrewClock现在在某方面来说已经是个功能完善的应用程序了。但是我们仍然可以在增加一些功能以改进用户体验。例如你可以使用下面的方法来改进你的应用程序：</p>
<ul>
<li>在保存茶叶的时候检查是否存在茶叶名称重名；</li>
<li>增加一个菜单选项以将泡茶统计清0；</li>
<li>在共享首选项中保存最后所选的泡茶名称和时间以便程序重启时有一个有意义的默认值；</li>
<li>增加用户从茶叶数据库中删除记录的选项。</li>
</ul>
<p>在<a href="http://github.com/cblunt/BrewClock">GitHub库</a> 可以获取到所有的源代码，库中的未来的分支包含着Activitiy的解决方案 你可以通过切换你的本地代码拷贝到tutorial_2分支，下载这个开发教程源代码：<br />
[code]</p>
<p>$ git clone git://github.com/cblunt/BrewClock.git</p>
<p>$ cd BrewClock</p>
<p>$ git checkout tutorial_2</p>
<p>[/code]<br />
我希望你喜欢这个教程，希望这个教程能帮助你设计和开发更棒的Android应用程序。请通过在下面的回复让我知道你的建议和意见，当然我也欢迎你将你建议写在email中并发送给我。</p>
<p><em>感谢<a href="http://blog.anselmbradford.com/">Anselm</a>的建议和反馈！ </em></p>
<p><em>（全文完）</em><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/4270.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/04/install-150x150.gif" alt="Eclipse开发Android应用程序入门" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4270.html" class="wp_rp_title">Eclipse开发Android应用程序入门</a></li><li ><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-150x150.jpg" alt="关于移动端的钓鱼式攻击" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_title">关于移动端的钓鱼式攻击</a></li><li ><a href="https://coolshell.cn/articles/12225.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/12/1053-DHH-150x150.jpg" alt="DHH 谈混合移动应用开发" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12225.html" class="wp_rp_title">DHH 谈混合移动应用开发</a></li><li ><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/11/inbox2-640x264-150x150.jpg" alt="Google Inbox如何跨平台重用代码？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_title">Google Inbox如何跨平台重用代码？</a></li><li ><a href="https://coolshell.cn/articles/4220.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/0.jpg" alt="一些有意思的文章和资源" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4220.html" class="wp_rp_title">一些有意思的文章和资源</a></li><li ><a href="https://coolshell.cn/articles/3589.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="食客还是大厨" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3589.html" class="wp_rp_title">食客还是大厨</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/4334.html">Eclipse开发Android应用程序入门:重装上阵</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/4334.html/feed</wfw:commentRss>
			<slash:comments>21</slash:comments>
		
		
			</item>
		<item>
		<title>Eclipse开发Android应用程序入门</title>
		<link>https://coolshell.cn/articles/4270.html</link>
					<comments>https://coolshell.cn/articles/4270.html#comments</comments>
		
		<dc:creator><![CDATA[Neo]]></dc:creator>
		<pubDate>Thu, 07 Apr 2011 08:40:36 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Eclipse]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=4270</guid>

					<description><![CDATA[<p>By Chris Blunt 翻译：赵锟 原文出处：http://www.smashingmagazine.com/2010/10/25/get-started...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/4270.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/4270.html">Eclipse开发Android应用程序入门</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>By <a title="Posts by Chris Blunt" href="http://www.smashingmagazine.com/author/chris-blunt/">Chris Blunt</a></p>
<p><strong>翻译：赵锟</strong><br />
原文出处：<a href="http://www.smashingmagazine.com/2010/10/25/get-started-developing-for-android-with-eclipse/">http://www.smashingmagazine.com/2010/10/25/get-started-developing-for-android-with-eclipse/</a></p>
<p>如今的移动设备应用程序开发充满着让人振奋的东西。功能强大的硬件支持，平板电脑，多样的软件平台（塞班 OS，iOS，WebOS，Windows Phone 7&#8230;)，移动设备开发者前景充满了机会和挑战。</p>
<p>当你想要开始开发你的移动设备程序时，如此多的选择可能让你产生困扰。究竟应该选择神马平台？我应该学习神马语言？为你计划的项目选择神马工具？在本教程中，你将学会如何在Google公司的开源移动设备操作系统Android下开发应用程序。</p>
<h3>为神马选Android</h3>
<p>Android是一个基于Linux内核的开源平台， 并且被安装在来自于不同厂商的上千种设备中。Android将各种移动设备的硬件如 电子罗盘，摄像头，GPS，方向感应，等等暴露给你的应用程序。<br />
<span id="more-4270"></span><br />
Android的免费开发工具可以让你以0成本开始编写你的软件。当你想向世界展示你的应用程序的时候，你可以将你的软件发布到Google的 Android 市场。向Andriod Market 发布程序只一次性的收取注册费用（25元），并且不像苹果的App Store ，对每一次的提交都要做检查，除非你的程序明显地违法，在经过一个快速检查的流程后，才能让你的程序提供给客户下载和购买。</p>
<p>下面是Android对于开发者的优点：</p>
<ul>
<li>Android的SDK可以在Windows,Mac和Linux上运行，因此你不需要为了开发环境支付额外的新硬件投入。（译者注：我曾近在Win7 64x + VMWare上成功的安装Mac Snow leopard + XCode的开发环境，对于爱用盗版的人来说，这点MS优势不是很大啊）</li>
<li>构建于JAVA上的SDK。如果你熟悉JAVA语言，你就是事半功倍了。（译者注：这个酷壳有篇文章讨论过，大家可以参看：<a href="https://coolshell.cn" target="_blank">https://coolshell.cn</a>）</li>
<li>你只要在Android Market上发布应用程序，你将有潜在的成千上万的用户。而且你不一定非要把程序发布在Android Market上，你还可以在你的博客上发布。而且有传言，Amazon已近在最近准备搭建他们自己的Android 应用程序商店了。</li>
<li>除了了技术性的<a href="http://developer.android.com/sdk/index.html">SDK 文档</a>外,还可以找到其他更多的使用者和开发者的资源。</li>
</ul>
<p>闲话少说——下面让我们进入正题，开始开发我们的Android应用程序。</p>
<h3>安装Eclipse和Android SDK</h3>
<p>Android应用程序的推荐开发环境是带有Android开发包插件(Android Devlopment Toolkit (ADT))的Eclipse。我在这里简要说明一下安装流程。如果你需要更多的细节，Google的<a href="http://developer.android.com/sdk/">开发人员网页</a>中详尽地解释了具体的安装配置过程</p>
<ul>
<li>为你的平台下载<a href="http://developer.android.com/">Android      SDK</a>（Windows ， Mac OS X 或者 Linux）。</li>
<li>在你的硬盘上解压下载文件 (在Linux, 我使用 /opt/local/).</li>
<li>如果你没有安装Eclipse，下载并安装<a href="http://eclipse.org/downloads/packages/eclipse-ide-java-developers/galileosr2">Eclipse JAVA 集成开发环境</a>包。 用于编程的话,      Google推荐使用Eclipse 3.5 (Galileo).</li>
<li>运行Eclipse 并选择<em>Help-&gt;Install New      Software</em>.</li>
<li>在Available Software窗口中点击Add按钮。</li>
<li>进入 Android Development Tools 的<em>Name</em>输入框, 在Location      输入框输入https://dl-ssl.google.com/android/eclipse/</li>
<li>检查可用软件中有Developer Tools并点击OK按钮。这将安装Android      Development Tools 和DDMS, Android的调试工具。</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4306" title="install" src="https://coolshell.cn/wp-content/uploads/2011/04/install.gif" alt="" width="500" height="519" /></p>
<ul>
<li>点击Next和Finish按钮以完成安装，安装完成后，你需要重启你的Eclipse一次。</li>
<li>在Eclipse重启后，选择Window-&gt;Preference 后你可以在分类列表中看到Android这一项了。</li>
<li>现在需要告诉Eclipse，你的Android SDK安装在什么地方。点击Android项后浏览选择你解压后的Android SDK所在的路径。例如/opt/local/android-sdk。</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4303" title="eclipse_android_preferences" src="https://coolshell.cn/wp-content/uploads/2011/04/eclipse_android_preferences.jpg" alt="" width="696" height="649" srcset="https://coolshell.cn/wp-content/uploads/2011/04/eclipse_android_preferences.jpg 696w, https://coolshell.cn/wp-content/uploads/2011/04/eclipse_android_preferences-300x279.jpg 300w" sizes="(max-width: 696px) 100vw, 696px" /></p>
<ul>
<li>点击OK按钮，保存信息。</li>
</ul>
<h3>选择Android 平台</h3>
<p>在你开始编写Android应用程序之前，你需要为你需要开发应用程序的Android设备下载SDK平台。每个平台都有可以安装在用户设备上的不同版本的SDK。对于Android1.5或以上版本，有两个可用的平台： <em>Android Open Source Project</em> 和 <em>Google</em>.</p>
<p><em>Android Open Source Project</em> 平台是开源的，但是不包括Google公司的私有化扩展，比如Google Map。如果不选择使用Google的API，Google的地图功能就不会在你的应用程序中生效。除非你有特别的原因，否则我们推荐你选择Google平台，因为这样你可享受到Google的扩展类库提供的便利。</p>
<ul>
<li>选择<em>Window Android SDK and AVD Manager</em>.</li>
<li>点击左栏中的<em>Available Packages</em> 并选择选择Respository中有效的Android SDK平台。</li>
<li>你可以选择列表中所需要的平台，或全选下载所有有效的平台。当你选择完毕，单击<em>Install Selected </em>并完成安装。</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4307" title="sdk" src="https://coolshell.cn/wp-content/uploads/2011/04/sdk.jpg" alt="" width="500" height="291" srcset="https://coolshell.cn/wp-content/uploads/2011/04/sdk.jpg 500w, https://coolshell.cn/wp-content/uploads/2011/04/sdk-300x174.jpg 300w" sizes="(max-width: 500px) 100vw, 500px" /><br />
一旦成功的下载所有的平台后，你就可以准备开始开发Android应用程序了。</p>
<h3>创建一个新的Android项目</h3>
<p>Eclipse的新建项目向导能为你创建一个新的Android项目，并生成可以开始运行的文件和代码。通过向导生成代码，可以让你马上得到一个Android程序运行的直观映像并为你提供了一个帮助你快速入门的方法：</p>
<ul>
<li>选择 <em>File-&gt;New-&gt;Project…</em></li>
<li>选择<em>Android Project</em></li>
<li>在<em>New Project</em> 对话框, 键入如下的设置:</li>
</ul>
<p>[code]<br />
Project Name: BrewClock<br />
Build Target: Google Inc. 1.6 (Api Level 4)<br />
Application Name: BrewClock<br />
Package Name: com.example.brewclock<br />
Create Activity: BrewClockActivity<br />
Min SDK Version: 4<br />
[/code]</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4304" title="eclipse_new_project_settings" src="https://coolshell.cn/wp-content/uploads/2011/04/eclipse_new_project_settings.jpg" alt="" width="525" height="1061" srcset="https://coolshell.cn/wp-content/uploads/2011/04/eclipse_new_project_settings.jpg 525w, https://coolshell.cn/wp-content/uploads/2011/04/eclipse_new_project_settings-148x300.jpg 148w, https://coolshell.cn/wp-content/uploads/2011/04/eclipse_new_project_settings-506x1024.jpg 506w" sizes="(max-width: 525px) 100vw, 525px" /></p>
<p>在点击了完成按钮之后，Eclipse将为你创建一个新的可以运行的Android项目。注意，你通知了Eclipse生成了一个叫做BrewClockActivity的Activity。这个Activity的代码用于运行你的应用程序。生成的代码将在程序运行时非常简单地显示一条“Hello World”消息。</p>
<h4>包</h4>
<p>包名是你的应用程序标示。当你开始准备在Android Market上发布你的应用程序的时候，Android用这个标识符精确地记录你的应用程序的更新过程，因此让包名唯一是非常重要的。尽管我们在这里使用了com.example.brewclock这样的名字空间，对于真实的应用程序，你应该选择类似于com.你的公司名.你的应用程序名 这样的包名。</p>
<h4>SDK 版本</h4>
<p>Min SDK Version 是你的Android程序所能运行得最早版本号。对于每个新发布的Android，SDK会增加并修改一些方法。通过选择一个版本号，Android（Android Market）会知道你的应用程序能运行在等于或晚于指定版本的设备之上。</p>
<h3>运行你的应用程序</h3>
<p>现在让我们开始在Eclipse中运行我们的应用程序。由于是第一次运行，Eclipse将会询问你的项目类型：</p>
<ul>
<li>选择<em>Run-&gt;Run</em> 或 按下 <em>Ctrl+F11</em>.</li>
<li>选择<em>Android Application</em> 并点击 <em>OK </em>按钮.</li>
</ul>
<p>Eclipse 将会在一个Android设备上运行一个应用程序。在这个时候，由于你没有任何Android设备，因此在运行时一定会返回一个失败，并且询问你是否要新建一个Android的虚拟设备。（AVD）<br />
<img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4305" title="eclipse_no_avd" src="https://coolshell.cn/wp-content/uploads/2011/04/eclipse_no_avd.jpg" alt="" width="534" height="172" srcset="https://coolshell.cn/wp-content/uploads/2011/04/eclipse_no_avd.jpg 534w, https://coolshell.cn/wp-content/uploads/2011/04/eclipse_no_avd-300x96.jpg 300w" sizes="(max-width: 534px) 100vw, 534px" /></p>
<h4>Android 虚拟设备</h4>
<p>Android 虚拟设备 (AVD) 是一个模拟真实世界中Android设备的模拟器，例如移动电话或平板电脑。你可以在不买任何真实Android设备情况下，使用AVD测试你的应用。</p>
<p>你可以创建任意多个你喜欢的AVD，每个可以建立在不同版本的Android平台之上。对于你创建的每个Android设备，你可以配置不同的硬件属性，比如是否具有物理键盘，是否支持GPS，摄像头的像素，等等。</p>
<p>在你开始运行你的应用程序之前，你需要创建你的AVD，来运行指定的SDK平台（Google APIs 1.6）。</p>
<p>现在让我开始:</p>
<ul>
<li>如果还没有开始运行你的应用程序，点击run（或按下 <em>Ctrl+F11</em>）。</li>
<li>当目标设备弹出警告，点击<em>Yes</em> 以创建新的AVD。</li>
<li>单击<em>Android SDK and AVD      Manager</em> 对话框内的<em>New</em> 按钮.</li>
<li>为你的AVD键入如下的设置：</li>
</ul>
<p>[code]<br />
Name: Android_1.6<br />
Target: Google APIs (Google Inc.) &#8211; API Level 4<br />
SD Card Size: 16 MiB<br />
Skin Built In: Default (HVGA)<br />
[/code]</p>
<ul>
<li>单击 <em>Create AVD</em> 让Android为你创建一个新虚拟设备。</li>
<li>关闭the <em>Android SDK and AVD Manager</em> 对话框.</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4308" title="sdk_manager_new_avd" src="https://coolshell.cn/wp-content/uploads/2011/04/sdk_manager_new_avd.jpg" alt="" width="400" height="574" srcset="https://coolshell.cn/wp-content/uploads/2011/04/sdk_manager_new_avd.jpg 400w, https://coolshell.cn/wp-content/uploads/2011/04/sdk_manager_new_avd-209x300.jpg 209w" sizes="(max-width: 400px) 100vw, 400px" /></p>
<h4>运行代码</h4>
<p>再次运行你的应用程序（<em>Ctrl+F11</em>）。 Eclipse 将build 你的项目并运行一个新的AVD。记住，AVD模拟了一个完全的Android系统，因此你需要有耐心来等待这个缓慢的启动过程，就如同你重启真实的Android设备一样。一个好的做法是不要关闭你的AVD，直到你完成了你一天的工作。<br />
当你的模拟器启动后，Eclipse自动地安装并运行你的应用程序。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4301" title="app_running-550-e1287474474253" src="https://coolshell.cn/wp-content/uploads/2011/04/app_running-550-e1287474474253.jpg" alt="" width="499" height="355" srcset="https://coolshell.cn/wp-content/uploads/2011/04/app_running-550-e1287474474253.jpg 499w, https://coolshell.cn/wp-content/uploads/2011/04/app_running-550-e1287474474253-300x213.jpg 300w" sizes="(max-width: 499px) 100vw, 499px" /></p>
<h3>开发你第一个Android应用</h3>
<p>生成的代码能良好的运行，但是你真正想要的是开发一个真实的应用程序。为此，我们首先果一个咸蛋的设计流程，并开始创建一个可以让你部署在Android设备上的应用。</p>
<p>大部分的开发者（包括我自己）都喜欢每天一杯咖啡或茶。在下一节中，你将开发一个简单的泡茶计数器应用程序来记录用户泡了多少杯茶，并为泡每杯茶做一个定时器。</p>
<p>你可以从<a href="http://github.com/cblunt/brewclock">GitHub</a>下载整个教程的源代码.</p>
<h4>设计用户界面</h4>
<p>在开发任何Android应用程序之前的第一步就是设计和开发用户界面。下面是一个我们这个应用程序的用户界面的一个概览。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4302" title="design_sketch" src="https://coolshell.cn/wp-content/uploads/2011/04/design_sketch.jpg" alt="" width="331" height="505" srcset="https://coolshell.cn/wp-content/uploads/2011/04/design_sketch.jpg 331w, https://coolshell.cn/wp-content/uploads/2011/04/design_sketch-196x300.jpg 196w" sizes="(max-width: 331px) 100vw, 331px" /></p>
<p>用户将能通过+和-按钮设置一个泡茶的定时器。当单击开始按钮，定时器将开始按指定的时间递减。除非用户再次点击按钮以取消计时，否则当定时器为0的时候，累计的泡茶计数brew将增加1。</p>
<h4>开发用户界面</h4>
<p>Android 用户界面或布局<em>layouts</em>, 是通过XML文档来描述的，可以在项目的res/layouts目录下找到。在之前运行在模拟器上代码中，我们可以看到由eclipse自动生成的布局代码在res/layouts/main.xml 中。</p>
<p>Eclipse有一个图形化的布局设计器，通过在屏幕上的拖拽控制来完成布局的设计，然而，我却发现直接写XML并使用图形布局来预览是更容易的方式。</p>
<p>现在让我们对main.xml做一些工作以达到上图的效果：</p>
<ul>
<li>在Eclipse中通过双击PackageExplorer的res/layouts/main.xml 来打开xml。</li>
<li>点击屏幕下方main.xml 来切换为xml视图。</li>
</ul>
<p>将main.xml中内容改为如下的内容：</p>
<p>[code]<br />
# /res/layouts/main.xml<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br />
&lt;LinearLayout<br />
  xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;<br />
  android:orientation=&quot;vertical&quot;<br />
  android:layout_width=&quot;fill_parent&quot;<br />
  android:layout_height=&quot;fill_parent&quot;&gt;<br />
  &lt;LinearLayout<br />
    android:orientation=&quot;horizontal&quot;<br />
    android:layout_width=&quot;fill_parent&quot;<br />
    android:layout_height=&quot;wrap_content&quot;<br />
    android:padding=&quot;10dip&quot;&gt;<br />
    &lt;TextView<br />
      android:layout_width=&quot;wrap_content&quot;<br />
      android:layout_height=&quot;wrap_content&quot;<br />
      android:textSize=&quot;20dip&quot;<br />
      android:text=&quot;Brews: &quot; /&gt;<br />
    &lt;TextView<br />
      android:layout_width=&quot;fill_parent&quot;<br />
      android:layout_height=&quot;wrap_content&quot;<br />
      android:text=&quot;None&quot;<br />
      android:gravity=&quot;right&quot;<br />
      android:textSize=&quot;20dip&quot;<br />
      android:id=&quot;@+id/brew_count_label&quot; /&gt;<br />
  &lt;/LinearLayout&gt;<br />
  &lt;LinearLayout<br />
    android:orientation=&quot;horizontal&quot;<br />
    android:layout_width=&quot;fill_parent&quot;<br />
    android:layout_height=&quot;wrap_content&quot;<br />
    android:layout_weight=&quot;1&quot;<br />
    android:gravity=&quot;center&quot;<br />
    android:padding=&quot;10dip&quot;&gt;<br />
    &lt;Button<br />
      android:id=&quot;@+id/brew_time_down&quot;<br />
      android:layout_width=&quot;wrap_content&quot;<br />
      android:layout_height=&quot;wrap_content&quot;<br />
      android:text=&quot;-&quot;<br />
      android:textSize=&quot;40dip&quot; /&gt;<br />
    &lt;TextView<br />
      android:id=&quot;@+id/brew_time&quot;<br />
      android:layout_width=&quot;wrap_content&quot;<br />
      android:layout_height=&quot;wrap_content&quot;<br />
      android:text=&quot;0:00&quot;<br />
      android:textSize=&quot;40dip&quot;<br />
      android:padding=&quot;10dip&quot; /&gt;<br />
    &lt;Button<br />
      android:id=&quot;@+id/brew_time_up&quot;<br />
      android:layout_width=&quot;wrap_content&quot;<br />
      android:layout_height=&quot;wrap_content&quot;<br />
      android:text=&quot;+&quot;<br />
      android:textSize=&quot;40dip&quot; /&gt;<br />
  &lt;/LinearLayout&gt;<br />
  &lt;Button<br />
    android:id=&quot;@+id/brew_start&quot;<br />
    android:layout_width=&quot;fill_parent&quot;<br />
    android:layout_height=&quot;wrap_content&quot;<br />
    android:layout_gravity=&quot;bottom&quot;<br />
    android:text=&quot;Start&quot; /&gt;<br />
&lt;/LinearLayout&gt;</p>
<p>[/code]</p>
<p>正如你所见的，Android的XML布局文件是繁琐的，但却能让你控制到屏幕的各个元素。</p>
<p>在Android中最重要的接口元素是布局Layout容器，例如例子中使用的LinearLayout 。这些元素对于用户是不可见的,但是却扮演者例如Buttons 和TextViews这些元素的布局容器。</p>
<p>Android中有几种不同类型的布局视图layout view，每一种都用于开发不同的布局。如同LinearLayout 和AbsoluteLayout ，TableLayout 可以让你使用更为复杂的基于表格结构的布局。你可以在SDK的API文档的<a href="http://developer.android.com/guide/topics/ui/layout-objects.html">通用布局对象</a>中查找到更多的布局。</p>
<h4>关联你的布局Layout与代码</h4>
<p>保存你的布局，在Eclipse中点击<em>Run</em>图标或按下<em>Ctrl+F11</em>重新在模拟器中运行你的程序。你现看到不是之前出现的Hello World消息了，你将看到Android显示了一个新的界面。</p>
<p>如果点击界面上的任何按钮，他们将期望的显示为高亮，但是不会执行任何操作。现在让我们在布局修改后改进一下我们的源码：</p>
<p># /src/com/example/brewclock/BrewClockActivity.java</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
...
import android.widget.Button;
import android.widget.TextView;

public class BrewClockActivity extends Activity {
  /** Properties **/
  protected Button brewAddTime;
  protected Button brewDecreaseTime;
  protected Button startBrew;
  protected TextView brewCountLabel;
  protected TextView brewTimeLabel;

  ...
 }
</pre>
<p>下一步,我们将修改调用onCreate。当Android启动你的应用程序的时候，Android会首先调用这个方法。 在Eclipse生成的代码中，onCreate把activity的视图设置成R.layout.main。这行代码告诉Android解释我们的布局配置XML文件，并显示它。</p>
<h4>资源对象</h4>
<p>在Android中，R是一个自动生成的对象，这是一个特殊的对象，你可以在代码中通过这个对象访问项目中的资源（布局，字符串，菜单，图标，&#8230;） 。每个资源都有一个给定的id。在上面的那个布局文件中，有一些@+id XML 属性。我们将通过这些值来关联布局中的Buttons 与TextViews和我们的代码和：</p>
<p># /src/com/example/brewclock/BrewClockActivity.java</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
...
public class BrewClockActivity extends Activity {
  ...
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Connect interface elements to properties
    brewAddTime = (Button) findViewById(R.id.brew_time_up);
    brewDecreaseTime = (Button) findViewById(R.id.brew_time_down);
    startBrew = (Button) findViewById(R.id.brew_start);
    brewCountLabel = (TextView) findViewById(R.id.brew_count_label);
    brewTimeLabel = (TextView) findViewById(R.id.brew_time);
  }
}
</pre>
<h4>监听事件</h4>
<p>为了检测到用户单击我们的按钮，我们需要实现一个监听器listener。你可能会从其他的事件驱动系统中熟悉监听器或回调函数<em>callbacks</em>。比如Javascript/JQuery事件或Rails的回调函数。</p>
<p>Android通过Listener接口提供相似的机制，例如OnClickListener，这个接口中定义了那些会被事件触发的方法。当用户点击屏幕的时候，实现OnClickListener 接口将会通知你的应用程序，并告诉他们所按得屏幕按钮。你当然也需要告诉每个button的ClickListener，以便Android知道具体通知到那个监听器：</p>
<p># /src/com/example/brewclock/BrewClockActivity.java</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
...
// Be sure not to import
// `android.content.dialoginterface.OnClickListener`.
import android.view.View.OnClickListener;

public class BrewClockActivity extends Activity
  implements OnClickListener {
  ...
  public void onCreate(Bundle savedInstanceState) {
    ...
    // Setup ClickListeners
    brewAddTime.setOnClickListener(this);
    brewDecreaseTime.setOnClickListener(this);
    startBrew.setOnClickListener(this);
  }
  ...
  public void onClick(View v) {
    // TODO: Add code to handle button taps
  }
}
</pre>
<p>下一步，我们将增加每个按钮按下的处理过程。我们将为Activity类增加4个属性，这些属性将用来让用户设置和记录我们泡茶时间，泡茶计数，计时器是否在运行的标志。</p>
<p># /src/com/example/brewclock/BrewClockActivity.java</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
...
public class BrewClockActivity extends Activity
  implements OnClickListener {
  ...
  protected int brewTime = 3;
  protected CountDownTimer brewCountDownTimer;
  protected int brewCount = 0;
  protected boolean isBrewing = false;
  ...
  public void onClick(View v) {
    if(v == brewAddTime)
      setBrewTime(brewTime + 1);
    else if(v == brewDecreaseTime)
      setBrewTime(brewTime -1);
    else if(v == startBrew) {
      if(isBrewing)
        stopBrew();
      else
        startBrew();
    }
  }
}
</pre>
<p>注意我们使用了Android提供的类CountDownTimer 。这让我们非常容易的创建和开始一个简单的递减计数，这个递减计数在递减运行的时候，每当执行一个递减就发出一个通知。你将在下面的startBrew 方法中使用到这个计数器。</p>
<p>在下面的方法是所有处理逻辑，这些处理逻辑用于处理设置泡茶时间，开始停止计数和维护计数器。我们同样地在onCreate方法中来初始化我们的 brewTime和 brewCount变量。</p>
<p>将这些代码放入到不同的类中是一种好做法。但是为了简洁，我把我们所有的代码都放到了BrewClockActivity中：</p>
<p># /src/com/example/brewclock/BrewClockActivity.java</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">
...
public class BrewClockActivity extends Activity
  implements OnClickListener {
  ...
  public void onCreate(Bundle savedInstanceState) {
    ...
    // Set the initial brew values
    setBrewCount(0);
    setBrewTime(3);
  }

  /**
   * Set an absolute value for the number of minutes to brew.
   * Has no effect if a brew is currently running.
   * @param minutes The number of minutes to brew.
   */
  public void setBrewTime(int minutes) {
    if(isBrewing)
      return;

    brewTime = minutes;

    if(brewTime &lt; 1)
      brewTime = 1;

    brewTimeLabel.setText(String.valueOf(brewTime) + &quot;m&quot;);
  }

  /**
   * Set the number of brews that have been made, and update
   * the interface.
   * @param count The new number of brews
   */
  public void setBrewCount(int count) {
    brewCount = count;
    brewCountLabel.setText(String.valueOf(brewCount));
  }

  /**
   * Start the brew timer
   */
  public void startBrew() {
    // Create a new CountDownTimer to track the brew time
    brewCountDownTimer = new CountDownTimer(brewTime * 60 * 1000, 1000) {
      @Override
      public void onTick(long millisUntilFinished) {
        brewTimeLabel.setText(String.valueOf(millisUntilFinished / 1000) + &quot;s&quot;);
      }

      @Override
      public void onFinish() {
        isBrewing = false;
        setBrewCount(brewCount + 1);

        brewTimeLabel.setText(&quot;Brew Up!&quot;);
        startBrew.setText(&quot;Start&quot;);
      }
    };

    brewCountDownTimer.start();
    startBrew.setText(&quot;Stop&quot;);
    isBrewing = true;
  }

  /**
   * Stop the brew timer
   */
  public void stopBrew() {
    if(brewCountDownTimer != null)
      brewCountDownTimer.cancel();

    isBrewing = false;
    startBrew.setText(&quot;Start&quot;);
  }
  ...
}
</pre>
<p>这段代码唯一和Android相关的就是使用setText方法来设置文本的显示文字。在startBrew方法中，我们创建，并开始了一个CountDownTimer来开每秒递减计数直到计数器为0。注意，我们定义了CountDownTimer以内联方式监听onTick 和 onFinish方法。 onTick 方法将每1000毫秒（1秒）执行一次，并递减, 当计数器为0的时候，onFinish方法被调用。</p>
<h4>避免在你的代码中硬编码</h4>
<p>为了使教程代码简单，我故意地在程序中将控件的标号直接写到字串中（例如： &#8220;Brew Up!&#8221;, &#8220;Start&#8221;, &#8220;Stop&#8221;） 通常，这不是一个好的做法，因为如果在大型项目中，这样做会使得修改变得麻烦。</p>
<p>Android 提供了一种简洁的方法让你使用R对象来使字符串和代码分离。R 让你在xml文件（res/values/strings.xml）定义所有你程序中字符串，并让你可以在代码中应用到这些字符串。例如：</p>
<p># /res/values/strings.xml</p>
<p>[code]<br />
&lt;string name=&quot;brew_up_label&quot;&gt;Brew Up!&lt;/string&gt;<br />
&#8230;<br />
[/code]</p>
<p># /res/com/example/brewclock/BrewClockActivity.java</p>
<p>[code]<br />
&#8230;<br />
brewLabel.setText(R.string.brew_up_label);<br />
&#8230;<br />
[/code]</p>
<p>现在，如果你想改变Brew Up! 字样，你只要一次性的修改strings.xml文件就行了。你的应用将生成一堆代码来保证你程序中所有使用到这些字符串的地方都能被生效！</p>
<h4>运行Brew Clock</h4>
<p>代码完成之后，现在是试运行程序的时候了。单击<em>Run</em> 或 <em>Ctrl+F11</em> 在模拟器中启动我们的应用. 所有都运行良好，你将会看到你创建的用户界面在准备时间一到就可以喝你所泡的茶了！试着设置不同的时间，并点击<em>Start</em> 观看倒计时。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-4300" title="app_finished-550-e1287474491689" src="https://coolshell.cn/wp-content/uploads/2011/04/app_finished-550-e1287474491689.jpg" alt="" width="499" height="355" srcset="https://coolshell.cn/wp-content/uploads/2011/04/app_finished-550-e1287474491689.jpg 499w, https://coolshell.cn/wp-content/uploads/2011/04/app_finished-550-e1287474491689-300x213.jpg 300w" sizes="(max-width: 499px) 100vw, 499px" /></p>
<h3>总结</h3>
<p>在这个关于Android的简单介绍中，你已学会如何安装Android SDK和Eclipse的Android 开发工具插件（ADT）。你也学会如何创建一个模拟设备，并通过这个设备来测试你的应用程序。你还学会了如何开发Android应用程序。上面了那些作为标题的关键概念在以后你自己开发Android应用程序的时候将会经常用到。</p>
<p>我们希望，这个教程能激发你的开发移动应用程序的欲望，并步入这个令人激动的领域。Android为当前和即将到来的移动设备应用程序开发提供了一条宽广的道路。如果你已经开发你自己的移动应用，请在评论中告诉我们。</p>
<p><em>(ik), (vf)</em></p>
<p><em>（全文完）</em><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/4334.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/04/1_starting_point_full-150x150.jpg" alt="Eclipse开发Android应用程序入门:重装上阵" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4334.html" class="wp_rp_title">Eclipse开发Android应用程序入门:重装上阵</a></li><li ><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-150x150.jpg" alt="关于移动端的钓鱼式攻击" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_title">关于移动端的钓鱼式攻击</a></li><li ><a href="https://coolshell.cn/articles/12225.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/12/1053-DHH-150x150.jpg" alt="DHH 谈混合移动应用开发" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12225.html" class="wp_rp_title">DHH 谈混合移动应用开发</a></li><li ><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/11/inbox2-640x264-150x150.jpg" alt="Google Inbox如何跨平台重用代码？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_title">Google Inbox如何跨平台重用代码？</a></li><li ><a href="https://coolshell.cn/articles/4220.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/0.jpg" alt="一些有意思的文章和资源" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4220.html" class="wp_rp_title">一些有意思的文章和资源</a></li><li ><a href="https://coolshell.cn/articles/3589.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="食客还是大厨" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3589.html" class="wp_rp_title">食客还是大厨</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/4270.html">Eclipse开发Android应用程序入门</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/4270.html/feed</wfw:commentRss>
			<slash:comments>34</slash:comments>
		
		
			</item>
		<item>
		<title>JavaMail使用</title>
		<link>https://coolshell.cn/articles/4261.html</link>
					<comments>https://coolshell.cn/articles/4261.html#comments</comments>
		
		<dc:creator><![CDATA[jjzhx_1211]]></dc:creator>
		<pubDate>Wed, 06 Apr 2011 15:05:39 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[Web开发]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaMail]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=4261</guid>

					<description><![CDATA[<p>（本文由网友jjzhx_1211投递，感谢!） 使用JavaMail需要两个包：activation-1.1.jar和mail-1.4.2.jar（当然现在最新...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/4261.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/4261.html">JavaMail使用</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>（<strong>本文由网友jjzhx_1211投递，感谢!</strong>）</p>
<p>使用JavaMail需要两个包：activation-1.1.jar和mail-1.4.2.jar（当然现在最新的版本已经不止了），也可以直接包含Java SE 6的j2ee.jar，自带了前面的两个包。我把邮件功能写成了一个服务，发送邮件的数据都通过Map&lt;String, String&gt;类型的参数封装了起来。<strong>代码见文章最后</strong>。</p>
<h4>Session</h4>
<p>Session 定义了一个基本的邮件会话，任何工作都是基于这个Session的。Session 对象需要一个 java.util.Properties 对象来得到类似 邮件服务器，用户名，密码这样的信息。Session 的构造函数是私有的，可以通过 getDefaultInstance() 方法来取得一个单一的可以被共享的默认session 如：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Properties props = new Properties();
Session session = Session.getDefaultInstance(props,null);</pre>
<p>或者，可以使用 getInstance() 方法来创建一个唯一的 session如：</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Properties props = new Properties();
Session session = Session.getInstance(props,null);</pre>
<p>在这两种方法中 其中的 null 参数是一个 Authenticator 对象，在这里没有被使用的，所以就是null。在大多数案例中，使用一个共享session 已经做够了。</p>
<p><span id="more-4261"></span></p>
<h4>Message</h4>
<p>一旦你创建了Session对象，那么下面要做的就是创建message来发送。Message是一个抽象类，在大部分应用中你可以使用它的子类javax.mail.internet.MimeMessage 。MimeMessage 是一个理解在不同RFCs中定义的MIME类型以及headers的e-mail message。Message headers 必须使用 US-ASCII 字符集。可以用如下的方法创建一个Message</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">MimeMessage message = new MimeMessage(session);</code></p>
<p>我们注意到，这里需要用session对象作为构造函数的参数。当然，还有其它的构造函数，比如从用RFC822格式化过的输入流来创建message。</p>
<p>一旦你得到了 message ,你就可以来设置它的各个部分（parts）。设置内容（content）的基本的机制是使用setContent() 方法。</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">message.setContent(&quot;Email Content. &quot;,&quot;text/plain&quot;);</code></p>
<p>如果，你能够明确你的使用MimeMessage来创建message 并且只是使用普通的文本（plain text） 那么你也可以使用 setText() 方法，setTest()方法只需要设置具体的内容，它默认的MIME类型是 text/plain</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">message.setText(&quot;Email Content. &quot;);</code></p>
<p>对于普通文本类型的邮件，有一种机制是首选（ message.setText(&#8220;Email Content. &#8220;)）的设置内容的方法。如果要创建其它类型的message ，比如　HTML类型的message   那么还是需要使用前者　（　message.setContent(&#8220;Email Content. &#8220;,&#8221;text/html&#8221;);　）<br />
设置主题（subject ），使用setSubject() 方法</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">message.setSubject(&quot; Subject &quot;);</code></p>
<h4>Address</h4>
<p>当你已经创建Session 以及 Message，并且已经为message 填充了内容，那么接下来要做的就是给你的邮件添加一个地址（Address）。　就像Message一样，Address也是一个抽象类，我们可以使用它的一个子</p>
<p>javax.mail.internet.InternetAddress</p>
<p>创建一个地址非常简单</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">Address address = new InternetAddress(&quot;&lt;a href=&quot;mailto:suixin@asiainfo.com&quot;&gt;suixin@asiainfo.com&lt;/a&gt;&quot;);</code></p>
<p>如果，你希望在出现邮件地址的地方出现一个名称，那么你只需要再多传递一个参数。</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">Address address = new InternetAddress(&quot;&lt;a href=&quot;mailto:suixin@asiainfo.com&amp;quot;,&amp;quot;Steve&quot;&gt;suixin@asiainfo.com&quot;,&quot;Steve&lt;/a&gt;&quot;);</code></p>
<p>你需要为 message 的from以及 to 字段创建address对象。为了识别发送者，你需要使用setFrom() 和 setReplyTo() 方法。</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">messge.setFrom(address);</code></p>
<p>如果你的message 需要显示多个 from 地址，可以使用 addFrom() 方法</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Address address[] = {....};
message.addFrom(address);</pre>
<p>为了辨识message 的收件人，你需要使用 setRecipient() 方法。这个方法除了address参数之外，还需要一</p>
<p>Message.RecipientType 。<br />
message.addRecipient(type,address);<br />
Message.RecipientType有几个预先定义好的类型<br />
Message.RecipientType.TO　　收件人<br />
Message.RecipientType.CC　　抄送<br />
Message.RecipientType.BCC　 暗送</p>
<p>如果你的一封邮件，需要发送给你的老师，并还要给你的几个同学，那么你可以这样</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Address toAddress = new InternetAddress(&quot;&lt;a href=&quot;mailto:teacher@17288.com&quot;&gt;teacher@17288.com&lt;/a&gt;&quot;);
Address[] ccAddress = {new InternetAddress(&quot;&lt;a href=&quot;mailto:schoolmate1@17288.com&amp;quot;),new&quot;&gt;schoolmate1@17288.com&quot;),new&lt;/a&gt; InternetAddress(&quot;&lt;a href=&quot;mailto:schoolmate2@17288.com&quot;&gt;schoolmate2@17288.com&lt;/a&gt;&quot;)};
message.addRecipient(Message.RecipientType.To, toAddress);
message.addRecipient(Message.RecipientType.CC, ccAddress);</pre>
<p>JavaMail 没有提供电子邮件地址有效性的检测。这些超越了JavaMail API的范围。</p>
<h4>Authenticator</h4>
<p>通过Authenticator设置用户名、密码，来访问受保护的资源，这里的资源一般指的是邮件服务器。</p>
<p>Authenticator也是一个抽象类，你需要自己编写子类已备应用。你需要实现getPasswordAuthentication()方法，并返回一个PasswordAuthentication实例。你必须在 session被创建时， 注册你的 Authenticator。这样，当需要进行认证是，你的Authenticator就可以被得到。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Properties props = new Properties();
//设置属性
Authenticator auth = new YourAuthenticator();
Session session = Session.getDefaultInstance(props, auth);</pre>
<h4>Transport</h4>
<p>发送消息最后的一步就是使用Transport类，你可以通过两种方法来进行发送。<br />
Transport 是一个抽象类，你可以调用它静态的send() 方法来发送</p>
<p><code data-enlighter-language="java" class="EnlighterJSRAW">Transport.send(message);</code></p>
<p>或者，你可以为你使用的协议从session中取得一个指定的实例，</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Transport transport = session.getTransport(&quot;smtp&quot;);
transport.sendMessage(message, message.getAllRecipients());
transport.close();</pre>
<h4>Store and Folder</h4>
<p>这两个类重要用于取得信息。在创建了Session之后，需要连接到一个 Store ，你需要告诉Store你使用的是什么协议。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">// Store store = session.getStore(&quot;imap&quot;);
Store store = session.getStore(&quot;pop3&quot;);
store.connect(host, username, password);</pre>
<p>在连接到一个 Store 后，你可以得到一个 Folder，当然，这个Floder必须是打开的。</p>
<pre data-enlighter-language="java" class="EnlighterJSRAW">Folder folder = store.getFolder(&quot;INBOX&quot;);
folder.open(Folder.READ_ONLY);
Message message[] = folder.getMessages();</pre>
<p>如果使用POP3那么，INDEX是唯一可用的文件夹。如果使用的是IMAP，你就可以使用其它的文件夹。</p>
<h4>代码</h4>
<pre data-enlighter-language="java" class="EnlighterJSRAW">public boolean sendEmail(Map&lt;String, String&gt; data) {
    // 创建Properties 对象
    Properties props = System.getProperties();
    props.put(&quot;mail.smtp.host&quot;, Constants.HOST); // 全局变量
    props.put(&quot;mail.smtp.auth&quot;, &quot;true&quot;);

    // 创建邮件会话
    Session session = Session.getDefaultInstance(props,
    new Authenticator() { // 验账账户
        @Override
        public PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(Constants.USERNAME,
                                              Constants.PASSWORD);
        }
    });

    try {
        // 定义邮件信息
        MimeMessage message = new MimeMessage(session);
        message.setFrom(new InternetAddress(Constants.FROM));
        message.addRecipient(
            Message.RecipientType.TO,
            new InternetAddress(
                // 这里可以添加多个目的用户
                data.get(Constants.EMAIL_TO)
            )
        );
        // 添加邮件发送时间（不知道体现在哪儿）
        message.setSentDate(new Date());
        // 要编码，否则中文会出乱码，貌似这个方法是对数据进行了
        //(&quot;=?GB2312?B?&quot;+enc.encode(subject.getBytes())+&quot;?=&quot;)形势的包装
        message.setSubject(MimeUtility.encodeText(data.get(Constants.EMAIL_SUBJECT), &quot;gbk&quot;, &quot;B&quot;));

        MimeMultipart mmp = new MimeMultipart();
        MimeBodyPart mbp_text = new MimeBodyPart();
        // &quot;text/plain&quot;是文本型，没有样式，
        //&quot;text/html&quot;是html样式，可以解析html标签
        mbp_text.setContent(data.get(Constants.EMAIL_TEXT),
                            &quot;text/html;charset=gbk&quot;);
        mmp.addBodyPart(mbp_text); // 加入邮件正文

        // 处理附件，可以添加多个附件
        if (data.get(Constants.EMAIL_ATTACHMENT) != null) {
            String[] files = data.get(Constants.EMAIL_ATTACHMENT).split(&quot;,&quot;);
            if (files.length != 0) {
                for (String file : files) {
                    MimeBodyPart mbp_file = new MimeBodyPart();
                    FileDataSource fds = new FileDataSource(file);
                    mbp_file.setDataHandler(new DataHandler(fds));
                    mbp_file.setFileName(MimeUtility.encodeText(fds.getName(), &quot;gbk&quot;, &quot;B&quot;));
                    mmp.addBodyPart(mbp_file);
                }
            }
        }
        message.setContent(mmp);
        // message.setText(data.get(Constants.EMAIL_TEXT));

        // 发送消息
        // session.getTransport(&quot;smtp&quot;).send(message); //也可以这样创建Transport对象
        Transport.send(message);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}
</pre>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/03/cow-copy-150x150.jpg" alt="Java中的CopyOnWrite容器" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11175.html" class="wp_rp_title">Java中的CopyOnWrite容器</a></li><li ><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/05/图1-3-150x150.jpg" alt="无锁HashMap的原理与实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9703.html" class="wp_rp_title">无锁HashMap的原理与实现</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/4261.html">JavaMail使用</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/4261.html/feed</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>JDK里的设计模式</title>
		<link>https://coolshell.cn/articles/3320.html</link>
					<comments>https://coolshell.cn/articles/3320.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 26 Nov 2010 00:44:37 +0000</pubDate>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JDK]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=3320</guid>

					<description><![CDATA[<p>下面是JDK中有关23个经典设计模式的示例，在stakeoverflow也有相应的讨论： http://stackoverflow.com/questions/...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/3320.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/3320.html">JDK里的设计模式</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>下面是JDK中有关23个经典设计模式的示例，在stakeoverflow也有相应的讨论：<br />
<a href="http://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns" target="_blank">http://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns</a></p>
<h4><strong><span style="text-decoration: underline;">Structural（结构模式）</span></strong></h4>
<div><strong>Adapter:</strong><br />
把一个接口或是类变成另外一种。</p>
<ul>
<li>java.util.Arrays#asList()</li>
<li>javax.swing.JTable(TableModel)</li>
<li>java.io.InputStreamReader(InputStream)</li>
<li>java.io.OutputStreamWriter(OutputStream)</li>
<li>javax.xml.bind.annotation.adapters.XmlAdapter#marshal()</li>
<li>javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()</li>
</ul>
<p><strong>Bridge:</strong><br />
把抽象和实现解藕，于是接口和实现可在完全独立开来。</p>
<ul>
<li>AWT (提供了抽象层映射于实际的操作系统)</li>
<li>JDBC</li>
</ul>
<p><strong>Composite:</strong><br />
让使用者把单独的对象和组合对象混用。</p>
<ul>
<li>javax.swing.JComponent#add(Component)</li>
<li>java.awt.Container#add(Component)</li>
<li>java.util.Map#putAll(Map)</li>
<li>java.util.List#addAll(Collection)</li>
<li>java.util.Set#addAll(Collection)</li>
</ul>
</div>
<p><span id="more-3320"></span></p>
<div>
<p><strong>Decorator:</strong><br />
为一个对象动态的加上一系列的动作，而不需要因为这些动作的不同而产生大量的继承类。这个模式在JDK中几乎无处不在，所以，下面的列表只是一些典型的。</p>
<ul>
<li>java.io.BufferedInputStream(InputStream)</li>
<li>java.io.DataInputStream(InputStream)</li>
<li>java.io.BufferedOutputStream(OutputStream)</li>
<li>java.util.zip.ZipOutputStream(OutputStream)</li>
<li>java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]()</li>
</ul>
<p><strong>Facade:</strong><br />
用一个简单的接口包状一组组件，接口，抽象或是子系统。</p>
<ul>
<li>java.lang.Class</li>
<li>javax.faces.webapp.FacesServlet</li>
</ul>
<p><strong>Flyweight:</strong><br />
有效率地存储大量的小的对象。</p>
<ul>
<li>java.lang.Integer#valueOf(int)</li>
<li>java.lang.Boolean#valueOf(boolean)</li>
<li>java.lang.Byte#valueOf(byte)</li>
<li>java.lang.Character#valueOf(char)</li>
</ul>
<p><strong>Proxy:</strong><br />
用一个简单的对象来代替一个复杂的对象。</p>
<ul>
<li>java.lang.reflect.Proxy</li>
<li>RMI</li>
</ul>
</div>
<div>
<h4><strong><span style="text-decoration: underline;">Creational（创建模式）</span></strong></h4>
</div>
<div><strong> </strong><strong>Abstract factory:</strong><br />
创建一组有关联的对象实例。这个模式在JDK中也是相当的常见，还有很多的framework例如Spring。我们很容易找到这样的实例。</p>
<ul>
<li>java.util.Calendar#getInstance()</li>
<li>java.util.Arrays#asList()</li>
<li>java.util.ResourceBundle#getBundle()</li>
<li>java.sql.DriverManager#getConnection()</li>
<li>java.sql.Connection#createStatement()</li>
<li>java.sql.Statement#executeQuery()</li>
<li>java.text.NumberFormat#getInstance()</li>
<li>javax.xml.transform.TransformerFactory#newInstance()</li>
</ul>
<p><strong>Builder:</strong><br />
主要用来简化一个复杂的对象的创建。这个模式也可以用来实现一个 <a href="http://en.wikipedia.org/wiki/Fluent_interface" target="_blank">Fluent Interface</a>。</p>
<ul>
<li>java.lang.StringBuilder#append()</li>
<li>java.lang.StringBuffer#append()</li>
<li>java.sql.PreparedStatement</li>
<li>javax.swing.GroupLayout.Group#addComponent()</li>
</ul>
<p><strong>Factory:</strong><br />
简单来说，按照需求返回一个类型的实例。</p>
<ul>
<li>java.lang.Proxy#newProxyInstance()</li>
<li>java.lang.Object#toString()</li>
<li>java.lang.Class#newInstance()</li>
<li>java.lang.reflect.Array#newInstance()</li>
<li>java.lang.reflect.Constructor#newInstance()</li>
<li>java.lang.Boolean#valueOf(String)</li>
<li>java.lang.Class#forName()</li>
</ul>
<p><strong>Prototype:</strong><br />
使用自己的实例创建另一个实例。有时候，创建一个实例然后再把已有实例的值拷贝过去，是一个很复杂的动作。所以，使用这个模式可以避免这样的复杂性。</p>
<ul>
<li>java.lang.Object#clone()</li>
<li>java.lang.Cloneable</li>
</ul>
<p><strong>Singleton:</strong><br />
只允许一个实例。在 Effective Java中建议使用Emun.</p>
<ul>
<li>java.lang.Runtime#getRuntime()</li>
<li>java.awt.Toolkit#getDefaultToolkit()</li>
<li>java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()</li>
<li>java.awt.Desktop#getDesktop()</li>
</ul>
<h4><strong><span style="text-decoration: underline;">Behavioral(行为模式)</span></strong></h4>
<p><strong>Chain of responsibility:</strong><br />
把一个对象在一个链接传递直到被处理。在这个链上的所有的对象有相同的接口（抽象类）但却有不同的实现。</p>
<ul>
<li>java.util.logging.Logger#log()</li>
<li>javax.servlet.Filter#doFilter()</li>
</ul>
<p><strong>Command:</strong><br />
把一个或一些命令封装到一个对象中。</p>
<ul>
<li>java.lang.Runnable</li>
<li>javax.swing.Action</li>
</ul>
<p><strong>Interpreter:</strong><br />
一个语法解释器的模式。</p>
<ul>
<li>java.util.Pattern</li>
<li>java.text.Normalizer</li>
<li>java.text.Format</li>
</ul>
<p><strong>Iterator:</strong><br />
提供一种一致的方法来顺序遍历一个容器中的所有元素。</p>
<ul>
<li>java.util.Iterator</li>
<li>java.util.Enumeration</li>
</ul>
<p><strong>Mediator:</strong><br />
用来减少对象单的直接通讯的依赖关系。使用一个中间类来管理消息的方向。</p>
<ul>
<li>java.util.Timer</li>
<li>java.util.concurrent.Executor#execute()</li>
<li>java.util.concurrent.ExecutorService#submit()</li>
<li>java.lang.reflect.Method#invoke()</li>
</ul>
<p><strong>Memento:</strong><br />
给一个对象的状态做一个快照。Date类在内部使用了一个long型来做这个快照。</p>
<ul>
<li>java.util.Date</li>
<li>java.io.Serializable</li>
</ul>
<p><strong>Null Object:</strong><br />
这个模式用来解决如果一个Collection中没有元素的情况。</p>
<ul>
<li>java.util.Collections#emptyList()</li>
<li>java.util.Collections#emptyMap()</li>
<li>java.util.Collections#emptySet()</li>
</ul>
<p><strong>Observer:</strong><br />
允许一个对象向所有的侦听的对象广播自己的消息或事件。</p>
<ul>
<li>java.util.EventListener</li>
<li>javax.servlet.http.HttpSessionBindingListener</li>
<li>javax.servlet.http.HttpSessionAttributeListener</li>
<li>javax.faces.event.PhaseListener</li>
</ul>
<p><strong>State:</strong><br />
这个模式允许你可以在运行时很容易地根据自身内部的状态改变对象的行为。</p>
<ul>
<li>java.util.Iterator</li>
<li>javax.faces.lifecycle.LifeCycle#execute()</li>
</ul>
<p><strong>Strategy:</strong><br />
定义一组算法，并把其封装到一个对象中。然后在运行时，可以灵活的使用其中的一个算法。</p>
<ul>
<li>java.util.Comparator#compare()</li>
<li>javax.servlet.http.HttpServlet</li>
<li>javax.servlet.Filter#doFilter()</li>
</ul>
<p><strong>Template method:</strong><br />
允许子类重载部分父类而不需要完全重写。</p>
<ul>
<li>java.util.Collections#sort()</li>
<li>java.io.InputStream#skip()</li>
<li>java.io.InputStream#read()</li>
<li>java.util.AbstractList#indexOf()</li>
</ul>
<p><strong>Visitor:</strong></p>
<p>作用于某个对象群中各个对象的操作. 它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作.</p>
<ul>
<li>javax.lang.model.element.Element 和javax.lang.model.element.ElementVisitor</li>
<li>javax.lang.model.type.TypeMirror 和javax.lang.model.type.TypeVisitor</li>
</ul>
<p>（全文完）</p>
</div>
<p><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.k8s-150x150.png" alt="Go 编程模式：k8s Visitor 模式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_title">Go 编程模式：k8s Visitor 模式</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li><li ><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="面向GC的Java编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11541.html" class="wp_rp_title">面向GC的Java编程</a></li><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/3320.html">JDK里的设计模式</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/3320.html/feed</wfw:commentRss>
			<slash:comments>150</slash:comments>
		
		
			</item>
	</channel>
</rss>
