<?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>酷壳 - CoolShell.cn</title>
	<atom:link href="http://coolshell.cn/feed" rel="self" type="application/rss+xml" />
	<link>http://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - http://coolshell.cn</description>
	<lastBuildDate>Wed, 22 May 2013 01:41:08 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>浏览器的渲染原理简介</title>
		<link>http://coolshell.cn/articles/9666.html</link>
		<comments>http://coolshell.cn/articles/9666.html#comments</comments>
		<pubDate>Wed, 22 May 2013 00:17:47 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[Web开发]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[DOM]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9666</guid>
		<description><![CDATA[看到这个标题大家一定会想到这篇神文《How Browsers Work》，这篇文章把浏览器的很多细节讲得很细， [...]]]></description>
				<content:encoded><![CDATA[<p>看到这个标题大家一定会想到这篇神文《<a href="http://taligarsiel.com/Projects/howbrowserswork1.htm" target="_blank">How Browsers Work</a>》，这篇文章把浏览器的很多细节讲得很细，而且也被<a href="http://ux.sohu.com/topics/50972d9ae7de3e752e0081ff" target="_blank">翻译成了中文</a>。为什么我还想写一篇呢？因为两个原因，</p>
<p style="padding-left: 30px;">1）这篇文章太长了，阅读成本太大，不能一口气读完。</p>
<p style="padding-left: 30px;">2）花了大力气读了这篇文章后可以了解很多，但似乎对工作没什么帮助。</p>
<p>所以，我准备写下这篇文章来解决上述两个问题。希望你能在上班途中，或是坐马桶时就能读完，并能从中学会一些能用在工作上的东西。</p>
<h4>浏览器工作大流程</h4>
<p>废话少说，先来看个图：</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9667" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/Render-Process.jpg" width="712" height="231" /></p>
<p style="text-align: left;">从上面这个图中，我们可以看到那么几个事：</p>
<p><span id="more-9666"></span></p>
<p style="text-align: left;">1）浏览器会解析三个东西：</p>
<ul>
<li>一个是HTML/SVG/XHTML，事实上，Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree。</li>
</ul>
<ul>
<li>CSS，解析CSS会产生CSS规则树。</li>
</ul>
<ul>
<li>Javascript，脚本，主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree.</li>
</ul>
<p>2）解析完成后，浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。注意：</p>
<ul>
<li>Rendering Tree 渲染树并不等同于DOM树，因为一些像Header或display:none的东西就没必要放在渲染树中了。</li>
</ul>
<ul>
<li>CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。也就是所谓的Frame。</li>
</ul>
<ul>
<li>然后，计算每个Frame（也就是每个Element）的位置，这又叫layout和reflow过程。</li>
</ul>
<p>3）最后通过调用操作系统Native GUI的API绘制。</p>
<h4>DOM解析</h4>
<p>HTML的DOM Tree解析如下：</p>
<pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Web page parsing&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div&gt;
        &lt;h1&gt;Web page parsing&lt;/h1&gt;
        &lt;p&gt;This is an example Web page.&lt;/p&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>上面这段HTML会解析成这样：</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9669" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/DOM-Tree-01.jpg" width="456" height="300" /></p>
<p>下面是另一个有SVG标签的情况。</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9670" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/DOM-Tree-02.jpg" width="408" height="320" /></p>
<h4>CSS解析</h4>
<p>CSS的解析大概是下面这个样子（下面主要说的是Gecko也就是Firefox的玩法），假设我们有下面的HTML文档：</p>
<pre class="brush: xml; title: ; notranslate">
&lt;doc&gt;
&lt;title&gt;A few quotes&lt;/title&gt;
&lt;para&gt;
  Franklin said that &lt;quote&gt;&quot;A penny saved is a penny earned.&quot;&lt;/quote&gt;
&lt;/para&gt;
&lt;para&gt;
  FDR said &lt;quote&gt;&quot;We have nothing to fear but &lt;span&gt;fear itself.&lt;/span&gt;&quot;&lt;/quote&gt;
&lt;/para&gt;
&lt;/doc&gt;
</pre>
<p>于是DOM Tree是这个样子：</p>
<p><img class="aligncenter size-full wp-image-9672" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/DOM-Tree-Example.jpg" width="368" height="318" /></p>
<p>然后我们的CSS文档是这样的：</p>
<pre class="brush: xml; title: ; notranslate">  /* rule 1 */ doc { display: block; text-indent: 1em; }
/* rule 2 */ title { display: block; font-size: 3em; }
/* rule 3 */ para { display: block; }
/* rule 4 */ [class=&quot;emph&quot;] { font-style: italic; }</pre>
<p>于是我们的CSS Rule Tree会是这个样子：</p>
<p><img class="aligncenter size-full wp-image-9673" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/CSS-Rule-Tree-Example.jpg" width="397" height="238" /></p>
<p>注意，图中的第4条规则出现了两次，一次是独立的，一次是在规则3的子结点。所以，我们可以知道，建立CSS Rule Tree是需要比照着DOM Tree来的。CSS匹配DOM Tree主要是从右到左解析CSS的Selector，好多人以为这个事会比较快，其实并不一定。关键还看我们的CSS的Selector怎么写了。</p>
<p><strong>注意：CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以，你就会在N多地方看到很多人都告诉你，DOM树要小，CSS尽量用id和class，千万不要过渡层叠下去，……</strong></p>
<p>通过这两个树，我们可以得到一个叫Style Context Tree，也就是下面这样（把CSS Rule结点Attach到DOM Tree上）：</p>
<p><img class="aligncenter size-full wp-image-9674" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/CSS-Content-Tree-Example.jpg" width="405" height="318" /></p>
<p>所以，Firefox基本上来说是通过CSS 解析 生成 CSS Rule Tree，然后，通过比对DOM生成Style Context Tree，然后Firefox通过把Style Context Tree和其Render Tree（Frame Tree）关联上，就完成了。注意：Render Tree会把一些不可见的结点去除掉。而<strong>Firefox中所谓的Frame就是一个DOM结点，不要被其名字所迷惑了</strong>。</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9677" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/Firefox-style-context-tree.png" width="328" height="366" /></p>
<p>注：Webkit不像Firefox要用两个树来干这个，Webkit也有Style对象，它直接把这个Style对象存在了相应的DOM结点上了。</p>
<h4>渲染</h4>
<p>渲染的流程基本上如下（黄色的四个步骤）：</p>
<ol>
<li>计算CSS样式</li>
<li>构建Render Tree</li>
<li>Layout &#8211; 定位坐标和大小，是否换行，各种position, overflow, z-index属性 ……</li>
<li>正式开画</li>
</ol>
<p style="text-align: center;"><img class="aligncenter  wp-image-9675" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/Render-Process-Skipping.jpg" width="712" height="196" /></p>
<p style="text-align: left;">注意：上图流程中有很多连接线，这表示了Javascript动态修改了DOM属性或是CSS属会导致重新Layout，有些改变不会，就是那些指到天上的箭头，比如，修改后的CSS rule没有被匹配到，等。</p>
<p style="text-align: left;">这里重要要说两个概念，一个是Reflow，另一个是Repaint。这两个不是一回事。</p>
<ul>
<li>Repaint——屏幕的一部分要重画，比如某个CSS的背景色变了。但是元素的几何尺寸没有变。</li>
</ul>
<ul>
<li>Reflow——意味着元件的几何尺寸变了，我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow，或是Layout。（<strong>HTML使用的是flow based layout，也就是流式布局，所以，如果某元件的几何尺寸发生了变化，需要重新布局，也就叫reflow</strong>）reflow 会从&lt;html&gt;这个root frame开始递归往下，依次计算所有的结点几何尺寸和位置，在reflow过程中，可能会增加一些frame，比如一个文本字符串必需被包装起来。</li>
</ul>
<p>下面是一个打开Wikipedia时的Layout/reflow的视频（注：HTML在初始化的时候也会做一次reflow，叫 <dfn>intial reflow</dfn>），你可以感受一下：</p>
<p><center><object width="480" height="400" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" align="middle"><param name="src" value="http://player.youku.com/player.php/sid/XMzI5MDg0OTA0/v.swf" /><param name="allowfullscreen" value="true" /><param name="quality" value="high" /><param name="allowscriptaccess" value="always" /><embed width="480" height="400" type="application/x-shockwave-flash" src="http://player.youku.com/player.php/sid/XMzI5MDg0OTA0/v.swf" allowfullscreen="true" quality="high" allowscriptaccess="always" align="middle" /></object></center>Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法，一个结点的reflow很有可能导致子结点，甚至父点以及同级结点的reflow。<strong>在一些高性能的电脑上也许还没什么，但是如果reflow发生在手机上，那么这个过程是非常痛苦和耗电的</strong>。</p>
<p>所以，下面这些动作有很大可能会是成本比较高的。</p>
<ul>
<li>当你增加、删除、修改DOM结点时，会导致Reflow或Repaint</li>
<li>当你移动DOM的位置，或是搞个动画的时候。</li>
<li>当你修改CSS样式的时候。</li>
<li>当你Resize窗口的时候（移动端没有这个问题），或是滚动的时候。</li>
<li>当你修改网页的默认字体时。</li>
</ul>
<p style="text-align: left;">注：display:none会触发reflow，而visibility:hidden只会触发repaint，因为没有发现位置变化。</p>
<p style="text-align: left;">多说两句关于滚屏的事，通常来说，如果在滚屏的时候，我们的页面上的所有的像素都会跟着滚动，那么性能上没什么问题，因为我们的显卡对于这种把全屏像素往上往下移的算法是很快。但是如果你有一个fixed的背景图，或是有些Element不跟着滚动，有些Elment是动画，那么这个滚动的动作对于浏览器来说会是相当相当痛苦的一个过程。你可以看到很多这样的网页在滚动的时候性能有多差。因为滚屏也有可能会造成reflow。</p>
<p style="text-align: left;">基本上来说，reflow有如下的几个原因：</p>
<ul>
<li>Initial。网页初始化的时候。</li>
<li>Incremental。一些Javascript在操作DOM Tree时。</li>
<li>Resize。其些元件的尺寸变了。</li>
<li>StyleChange。如果CSS的属性发生变化了。</li>
<li>Dirty。几个Incremental的reflow发生在同一个frame的子树上。</li>
</ul>
<p style="text-align: left;">好了，我们来看一个示例吧：</p>
<pre class="brush: jscript; title: ; notranslate">var bstyle = document.body.style; // cache

bstyle.padding = &quot;20px&quot;; // reflow, repaint
bstyle.border = &quot;10px solid red&quot;; //  再一次的 reflow 和 repaint

bstyle.color = &quot;blue&quot;; // repaint
bstyle.backgroundColor = &quot;#fad&quot;; // repaint

bstyle.fontSize = &quot;2em&quot;; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));</pre>
<p style="text-align: left;">当然，我们的浏览器是聪明的，它不会像上面那样，你每改一次样式，它就reflow或repaint一次。<strong>一般来说，浏览器会把这样的操作积攒一批，然后做一次reflow，这又叫异步reflow或增量异步reflow</strong>。但是有些情况浏览器是不会这么做的，比如：resize窗口，改变了页面默认的字体，等。对于这些操作，浏览器会马上进行reflow。</p>
<p style="text-align: left;">但是有些时候，我们的脚本会阻止浏览器这么干，比如：如果我们请求下面的一些DOM值：</p>
<ol>
<li>offsetTop, offsetLeft, offsetWidth, offsetHeight</li>
<li>scrollTop/Left/Width/Height</li>
<li>clientTop/Left/Width/Height</li>
<li>IE中的 getComputedStyle(), 或 currentStyle</li>
</ol>
<p style="text-align: left;">因为，如果我们的程序需要这些值，那么浏览器需要返回最新的值，而这样一样会flush出去一些样式的改变，从而造成频繁的reflow/repaint。</p>
<h4 style="text-align: left;">减少reflow/repaint</h4>
<p>下面是一些Best Practices：</p>
<p><strong>1）不要一条一条地修改DOM的样式。与其这样，还不如预先定义好css的class，然后修改DOM的className。</strong></p>
<pre class="brush: jscript; title: ; notranslate">// bad
var left = 10,
top = 10;
el.style.left = left + &quot;px&quot;;
el.style.top  = top  + &quot;px&quot;;

// Good
el.className += &quot; theclassname&quot;;

// Good
el.style.cssText += &quot;; left: &quot; + left + &quot;px; top: &quot; + top + &quot;px;&quot;;</pre>
<p><strong>2）把DOM离线后修改。如：</strong></p>
<ul>
<li>使用documentFragment 对象在内存里操作DOM</li>
<li>先把DOM给display:none(有一次repaint)，然后你想怎么改就怎么改。比如修改100次，然后再把他显示出来。</li>
<li>clone一个DOM结点到内存里，然后想怎么改就怎么改，改完后，和在线的那个的交换一下。</li>
</ul>
<p>3）<strong>不要把DOM结点的属性值放在一个循环里当成循环里的变量。</strong>不然这会导致大量地读写这个结点的属性。</p>
<p>4）<strong>尽可能的修改层级比较低的DOM</strong>。当然，改变层级比较底的DOM有可能会造成大面积的reflow，但是也可能影响范围很小。</p>
<p>5）<strong>为动画的HTML元件使用fixed或absoult的position</strong>，那么修改他们的CSS是不会reflow的。</p>
<p>6）<strong>千万不要使用table布局</strong>。因为可能很小的一个小改动会造成整个table的重新布局。</p>
<blockquote><p>In this manner, the user agent can begin to lay out the table once the entire first row has been received. Cells in subsequent rows do not affect column widths. Any cell that has content that overflows uses the ‘overflow’ property to determine whether to clip the overflow content.</p>
<p><cite><a href="http://www.w3.org/TR/CSS21/tables.html#fixed-table-layout">Fixed layout, CSS 2.1 Specification</a></cite></p></blockquote>
<blockquote><p>This algorithm may be inefficient since it requires the user agent to have access to all the content in the table before determining the final layout and may demand more than one pass.</p>
<p><cite><a href="http://www.w3.org/TR/CSS21/tables.html#auto-table-layout">Automatic layout, CSS 2.1 Specification</a></cite></p></blockquote>
<h4>几个工具和几篇文章</h4>
<p>有时候，你会也许会发现在IE下，你不知道你修改了什么东西，结果CPU一下子就上去了到100%，然后过了好几秒钟repaint/reflow才完成，这种事情以IE的年代时经常发生。所以，我们需要一些工具帮我们看看我们的代码里有没有什么不合适的东西。</p>
<ul>
<li>Chrome下，Google的<a href="http://code.google.com/webtoolkit/speedtracer/">SpeedTracer</a>是个非常强悍的工作让你看看你的浏览渲染的成本有多大。其实Safari和Chrome都可以使用开发者工具里的一个Timeline的东东。</li>
</ul>
<ul>
<li>Firefox下这个基于Firebug的叫<a href="https://addons.mozilla.org/en-US/firefox/addon/firebug-paint-events/" target="_blank">Firebug Paint Events</a>的插件也不错。</li>
</ul>
<ul>
<li>IE下你可以用一个叫<a href="http://ajax.dynatrace.com/pages/">dynaTrace</a>的IE扩展。</li>
</ul>
<p>最后，别忘了下面这几篇提高浏览器性能的文章：</p>
<ul>
<li><a href="http://code.google.com/speed/page-speed/docs/rules_intro.html">Google &#8211; Web Performance Best Practices</a></li>
<li><a href="http://developer.yahoo.com/performance/rules.html">Yahoo &#8211; Best Practices for Speeding Up Your Web Site</a></li>
<li><a href="http://stevesouders.com/hpws/rules.php">Steve Souders &#8211; 14 Rules for Faster-Loading Web Sites</a></li>
</ul>
<h4>参考</h4>
<ul>
<li>David Baron的演讲：Fast CSS: How Browsers Lay Out Web Pages：<a href="http://dbaron.org/talks/2012-03-11-sxsw/slide-1.xhtml" target="_blank">slideshow</a>, <a href="http://dbaron.org/talks/2012-03-11-sxsw/master.xhtml">all slides</a>, <a href="http://audio.sxsw.com/2012/podcasts/11-ACC-Fast_CSS_How_Browser_Layout.mp3">audio (MP3)</a>, <a href="http://schedule.sxsw.com/2012/events/event_IAP12909">Session page</a>, <a href="http://lanyrd.com/2012/sxsw-interactive/spmbt/">Lanyrd page</a></li>
</ul>
<ul>
<li>How Browsers Work: <a href="http://taligarsiel.com/Projects/howbrowserswork1.htm" target="_blank">http://taligarsiel.com/Projects/howbrowserswork1.htm</a></li>
</ul>
<ul>
<li>Mozilla 的 Style System Overview：<a href="https://developer.mozilla.org/en-US/docs/Style_System_Overview" target="_blank">https://developer.mozilla.org/en-US/docs/Style_System_Overview</a></li>
</ul>
<ul>
<li>Mozilla 的 Note of reflow： <a href="http://www-archive.mozilla.org/newlayout/doc/reflow.html" target="_blank">http://www-archive.mozilla.org/newlayout/doc/reflow.html</a></li>
</ul>
<ul>
<li>Rendering: repaint, reflow/relayout, restyle：<a href="http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/" target="_blank">http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/</a></li>
</ul>
<ul>
<li>Effective Rendering CSS：<a href="http://css-tricks.com/efficiently-rendering-css/" target="_blank">http://css-tricks.com/efficiently-rendering-css/</a></li>
</ul>
<ul>
<li><strong></strong>Webkit Rendering文档：<a href="http://trac.webkit.org/wiki/WebCoreRendering" target="_blank">http://trac.webkit.org/wiki/WebCoreRendering</a></li>
</ul>
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-3684" data-post-type="none" >2011年02月16日 -- <a href="http://coolshell.cn/articles/3684.html" class="wp_rp_title">Web开发人员速查卡</a></li><li data-position="1" data-poid="in-6840" data-post-type="none" >2012年03月19日 -- <a href="http://coolshell.cn/articles/6840.html" class="wp_rp_title">CSS 布局:40个教程、技巧、例子和最佳实践</a></li><li data-position="2" data-poid="in-4795" data-post-type="none" >2011年06月08日 -- <a href="http://coolshell.cn/articles/4795.html" class="wp_rp_title">开源中最好的Web开发的资源</a></li><li data-position="3" data-poid="in-1949" data-post-type="none" >2009年12月15日 -- <a href="http://coolshell.cn/articles/1949.html" class="wp_rp_title">Web中的省略号</a></li><li data-position="4" data-poid="in-6913" data-post-type="none" >2012年03月24日 -- <a href="http://coolshell.cn/articles/6913.html" class="wp_rp_title">神奇的CSS形状 </a></li><li data-position="5" data-poid="in-3013" data-post-type="none" >2010年09月28日 -- <a href="http://coolshell.cn/articles/3013.html" class="wp_rp_title">一些非常有意思的杂项资源</a></li><li data-position="6" data-poid="in-6043" data-post-type="none" >2011年12月07日 -- <a href="http://coolshell.cn/articles/6043.html" class="wp_rp_title">Web开发中需要了解的东西</a></li><li data-position="7" data-poid="in-5537" data-post-type="none" >2011年11月24日 -- <a href="http://coolshell.cn/articles/5537.html" class="wp_rp_title">一些文章资源和趣闻</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9666.html/feed</wfw:commentRss>
		<slash:comments>24</slash:comments>
<enclosure url="http://audio.sxsw.com/2012/podcasts/11-ACC-Fast_CSS_How_Browser_Layout.mp3" length="20902392" type="audio/mpeg" />
		</item>
		<item>
		<title>疫苗：Java HashMap的死循环</title>
		<link>http://coolshell.cn/articles/9606.html</link>
		<comments>http://coolshell.cn/articles/9606.html#comments</comments>
		<pubDate>Fri, 10 May 2013 00:12:12 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<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[在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障，并且这个事发生了很多次，原因是在Java语言在并 [...]]]></description>
				<content:encoded><![CDATA[<p><img class="size-medium wp-image-9618 alignright" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/race_condition-300x190.jpg" width="300" height="190" />在淘宝内网里看到同事发了贴说了一个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="http://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 class="brush: java; highlight: [19]; title: ; notranslate">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 class="brush: java; highlight: [7]; title: ; notranslate">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 class="brush: java; highlight: [9]; title: ; notranslate">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 class="brush: java; highlight: [12,14,15,16]; title: ; notranslate">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 class="aligncenter  wp-image-9607" alt="" src="http://coolshell.cn//wp-content/uploads/2013/05/HashMap01.jpg" /></p>
<h4>并发下的Rehash</h4>
<p><strong>1）假设我们有两个线程。</strong>我用红色和浅蓝色标注了一下。</p>
<p>我们再回头看一下我们的 transfer代码中的这个细节：</p>
<pre class="brush: java; highlight: [2]; title: ; notranslate">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 class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://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>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8638" data-post-type="none" >2012年11月22日 -- <a href="http://coolshell.cn/articles/8638.html" class="wp_rp_title">为什么不能用微信或米聊这类的软件</a></li><li data-position="1" data-poid="in-6424" data-post-type="none" >2012年01月06日 -- <a href="http://coolshell.cn/articles/6424.html" class="wp_rp_title">Hash Collision DoS 问题</a></li><li data-position="2" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="3" data-poid="in-8138" data-post-type="none" >2012年08月22日 -- <a href="http://coolshell.cn/articles/8138.html" class="wp_rp_title">为什么我反对纯算法面试题</a></li><li data-position="4" data-poid="in-6994" data-post-type="none" >2012年04月11日 -- <a href="http://coolshell.cn/articles/6994.html" class="wp_rp_title">我们需要专职的QA吗？</a></li><li data-position="5" data-poid="in-2483" data-post-type="none" >2010年06月02日 -- <a href="http://coolshell.cn/articles/2483.html" class="wp_rp_title">四个流行的Java连接池</a></li><li data-position="6" data-poid="in-1391" data-post-type="none" >2009年09月03日 -- <a href="http://coolshell.cn/articles/1391.html" class="wp_rp_title">编程真难啊</a></li><li data-position="7" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9606.html/feed</wfw:commentRss>
		<slash:comments>43</slash:comments>
		</item>
		<item>
		<title>“C++的数组不支持多态”？</title>
		<link>http://coolshell.cn/articles/9543.html</link>
		<comments>http://coolshell.cn/articles/9543.html#comments</comments>
		<pubDate>Mon, 29 Apr 2013 08:17:40 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9543</guid>
		<description><![CDATA[先是在微博上看到了个微博和云风的评论，然后我回了“楼主对C的内存管理不了解”。 后来引发了很多人的讨论，大量的 [...]]]></description>
				<content:encoded><![CDATA[<p>先是在微博上看到了个<a href="http://weibo.com/1876004965/zueproucp" target="_blank">微博</a>和云风的评论，然后我回了“楼主对C的内存管理不了解”。</p>
<p><a href="http://coolshell.cn//wp-content/uploads/2013/04/weibo.jpg"><img class="aligncenter size-full wp-image-9544" alt="" src="http://coolshell.cn//wp-content/uploads/2013/04/weibo.jpg" width="580" height="211" /></a></p>
<p>后来引发了很多人的讨论，大量的人又借机来黑C++，比如：</p>
<blockquote><p>//<a href="http://weibo.com/n/Baidu-ThursdayWang">@Baidu-ThursdayWang</a>:这不就c++弱爆了的地方吗，需要记忆太多东西</p>
<p>//<a href="http://weibo.com/n/%E7%BC%96%E7%A8%8B%E6%B5%AA%E5%AD%90%E5%BC%A0%E5%8F%91%E8%B4%A2">@编程浪子张发财</a>:这个跟C关系真不大。不过我得验证一下，感觉真的不应该是这样的。如果基类的析构这种情况不能 调用，就太弱了。</p>
<p>//<a href="http://weibo.com/1401324585" target="_blank">@程序元</a>：现在看来，当初由于毅力不够而没有深入纠缠c++语言特性的各种犄角旮旯的坑爹细枝末节，实是幸事。为现在还沉浸于这些诡异特性并乐此不疲的同志们感到忧伤。</p></blockquote>
<p>然后，也出现了一些乱七八糟的理解：</p>
<p><span id="more-9543"></span></p>
<blockquote><p>//<a href="http://weibo.com/n/BA5BO">@BA5BO</a>: 数组是基于拷贝的，而多态是基于指针的，派生类赋值给基类数组只是拷贝复制了一个基类新对象，当然不需要派生类析构函数</p>
<p>//<a href="http://weibo.com/n/%E7%BC%96%E7%A8%8B%E6%B5%AA%E5%AD%90%E5%BC%A0%E5%8F%91%E8%B4%A2">@编程浪子张发财</a>:我突然理解是怎么回事了，这种情况下数组中各元素都是等长结构体，类型必须一致，的确没法多态。这跟C#和java不同。后两者对于引用类型存放的是对象指针。</p></blockquote>
<p>等等，看来我必需要写一篇博客以正视听了。</p>
<p>因为没有看到上下文，我就猜测讨论的可能会是下面这两种情况之一：</p>
<p style="padding-left: 30px;">1) 一个Base*[]的指针数组中，存放了一堆派生类的指针，这样，你delete [] pBase; 只是把指针数组给删除了，并没有删除指针所指向的对象。这个是最基础的C的问题。你先得for这个指针数组，把数据里的对象都delete掉，然后再删除数组。很明显，这和C++没有什么关系。</p>
<p style="padding-left: 30px;">2）第二种可能是：Base *pBase = new Derived[n] 这样的情况。这种情况下，delete[] pBase 明显不会调用虚析构函数（当然，这并不一定，我后面会说） ，这就是上面云风回的微博。对此，我觉得如果是这个样子，这个程序员<strong>完全没有搞懂C语言中的指针和数组是怎么一回事</strong>，也没有搞清楚， 什么是对象，什么是对象的指针和引用，这完全就是C语言没有学好。</p>
<p>后来，在看到了 <a href="http://weibo.com/n/GeniusVczh">@GeniusVczh</a> 的原文 《<a id="viewpost1_TitleUrl" href="http://www.cppblog.com/vczh/archive/2013/04/27/199765.html">如何设计一门语言（一）——什么是坑(a)</a>》最后时，才知道了说的是第二种情况。也就是下面的这个示例（我加了虚的析构函数这样方便编译）：</p>
<pre class="brush: cpp; title: ; notranslate">class Base
{
  public:
    virtual ~B(){ cout &lt;&lt;&quot;B::~B()&quot;&lt;&lt;endl; }
};

class Derived : public Base
{
  public:
    virtual ~D() { cout &lt;&lt;&quot;D::D~()&quot;&lt;&lt;endl; }
};

Base* pBase = new Derived[10];
delete[] pBase;</pre>
<h4>C语言补课</h4>
<p>我先不说这段C++的程序在什么情况下能正确调用派生类的析构函数，我还是先来说说C语言，这样我在后面说这段代码时你就明白了。</p>
<p>对于上面的：</p>
<pre class="brush: cpp; title: ; notranslate">Base* pBase = new Derived[10];</pre>
<p>这个语言和下面的有什么不同吗？</p>
<pre class="brush: cpp; title: ; notranslate">Derived d[10];

Base* pBase = d;</pre>
<p>一个是堆内存动态分配，一个是栈内存静态分配。只是内存的位置和类型不一样，在语法和使用上没有什么不一样的。（如果你把Base 和 Derived想成struct，把new想成malloc() ，你还觉得这和C++有什么关系吗？）</p>
<p><strong>那么，你觉得pBase这个指针是指向对象的，是对象的引用，还是指向一个数组的，是数组的引用？</strong></p>
<p>于是乎，你可以想像一下下面的场景：</p>
<pre class="brush: cpp; title: ; notranslate">int *pInt; char* pChar;

pInt = (int*)malloc(10*sizeof(int));

pChar = (char*)pInt;</pre>
<p><strong>对上面的pInt和pChar指针来说，pInt[3]和pChar[3]所指向的内容是否一样呢？当然不一样，因为int是4个字节，char是1个字节，步长不一样，所以当然不一样。</strong></p>
<p><strong>那么再回到那个把Derived[]数组的指针转成Base类型的指针pBase，那么pBase[3]是否会指向正确的Derrived[3]呢？</strong></p>
<p>我们来看个纯C语言的例程，下面有两个结构体，就像继承一样，我还别有用心地加了一个void *vptr，好像虚函数表一样：</p>
<pre class="brush: cpp; title: ; notranslate">
    struct A {
        void *vptr;
        int i;
    };

    struct B{
        void *vptr;
        int i;
        char c;
        int j;
    }b[2] ={
        {(void*)0x01, 100, 'a', -1},
        {(void*)0x02, 200, 'A', -2}
    };
</pre>
<p>注意：我用的是G++编译的，在64bits平台上编译的，其中的sizeof(void*)的值是8。</p>
<p>我们看一下栈上内存分配：</p>
<pre class="brush: cpp; title: ; notranslate">
    struct A *pa1 = (struct A*)(b);
</pre>
<p>用gdb我们可以看到下面的情况：(pa1[1]的成员的值完全乱掉了)</p>
<pre class="brush: bash; highlight: [6]; title: ; notranslate">(gdb) p b
$7 = {{vptr = 0x1, i = 100, c = 97 'a', j = -1}, {vptr = 0x2, i = 200, c = 65 'A', j = -2}}
(gdb) p pa1[0]
$8 = {vptr = 0x1, i = 100}
(gdb) p pa1[1]
$9 = {vptr = 0x7fffffffffff, i = 2}
</pre>
<p>我们再来看一下堆上的情况：（我们动态了struct B [2]，然后转成struct A *，然后对其成员操作）</p>
<pre class="brush: cpp; title: ; notranslate">
    struct A *pa = (struct A*)malloc(2*sizeof(struct B));
    struct B *pb = (struct B*)pa；

    pa[0].vptr = (void*) 0x01;
    pa[1].vptr = (void*) 0x02;

    pa[0].i = 100;
    pa[1].i = 200;
</pre>
<p>用gdb来查看一下变量，我们可以看到下面的情况：（pa没问题，但是pb[1]的内存乱掉了）</p>
<pre class="brush: bash; highlight: [8]; title: ; notranslate">(gdb) p pa[0]
$1 = {vptr = 0x1, i = 100}
(gdb) p pa[1]
$2 = {vptr = 0x2, i = 200}
(gdb) p pb[0]
$3 = {vptr = 0x1, i = 100, c = 0 '&#92;&#48;00', j = 2}
(gdb) p pb[1]
$4 = {vptr = 0xc8, i = 0, c = 0 '&#92;&#48;00', j = 0}
</pre>
<p>可见，这完全就是C语言里乱转型造成了内存的混乱，这和C++一点关系都没有。而且，C++的任何一本书都说过，父类对象和子类对象的转型会带来严重的内存问题。</p>
<p>但是，如果在64bits平台下，如果把我们的structB改一下，改成如下（把struct B中的int j给注释掉）：</p>
<pre class="brush: cpp; title: ; notranslate">
    struct A {
        void *vptr;
        int i;
    };

    struct B{
        void *vptr;
        int i;
        char c;
        //int j; &lt;---注释掉int j
    }b[2] ={
        {(void*)0x01, 100, 'a'},
        {(void*)0x02, 200, 'A'}
    };
</pre>
<p>你就会发现，上面的内存混乱的问题都没有了，因为struct A和struct B的size是一样的：</p>
<pre class="brush: bash; title: ; notranslate">(gdb) p sizeof(struct A)
$6 = 16
(gdb) p sizeof(struct B)
$7 = 16</pre>
<p>注：如果不注释int j，那么sizeof(struct B)的值是24。</p>
<p>这就是C语言中的内存对齐，内存对齐的原因就是为了更快的存取内存（详见《<a title="深入理解C语言" href="http://coolshell.cn/articles/5761.html" target="_blank">深入理解C语言</a>》）</p>
<p>如果内存对齐了，而且struct A中的成员的顺序在struct B中是一样的而且在最前面话，那么就没有问题。</p>
<h4>再来看C++的程序</h4>
<p>如果你看过我5年前写的《<strong><a href="http://blog.csdn.net/haoel/article/details/1948051" target="_blank">C++虚函数表解析</a></strong>》以及《<strong>C++内存对象布局 <a href="http://blog.csdn.net/haoel/article/details/3081328" target="_blank">上篇</a>、<a href="http://blog.csdn.net/haoel/article/details/3081385" target="_blank">下篇</a></strong>》，你就知道C++的标准会把虚函数表的指针放在类实例的最前面，你也就知道为什么我别有用心地在struct A和struct B前加了一个 void *vptr。C++之所以要加在最前面就是为了转型后，不会找不到虚表了。</p>
<p>好了，到这里，我们再来看C++，看下面的代码：</p>
<pre class="brush: cpp; title: ; notranslate">
#include
using namespace std;

class B
{
  int b;
  public:
    virtual ~B(){ cout &lt;&lt;&quot;B::~B()&quot;&lt;&lt;endl; }
};

class D: public B
{
  int i;
  public:
    virtual ~D() { cout &lt;&lt;&quot;D::~D()&quot;&lt;&lt;endl; }
};

int main(void)
{
    cout &lt;&lt; &quot;sizeB:&quot; &lt;&lt; sizeof(B) &lt;&lt; &quot; sizeD:&quot;&lt;&lt; sizeof(D) &lt;&lt;endl;
    B *pb = new D[2];

    delete [] pb;

    return 0;
}
</pre>
<p><strong>上面的代码可以正确执行，包括调用子类的虚函数！因为内存对齐了</strong>。在我的64bits的CentOS上——sizeof(B):16 ，sizeof(D):16</p>
<p><strong>但是，如果你在class D中再加一个int成员的问题，这个程序就Segmentation fault了</strong>。因为—— sizeof(B):16 ，sizeof(D):24。pb[1]的虚表找到了一个错误的内存上，内存乱掉了。</p>
<p>再注：我在Visual Studio 2010上做了一下测试，对于 struct 来说，其表现和gcc的是一样的，但对于class的代码来说，其可以“正确调用到虚函数”无论父类和子类有没有一样的size。</p>
<p>然而，在C++的标准中，下面这样的用法是undefined! 你可以看看StackOverflow上的相关问题讨论：《<a title="Why is it undefined behavior to delete[] an array of derived objects via a base pointer?" href="http://stackoverflow.com/questions/6171814/why-is-it-undefined-behavior-to-delete-an-array-of-derived-objects-via-a-base" target="_blank">Why is it undefined behavior to delete[] an array of derived objects via a base pointer?</a>》（同样，你也可以看看《More Effective C++》中的条款三）</p>
<pre class="brush: cpp; title: ; notranslate">Base* pBase = new Derived[10];

delete[] pBase;</pre>
<p>所以，微软C++编程译器define这个事让我非常不解，对微软的C++编译器再度失望，看似默默地把其编译对了很漂亮，实则误导了好多人把这种undefined的东西当成defined来用，还赞扬做得好，真是令人无语。<strong>（</strong><a href="http://weibo.com/2087077260/zup0V7LLM" target="_blank">就像微博上的这个贴一样</a>，说VC多么牛，还说这是OO的特性。我勒个去！<strong>）</strong></p>
<p style="text-align: center;"><a href="http://coolshell.cn//wp-content/uploads/2013/04/hehe.png"><img alt="" src="http://coolshell.cn//wp-content/uploads/2013/04/hehe.png" width="530" height="173" /></a></p>
<p>现在，你终于知道Base* pBase = new Derived[10];这个问题是C语言的转型的问题，你也应该知道用于数组的指针是怎么回事了吧？<strong>这是一个很奇葩的代码！请你不要像那些人一样在微博上和这里的评论里高呼并和我理论到：“微软的C++编译器支持这个事！”。</strong></p>
<p>最后，我越来越发现，<span style="color: #cc0000; font-size: 14px;"><strong>很多说C++难用的人，其实是不懂C语言</strong></span>。</p>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="1" data-poid="in-8309" data-post-type="none" >2012年09月20日 -- <a href="http://coolshell.cn/articles/8309.html" class="wp_rp_title">C/C++语言中闭包的探究及比较</a></li><li data-position="2" data-poid="in-8398" data-post-type="none" >2012年10月16日 -- <a href="http://coolshell.cn/articles/8398.html" class="wp_rp_title">xkcd 神图“Click and Drag”</a></li><li data-position="3" data-poid="in-8138" data-post-type="none" >2012年08月22日 -- <a href="http://coolshell.cn/articles/8138.html" class="wp_rp_title">为什么我反对纯算法面试题</a></li><li data-position="4" data-poid="in-7126" data-post-type="none" >2012年04月19日 -- <a href="http://coolshell.cn/articles/7126.html" class="wp_rp_title">这到底是谁之错？</a></li><li data-position="5" data-poid="in-8275" data-post-type="none" >2012年09月14日 -- <a href="http://coolshell.cn/articles/8275.html" class="wp_rp_title">对九个超级程序员的采访</a></li><li data-position="6" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="7" data-poid="in-8239" data-post-type="none" >2012年09月07日 -- <a href="http://coolshell.cn/articles/8239.html" class="wp_rp_title">无锁队列的实现</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9543.html/feed</wfw:commentRss>
		<slash:comments>124</slash:comments>
		</item>
		<item>
		<title>Unix考古记：一个“遗失”的shell</title>
		<link>http://coolshell.cn/articles/9410.html</link>
		<comments>http://coolshell.cn/articles/9410.html#comments</comments>
		<pubDate>Fri, 26 Apr 2013 14:29:56 +0000</pubDate>
		<dc:creator>Leo</dc:creator>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[轶事趣闻]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[Interpreter]]></category>
		<category><![CDATA[Ken Thompson]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[Unix]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9410</guid>
		<description><![CDATA[(感谢网友Leo投递此文) 谨以此文纪念伟大的计算机科学巨匠Ken Thompson和Dennis Ritch [...]]]></description>
				<content:encoded><![CDATA[<p><span style="color: #cc0000;"><strong>(感谢网友Leo投递此文)</strong></span></p>
<p>谨以此文纪念伟大的计算机科学巨匠<a href="http://en.wikipedia.org/wiki/Ken_Thompson" target="_blank">Ken Thompson</a>和<a href="http://en.wikipedia.org/wiki/Dennis_Ritchie" target="_blank">Dennis Ritchie</a>，并同时向其他所有为Unix发展做出贡献的黑客致敬。</p>
<h4>历史的尘埃</h4>
<p>Unix作为一个举世闻名的操作系统已有40余年的历史，围绕着这个古老的操作系统的发展又衍生出了一系列外围软件生态群，其中一个非常重要的组件就是shell。<strong>它是操作系统最外层的接口，负责直接面向用户交互并提供内核服务，</strong>包括命令行接口(CLI)或图形界面接口(GUI)两种形式。以CLI为例，它提供一套命令规范，是一种解释性语言，将用户输入经过解释器(interpreter)输出使其转化成真正的系统调用，实现人机交互的功能。</p>
<p>和操作系统一样，shell也经历了一个漫长的演变史。如今大部分资料讲述最古老的shell都是从1977年的<a href="http://en.wikipedia.org/wiki/Bourne_shell" target="_blank">Bourne Shell</a>说起的，它最初移植到<a href="http://en.wikipedia.org/wiki/Version_7_Unix" target="_blank">Unix V7</a>上，被追认整个shell家族成员的鼻祖，后来的种群都是从其身上分支出来的。</p>
<p><img class="aligncenter" alt="Linux shells since 1977 " src="https://www.ibm.com/developerworks/linux/library/l-linux-shells/figure1.gif" /></p>
<p>对于1977年之前的历史很多资料大多一笔带过或略过不提。事实上，第一个移植到Unix上的shell却不是<a href="http://en.wikipedia.org/wiki/Stephen_Richard_Bourne" target="_blank">Steve Bourne</a>写的，早在1975年5月，贝尔实验室就对外发布了第一个广泛传播的Unix版本——<a href="http://en.wikipedia.org/wiki/UNIX_V6" target="_blank">Unix V6</a>（之前开发的版本只供内部研究之用），其根目录下的/bin/sh是第一个Unix自带的shell，由Ken Thompson写的，因此也被称为<a href="http://en.wikipedia.org/wiki/Thompson_shell" target="_blank">Thompson Shell</a>。甚至，更早可以追溯到1971年的时候，Thompson Shell就作为一个独立于内核的应用程序而实现了，只不过从1975年正式问世到1977年被取代，短短两年的寿命使得它很少为大多数人所认识。</p>
<p><span id="more-9410"></span></p>
<p>关于Thompson Shell被取代的原因在后文中会给出说明，这里着重介绍一下该shell本身的一些技术细节。坦白讲，关于Thompson Shell的资料有点稀缺，但至少还能从网上找到<a href="http://minnie.tuhs.org/Archive/PDP-11/Distributions/research/Dennis_v6/" target="_blank">源代码</a>和<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man" target="_blank">在线文档</a>。Thompson Shell本身是由一个不足900行代码的解释器和一些外部命令工具组件(utilities)构成，用<a href="http://en.wikipedia.org/wiki/K%26R_C#K.26R_C" target="_blank">K&amp;R C</a>写成，下面给出各个组件的相关源码和文档链接。</p>
<ul>
<li><strong>解释器sh</strong>：解析各种shell命令，包括内置命令和外部命令；源码sh.c；安装路径/bin/sh；手册<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/sh.1" target="_blank">sh(1)</a>。</li>
</ul>
<ul>
<li><strong>内置命令</strong>手册包括<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/chdir.1" target="_blank">chdir(1)</a>，<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/login.1" target="_blank">login(1)</a>，<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/newgrp.1" target="_blank">newgrp(1)</a>，<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/shift.1" target="_blank">shift(1)</a>，<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/wait.1" target="_blank">wait(1)</a>。</li>
</ul>
<p>下面是外部命令：</p>
<ul>
<li><strong>exit命令</strong>：退出一个文件；源码exit.c；安装路径/bin/exit；手册<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/exit.1" target="_blank">exit(1)</a>。</li>
</ul>
<ul>
<li><strong>goto命令</strong>：在一个文件内跳转shell控制流程；源码goto.c；安装路径/bin/goto；手册<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/goto.1" target="_blank">goto(1)</a>。</li>
</ul>
<ul>
<li><strong>if命令</strong>：条件判断表达式，是test命令的前身；源码if.c；安装路径/bin/if), 手册<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/if.1" target="_blank">if(1)</a>。</li>
</ul>
<ul>
<li><strong>glob命令</strong>：扩展命令参数通配符；源码glob.c；安装路径/etc/glob；手册<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man8/glob.8" target="_blank">glob(8)</a>。</li>
</ul>
<h4>命令结构和规范</h4>
<p>尽管后来遭“埋汰”，Thompson Shell仍有着不容否认的历史地位，其最大的价值在于<strong>它奠定了shell命令语言结构和规范的基础，而且其解释器具有跨平台的可移植性，并影响到了后来包括Bourne Shell在内的各种脚本语言设计实现。</strong>下面我们就以其中5个特性重温一些大家已经耳熟能详的命令规范，你也可以通过<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/sh.1" target="_blank">sh(1)</a>手册查看原始资料。</p>
<ul>
<li><strong>过滤器/管道线(filter/pipeline)。</strong>这绝对是要载入Unix史册的发明，创立者是<a href="http://en.wikipedia.org/wiki/Douglas_McIlroy" target="_blank">Douglas McIlroy</a>，Thompson Shell引入并实现了这个伟大的概念——一个或多个命令组成一根过滤器的链条，由&#8217;|'或&#8217;^'符号分隔。除最后一个命令之外，每个命令的标准输出都被作为下一个命令的标准输入。这样每个命令都作为一个独立的进程来运行，并通过管道与邻近的进程相连接。圆括弧内的命令序列整体上可以替代单个命令作为过滤器实现，比如用户可以输入&#8221;(A;B)|C&#8221;。</li>
</ul>
<ul>
<li><strong>命令序列和后台进程。</strong>分号&#8217;;'指示多个命令序列化执行。&#8217;&amp;&#8217;符号指示该命令在后台异步执行，使得前面的管道线不必等待其终止，仅仅报告一个进程id，这样用户以后可以通过kill命令与它通信。有益于进程管理。</li>
</ul>
<ul>
<li><strong>I/O重定向。</strong>它利用了Unix设计上的一个重要特性——<strong>一切皆文件</strong>，用三个符号表示：&#8221;重定向输出，如果文件不存在则创建它，如果文件存在则截断它；&#8217;&gt;&gt;&#8217;追加模式重定向输出，如果文件不存在则创建它，如果文件存在则追加输出至末尾处。</li>
</ul>
<ul>
<li><strong>通配符扩展(globbing)。</strong>通配符的概念源自于正则表达式，使得解释器智能地处理用户不完全输入，比如记不清文件名、一次性输入多个文件等。&#8217;?'匹配任意单一字符；&#8217;*'匹配任意字符串（包括空串）；成对&#8217;['和']&#8216;定义了字符集合一个类，可匹配方括号内任意成员，用&#8217;-'两端可指定一系列连续字符匹配范围。</li>
</ul>
<ul>
<li><strong>参数传递。</strong>这里主要引入了位置参数和选项参数的概念：&#8217;$n&#8217;指示shell调用的第n个参数替代；还定义了两个选项参数&#8217;-t&#8217;和&#8217;-c&#8217;，前者用于交互，导致shell从标准输入中读入一行作为用户执行的系统命令，后者指示shell将附带的下一个参数作为命令执行（可正确处理换行符），是对&#8217;-t&#8217;的补充，特别是调用者已经读取了命令其中某些字符的情况下。如果不带选项参数则直接读取文件名</li>
</ul>
<h4>解释器的原理与实现</h4>
<p>接下来马上要进入核心部分了，为了搞懂shell解释器原理，我们要对其整个工作流程做个描述（这里给出一份带注解的sh.c源码剖析）。读过《编译原理》的同学知道，解释器的实现跟编译器差不多，只不过省略了生成目标代码这一步，直接将用户输入（shell命令）转化成输出（系统调用）。<strong>软件前端是一致的，包括预处理、词法扫描、语法分析和语义分析，最后还要附加一个进程管理。</strong>当然相较于现代编译器，Thompson Shell解释器在算法和规模上都要简单得多，不过原理上是相通的，何况年代上要比Lex &amp; Yacc还要早。麻雀虽小，五脏俱全，对于初学者来说，从Thompson Shell去入手编译原理或许不失为一种好选择。</p>
<h4>预处理(preprocessor)</h4>
<p>同C预处理器需要事先将源代码中包含的宏和头文件展开一样，Thompson Shell首先需要处理命令中的<strong>选项参数</strong>和<strong>位置参数</strong>。选项参数有两种&#8217;-t&#8217;和&#8217;-c&#8217;，决定了shell从标准输入还是参数缓存中读取字符（见<a href="http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/man/man1/sh.1" target="_blank">sh(1)</a>）。此外字符序列中还要处理<strong>反斜杠&#8217;\&#8217;</strong>，判断是转义字符还是行接续符，前者对下一个字符设置引用标识，表明做普通字符处理，后者将紧邻其后换行符过滤掉。</p>
<p>位置参数是<strong>美元符号&#8217;$&#8217;</strong>打头的，后带一个数字，如&#8217;$n&#8217;，预处理器对shell命令参数从头开始计数，返回数字n指定的参数位置。如果遇上double&#8217;$$&#8217;，则表示当前的进程标识，调用getpid()获取。</p>
<p>注意到预处理器需要一次读取多个字符，这样就会多读一个不必要的字符。对此解释器提供了一种<strong>预读(peek)</strong>方式，即每次从输入流读取一个字符时，放入一个预读缓存里（只有一个int大小的堆栈），也叫<strong>回退(push back)</strong>。此后先从预读缓存中读取，如果缓存被读完，则从输入流中读取。</p>
<h4>词法扫描(lexical scanning)</h4>
<p>经过预处理后的字符序列将被切割成为一系列<strong>词法记号(token)</strong>，安置在token列表中，扫描器将对以下几类字符做如下处理。</p>
<ul>
<li><strong>空格和tab</strong>：简单过滤。</li>
</ul>
<ul>
<li><strong>引号</strong>：需要成对出现，字符本身被过滤，一对引号之间所有字符都被设置引用标识，作为一个token。</li>
</ul>
<ul>
<li><strong>元字符</strong>：如&#8217;&amp;&#8217;，&#8217;|'等，字符本身作为一个单独token。</li>
</ul>
<ul>
<li><strong>其他字符</strong>：一律填充token，直到碰上以上字符分隔为止。</li>
</ul>
<p>举一个例子，当我们输入命令&#8221;(ls; cat tail) &gt;junk&#8221;，那么token列表映像将是这样的：</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9537" alt="" src="http://coolshell.cn//wp-content/uploads/2013/04/图1.jpg" width="523" height="176" /></p>
<h4>语法分析(syntax parser)</h4>
<p>语法分析就是将token列表中的元素作为<strong>表达式(expression)</strong>并以节点为单位构建语法树，简单命令是一个表达式，而复合命令以及命令序列是多个表达式的组合。Thompson Shell中以简单数组作为语法树的容器，实际上这是结构体的一种变形，只不过每个成员字段大小都一样（都是sizeof int）而已。一个语法树节点最多有6个字段（大小根据类型可变），分别是</p>
<ul>
<li><strong>DTYP（节点类型）</strong>：每个节点都有唯一的类型，又分为四种——TCOM（简单命令）、TPAR（复合命令）、TFIL（过滤器/管道线）、TLST（命令序列）。</li>
</ul>
<ul>
<li><strong>DLEF（左子树节点）</strong>：相当于链表指针，根据DTYP定义有所不同。如过滤器类型左子树节点为前一个命令的输出重定向文件，右子树节点为后一个命令的输入重定向文件。</li>
</ul>
<ul>
<li><strong>DRIG（右子树节点）</strong>：同上。</li>
</ul>
<ul>
<li><strong>DFLG（节点属性）</strong>：这是个标志位(flag)，决定该节点包含命令的属性以及以什么样的状态执行。</li>
</ul>
<ul>
<li><strong>DSPR（子命令）</strong>：两重含义，对于简单命令，该字段为空；对于复合命令，该字段指向子语法树节点。</li>
</ul>
<ul>
<li><strong>DCOM（命令字符）</strong>：引用命令字符序列。</li>
</ul>
<p>语法树节点生成顺序根据token列表中每个元素的<strong>优先级(priority)</strong>而定，首先遍历整个列表，找到优先级最高的token作为根节点，再分别生成左右子树，这是一种最简单的<strong>自顶向下(top-down)</strong>解决方案。各个token优先级视DTYP字段而定</p>
<table class="aligncenter" width="367" border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="91">
<p align="center">优先级</p>
</td>
<td valign="top" width="180">
<p align="center">Token</p>
</td>
<td valign="top" width="96">
<p align="center">DTYP</p>
</td>
</tr>
<tr>
<td valign="top" width="91">
<p align="center">第一级</p>
</td>
<td valign="top" width="180">
<p align="center">&#8216;&amp;&#8217;  &#8217;;&#8217;  &#8217;\n&#8217;</p>
</td>
<td valign="top" width="96">
<p align="center">TLST</p>
</td>
</tr>
<tr>
<td valign="top" width="91">
<p align="center">第二级</p>
</td>
<td valign="top" width="180">
<p align="center">&#8216;|&#8217;  &#8217;^&#8217;</p>
</td>
<td valign="top" width="96">
<p align="center">TFIL</p>
</td>
</tr>
<tr>
<td valign="top" width="91">
<p align="center">第三级</p>
</td>
<td valign="top" width="180">
<p align="center"> &#8217;(&#8216;  &#8217;)&#8217;</p>
</td>
<td valign="top" width="96">
<p align="center">TPAR</p>
</td>
</tr>
<tr>
<td valign="top" width="91">
<p align="center">第四级</p>
</td>
<td valign="top" width="180">
<p align="center">其它字符</p>
</td>
<td valign="top" width="96">
<p align="center">TCOM</p>
</td>
</tr>
</tbody>
</table>
<p>语法树的构建过程中还使用了一种基于<strong>“有限状态机(finite-state machine)”</strong>的动态规划算法，其实现是将整个逻辑流程划分为四个状态：syntax、syn1、syn2、syn3，对应于上面token优先级，程序在每个状态下都生成一个相应类型的节点，同时还生成四种策略，以决议下一步将转移到何种状态（根据优先级搜索对应的token）。这个四种策略分别是</p>
<ul>
<li><strong>生成左子树</strong>：左边token列表递进到下层状态。</li>
</ul>
<ul>
<li><strong>生成右子树</strong>：右边token列表并回溯到上层状态或递归调用。</li>
</ul>
<ul>
<li><strong>找不到对应token</strong>：保持原有token列表递进到下层状态。</li>
</ul>
<ul>
<li><strong>生成节点</strong>：直接返回节点。</li>
</ul>
<p>当我们遍历完整个token列表后，程序总是能返回最初的调用点，即根节点上，从而生成一棵完整的语法树。这种算法的好处是<strong>程序员不必关注具体实现的每个细枝末节，只要关注相应的状态并制定对应的转移策略即可。</strong>还值得一提的是每个转移策略都是发生在赋值语句或返回语句上，并使用函数实参保存临时变量，这样就避免了调用次数过多导致堆栈溢出。</p>
<p>依旧举两个个例子，比如命令&#8221;A &amp; ; B | C&#8221;对应的语法树</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9538" alt="" src="http://coolshell.cn//wp-content/uploads/2013/04/图2.jpg" width="350" height="264" /></p>
<p>命令&#8221;(A ; B) | C&#8221;对应的语法树：</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9539" alt="" src="http://coolshell.cn//wp-content/uploads/2013/04/图3.jpg" width="350" height="345" /></p>
<h4>语义分析(Semantic Analyzer)</h4>
<p>语法分析仅仅停留在token表达式合法性层面上，它并不知道该表达式是否有意义，比如哪些命令是要后台运行，哪些命令的I/O被重定向到管道线上，通配符该如何扩展等等，这时候要靠语义分析了。这里的“语义”体现在对特殊字符的动态处理以及语法树节点的字段设置，根据<strong>上下文(context)</strong>而定。比如对于元字符&#8217;&gt;&#8217;，我们要判断输出重定向到哪个文件，是截断还是追加。对于通配符&#8217;?'、&#8217;*'和&#8217;[...]&#8216;，我们要决定对哪些字符进行扩展，这些在/etc/glob中专门处理。对于语法树节点，除了自身固有属性之外，还需要继承上层节点的属性，以及下推属性到下层子树节点，下面列了一张表格说明。</p>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="67">
<p align="center">DTYP</p>
</td>
<td valign="top" width="217">
<p align="center">DLEF/DRIG</p>
</td>
<td valign="top" width="227">
<p align="center">DFLG</p>
</td>
<td valign="top" width="57">
<p align="center">DSPR</p>
</td>
</tr>
<tr>
<td valign="top" width="67">
<p align="center">TLST</p>
</td>
<td valign="top" width="217">可以为空，也可以是其它节点，类型可以是TLST/TFIL/TCOM</td>
<td valign="top" width="227">自身属性为0；如果带&#8217;&amp;&#8217;，则下推属性FINT|FAND|FPRS到左右子树（忽略信号、后台异步，打印pid）</td>
<td valign="top" width="57">空</td>
</tr>
<tr>
<td valign="top" width="67">
<p align="center">TFIL</p>
</td>
<td valign="top" width="217">必须同时存在、，类型只能是TCOM或TPAR</td>
<td valign="top" width="227">自身属性继承自上层TLST；下推FPIN到左子树节点；下推FPOU到右子树节点。</td>
<td valign="top" width="57">空</td>
</tr>
<tr>
<td valign="top" width="67">
<p align="center">TPAR</p>
</td>
<td valign="top" width="217">空</td>
<td rowspan="2" valign="top" width="227">继承上层的TLST和TFIL；如果是追加模式重定向输出，加上FCAT；如果是复合命令中最后一个子命令，加上FPAR， 将不会fork子进程。</td>
<td valign="top" width="57">子命令</td>
</tr>
<tr>
<td valign="top" width="67">
<p align="center">TCOM</p>
</td>
<td valign="top" width="217">左子树节点为输入重定向文件，右子树为节点输出重定向文件。</td>
<td valign="top" width="57">空</td>
</tr>
</tbody>
</table>
<h4>执行命令(Executor)</h4>
<p>当前面一系列步骤之后，如果错误计数为0，则解释器从语法树的根节点开始，<strong>深度优先遍历</strong>所有节点，并根据前面语法和语义分析得到的类型和属性，一一执行所包含的命令，以生成最后的系统调用。</p>
<p>对于<strong>命令序列(TLST)节点</strong>，从左至右顺序执行子树节点命令。</p>
<p>对于<strong>过滤器(TFIL)节点</strong>，创建管道文件句柄，作为左右子树的重定向文件。</p>
<p>对于<strong>简单命令(TCOM)和复合命令(TPAR)节点</strong>，首先筛选出系统内置命令(built-in)，对于剩下的外部命令则fork一个子进程执行它。如果是复合命令中最后一个子命令，那么仍在原来的进程上执行而不必创建新进程。可执行文件路径按先后顺序搜索：①本地路径；②/bin；③/usr/bin。</p>
<p><strong>多进程环境下，特别要注意文件句柄管理</strong>。命令间共享标准输入输出设备之外，还会重定向到管道线，而父进程在fork之后子进程会获取一份文件句柄拷贝，所以<span style="color: #ff0000;"><strong>父进程必须在fork之后立即关闭闲置的管道线句柄（如果有的话）以免造成资源泄漏，子进程也将在重定向之后关闭管道线句柄。</strong></span></p>
<p>对于<strong>后台命令</strong>需要打印pid，但不需要响应中断信号，父进程也不必等待子进程终止。其余进程命令执行中可捕获中断信号，并转入相应的处理函数。</p>
<p>解释器用内置的errno全局变量保存进程终止状态，并生成<strong>终止报告(termination report)</strong>，系统调用wait()用于返回终止进程的pid并输出报告消息索引。</p>
<h4>孰优孰劣</h4>
<p>尽管Thompson Shell是一款优秀的命令解释器，还产生了多项历史创举，但遗憾的是依然得不到命运女神的垂青，这要归咎于其自身的缺陷——<strong>功能单一、命令分散、控制流过于简单，尚无法用来编写脚本(script)</strong>。随着Unix日益壮大，它已经无法应付趋于繁杂的编程项目了。那时还出现了一个叫<a href="http://en.wikipedia.org/wiki/John_Mashey" target="_blank">John Mashey</a>的人写的<a href="http://en.wikipedia.org/wiki/PWB_shell" target="_blank">PWB Shell</a>（又叫做Mashey Shell），基于Thompson Shell做了些改进，扩展了命令集，增加了shell变量，还增加了if-then-else-endif，for，while等控制逻辑。不幸的是它比Thompson Shell更短命，因为1977年它遇上了一个强劲的对手。</p>
<p>没错，那就是Bourne Shell，它的主要优点是真正实现了结构化脚本编程，比之前的shell实现得都要好，更要命的是它与前两个shell都不兼容，于是一场标准化的论战开始了。在<a href="http://en.wikipedia.org/wiki/David_Korn_(computer_scientist)" target="_blank">David G. Korn</a>（<a href="http://en.wikipedia.org/wiki/Korn_shell" target="_blank">ksh</a>作者）写的<a href="http://www.in-ulm.de/~mascheck/bourne/korn.html" target="_blank">&#8220;ksh &#8211; An Extensible High Level Language&#8221;</a>一文中提及，Steve Bourne和John Mashey在三次连续的Unix用户组集会上争论他们各自的理由。在这些集会之间，各自增进他们的shell来拥有对方的功能。还设立了一个委员会来选择标准shell，最终还是选择了Bourne shell作为标准。</p>
<p>于是从Unix V7开始就有了前面所说的&#8221;Bourne Shell Family&#8221;。然而历史上没有完美的技术，随着八、九十年代操作系统迅猛发展，针对Bourne Shell的诟病也越来越多了。在解释器本身实现上，我看到网上一个对其评价是<a href="http://lwn.net/Articles/471015/" target="_blank">&#8220;universally considered to be one of the most horrible C code ever written&#8221;</a>，至于原因去看一下mac.h就知道了，包括基本运算符、关键字在内的大量宏定义使得整个代码看上去简直不是C写的，也许Bourne是想把解释器打造成自己独特的风格吧，也难怪后来的bash以<strong>&#8220;born again&#8221;</strong>命名就是对其祖先的戏谑性调侃。另外<a href="http://www.in-ulm.de/~mascheck/bourne/segv.html" target="_blank">内存管理</a>上的一些毛病带来平台可移植性问题，至于其中的技术细节有点高级，超出本文范畴。</p>
<h4>Thompson Again Shell?</h4>
<p>虽然历史没有给Thompson Shell一个机会，但它并非就此同Unix V6那样一同沦为开源博物馆上的古老“化石”。作为出自顶级黑客之手的作品，作为伴随Unix那样伟大操作系统一同曾经流行计算机的产物，至今仍受国内外程序员的缅怀，或将其改写，或为其作注。比如国外一个站点<a href="http://v6shell.org/" target="_blank">v6shell.org</a>上就实现了一个免费开源的可移植性shell，它兼容并扩充原来的Thompson Shell并且可用来做脚本编程。再比如中国程序员<a href="http://blog.chinaunix.net/uid-20106293-id-142129.html" target="_blank">寒蝉退士</a>在其个人博客上发布了一个注解版，并对原版做了一些改写，主要是将<strong>K&amp;R C</strong>转为<strong>ANSI C</strong>，并且符合<strong>POSIX规范</strong>，使原本晦涩难懂的源码变得清晰易读起来。正是因为接触到他的版本激起了我对老Unix的考古兴趣，才有了这篇“考古笔记”。我在想不知今后会不会像bash那样，出一个tash来呢？</p>
<h4>一些感想</h4>
<p>本来全文应该就此结束了，但此时此刻不禁想多说几句。这篇笔记当初并非有意而为之，在hacking源码的过程中感想积累多了也就逐渐成章了。看代码、作注解、查资料、写此文，前后历经四个多礼拜，是在繁杂的工作中“挤乳沟”挤出来的零散时间片拼凑起来的，虽然文字不长但也算耗费了一番心血，酸甜苦辣心中自明，体会到踏上社会之后潜下心做研究之艰难。如今面对这样一份不到900行写成的，没有一行多余的代码，<strong>简洁(clarity)、干净(clean)、快速(fast)，</strong>这就是Pure C的魅力，我深为这种厚重的编程功力所折服，正所谓<strong>“大道至简”</strong>吧。虽然要完全弄懂它需要很多时间，但我相信这种代价却是值得的。</p>
<p>最后再八卦一下，2011年Dennis Ritchie去世了，有人生前问过他“学C需要多久才能成为熟练开发者并写出重要产品代码？”，Ritchie回答“我不知道，我从没去学过C。”<a href="http://www.cs.columbia.edu/~aho/Talks/12-09-07_DMR.pdf" target="_blank">(I don’t know. I never had to learn C.)</a>其实这里已经给出了答案——<strong>那就是没有比去阅读Unix源代码更好的选择了，某种意义上C语言就是为Unix而生的。</strong></p>
<p><img class="aligncenter" alt="Dennis Mac Ritchie" src="http://th05.deviantart.net/fs71/PRE/f/2011/296/7/2/dennis_ritchie_by_juanosborne-d4dooi9.jpg" width="611" height="314" /></p>
<h4>参考资料</h4>
<p><a href="http://www.tuhs.org/" target="_blank">The Unix Heritage Society</a>：Unix社区遗产，上面有v6和v7以及其它一些衍生版本的操作系统源代码。</p>
<p><a href="http://www.in-ulm.de/~mascheck/bourne/" target="_blank">The Traditional Bourne Shell Family</a>：Bourne Shell家族简史。</p>
<p><a href="http://v6shell.org/" target="_blank">v6shell</a>：osh，一个基于Thompson Shell的开源可移植性old shell。</p>
<p><a href="http://blog.chinaunix.net/uid-20106293-id-142129.html" target="_blank">寒蝉退士的博客</a>：Thompson Shell的一个注解版。</p>
<p><a href="https://www.ibm.com/developerworks/linux/library/l-linux-shells/index.html?ca=drs-" target="_blank">Evolution of shells in Linux</a>：简述Linux Shell演变史。</p>
<p>附录一个中文注释的 <a href="http://coolshell.cn//wp-content/uploads/2013/04/shell源码.zip">shell源码</a></p>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8619" data-post-type="none" >2012年11月23日 -- <a href="http://coolshell.cn/articles/8619.html" class="wp_rp_title">你可能不知道的Shell</a></li><li data-position="1" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="2" data-poid="in-1761" data-post-type="none" >2009年11月12日 -- <a href="http://coolshell.cn/articles/1761.html" class="wp_rp_title">Go语言源码的一个改动</a></li><li data-position="3" data-poid="in-8115" data-post-type="none" >2012年08月20日 -- <a href="http://coolshell.cn/articles/8115.html" class="wp_rp_title">GCC 用 C++ 来编译</a></li><li data-position="4" data-poid="in-9070" data-post-type="none" >2013年02月17日 -- <a href="http://coolshell.cn/articles/9070.html" class="wp_rp_title">AWK 简明教程</a></li><li data-position="5" data-poid="in-2322" data-post-type="none" >2010年04月09日 -- <a href="http://coolshell.cn/articles/2322.html" class="wp_rp_title">Unix传奇(上篇)</a></li><li data-position="6" data-poid="in-7886" data-post-type="none" >2012年07月13日 -- <a href="http://coolshell.cn/articles/7886.html" class="wp_rp_title">代码执行的效率</a></li><li data-position="7" data-poid="in-8883" data-post-type="none" >2013年01月09日 -- <a href="http://coolshell.cn/articles/8883.html" class="wp_rp_title">应该知道的Linux技巧</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9410.html/feed</wfw:commentRss>
		<slash:comments>21</slash:comments>
		</item>
		<item>
		<title>PFIF网上寻人协议</title>
		<link>http://coolshell.cn/articles/9508.html</link>
		<comments>http://coolshell.cn/articles/9508.html#comments</comments>
		<pubDate>Sun, 21 Apr 2013 16:20:16 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[技术读物]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Atom]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[PFIF]]></category>
		<category><![CDATA[RSS]]></category>
		<category><![CDATA[XML]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9508</guid>
		<description><![CDATA[本文的主要内容来自Wikipedia(http://en.wikipedia.org/wiki/People_ [...]]]></description>
				<content:encoded><![CDATA[<p>本文的主要内容来自Wikipedia(<a href="http://en.wikipedia.org/wiki/People_Finder_Interchange_Format" target="_blank">http://en.wikipedia.org/wiki/People_Finder_Interchange_Format</a>)</p>
<p>PFIF全称People Finder Interchange Format，是一个应用广泛的数据开源的标准协议，这个协议主要是设计用来在不同的政府、救援组织、或是其它的一些灾难中生存者和其亲人联系的网站间进行数据交换的一种协议。</p>
<p><img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/04/Google-Person-Finder.png" width="492" height="249" /></p>
<p>这个协议基于XML，信息中包括人的身份标识，还有人目前的位置和状态等一些信息。PFIF可以通过Atom和RSS feed出去。PFIF可以允许不同的寻人站点进行数据交换和合并。每一条记录都有一个唯一的标识，这个标识说明了这是由哪个域名创建的。这样，当A站点获得B点的某个人的数据时，在A站点可以对这个人的增加的信息可以转到其它站点上再被增加相关的信息，因为有一个唯一的ID，所以信息可以在不同的站点上被附加。</p>
<p>从wikipedia上看，说起PFIF这个事，得回到2001年的911事件，那时人们一共使用了超过25个不同的在线论坛和网上寻人站来查找相关的亲人和朋友（注：寻人网站英文叫：Survivor Registry，生还者登记网站）。其中一个最大的网站是由伯克利大学的学生Ka-Ping Yee 和 Miriam Walker 开发运行在Millennium计算集群上的safe.millennium.berkeley.edu网站。那时，为了减少各种网站间的混乱，伯克利的寻人网站开始从其它几个比较大的寻人站点收集相关的数据，并人肉整合到一起。</p>
<p><span id="more-9508"></span></p>
<p>2005年，在<a title="Hurricane Katrina" href="http://en.wikipedia.org/wiki/Hurricane_Katrina">卡特里娜飓风</a> 灾难的时候，有数据百万人迁移。于是相关的寻人网站又出现了，而且比911的还要多。于是有很多的志愿者开发了一个叫 <a title="Katrina PeopleFinder Project" href="http://en.wikipedia.org/wiki/Katrina_PeopleFinder_Project">Katrina PeopleFinder Project（卡特里娜寻人项目）</a> 他们人肉地收集不同站点的数据，并统一格式放到一个由Salesfore.com提供一个数据库中。这个项目的组织者David Geilhufe 呼吁一个技术标准以便这些寻人网站间的数据可以自动地整合共享在一起。于是之前伯克利的那个 <a href="http://zesty.ca/" target="_blank">Ka-Ping Yee</a> 开始和志愿者 Kieran Lal，Jonathan Plax 和 <a title="CiviCRM" href="http://en.wikipedia.org/wiki/CiviCRM">CiviCRM</a> 团队一同工作，于是开始了草拟了第一版的PFIF协议，其于2005年9月4日发布，1.1版于第二天发布，其中修改了一些错误。随后，Salesfore.com的数据库开始支持这一标准，然后，Yahoo!和Google的寻人网站也加入这一协议。</p>
<p>接下来， <a title="2010 Haiti earthquake" href="http://en.wikipedia.org/wiki/2010_Haiti_earthquake">2010年的海地地震</a> 时，Google发布了自己的 <a title="Google Person Finder" href="http://en.wikipedia.org/wiki/Google_Person_Finder">Google Person Finder</a>，其基于PFIF协议和CNN，纽约时报，以及美国国家医学图书馆和其它的一些寻人网站进行数据交换。然而，PFIF1.1是基于美国的社会标准搞的，并不适用于海地。于是2010年1月26日，PFIF1.2发布，其增加了几个字段用于标记生还者的国家和国际区号，还有性别，年纪，生日，状态，还有相同人的关联。</p>
<p>PFIF 1.3 于2011年3月发布，其主要解决了个人隐私问题，其加入了一个字段指明该信息的一个有效时间，过期的数据会被删除。PFIF1.3同时移除了英式的first-name和last-name，取而代之的是full-name。</p>
<p>PFIF 1.4 于2012年5月发布，其加入了一个字段用于链接这个人在互联网上的个人资源链接，这样可以用于合并相同的人（比如：指向同一个微博网址），还支持了多个照片。</p>
<p style="text-align: center;"><strong>PFIF1.4的Spec链接：<a href="http://zesty.ca/pfif/1.4/" target="_blank">http://zesty.ca/pfif/1.4/ </a></strong></p>
<p>如下的网站有软件实现了PFIF：</p>
<ul>
<li><a title="Google Person Finder" href="http://en.wikipedia.org/wiki/Google_Person_Finder">Google Person Finder</a></li>
<li><a title="Sahana FOSS Disaster Management System" href="http://en.wikipedia.org/wiki/Sahana_FOSS_Disaster_Management_System">Sahana Eden</a></li>
<li><a href="http://pl.nlm.nih.gov/index.php" rel="nofollow">National Library of Medicine People Locator</a></li>
<li><a title="Ushahidi" href="http://en.wikipedia.org/wiki/Ushahidi#Ushahidi">Ushahidi</a></li>
<li><a href="http://code.google.com/p/pfifnet/" rel="nofollow">PFIF .NET Library</a></li>
<li><a href="http://erislabs.net/ianb/projects/pfif/" rel="nofollow">XML::PFIF Perl module</a></li>
</ul>
<p>本次四川地震，谷歌率先发布了他人寻人网站：<a href="https://google.org/personfinder/2013-sichuan-earthquake" target="_blank">https://google.org/personfinder/2013-sichuan-earthquake</a>。接下来，国内的百度，360，搜索，一淘，CSDN，高德……都发布了自己的寻人网站，微博上，大家都在说这些企业不应该搞这么多这样的网站，这样只会造成混乱。而且大家都在呼吁大家一起运作一个网站，共享数据，共享信息。晚上，我在微博上看到了这个PFIF协议，于是写下这篇文章。</p>
<p>关于Google 的寻人的数据可以通过Google PersonFinder API 下载和上传，这里是其API页面：</p>
<p style="text-align: center;"><strong><a href="http://code.google.com/p/googlepersonfinder/wiki/DataAPI" target="_blank">http://code.google.com/p/googlepersonfinder/wiki/DataAPI</a></strong></p>
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-2105" data-post-type="none" >2010年02月08日 -- <a href="http://coolshell.cn/articles/2105.html" class="wp_rp_title">分享：我的Reader订阅</a></li><li data-position="1" data-poid="in-604" data-post-type="none" >2009年04月22日 -- <a href="http://coolshell.cn/articles/604.html" class="wp_rp_title">早期XML Schema中的open content模型</a></li><li data-position="2" data-poid="in-4905" data-post-type="none" >2011年06月29日 -- <a href="http://coolshell.cn/articles/4905.html" class="wp_rp_title">语言的数据亲和力</a></li><li data-position="3" data-poid="in-3684" data-post-type="none" >2011年02月16日 -- <a href="http://coolshell.cn/articles/3684.html" class="wp_rp_title">Web开发人员速查卡</a></li><li data-position="4" data-poid="in-2504" data-post-type="none" >2010年06月09日 -- <a href="http://coolshell.cn/articles/2504.html" class="wp_rp_title">信XML，得永生！</a></li><li data-position="5" data-poid="in-3585" data-post-type="none" >2011年01月27日 -- <a href="http://coolshell.cn/articles/3585.html" class="wp_rp_title">SOAP的S是Simple</a></li><li data-position="6" data-poid="in-3609" data-post-type="none" >2011年01月28日 -- <a href="http://coolshell.cn/articles/3609.html" class="wp_rp_title">那些炒作过度的技术和概念</a></li><li data-position="7" data-poid="in-3498" data-post-type="none" >2011年01月19日 -- <a href="http://coolshell.cn/articles/3498.html" class="wp_rp_title">信XML，得自信</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9508.html/feed</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>“作环保的程序员，从不用百度开始”</title>
		<link>http://coolshell.cn/articles/9308.html</link>
		<comments>http://coolshell.cn/articles/9308.html#comments</comments>
		<pubDate>Sat, 23 Mar 2013 13:47:20 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[轶事趣闻]]></category>
		<category><![CDATA[baidu]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9308</guid>
		<description><![CDATA[酷壳对来自百度搜索引擎的访问会弹窗，但是我的这个行为发酵出了一些事情，这里把这个事情说明如下，我会更新相关的东 [...]]]></description>
				<content:encoded><![CDATA[<p>酷壳对来自百度搜索引擎的访问会弹窗，但是我的这个行为发酵出了一些事情，这里把这个事情说明如下，我会更新相关的东西。内行看门道，外行看热闹。</p>
<h4>事由</h4>
<p><strong>2月6日</strong> 看到<a href="http://weibo.com/1497035431/zi69DBK3b" target="_blank">梁斌同学的微博</a>（起因可能是因为梁斌同学在微博上对帮助百度的一些工程师们说话导致他的“<a href="http://xunren.thuir.org/" target="_blank">微博寻人</a>”全站被百度屏蔽）</p>
<p><img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/01.png" width="591" height="348" /></p>
<p>我看到后，觉得梁斌同学有点太看重被百度收录了，没有站长应该有的气质，所以，我<a href="http://weibo.com/1401880315/zibYUvZYd" target="_blank">回了一个微博</a>——</p>
<blockquote><p>“我的酷壳倒反而因为被百度收录而感到掉价！”</p></blockquote>
<p><strong>2月6日当天</strong>，我给coolshell做了个弹窗，并发布微博—— （该微博目前已被新浪管理员删除，后面有说明）</p>
<p><span id="more-9308"></span></p>
<blockquote><p>“搞定收工！从百度访问过来的访问弹出对话框。（CoolShell上的网页有缓存，要过些时间才有效）”</p>
<p><img class="aligncenter size-full wp-image-9315" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/02.png" width="522" height="261" /></p></blockquote>
<p><strong>2月21日</strong>：百度的法律顾问发来邮件。</p>
<blockquote><p>From: xxxxxx@baidu.com<br />
To: haoel@hotmail.com<br />
CC: xxxxxx@baidu.com<br />
Subject: 答复: 网站coolshell.cn弹窗事宜<br />
Date: Thu, 21 Feb 2013 07:05:09 +0000</p>
<p>陈浩，您好！</p>
<p>我是百度法务部法律顾问，就您的网站上有贬损百度商标的弹窗，以及通过微博等途径予以传播事宜，我们希望您及时终止。</p>
<p>如您不希望百度搜索收录您的网页，您可以通过Robots 协议予以规定。关于如何禁止百度Robots收录您的网站，如您需要技术方面的支持，我可以协助联系百度的工程师与您沟通。</p>
<p>如有任何问题，请随时联系。</p>
<p>谢谢！</p>
<p>段志勇</p></blockquote>
<p>我当天回复邮件到——</p>
<blockquote><p>『我是酷壳的法律顾问，请百度停止收录酷壳的网页，以及在所有百度产品线里删除酷壳的文章，尤其是百度文库里我所有的文章和PPT，你们已经违反了中华人民共和国版权著作法，酷壳将保留行使法律的权力』</p></blockquote>
<p><strong>3月2日</strong>：<a href="http://service.account.weibo.com/show?rid=K1CaJ6QFe6K4d" target="_blank">新浪微博举报大厅</a>。（把我2月6日弹窗的微博给删除了，注意，其中没有我自辩的过程，还有其中荒唐的逻辑）</p>
<p style="padding-left: 30px;"><a href="http://service.account.weibo.com/show?rid=K1CaJ6QFe6K4d" target="_blank">http://service.account.weibo.com/show?rid=K1CaJ6QFe6K4d</a></p>
<p style="padding-left: 30px;">我问新浪为什么没有我自辩的过程，新浪微博客服回服如下：</p>
<blockquote><p> 尊敬的新浪微博用户： 您好！关于您反馈的被举报问题，经核实此判决符合社区公约规定判定无误，感谢您的支持，祝您生活愉快~~</p></blockquote>
<p style="padding-left: 30px;">我没有多理会，留下一条“<a href="http://weibo.com/1401880315/zlCT8v4si" target="_blank">多谢新浪和百度的自黑</a>”的微博我也没管这事了。</p>
<p><strong>3月22日</strong>：收到了来自百度律师代理的邮件，如下：</p>
<blockquote><p>From: xxxxx@teehowe.com<br />
To: haoel@hotmail.com<br />
Subject: 关于贵方酷壳网弹窗构成对百度公司的不正当竞争事宜<br />
Date: Fri, 22 Mar 2013 10:07:10 +0800</p>
<p>陈先生，您好！</p>
<p>我们，北京天昊联合知识产权代理有限公司，受百度在线网络技术（北京）有限公司（以下简称“百度公司”）委托就题述事宜特致函贵方（委托书请见附件）。</p>
<p>百度公司近日发现：用户在使用谷歌、360等浏览器通过百度搜索访问您方酷壳网（<a title="http://coolshell.cn/" href="http://coolshell.cn/" target="_blank">http://coolshell.cn/</a>）时，会弹窗一个小窗，上面将百度LOGO打叉，并使用“DO EVIL”、“做环保的程序员，从不用百度开始！”等标语，详细截图后附。我们认为：您方弹窗所含图像及语言描述缺乏事实基础，带有较强的感情色彩，足以误导互联网用户对百度公司产生不合理的怀疑乃至负面评价，从而对百度公司的商业信誉和品牌形象带来一定程度的贬损。根据《反不正当竞争法》第2、14、20条之规定，您方行为已构成对百度公司的不正当竞争。</p>
<p>我们希望您方在收到此函后，清除所有相关侵权程序，立即停止对百度公司的所有侵权行为。我所当事人要求：贵方最迟于<strong><span style="text-decoration: underline;">2013年3月25日</span></strong>前向以下通信地址做出实质回应：</p>
<p>联系人：郑洪<br />
地址：北京市东城区建国门内大街28号民生金融中心D座10层<br />
邮编：100005<br />
电话：010-8529 5526<br />
传真：010-8529 5528</p>
<p>此信函不影响我方当事人依法所享有的其他任何权利或法律救济途径。我们希望此纠纷能尽快解决，以维护互联网市场的健康有序发展。</p>
<p>期待你方及时回复。如有任何问题，请随时与我们联系！</p>
<p>郑洪</p></blockquote>
<p>弹窗的抓图附件我就不列了，其中有一个委托书附件如下：</p>
<p style="text-align: center;"><img class="aligncenter  wp-image-9324" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/委托书.png" width="470" height="500" /></p>
<h4>几个观点</h4>
<p><strong>1）我非常不喜欢百度公司的非常浓重的商业化</strong></p>
<p style="padding-left: 30px;">我在《<strong><a href="http://coolshell.cn/articles/7186.html" target="_blank">做个环保主义的程序员</a></strong>》一文中说过一些百度的问题，如：</p>
<ul>
<li><strong>搜索结果很差</strong>。一些非技术的东西都搜不出来。技术文章就更不要说了。再比如百度抓取酷壳的网页，一方面是不及时，另一方面是有选择地抓，很多网页并没有抓取到源文，而是抓取到那些转载过去没有注明出处的网站，像《<strong><a href="http://coolshell.cn/articles/7186.html" target="_blank">做个环保主义的程序员</a></strong>》文章发布一年多了，过去的一年在百度里就查不到（这几天又能查到了）。（<strong>我很想了解百度的一些抓取网页的算法和搜索排名的算法，感觉相当诡异</strong>）</li>
</ul>
<ul>
<li><strong>有很多虚假广告</strong>。<strong>我觉得一家公司商业化并没有什么问题，但是这种商业化不应建立在牺牲用户利益的基础上的，这是最最基本的底线</strong>。我觉得百度的商业上在这方面突破了太多的底线。</li>
</ul>
<p><strong>2）百度应该可以做得更好</strong></p>
<p style="padding-left: 30px;">@<a title="陈晓鸣在百度" href="http://weibo.com/acumon">陈晓鸣在百度</a>在私下给我介绍了一些百度的广告方面的技术细节，说是以前的那个竞价排名不存在了。但是难免有一些垃圾和造假。就像淘宝一样也有假货和诈骗。是的，<strong>这中国目前这个大环境下，要有一个干净的平台的确不容易。但是我希望百度能像淘宝一样，在业务上做一些打击虚假信息的活动——建立举报制，曝光所有的虚假和欺诈信息，并有一些惩罚措施。可惜百度做得还很不够主动</strong>。（<span style="color: #cc0000;"><strong>与其花时间在我这里，不如花时间做好你自己的事</strong></span>）</p>
<p style="padding-left: 30px;"><strong>灰尘总是会有的，重点不在于灰尘和垃圾总是会有，重点在于想不想打扫。想不想打扫这是态度问题</strong>。</p>
<p><strong>3）看不起百度并不是看不起百度的技术人员</strong></p>
<p style="padding-left: 30px;"><strong>我是比较敬重百度的技术人员的。我还是能够“一分为二的看问题”</strong>。比如：deep learning专家余凯、主导凤巢设计的戴文渊，自然语言处理顶级会议的首任华人主席王海峰，架构专家，移动云技术负责人林仕鼎等等。都是值得我学习的很不错的技术牛人。</p>
<p style="padding-left: 30px;">我一向是站在技术人员这边的。这点，在这个事件中也不会改变。<strong>我还是会推荐一些刚毕业的实在找不到更好工作的学生去百度</strong>。正如我在《<a title="来信， 创业 和 移动互联网" href="http://coolshell.cn/articles/5815.html" target="_blank">来信，创业，移动互联网</a>》一文中说的那样。入世和出世，取其精华去其糟粕。</p>
<p>4）<strong>关于弹窗这个事</strong></p>
<p style="padding-left: 30px;">关于弹窗这个事，<strong>我非常高兴酷壳成为了百度的竞争对手</strong>。我会接受网友的意见，<span style="color: #cc0000;"><strong>我会将把弹窗这个事变成不弹窗，直接嵌在酷壳的每一篇文章里</strong></span>。酷壳上基本坚持不投放任何广告，这回一定要做个公益广告。</p>
<p style="padding-left: 30px;">关于法律上的一些事情，我无所谓，<span style="color: #cc0000;"><strong>随时欢迎百度来起诉我，不来起诉就是怂包</strong></span>。以前当过原告起诉过清华大学出版社，今天当个被告，这样我的人生经历就完整了。大家知道，人生经历对我很重要。</p>
<p><strong>5）感动和回报</strong></p>
<p style="padding-left: 30px;">我把百度委托律师给我的邮件放到了我的微博里（<a title="新浪微博上的百度律师邮件" href="http://weibo.com/1401880315/zoF7ucEeR" target="_blank">点击这里</a>），很多朋友说要捐钱给我打官司。这点到是不需要了。但是我真的很感动。所以——</p>
<p style="padding-left: 30px;"><span style="color: #cc0000; font-size: 16px;"><strong>我觉得我应该更多的珍惜大家对我的支持，我愿意自己出钱，来鼓励那些想环保不用百度的程序员，尤其是那些囊中羞涩的学生可以更好地使用互联网。如果你们在访问一些网站有什么困难的话，可以私下联系我，我愿意为你们提供相关的技术和资金支持。这个事只能在私下做，你们懂的</strong></span>。</p>
<p style="padding-left: 30px;">我个人用的是购买了一个最便宜的国外VPS（<a href="http://www.laoyao.cc/post/2808.html" target="_blank">关于VPS，你可以看看这篇文章</a>），然后用chrome + SwitchySharp + myentunnel + SSH的方案（SSH帐号你可以google免费的，但是要很努力，你也可以自己买一个，可以搜一下“购买SSH帐号”），这样的方法可以在网上搜。比如这篇文章： <a href="http://handsomeliuyang.iteye.com/blog/1290229" target="_blank">http://handsomeliuyang.iteye.com/blog/1290229</a></p>
<h4>附录：弹窗代码</h4>
<p>大家问我那个弹窗是怎么做的，很简单的，可以看看coolshell.cn的源代码。就是从referrer中匹配baidu。我用了jquery的一个插件：<a href="http://dinbror.dk/bpopup/" target="_blank">bPopup</a>，关于那个no baidu插图来自：<a href="http://www.douban.com/online/10132155/" target="_blank">豆瓣的拒绝百度的兴趣小组</a>。</p>
<p>源码如下：<strong><a href="http://weibo.com/n/Ninja_Lu" target="_blank">@Ninja_Lu</a> 做了一个github的：<a href="https://github.com/lurongkai/anti-baidu" target="_blank">https://github.com/lurongkai/anti-baidu </a></strong></p>
<pre class="brush: xml; title: ; notranslate">
&lt;script src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;http://coolshell.cn/wp-content/themes/inove/js/jquery.bpopup-0.8.0.min.js&quot;&gt;&lt;/script&gt;

&lt;script type=&quot;text/javascript&quot;&gt;
;(function($) {
    $(function() {
        var url=document.referrer;
        if ( url &amp;&amp; url.search(&quot;http://&quot;)&gt;-1) {
            var refurl =  url.match(/:\/\/(.[^/]+)/)[1];
            if(refurl.indexOf(&quot;baidu.com&quot;)&gt;-1){
                $('#nobaidu_dlg').bPopup();
            }
        }
    });

})(jQuery);
&lt;/script&gt;

&lt;div id=&quot;nobaidu_dlg&quot; style=&quot;background-color:#fff; border-radius:15px;color:#000;display:none;padding:20px;min-width:450px;min-height:180px;&quot;&gt;
    &lt;img src=&quot;http://coolshell.cn/wp-content/themes/inove/img/nobaidu.jpg&quot; align=&quot;left&quot;&gt;
     &lt;p style=&quot;margin-left:200px;margin-top: 20px; line-height: 30px;&quot;&gt;
     检测到你还在使用百度这个搜索引擎，&lt;br/&gt;
     做为一个程序员，这是一种自暴自弃！&lt;br/&gt;
     &lt;br/&gt;
     &lt;/p&gt;
     &lt;p align=&quot;center&quot; style=&quot;margin-top:20px;&quot;&gt;
     &lt;b&gt;&lt;a href=&quot;http://coolshell.cn/articles/7186.html&quot;&gt;作环保的程序员，从不用百度开始！&lt;/a&gt;&lt;/b&gt;
     &lt;/p&gt;
&lt;/div&gt;
</pre>
<p>P.S. robots.txt我已经加上了。</p>
<p>（全文完，谢谢大家的支持）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8170" data-post-type="none" >2012年08月27日 -- <a href="http://coolshell.cn/articles/8170.html" class="wp_rp_title">一次Ajax查错的经历</a></li><li data-position="1" data-poid="in-7048" data-post-type="none" >2012年04月17日 -- <a href="http://coolshell.cn/articles/7048.html" class="wp_rp_title">挑战无处不在</a></li><li data-position="2" data-poid="in-8138" data-post-type="none" >2012年08月22日 -- <a href="http://coolshell.cn/articles/8138.html" class="wp_rp_title">为什么我反对纯算法面试题</a></li><li data-position="3" data-poid="in-7126" data-post-type="none" >2012年04月19日 -- <a href="http://coolshell.cn/articles/7126.html" class="wp_rp_title">这到底是谁之错？</a></li><li data-position="4" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="5" data-poid="in-6994" data-post-type="none" >2012年04月11日 -- <a href="http://coolshell.cn/articles/6994.html" class="wp_rp_title">我们需要专职的QA吗？</a></li><li data-position="6" data-poid="in-8398" data-post-type="none" >2012年10月16日 -- <a href="http://coolshell.cn/articles/8398.html" class="wp_rp_title">xkcd 神图“Click and Drag”</a></li><li data-position="7" data-poid="in-8387" data-post-type="none" >2012年10月14日 -- <a href="http://coolshell.cn/articles/8387.html" class="wp_rp_title">Bret Victor &#8211; Learnable Programming</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9308.html/feed</wfw:commentRss>
		<slash:comments>565</slash:comments>
		</item>
		<item>
		<title>《Rework》摘录及感想</title>
		<link>http://coolshell.cn/articles/9156.html</link>
		<comments>http://coolshell.cn/articles/9156.html#comments</comments>
		<pubDate>Mon, 11 Mar 2013 00:25:34 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[技术管理]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[职场生涯]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Rework]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9156</guid>
		<description><![CDATA[读了《Rework》这本书好多遍，每次读都有不同的感想。但从来没有把这些感想记录下来，今天把《Rework》书 [...]]]></description>
				<content:encoded><![CDATA[<p><img class="alignright  wp-image-9277" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/rework.jpg" width="235" height="360" />读了《Rework》这本书好多遍，每次读都有不同的感想。但从来没有把这些感想记录下来，今天把《Rework》书中的一些章节做一些摘录，并把我的一些感想总结出来。供大家参考。这是一本平生以来让我中毒很深的书，也是一本让我思考得很多的书。希望看到这篇文章的人都能好好地读读这本书。这本书并不难读，是一本你可以一口气不中断就可以读完的书。</p>
<h4>现实世界</h4>
<p>“这在现实世界里面行不通”，当你向人们介绍一个新创意时，人们总是这么回答你。这个“现实世界”听起来如此令人沮丧，……只有人耳熟能详，习以为常的事情才会胜利，即使是这些事情已经漏洞百出陈腐低效。</p>
<p>揭开“现实世界”这个锅盖，你会发现居住在里的人都充斥着悲观主义和失望的情绪。更糟的是，他们想将别人拖进他们的坟墓。如果你是充满希望和野心的人，他们会试着说服你，你的想法是不可能的。他们会说你在浪费时间。</p>
<p><strong>“现实世界”并不存在，那只是人的一个借口。只是某些人为了开脱 自己的无所作为，跟你一点关系也没有。</strong></p>
<blockquote><p><strong>感想</strong>：我经常会向一同事和朋友提及一些我的想法，朋友同事们经常会回答我——这个事某某人，某某团队做过了，没成功。或是对我说，你做这个事的时候，要小心这个要小心那个。我觉得，这个时候是最考验我们的时候了，要有一个清醒的头脑去分析别人的话，别人真不代表自己。这个世界上大多数人都是比较保守的，大多数都对这个现实世界都有或多或少的恐惧感。当然，你可以选择做大众，但是如果你想让你的人生有些不同，有些精彩，我还是建议你不要和大多数人想得一样，<strong>如果你和大多数人的想法一样，你必然会和大多数人一样的平庸</strong>。当然，如果你和大多数人不一样，你要么就是天才，要么就是傻瓜。要证明你自己是不是傻瓜，我们可以看看我们过去有没有过一些小成功或小成绩。如果有，那么就应该大胆地坚持自己的想法。</p></blockquote>
<h4>被高估的“从错误中学习”</h4>
<p>你真的从错误和失败里面学到什么了吗？你也许学到了别再重蹈覆辙，但是这有什么意义吗？你仍然不知道接下来该做什么。</p>
<p><span id="more-9156"></span></p>
<p><strong>相反的应该从成功中汲取养分。成功給予真正靠得住的教材。</strong></p>
<p>失败并不是成功的先决条件。自然规律是，<strong>逗留在过去的失败中是无法进化的，进化是建立在成功的基础上的</strong>。</p>
<blockquote><p><strong>感想</strong>：我见过和很多人都在抱怨这不好那不好，但是他们其实并不知道什么是好的，因为——没有见过好的，你将永远不知道什么是好的。就好像你没有见过什么是汽车，你就只会整天在抱怨为什么骑自行车太累。回头想想我们的编程的这个过程也是一样，我们编程技能的提高基本上都是在看到别人的那些漂亮优雅的代码。所以，你一定要去看看那些优秀人干是怎么想的，怎么干的，去那些成功的公司开开眼界。另外，你应该多想想你过去做成功过什么事？那些才是你的长处，才是让你进化的前提。</p></blockquote>
<h4>计划就是瞎猜</h4>
<p>除非你是算命先生，长期的商业计划是种幻想。有太多的事实证明那是超出你的掌控的：市场环境、对手、顾客、经济等等。做计划让你觉得一切尽在掌握但实际上你没有。</p>
<p><strong>当你把计划变成猜测时，就等于进入一个危险的境地。做计划就是在用过去推导未来，等于给你戴上了眼罩。</strong></p>
<blockquote><p><strong>感想</strong>：你有职业规划吗？如果你有的话，那么你就一定就错了。职业规划是一件很扯淡的事情。我和一些高手都交流过，其实这些人在当初都并不有什么职业规划的，要说有的话，也就是想把技术搞透搞精。这些人在一开始从来没有想过要当个什么经理或是什么架构师之类的东西，这些人就是对技术有非常大的热情，把身边的那些看得见够得着的事情做到好好地，并且保持不持续强大的好奇心努力地学习自己不懂的东西。一个坚定不移的决定和意志力会比任何的计划和职业规划都重要。<strong>你问问自己，想不想当程序员，能不能一辈子都当一个程序员，能不能写程序写一辈子？</strong>（关于做一辈子程序员这个事，大家可以看看我的<a href="http://weibo.com/1401880315/zmebaF5tQ" target="_blank">新浪微博</a> ——<em>没哪个行业能像计算机行业这么活跃、刺激和有趣了。不仅是新兴工业革命的主力，又渗入到所有的行业中，干一辈子值了。//<a href="http://weibo.com/n/_%E4%BD%A0%E4%BA%B2%E7%88%B1%E7%9A%84%E5%81%8F%E6%89%A7%E7%8B%82">@_你亲爱的偏执狂</a>: 程序员首先是工程师，Professional，就跟律师，医生一样，给大家解决问题；但是另一面呢，又是艺术家，创造新奇好玩的东西。这样的职业做一辈子有什么问题？</em>）</p></blockquote>
<h4>拒绝壮大</h4>
<p>规模越大你就得承受更大压力、需要更专业、拥有更强的能力。</p>
<p><strong>有没有注意到，一个小公司希望自己变大时，大公司却想要变得灵活变通</strong>。记住，一旦你变大了就很难在不解雇人、不破坏士气、不改变你的整个商业路线的情况下收缩规模。</p>
<p>扩张不必成为你的目标。我们也不是仅在讨论你已有员工数。 还有花费、租金、IT 基础结构、设备等。这些事情不会碰巧发生。 你来决定是否承受这些。如果你决定去承受，你也将遇到新的头痛问题。花费那么多，你强迫自己构建一个复杂的生意，有一大堆困难而高压的事情要解决。</p>
<p><strong>小公司并不是一个起步，小公司本身就是一个伟大的目标。</strong></p>
<blockquote><p><strong>感想</strong>：很多人都会以为拥有一支成百上千人的团队而成为一个成功的标志。就像很多朋友和猎头都会问我管多少人，当我说，我就管个十人不到的团队时，他们似乎都会觉得我很平庸。他们中的一些人基本上就不会再问我在干些什么了，因为他们可能觉得这么少的人都干什么大事呢？。当然，我说了他们也不一定听得懂。人多可能恰恰说明你可能在干一个劳动密集型的事情，这并没有什么可自豪的。真正自豪的不是在战争中用人海战术让大量的人去当炮灰，而是用一个小分队端掉敌军的军火库或指挥部。所以，<strong>关键不是你有多少人，关键是你做的事是不是有非凡的意义，而且你用了最小当量的资源。这就好像建立一个高性能的网站一样，用成百上千的服务器不算本事，谁用的少才是本事</strong>。</p></blockquote>
<h4>工作狂</h4>
<p>工作狂的行为不但没有必要，而且愚蠢至极。过多的工作并不代表你对项目更关注，也不代表你作了更多的贡献，这仅仅意味着你干了更多的活而已。<strong>工作狂制造的麻烦比解决的麻烦多</strong>。</p>
<p>工作狂往往不得要领。他们花大把大把的时间去解决问题，<strong>他们以为能靠蛮力来弥补思维上的惰性，其结果就是折腾出一堆粗糙无用的解决方案</strong>。</p>
<p><strong>如果你只是为了工作而工作，那么你就会丧失判断力。你的价值 观和决策方式都是扭曲</strong>。你没有能力去判断哪些工作值得做，哪些工作该放弃，最后搞得自己筋疲力尽，而一个筋疲力尽的人是无法作出明智的决定的。</p>
<p><strong>工作狂不是英雄。他们不是在节约时间而是在浪费生命。真正的英雄早已想出了办法，搞定一切，然后回家了。</strong></p>
<blockquote><p><strong>感想</strong>：这让我想到了那些为了冲业绩的业绩KPI的制订者们，很多时候，他们的价值观和决策真是的很扭曲的。他们生生地把一种技术密集型的工作变成了劳动密集型。<strong>他们其实就是在拼命地训练客户需要的那匹“更快的马”，而从来没有想过要去造个更快的交通工具。</strong></p>
<p>另外，每当我在优秀员工的评比和员工的绩效考核中的跨团队比较中我们能听到很多很多的人说，XX员工工作任劳任愿，工作得很晚很晚，付出很大。老实说，我真的为这样的价值观感到悲哀。最后，我还想说说关于超时工作，我也经常学习和做自己的事情到深夜，我相信很多人也这样，但我们应该认真思考一下Rework中的这个观点，<strong>我们超时工作是在使用蛮力呢？还是在使用热情和兴趣呢？</strong></p></blockquote>
<h4>挠自己的痒处</h4>
<p>想要创造一款伟大的产品或者是某项卓越的服务，最直接、最简单的方法就是去做你自己想用的东西。设计你了解的产品——你就能很快发现它到到底好不好用。</p>
<p><strong>最棒的是，“解决你实际遇到的问题”会让你爱上你做的事情</strong>。 你知道问题所在并且熟知解决它的价值。这是无法替代的。毕竟，你会充满希望的在接下来的日子里继续做。 甚至会占据你余生所有时间。所以，最好还是做自己真正关心的东西。</p>
<blockquote><p><strong>感想</strong>：这就是吃自己的狗食，做自己感兴趣的事。软件项目中，我最恨的就是那种闭门造车造出来的自己都不用的东西（不是从已有业务生长出来的东西），以及那些自己不动手就在边上指指点点的各种咨询师或是喜欢动用行政命令的高层管理者。</p>
<p>但是，在这里，我更想说说我所理解的另一层“挠自己痒处”——有天我和一前前同事聊天，她说她在那家公司十多年了，现在老了，虽然心不老还想折腾，但是对自己的能力没自信，求稳了。我听到很多朋友想对自己有个改变，比如有QA的同学想做开发，有生活在内地的朋友想来大城市的大公司里有更爽的经历，<strong>这些人明明想活得更有激情，但最终在现实面前认命妥协。我说既然有痒处，还比较痒，那就应该毫不犹豫革自己的命，轰轰烈烈地活一次</strong>。别等老了后悔当年没有勇气。“挠自己痒处”就是挑战自己，革自己的命，既然想了，就做吧，生命只有一次，值得我们轰轰烈烈地去为之付出。</p></blockquote>
<h4>“没时间”不是借口</h4>
<p>人们最常用的借口是：“时间不够。”他们宣称很想开一家公司，学一种乐器，写一本书，等等，但时间不够用。拜托，如果你善加利用，时间总是有的。</p>
<p>把看电视或玩魔兽的时间腾出来完成你的创意；把10点上订改成11点上床，这不是怂恿你通宵达旦或是一天干足16个小时——我们要说的是，第周匀出一些业余时间来，就足够你去做些事情了。</p>
<p>当你拥有某种强烈的渴望时，你就能挤出时间来——不管你身上是否背负着其他责任。<strong>事实上，真相是大多数的渴望并不是那么强烈。于是他们拿时间当借口来自我开脱。别给自己错口。</strong></p>
<p>另外，永远会有正当其时的时候，你总会觉得自己会么太年轻，要么太老，要么太忙，太穷，或是别的什么原因。<strong>如果你总是为遇到一个完美时机而发愁，那么，完美的时机绝对不会到来</strong>。</p>
<blockquote><p><strong>感想</strong>：我在“<a title="挑战无处不在 " href="http://coolshell.cn/articles/7048.html" target="_blank">挑战无处不在</a>”中也表达过这样的观点，<strong>关于热情和态度，说白了就是不要给自己找借口</strong>。比如：“工作忙事多没时间学所以可以不懂”，“工作中没用到所以可以不懂”，“工作没有挑战，一直没有遇到合适的项目”等等。而且，如果你只能在万事俱备的情况下才能做事，那么，你还有什么价值呢？人的价值和竞争力就是在条件并不完美的时候还能搞定事情。</p></blockquote>
<h4>画沙为界，立场明确</h4>
<p>坚定的信念能为你赢得超级粉丝，他们会为你马首是瞻，会舍身保护你，他们充满激情的口碑传播将胜过这世间一切的广告。</p>
<p>强大的主见，也是要付出代价的，在这个过程中，会有人诋毁你，说像傲慢，冷漠。没办法，这就是人生，有人喜欢你，就有人憎恨你。如果你的说法没有引起任何人的心烦意乱，只能说明你的推广力度可能还不够。（也可能代表你比较无趣）</p>
<p><strong>对我们来说，我们的产品所不能处理的和我们的产品所能处理的一样令人感到骄傲</strong>。</p>
<p>我们的产品不适合每一个人，没有关系，我们愿意为了那些更加深爱我们的客户而放弃另一部分客户。这就是我们的立场。</p>
<blockquote><p><strong>感想</strong>：我从来不想做一个大众脸。酷壳上有很多比较有争议的文章，也有很多人说我很极端，偏执，有优越感，清高……，说什么的都有，无所谓。我有一个做新闻编辑的太太，主辑要求文章要客观和没有观点，不温不火，本来好好的一篇有观点的文章被编辑过后只剩下了一堆食之无味的文字。<strong>我喜欢有鲜明的观点，因为鲜明的观点和立场能不但能让文章鲜活起来，而且还能迎来更多的不同意见和更多的思考</strong>（而不只是“顶”“赞”之类无意义的回复）。我并不希望我的观点是正确的，我只希望能和更多的人加入我一同思考，而思考最佳的催化剂就是争论。我从这个行为中收益到了很多很多。</p></blockquote>
<h4>找好退路无异于失败</h4>
<p>你还常常听到：“你的退出战略是什么？（万一不成功，你怎么办）”甚至在你刚开始启动时就听到它。这些人不知道怎么开始就要想到怎么结束？急什么呢？如果在全情投入之前就想怎么撤出，这种逻辑不是一般的混乱。</p>
<p>你正打算恋爱一场就计划着分手？你在第一次约会时就签订婚前协议？你会在婚礼早上先约见离婚律师？那也太荒谬了吧。</p>
<p><strong>你需要的是承诺战略而不是退出战略。你要考虑的是你的项目怎样发展和成功，而不是怎样撤退</strong>。如果整个战略是基于撤退的，一开始你就不会有机会成功。</p>
<blockquote><p><strong>感想</strong>：几年前，我有一个朋友被创新工场忽悠从美国退学回来创业，我非常质疑他退学创业这个事。他对我说，没事，反正就算失败我也不会失去什么。还有一个朋友一年前从美国回国创业，也对我说，就算没搞好也没什么。我都对他们说，如果你以为用试一试的态度就可以把一个事情搞成功，那么你让这世上那些Full Time全天候从事这个事情的并有一些积累的人情何以堪？如果你创业时都想好了失败，那就说你你对这个事没有必胜的信心，也说明连你自己都不相信这个事，你还干个什么劲啊？<strong>你与其把时间用在思考如果创业没成功你会怎么办上，你还如去思考一下如何做才有更大的胜算</strong>。</p></blockquote>
<h4>条件受限是好事</h4>
<p>“我没有足够的时间、钱、人手、经验”。不要现无谓的抱怨了。“少”不是什么坏事。“条件受限”貌似缺陷，实力优势。有限的资源能激发你在现有的条件下完成任务的能力。没有一点浪费空间，一切都需要你发挥最大的创造力。</p>
<p>你见过囚犯用肥皂和汤勺制作武器吗？你们是“创新”的典范。只有在条件受到限制时，我们才会发挥出“小材大用”的能力。</p>
<blockquote><p><strong>感想</strong>：我相信这世上很多事情都是被条件受限逼过去的。我回想到我以前经常在干的性能调优，想尽一切办法榨干系统资源这件事上，我就无法不赞同这句话。想想淘宝的TFS，就是一个因为条件受限到了不得不自己干的时候，被逼出来的东西。如果你没有足够多的人，你才会去想要怎么去优化工作和开发效率，于是才会逼着你去开发一些自动化的工具，而这些工具恰恰解放了生产力可以让你更快地干更多的事。<strong>只有条件受限，才会从劳动密集型中激发出知识密集型的东西</strong>。再回到以前我的那篇“<a title="我们需要专职的QA吗？" href="http://coolshell.cn/articles/6994.html" target="_blank">是否需要专职的QA</a>”一文说的到东西，如果你有很多很多帮你做测试的QA，你就不会去测试，你的团队也就不会有自动化测试等工具。这就好像在中国这个劳动力又多又廉价的大国下，基本上不需要你在技术上的创新，你只需要去不断地迁就这些低端用户，迁就这些用户越多，你还能有什么重大创新吗？真正的创新是帮助用户成长，而不是迁就用户。</p></blockquote>
<h4>与其做个半成品，不如做好半个产品</h4>
<p>同时做N件事的结果就是：一大把绝妙的点子最后被转化成一个蹩脚的产品。</p>
<p>有舍才有得，砍掉多余的野心，你就会发现慢慢做一件正事要胜过毛毛躁躁地做一堆傻事。</p>
<p>很多东西都是越简短越好。拿起斧子动手砍吧，为了一个“伟 大”的起点，让我们把那些“挺不错”地枝节给砍掉吧。</p>
<blockquote><p><strong>感想</strong>：这正如“<a title="为什么中国的网页设计那么烂？" href="http://coolshell.cn/articles/3605.html" target="_blank">为什么中国的网页设计这么烂</a>”中说的：“中国的学生只是去记忆东西而不是真正的理解。他们从来不花时间去思考，而只是贪婪地去获取更多的信息”。与其记忆那么多的东西，还不如好好理解部分的东西。还有一种说法是：“Done is better than Perfect!”，这句话某些时候说得也挺对的，尤其是对于那些完美地长期不能Done的项目。但是Done一个Ugly的东西还不如不做。所以平衡Done和Perfect的方式正好就是这句话——“与其做个半成品，不好做好半个产品”，因为，<strong>一个半成品会让人绝望，而半个好产品会让人有所期望，这就是其中的不同</strong>。</p></blockquote>
<h4>关注不变因素</h4>
<p><strong>很多公司和人都关注即将到来的大事件。他们热衷于新鲜热辣的事物，追逐最新的潮流和技术</strong>。</p>
<p>这是一条愚笨之路。一旦走上这条路，你就会关注时髦、放弃本质，把注意力放到不断变化的事物上，而不是持久不变的事物上。</p>
<p>你的事业的核心应该建立在不变的基础之上。<strong>你应该投资于那些人们现在需要，并且十年后仍然需要的事物上</strong>。</p>
<p>要记住，时尚会凋零。只有当你聚焦于长久的功能时，你才会发现自己把握住了永不落伍的东西。</p>
<blockquote><p><strong>感想</strong>：一年多前，我在《<a href="http://coolshell.cn/articles/5815.html" target="_blank">来信、创业和移动互联网</a>》中谈到过那个时尚的“移动互联网”，说了四个方向：阅读，分享交流，电商，推荐/提醒。大家可以看到现在地铁上已经不像以前很多人都在看报纸了，而是很多人都在看手机。而手机端的社交（分享和交流），电子商务，以及很多推荐、提醒都越来越火了。这些东西都是都是“常量”——十年前存在，未来十年也会存在，我们看到很多人太过着眼于手机上的应用，而不是那些不变的因素。今天还有两个巨火无比的流行词，一个是云计算，一个是大数据，那些一听到这两个词就会兴奋的人，我不知道他们有没有真正理解这两词？他们真正理解了云计算其实就是那个N多年前就提过的IT服务，关于大数据，我完全不知道为什么会火，你会因为听到中国人口有13亿你就会兴奋吗？老鼠的数量比较这个更多呢，呵呵。其实，数据无所谓大小之分，只有好数据和烂数据之分，还热数据和冷数据之分。十年前有两个更为流行的词：一个是计算网格，一个是数据网格，这两个词5年前就凋零了，今天的云计算和大数据，有多少人意识到了其中有什么相通的，或是其中的不变因素是什么？<strong>大数据和云计算其实都在描述两个东西，一个是超大规模的计算能力，另一个则是服务。还有一个词是“平台化”，这可能被大家忽略了，通过平台进行计算和数据服务，这才是那计算机存在以来基本不变的东西，无论你是移动互联网，还是互联网，不管是云计算，还是大数据，都需要一个平台提供服务</strong>。</p></blockquote>
<h4>会议有毒</h4>
<p>世人最可恨的打扰莫过于开会。原因是：</p>
<ul>
<li>会议中充斥着纸上谈兵和抽象的概念，大多是不切实际的。</li>
<li>会议中能传达的信息量少之又少。</li>
<li>人们在会议中容易跑题，堪比暴风雪里的芝加哥出租车还容易迷失方向。</li>
<li>会议要求做充分的准备，但是大多数人没有时间准备这些。</li>
<li>会议制定的议程常常是模糊的，根本就没有人真正清楚目标是什么。</li>
<li>会议中难免会轮到那么一两个低能人士发言，于是大家的时间都浪费在他们的扯淡上了。</li>
<li>会议具有自我繁殖功能。一次会议总能导致另外一次，以及再导出下一次，生生不息&#8230;&#8230;</li>
</ul>
<blockquote><p><strong>感想</strong>：这世上除了“他爹的TDD”开发模式，还有“他妈的TMD”开发，就是Team Meeting Driven，很多公司有太多太多的会要开了，开会基本上成了每天工作最主要的东西，对于一些管理者来说一星期中居然有80%时间都在开会。其实，这么多的会议并不意味着你在管理，只是意味着你对要管的东西完全不知道，需要通过开会来了解。很多会完全是没有议题的，大家坐在一起东拉西扯，非常非常地低效。我通常把这种会叫做“神仙会”，用个流行语来说，就是Cloud Meeting，大家神一要的各说各的，似乎，没有这种形式，不能证明参会者的存在，用会议来证明他们的存在，相当的可笑。对我来说，<strong>如果只是带一个或几个问题来开会，简直是就是扯谈，如果对于问题没有几个备选的解决方案和各方案的评估，完全没有必要开会</strong>。Amazon的会议是不会有PPT的，会议组织者会要要讨论的东西写好并打印出来，在会前给参会者把要讨论的东西打印出来，开会前10分钟左右，会场里没有任何声音，每个人都在读文档，全部人读完后，直接对议题发表自己的个人意见应该怎么干，然后很快形成共识，散会。</p></blockquote>
<h4>人人都得干活</h4>
<p>在一个小团队里，你需要的是干活的人，而不是监工。每个人都得做事，没有人可以袖手旁观 。</p>
<p>这意味着你在招聘中要避免招到监工型的人物，这些人喜欢对别人谆谆教导。对于小团队来讲监工型的人就是累赘。</p>
<p>监工们还喜欢把人拖去开会。实际上，会议是监工们最好的朋友，因为只有在开会时才显得出他们的重要。</p>
<blockquote><p><strong>感想</strong>：<strong>为什么会有办公室政治，那就是因为这个公司里有一部分人不干活，不做事，</strong>于是，他们就有大量地时间开始胡思乱想，他们花大量的时间不是想怎么去做事，而是想自己怎么更容易的打垮别人得到上面的认可，从而得到晋升。在大公司中这样的情况会比Startup的公司多得多。所以，如果你不想滋生办公室政治，那么你需要干两个事，第一个是最好不要变成大公司，第一个是让每个人都在实干。我最近看到其大公司，虽然很多东西不规范，而且很多东西在野蛮生长，有些事情也有点土，但绝大多数人都在实干，所以，只要每个人都在实干，就算干的方式不好，干出来的东西有问题，也比那些滋生办公室政治的公司强上几百倍</p></blockquote>
<h4>拒绝照搬 &amp; 将你的产品去商品化</h4>
<p>有时候，照猫画虎也是一种学习过程，就好像艺术系的学生通过临摹美术馆的作品来学习绘画。当你还是一个学生时，这种模仿是一种很有效的学习工具。不幸的是，商业战场上的模仿却不招人待见。而这也意味着你打算通过当盲从者或抄袭者的方式来建立你的事业，这注定是一个失败模式。</p>
<p>模仿的问题在于，简单的复制扼杀了深层的理解——而理解才能激发成长。你不但要知其然，还要知其所以然。而当你复制时，你会忽视这一点。你照搬的只是表面，而不是本质。</p>
<p>一旦你扬名立万，模模仿者会蜂拥而至，这就是生活。但你可以用一种绝佳的方式来保护自己不被 他们吞没：让你自己成为你的产品或服务的一部分。</p>
<blockquote><p><strong>感想</strong>：在《<a title="抄袭，腾讯 和 产品" href="http://coolshell.cn/articles/7617.html" target="_blank">抄袭，腾讯 和 产品</a>》中我谈到过这个事情，虽然我对抄袭和山寨很反感，但是我不得不承认这是这个世界的一部分，好的东西总是会被人复制的，这也不一定是一个坏事，这会让你更清楚认识到什么是真正产品的价值，什么是核心竞争力，你但凡有一点急功近利的想法你都要想一想那堆抄袭者，其中还不乏有钱有人的专业抄袭的公司。而面对被抄袭这样的事情，最好的解决方法是着眼着远期而不是短期——<strong>如果你着眼短期，你无疑会面对众多的抄袭和模仿者让你万劫不复，但是，如果你着眼长期，做一个3-5年需要花费大量精力才会成熟的产品，那么，那些急功近利的抄袭者会知难而退的，因为长期并不符合抄袭者的价值观</strong>。</p></blockquote>
<h4>做得比对手少</h4>
<p>传统智慧告诉我们，要想打败竞争者就要胜人一筹。如果人家有 4 个功能，你就得 5 个（或者 15 个，25 个）。如果人家花了$20,000，你就得花 $30,000。如果人家有 50 个员工，你就得要 100 个。</p>
<p>这样的冷战式的攀比思维会把人引上绝路。一旦被卷入“军备竞赛”，你就陷入了一场无止境的战争，这场战争会让你耗费大量的金钱、时间和动力。并且使你陷入长期的防御战中。处于防御状态的公司是没有预见力的；他们只能后知后觉，他们无法领跑，只能尾随。</p>
<p>那么你应该怎么做呢？比你的竞对手做得少，以此来打败他们。<strong>让自己去解决简单的问题，把那些纠结的、麻烦的、艰难的、讨厌的难题留给竞对手去解决</strong>。不要总想着去胜人一筹、去超过别人，试试相反的做法。</p>
<p>不要因为你的产品或服务不如别人的花哨就感到自惭形秽。把他们做得醒目高调，并引以为傲。就像对手那些强有力的销售他们多功能的产品一样销售你那简约的产品。</p>
<blockquote><p><strong>感想</strong>：一个最典型的例子就是iPad，它干得比Laptop少，比上网本少，就是一个很简单的上网和简单游戏的设备，但是他有非常简单的用户体验，让两三岁的儿童和六七十岁的老人都能很快上手。你相信吗？我花了好多年都没教会我父母用电脑以及手机里除了电话功能外的其它功能，但我只花了10分钟就教会他们使用iPad上网了。这就是“做得比对手少”的强大。<strong>只有简约的东西，才会显得更精致，才会显得更专业</strong>。</p></blockquote>
<h4>谁在乎他们在干什么</h4>
<p>不管怎样，终究是不值得过于关注你的竞争者。为什么？因为<strong>关注别人太多会让自己受到困扰</strong>。他们现在在做什么？他们下一步呢？我们该怎样作出回应？</p>
<p>每一个小小的动作都会被分析一下。那是一种可怕的心态。这会产生不可抗拒的压力和焦虑。这样的想法会滋长不好的东西。</p>
<p>这是没有意义的事情。竞争者的风景时时在变。你的竞争对手明天一个样儿，今天一个样儿。完全在你控制之外。去担心你所不能控制的事情有意义吗？</p>
<p>过于关注竞争者会混淆你的视野。当你一直吸收别人思想时， 你的机会则会减少。你变得反动而不是充满想象力。你只不过是将你竞对手的产品换了个包装。</p>
<p>如果你打算做一个“the iPod killer”或“the next Pokemon”，你已经死了。你是在承认你的竞争者所设定的参数。你没有跳出 Apple 的套路。他们制定了这个游戏规则。你不可能打败制定规则的那个人。你必须重新制定一个规则，而不是稍微改建一点点。</p>
<blockquote><p><strong>感想</strong>：这个社会浮躁之处就在于我们太多的观注了别人，人比人气死人。我们很多人都注意到了别人的风光，看到别人创业被注资，看到别人找到了好的工作，看到了别人不走正道而发达，看到了别人很轻松还挣得多，甚至看到别人的粉丝比自己多，等等，等等，这些东西让自己的心态变，变得非常地不淡定了。眼红也是魔鬼，因为眼红让人心理扭曲了的例子还少吗？<strong>不要在乎别人干了什么，你应该多看看自己的长处是什么，每个人都有每个人的路，你要做的是按照自己的节奏和自己擅长的方式行事，而不是小猫钓鱼</strong>。</p></blockquote>
<h4>养成对客户说“不”的习惯</h4>
<p>说“好的”很容易。我们很容易接受同意一个新功能、同意一个过于乐观的截止日期、笑纳一个平庸的设计。很快，一大堆你曾经说“yes”的事情就发生连锁反应，很多你不想要的东西越堆越高，甚至你都看不出原来想要的东西。</p>
<p>别相信“顾客永远是对的”这类的话。如果你是一个大厨，你的很多客人说你做的菜太咸或者太烫，你可以改。但是如果有一些挑剔的老主顾要求在宽面条里面加些香蕉，你千万不要理会他们，没关系。若是为了少数顾客的要求而毁了产品不值得。</p>
<p><strong>你的目标是确保你的产品与就是和你合拍的产品，你就是你自己产品最踏实的粉丝。你是最信赖它的那个人</strong>。那样的话，你会说：“我想你也会爱它的，因为我爱它。”</p>
<blockquote><p><strong>感想</strong>：亨利福特说过：“如果我要问我的客户要什么，他们会告诉我他们要一匹更快的马”，所以，过份的迁就用户并不是一件好的事，相反会是一件很不好的事。互联网和电视节目一样都有一个万恶的KPI，电子节目那万恶的KPI是收视率，而互联网的万恶KPI是流量。于是<strong>很多公司为了流量开始不择手段，就像电视节目用庸俗化来提高收视率一样，我们的一些互联网产品也使用庸俗化的东西来提高流量。我们要做的是一个让人称道的有品质的产品，而不是一个只有访问量的产品</strong>。</p></blockquote>
<h4>不要攀客户的高枝</h4>
<p>也许你曾经见过这样的场景：一个顾客向一家公司投了很多钱。这家公司想要尽可能的取悦那个顾客。为了迎合这个客户的要求而改变自己的产品，渐渐地，你的产品就会脱离普遍客户的基础。</p>
<p>而且，突然有一天，这个大客户绝尘而去，公司则会背负一个包袱——这个产品是围绕着一个已经离开了的人设计的。而其他人没法用。</p>
<p>人在变，环境在变，你不可能满足所有人的所有要求。<strong>公司要对某一类型的客户全情投入，而不是对某个善变的客户唯唯诺诺</strong>。</p>
<blockquote><p><strong>感想</strong>：你永远要找到自己的定位，你不可能满足所有的人。就像屌丝们喜欢的北京的动物园批发市场和高富帅们喜欢的北京燕莎商场一样，他们分别订位于不同的用户。你的产品从生下来的那一时刻就应该需要做好定位，是面对什么样的人群。而且，你也不可能实现所有人的需求的。有时候，失去一些客户并不是坏事，<strong>我们要做的是管理我们的客户，让客户认同我们，而不是被客户牵着走</strong>。</p></blockquote>
<h4>一夜成名只是传说</h4>
<p>你不会瞬间大红大紫，也不会一夜暴富，你所了解的那些道听途说的“一夜成名”的故事，深挖一点，你就能发现这些成功人士在到达引爆点之前，都已经在这个方向 上苦熬了很长时间。</p>
<p>把一夜成名的迷梦换成一步一个脚印的成长行动吧。道路很艰难，但你必须充满耐心。你得用功去做，在遇到伯乐前，你得努力很长时间。</p>
<blockquote><p><strong>感想</strong>：这和我在<a title="程序算法与人生选择" href="http://coolshell.cn/articles/8790.html">程序算法与人生选择</a>一文中所说的那个最短路径的算法的类比一样，与其展望要当什么架构师或是要成为牛人的憧憬，不如把身边看得见够得着的东西学扎实，干出色。一夜成名只是一个传说，你知道酷壳是因为我写十多年的博客，你知道我是因为我积累了十多年的编程，看看酷壳以前介绍过的<a href="http://coolshell.cn/articles/5651.html" target="_blank">王平同学</a>吧。<strong>很多事情都不是偶然的，都是有前兆的，还是我<a href="http://coolshell.cn/articles/7048.html" target="_blank">以前说过的那句话</a>，“如果一件事情以前没有发生过，未来也不会发生”，比如：如果你在学校里，在工作里，你的同学和同事并不经常来向你请教询问你的意见，那么你基本上很难成为一个Leader</strong>。</p></blockquote>
<h4>员工不是13岁</h4>
<p><strong>当你把员工当孩子看时，人们就会像孩子一样行事</strong>。</p>
<p>当公司里事事都要上报审批时，你就创造出了一种无脑文化。你成功地制造出了老板和员工之间的对立关系。这种关系在咆哮着：“我不相信你！”</p>
<p>当你处处限制员工，比如禁上他们在上班时访问外部网站或是开小差，你会得到什么好处？什么也得不到。人们需要开小差，这有助于打破整日的枯燥单调，花点时间上上Youtube或Facebook不会失去什么。</p>
<p>如果你要监控你的员工，你得想想你要花多少时间和金钱来监管员工。你浪费了多少钱去安装监控软件？你浪费了多少人力资源去监视员工？你浪费了多少时间去写没有人会看的规章制度？<strong>看看这些成本，你很快就发现，对员工的不信任才是最大的开销</strong>。</p>
<blockquote><p><strong>感想</strong>：我始终在跟我的团队成员说，最有效的管理就是自己管理自己，而不是还要专们的人来管你。不然的话，你一定会很难受的。如果你能管理好你的工作和任务，我们就不需要项目经理。如果你能管理得好你的做事的方法和流程，就不需要那些搞流程的。如果你能管理得好你的程序质量，我们就不需要QA来监管你…… 等等。<strong>其实，你们如果能管理得好自己，并能自我进化。你们甚至不需要一个经理。但是，你们可能会需要一个为你们跑腿打杂的人，其实，那个人就是经理</strong>。</p></blockquote>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-7048" data-post-type="none" >2012年04月17日 -- <a href="http://coolshell.cn/articles/7048.html" class="wp_rp_title">挑战无处不在</a></li><li data-position="1" data-poid="in-8170" data-post-type="none" >2012年08月27日 -- <a href="http://coolshell.cn/articles/8170.html" class="wp_rp_title">一次Ajax查错的经历</a></li><li data-position="2" data-poid="in-8275" data-post-type="none" >2012年09月14日 -- <a href="http://coolshell.cn/articles/8275.html" class="wp_rp_title">对九个超级程序员的采访</a></li><li data-position="3" data-poid="in-8138" data-post-type="none" >2012年08月22日 -- <a href="http://coolshell.cn/articles/8138.html" class="wp_rp_title">为什么我反对纯算法面试题</a></li><li data-position="4" data-poid="in-6994" data-post-type="none" >2012年04月11日 -- <a href="http://coolshell.cn/articles/6994.html" class="wp_rp_title">我们需要专职的QA吗？</a></li><li data-position="5" data-poid="in-7126" data-post-type="none" >2012年04月19日 -- <a href="http://coolshell.cn/articles/7126.html" class="wp_rp_title">这到底是谁之错？</a></li><li data-position="6" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="7" data-poid="in-8398" data-post-type="none" >2012年10月16日 -- <a href="http://coolshell.cn/articles/8398.html" class="wp_rp_title">xkcd 神图“Click and Drag”</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9156.html/feed</wfw:commentRss>
		<slash:comments>87</slash:comments>
		</item>
		<item>
		<title>实例分析Java Class的文件结构</title>
		<link>http://coolshell.cn/articles/9229.html</link>
		<comments>http://coolshell.cn/articles/9229.html#comments</comments>
		<pubDate>Tue, 05 Mar 2013 15:28:51 +0000</pubDate>
		<dc:creator>tiger.zhou</dc:creator>
				<category><![CDATA[Java语言]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9229</guid>
		<description><![CDATA[【感谢网友 @Krq_Tiger 投稿】 今天把之前在Evernote中的笔记重新整理了一下，发上来供对jav [...]]]></description>
				<content:encoded><![CDATA[<p><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 class="aligncenter" title="点击查看原始大小图片" alt="" src="http://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 class="aligncenter" title="点击查看原始大小图片" alt="" src="http://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 class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://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 class="brush: java; title: ; notranslate">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 class="aligncenter" alt="" src="http://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对应的是0&#215;0000,因此Class的minor_version 为0&#215;0000,从00000006h-00000007h对应的内容为0&#215;0032,因此Class文件的major_version版本为 0&#215;0032,这正好就是jdk1.6.0不带target参数编译后的Class对应的主次版本。</p>
<p><strong> 3）常量池的数量</strong><br />
接下来的2个字节从00000008h-00000009h表示常量池的数量，由上图可以知道其值为0&#215;0018，十进制为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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/6.png" width="342" height="120" /></p>
<p>其中class_index指向常量池中类型为CONSTANT_Class_info的常量，从TestClass的二进制文件结构中可以看出 class_index的值为0&#215;0004（地址为0000000bh-0000000ch)，也就是说指向第四个常量。</p>
<p>name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0&#215;0013，表示指向常量池中的第19个常量。</p>
<p>接下来又可以通过同样的方法来找到常量池中的所有常量。不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap -verbose TestClass 即可得到所有常量池中的常量，截图如下：</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://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，期值为0&#215;0021，根据前面说的 各种访问标示的标志位，我们可以知道：0&#215;0021=0&#215;0001|0&#215;0020 也即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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/8.png" width="671" height="396" /></p>
<p>从上图可以清楚到看到，类索引值为0&#215;0003，对应常量池的第三个常量，通过javap的结果，我们知道第三个常量为 CONSTANT_Class_info类型的常量，通过它可以知道类的全限定名称为：com/ejushang/TestClass /TestClass</p>
<p><strong> 7）u2 super_class</strong> 表示当前类的父类的索引值，索引值所指向的常量池中类型为CONSTANT_Class_info的常量，父类的索引值如下图所示，其值为0&#215;0004, 查看常量池的第四个常量，可知TestClass的父类的全限定名称为：java/lang/Object</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/9.png" width="670" height="395" /></p>
<p><strong>8）interfaces_count和  interfaces[interfaces_count]</strong>表示接口数量以及具体的每一个接口，TestClass的接口数量以及接口如下图所示，其中 0&#215;0001表示接口数量为1，而0&#215;0005表示接口在常量池的索引值，找到常量池的第五个常量，其类型为CONSTANT_Class_info，其 值为：com/ejushang/TestClass/Super</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://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 class="aligncenter" title="点击查看原始大小图片" alt="" src="http://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 class="aligncenter" title="点击查看原始大小图片" alt="" src="http://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 class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/15.png" width="669" height="402" /><br />
其中0x001A表示访问标示，通过查看access_flags表可知，其为ACC_PRIVATE,ACC_STATIC,ACC_FINAL,接下 来0&#215;0006和0&#215;0007分别表示常量池中第6和第7个常量，通过查看常量池可知，其值分别为：staticVar和I，其中staticVar为字 段名称，而I为字段的描述符，通过上面对描述符的解释，I所描述的是int类型的变量，接下来0&#215;0001表示staticVar这个字段表中的属性表的 数量，从上图可以staticVar字段对应的属性表有1个，0&#215;0008表示常量池中的第8个常量，查看常量池可以得知此属性为 ConstantValue属性，而ConstantValue属性的格式如下图所示：<br />
<img class="aligncenter" alt="" src="http://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表示常量池中的引用，本例中，其中为0&#215;0009，查看第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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/17.png" width="680" height="397" /><br />
其中0&#215;0002表示访问标示为ACC_PRIVATE,0x000A表示字段的名称，它指向常量池中的第10个常量，查看常量池可以知道字段名称为 instanceVar，而0&#215;0007表示字段的描述符，它指向常量池中的第7个常量，查看常量池可以知道第7个常量为I，表示类型为 instanceVar的类型为I，最后0&#215;0000表示属性表的数量为0.</p>
<p><strong> 10）methods_count 和 method_info</strong> ，其中methods_count表示方法的数量，而method_info表示的方法表，其中方法表的结构如下图所示：</p>
<p><img class="aligncenter" alt="" src="http://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 class="aligncenter" title="点击查看原始大小图片" alt="" src="http://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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/20.png" width="665" height="394" /><br />
从上图可以看出方法表的数量为0&#215;0002表示有两个方法，接下来我们来分析第一个方法，我们首先来看一下TestClass的第一个方法的access_flag，name_index,descriptor_index，截图如下：</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/21.png" width="671" height="396" /><br />
从上图可以知道access_flags为0&#215;0001，从上面对access_flags标志位的描述，可知方法的access_flags的取值为 ACC_PUBLIC,name_index为0x000B，查看常量池中的第11个常量，知道方法的名称为&lt;init&gt;，0x000C表示 descriptor_index表示常量池中的第12常量，其值为()V,表示&lt;init&gt;方法没有参数和返回值，其实这是编译器自动生成 的实例构造器方法。接下来的0&#215;0001表示&lt;init&gt;方法的方法表有1个属性，属性截图如下：<br />
<img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/22.png" width="679" height="396" /><br />
从上图可以看出0x000D对应的常量池中的常量为Code,表示的方法的Code属性，所以到这里大家应该明白方法的那些代码是存储在Class文件方法表中的属性表中的Code属性中。接下来我们在分析一下Code属性，Code属性的结构如下图所示：<br />
<img class="aligncenter" alt="" src="http://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类型的取值为0&#215;00-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属性的截图中可以看出，属性表的长度为0&#215;00000026,max_stack的 值为0&#215;0002,max_locals的取值为0&#215;0001,code_length的长度为0x0000000A，那么00000149h- 00000152h为字节码，接下来exception_table_length的长度为0&#215;0000，而attribute_count的值为 0&#215;0001，00000157h-00000158h的值为0x000E,它表示常量池中属性的名称，查看常量池得知第14个常量的值为 LineNumberTable，LineNumberTable用于描述java源代码的行号和字节码行号的对应关系，它不是运行时必需的属性，如果通 过-g:none的编译器参数来取消生成这项信息的话，最大的影响就是异常发生的时候，堆栈中不能显示出出错的行号，调试的时候也不能按照源代码来设置断 点，接下来我们再看一下LineNumberTable的结构如下图所示：</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/25.png" width="675" height="395" /></p>
<p>上面分析完了TestClass的第一个方法，通过同样的方式我们可以分析出TestClass的第二个方法，截图如下：</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/26.png" width="671" height="395" /></p>
<p>其中access_flags为0&#215;0001,name_index为0x000F,descriptor_index为0&#215;0010，通过查看常量池可 以知道此方法为public int instanceMethod(int param)方法。通过和上面类似的方法我们可以知道instanceMethod的Code属性为下图所示：</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/27.png" width="670" height="397" /></p>
<p>最后我们来分析一下，Class文件的属性，从00000191h-00000199h为Class文件中的属性表，其中0&#215;0011表示属性的名称，查看常量池可以知道属性名称为SourceFile，我们再来看看SourceFile的结构如下图所示：</p>
<p style="text-align: center;"><img class="aligncenter" alt="" src="http://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 class="aligncenter" alt="" src="http://coolshell.cn//wp-content/uploads/2013/03/29.png" width="681" height="395" /><br />
其中attribute_length为0&#215;00000002表示长度为2个字节，而soucefile_index的值为0&#215;0012,查看常量池的第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>
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-1106" data-post-type="none" >2009年07月03日 -- <a href="http://coolshell.cn/articles/1106.html" class="wp_rp_title">Java构造时成员初始化的陷阱</a></li><li data-position="1" data-poid="in-14" data-post-type="none" >2009年03月02日 -- <a href="http://coolshell.cn/articles/14.html" class="wp_rp_title">Java书籍Top 10</a></li><li data-position="2" data-poid="in-611" data-post-type="none" >2009年04月22日 -- <a href="http://coolshell.cn/articles/611.html" class="wp_rp_title">Java如何取源文件中文件名和行号</a></li><li data-position="3" data-poid="in-1984" data-post-type="none" >2009年12月21日 -- <a href="http://coolshell.cn/articles/1984.html" class="wp_rp_title">C语言的演变史</a></li><li data-position="4" data-poid="in-889" data-post-type="none" >2009年05月23日 -- <a href="http://coolshell.cn/articles/889.html" class="wp_rp_title">20非常有用的Java程序片段</a></li><li data-position="5" data-poid="in-6112" data-post-type="none" >2011年12月28日 -- <a href="http://coolshell.cn/articles/6112.html" class="wp_rp_title">由一个问题到 Resin ClassLoader 的学习</a></li><li data-position="6" data-poid="in-2235" data-post-type="none" >2010年03月28日 -- <a href="http://coolshell.cn/articles/2235.html" class="wp_rp_title">JAVA的字符串拼接与性能</a></li><li data-position="7" data-poid="in-1051" data-post-type="none" >2009年06月18日 -- <a href="http://coolshell.cn/articles/1051.html" class="wp_rp_title">如何在Java中避免equals方法的隐藏陷阱</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9229.html/feed</wfw:commentRss>
		<slash:comments>30</slash:comments>
		</item>
		<item>
		<title>并发框架Disruptor译文</title>
		<link>http://coolshell.cn/articles/9169.html</link>
		<comments>http://coolshell.cn/articles/9169.html#comments</comments>
		<pubDate>Thu, 28 Feb 2013 12:13:46 +0000</pubDate>
		<dc:creator>方 腾飞</dc:creator>
				<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[（感谢同事方腾飞投递本文） Martin Fowler在自己网站上写了一篇LMAX架构的文章，在文章中他介绍了 [...]]]></description>
				<content:encoded><![CDATA[<p><strong>（感谢同事<a href="http://ifeve.com" target="_blank">方腾飞</a>投递本文）</strong></p>
<p><img class="alignright size-medium wp-image-9188" alt="" src="http://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>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-7829" data-post-type="none" >2012年07月11日 -- <a href="http://coolshell.cn/articles/7829.html" class="wp_rp_title">28个Unix/Linux的命令行神器</a></li><li data-position="1" data-poid="in-7886" data-post-type="none" >2012年07月13日 -- <a href="http://coolshell.cn/articles/7886.html" class="wp_rp_title">代码执行的效率</a></li><li data-position="2" data-poid="in-8239" data-post-type="none" >2012年09月07日 -- <a href="http://coolshell.cn/articles/8239.html" class="wp_rp_title">无锁队列的实现</a></li><li data-position="3" data-poid="in-7490" data-post-type="none" >2012年06月20日 -- <a href="http://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li data-position="4" data-poid="in-7992" data-post-type="none" >2012年08月06日 -- <a href="http://coolshell.cn/articles/7992.html" class="wp_rp_title">C++的坑真的多吗？</a></li><li data-position="5" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="6" data-poid="in-6335" data-post-type="none" >2012年01月05日 -- <a href="http://coolshell.cn/articles/6335.html" class="wp_rp_title">Resin服务器getResource揭秘</a></li><li data-position="7" data-poid="in-2483" data-post-type="none" >2010年06月02日 -- <a href="http://coolshell.cn/articles/2483.html" class="wp_rp_title">四个流行的Java连接池</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9169.html/feed</wfw:commentRss>
		<slash:comments>28</slash:comments>
		</item>
		<item>
		<title>sed 简明教程</title>
		<link>http://coolshell.cn/articles/9104.html</link>
		<comments>http://coolshell.cn/articles/9104.html#comments</comments>
		<pubDate>Wed, 20 Feb 2013 00:36:48 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[编程工具]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[sed]]></category>
		<category><![CDATA[Unix]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9104</guid>
		<description><![CDATA[awk于1977年出生，今年36岁本命年，sed比awk大2-3岁，awk就像林妹妹，sed就是宝玉哥哥了。所 [...]]]></description>
				<content:encoded><![CDATA[<p><img class="alignright  wp-image-9126" alt="" src="http://coolshell.cn//wp-content/uploads/2013/02/sed-superman.png" width="216" height="216" />awk于1977年出生，今年36岁本命年，sed比awk大2-3岁，awk就像林妹妹，sed就是宝玉哥哥了。所以 <a title="AWK 简明教程" href="http://coolshell.cn/articles/9070.html" target="_blank">林妹妹跳了个Topless</a>，他的哥哥sed坐不住了，也一定要出来抖一抖。</p>
<p>sed全名叫stream editor，流编辑器，用程序的方式来编辑文本，相当的hacker啊。sed基本上就是玩正则模式匹配，所以，玩sed的人，正则表达式一般都比较强。</p>
<p>同样，本篇文章不会说sed的全部东西，你可以参看<a href="http://www.gnu.org/software/sed/manual/sed.html" target="_blank">sed的手册</a>，我这里主要还是想和大家竞争一下那些从手机指缝间或马桶里流走的时间，用这些时间来学习一些东西。当然，接下来的还是要靠大家自己双手。</p>
<h4>用s命令替换</h4>
<p>我使用下面的这段文本做演示：</p>
<pre class="brush: bash; title: ; notranslate">$ cat pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam</pre>
<p>把其中的my字符串替换成Hao Chen&#8217;s，下面的语句应该很好理解（s表示替换命令，/my/表示匹配my，/Hao Chen&#8217;s/表示把匹配替换成Hao Chen&#8217;s，/g 表示一行上的替换所有的匹配）：</p>
<pre class="brush: bash; title: ; notranslate">$ sed &quot;s/my/Hao Chen's/g&quot; pets.txt
This is Hao Chen's cat
  Hao Chen's cat's name is betty
This is Hao Chen's dog
  Hao Chen's dog's name is frank
This is Hao Chen's fish
  Hao Chen's fish's name is george
This is Hao Chen's goat
  Hao Chen's goat's name is adam</pre>
<p>注意：如果你要使用单引号，那么你没办法通过\&#8217;这样来转义，就有双引号就可以了，在双引号内可以用\&#8221;来转义。</p>
<p><span id="more-9104"></span></p>
<p>再注意：上面的sed并没有对文件的内容改变，只是把处理过后的内容输出，如果你要写回文件，你可以使用重定向，如：</p>
<pre class="brush: bash; title: ; notranslate">$ sed &quot;s/my/Hao Chen's/g&quot; pets.txt &gt; hao_pets.txt</pre>
<p>或使用 -i 参数直接修改文件内容：</p>
<pre class="brush: bash; title: ; notranslate">$ sed -i &quot;s/my/Hao Chen's/g&quot; pets.txt</pre>
<p>在每一行最前面加点东西：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/^/#/g' pets.txt
#This is my cat
#  my cat's name is betty
#This is my dog
#  my dog's name is frank
#This is my fish
#  my fish's name is george
#This is my goat
#  my goat's name is adam</pre>
<p>在每一行最后面加点东西：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/$/ --- /g' pets.txt
This is my cat ---
  my cat's name is betty ---
This is my dog ---
  my dog's name is frank ---
This is my fish ---
  my fish's name is george ---
This is my goat ---
  my goat's name is adam ---</pre>
<p>顺手介绍一下正则表达式的一些最基本的东西：</p>
<ul>
<li>^ 表示一行的开头。如：/^#/ 以#开头的匹配。</li>
<li>$ 表示一行的结尾。如：/}$/ 以}结尾的匹配。</li>
<li>\&lt; 表示词首。 如 \&lt;abc 表示以 abc 为首的詞。</li>
<li>\&gt; 表示词尾。 如 abc\&gt; 表示以 abc 結尾的詞。</li>
<li>. 表示任何单个字符。</li>
<li>* 表示某个字符出现了0次或多次。</li>
<li>[ ] 字符集合。 如：[abc]表示匹配a或b或c，还有[a-zA-Z]表示匹配所有的26个字符。如果其中有^表示反，如[^a]表示非a的字符</li>
</ul>
<p>正规则表达式是一些很牛的事，比如我们要去掉某html中的tags：</p>
<pre class="brush: xml; title: html.txt; notranslate">

&lt;b&gt;This&lt;/b&gt; is what &lt;span style=&quot;text-decoration: underline;&quot;&gt;I&lt;/span&gt; meant. Understand?

</pre>
<p>看看我们的sed命令</p>
<pre class="brush: bash; title: ; notranslate">
# 如果你这样搞的话，就会有问题
$ sed 's/&lt;.*&gt;//g' html.txt
 Understand?

# 要解决上面的那个问题，就得像下面这样。
# 其中的'[^&gt;]' 指定了除了&gt;的字符重复0次或多次。
$ sed 's/&lt;[^&gt;]*&gt;//g' html.txt
This is what I meant. Understand?</pre>
<p>我们再来看看指定需要替换的内容：</p>
<pre class="brush: bash; highlight: [4]; title: ; notranslate">$ sed &quot;3s/my/your/g&quot; pets.txt
This is my cat
  my cat's name is betty
This is your dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam</pre>
<p>下面的命令只替换第3到第6行的文本。</p>
<pre class="brush: bash; highlight: [4,5,6,7]; title: ; notranslate">$ sed &quot;3,6s/my/your/g&quot; pets.txt
This is my cat
  my cat's name is betty
This is your dog
  your dog's name is frank
This is your fish
  your fish's name is george
This is my goat
  my goat's name is adam</pre>
<p>&nbsp;</p>
<pre class="brush: bash; title: ; notranslate">$ cat my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam</pre>
<p>只替换每一行的第一个s：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/s/S/1' my.txt
ThiS is my cat, my cat's name is betty
ThiS is my dog, my dog's name is frank
ThiS is my fish, my fish's name is george
ThiS is my goat, my goat's name is adam</pre>
<p>只替换每一行的第二个s：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/s/S/2' my.txt
This iS my cat, my cat's name is betty
This iS my dog, my dog's name is frank
This iS my fish, my fish's name is george
This iS my goat, my goat's name is adam</pre>
<p>只替换第一行的第3个以后的s：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/s/S/3g' my.txt
This is my cat, my cat'S name iS betty
This is my dog, my dog'S name iS frank
This is my fiSh, my fiSh'S name iS george
This is my goat, my goat'S name iS adam</pre>
<h4>多个匹配</h4>
<p>如果我们需要一次替换多个模式，可参看下面的示例：（第一个模式把第一行到第三行的my替换成your，第二个则把第3行以后的This替换成了That）</p>
<pre class="brush: bash; title: ; notranslate">$ sed '1,3s/my/your/g; 3,$s/This/That/g' my.txt
This is your cat, your cat's name is betty
This is your dog, your dog's name is frank
That is your fish, your fish's name is george
That is my goat, my goat's name is adam</pre>
<p>上面的命令等价于：（注：下面使用的是sed的-e命令行参数）</p>
<pre class="brush: bash; title: ; notranslate">sed -e '1,3s/my/your/g' -e '3,$s/This/That/g' my.txt</pre>
<p>我们可以使用&amp;来当做被匹配的变量，然后可以在基本左右加点东西。如下所示：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/my/[&amp;]/g' my.txt
This is [my] cat, [my] cat's name is betty
This is [my] dog, [my] dog's name is frank
This is [my] fish, [my] fish's name is george
This is [my] goat, [my] goat's name is adam</pre>
<h4>圆括号匹配</h4>
<p>使用圆括号匹配的示例：（圆括号括起来的正则表达式所匹配的字符串会可以当成变量来使用，sed中使用的是\1,\2&#8230;）</p>
<pre class="brush: bash; title: ; notranslate">$ sed 's/This is my \([^,]*\),.*is \(.*\)/\1:\2/g' my.txt
cat:betty
dog:frank
fish:george
goat:adam</pre>
<p>上面这个例子中的正则表达式有点复杂，解开如下（去掉转义字符）：</p>
<p>正则为：This is my ([^,]*),.*is (.*)<br />
匹配为：This is my (cat),&#8230;&#8230;&#8230;.is (betty)</p>
<p>然后：\1就是cat，\2就是betty</p>
<h4>sed的命令</h4>
<p>让我们回到最一开始的例子pets.txt，让我们来看几个命令：</p>
<h5>N命令</h5>
<p>先来看N命令 —— 把下一行的内容纳入当成缓冲区做匹配。</p>
<p>下面的的示例会把原文本中的偶数行纳入奇数行匹配，而s只匹配并替换一次，所以，就成了下面的结果：</p>
<pre class="brush: bash; title: ; notranslate">$ sed 'N;s/my/your/' pets.txt
This is your cat
  my cat's name is betty
This is your dog
  my dog's name is frank
This is your fish
  my fish's name is george
This is your goat
  my goat's name is adam</pre>
<p>也就是说，原来的文件成了：</p>
<pre class="brush: bash; title: ; notranslate">This is my cat\n  my cat's name is betty
This is my dog\n  my dog's name is frank
This is my fish\n  my fish's name is george
This is my goat\n  my goat's name is adam</pre>
<p>这样一来，下面的例子你就明白了，</p>
<pre class="brush: bash; title: ; notranslate">$ sed 'N;s/\n/,/' pets.txt
This is my cat,  my cat's name is betty
This is my dog,  my dog's name is frank
This is my fish,  my fish's name is george
This is my goat,  my goat's name is adam</pre>
<h5>a命令和i命令</h5>
<p>a命令就是append， i命令就是insert，它们是用来添加行的。如：</p>
<pre class="brush: bash; highlight: [2,12]; title: ; notranslate"># 其中的1i表明，其要在第1行前插入一行（insert）
$ sed &quot;1 i This is my monkey, my monkey's name is wukong&quot; my.txt
This is my monkey, my monkey's name is wukong
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

# 其中的1a表明，其要在最后一行后追加一行（append）
$ sed &quot;$ a This is my monkey, my monkey's name is wukong&quot; my.txt
This is my cat, my cat's name is betty
This is my monkey, my monkey's name is wukong
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam</pre>
<p>我们可以运用匹配来添加文本：</p>
<pre class="brush: bash; highlight: [6]; title: ; notranslate"># 注意其中的/fish/a，这意思是匹配到/fish/后就追加一行
$ sed &quot;/fish/a This is my monkey, my monkey's name is wukong&quot; my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my monkey, my monkey's name is wukong
This is my goat, my goat's name is adam</pre>
<p>下面这个例子是对每一行都挺插入：</p>
<pre class="brush: bash; title: ; notranslate">$ sed &quot;/my/a ----&quot; my.txt
This is my cat, my cat's name is betty
----
This is my dog, my dog's name is frank
----
This is my fish, my fish's name is george
----
This is my goat, my goat's name is adam
----</pre>
<h5>c命令</h5>
<p>c 命令是替换匹配行</p>
<pre class="brush: bash; title: ; notranslate">$ sed &quot;2 c This is my monkey, my monkey's name is wukong&quot; my.txt
This is my cat, my cat's name is betty
This is my monkey, my monkey's name is wukong
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

$ sed &quot;/fish/c This is my monkey, my monkey's name is wukong&quot; my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my monkey, my monkey's name is wukong
This is my goat, my goat's name is adam</pre>
<h5>d命令</h5>
<p>删除匹配行</p>
<pre class="brush: bash; title: ; notranslate">$ sed '/fish/d' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my goat, my goat's name is adam

$ sed '2d' my.txt
This is my cat, my cat's name is betty
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

$ sed '2,$d' my.txt
This is my cat, my cat's name is betty</pre>
<h5>p命令</h5>
<p>打印命令</p>
<p>你可以把这个命令当成grep式的命令</p>
<pre class="brush: bash; title: ; notranslate"># 匹配fish并输出，可以看到fish的那一行被打了两遍，
# 这是因为sed处理时会把处理的信息输出
$ sed '/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

# 使用n参数就好了
$ sed -n '/fish/p' my.txt
This is my fish, my fish's name is george

# 从一个模式到另一个模式
$ sed -n '/dog/,/fish/p' my.txt
This is my dog, my dog's name is frank
This is my fish, my fish's name is george

#从第一行打印到匹配fish成功的那一行
$ sed -n '1,/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george</pre>
<h4>几个知识点</h4>
<p>好了，下面我们要介绍四个sed的基本知识点：</p>
<h5>Pattern Space</h5>
<p>第零个是关于-n参数的，大家也许没看懂，没关系，我们来看一下sed处理文本的伪代码，并了解一下Pattern Space的概念：</p>
<pre class="brush: cpp; title: ; notranslate">foreach line in file {
    //放入把行Pattern_Space
    Pattern_Space &lt;= line;

    // 对每个pattern space执行sed命令
    Pattern_Space &lt;= EXEC(sed_cmd, Pattern_Space);

    // 如果没有指定 -n 则输出处理后的Pattern_Space
    if (sed option hasn't &quot;-n&quot;)  {
       print Pattern_Space
    }
}</pre>
<h5>Address</h5>
<p>第一个是关于address，几乎上述所有的命令都是这样的（注：其中的!表示匹配成功后是否执行命令）</p>
<p>[address[,address]][!]{cmd}</p>
<p>address可以是一个数字，也可以是一个模式，你可以通过逗号要分隔两个address 表示两个address的区间，参执行命令cmd，伪代码如下：</p>
<pre class="brush: cpp; title: ; notranslate">
bool bexec = false
foreach line in file {
    if ( match(address1) ){
        bexec = true;
    }

    if ( bexec == true) {
        EXEC(sed_cmd);
    }

    if ( match (address2) ) {
        bexec = false;
    }
}</pre>
<p>关于address可以使用相对位置，如：</p>
<pre class="brush: bash; title: ; notranslate"># 其中的+3表示后面连续3行
$ sed '/dog/,+3s/^/# /g' pets.txt
This is my cat
  my cat's name is betty
# This is my dog
#   my dog's name is frank
# This is my fish
#   my fish's name is george
This is my goat
  my goat's name is adam</pre>
<h5>命令打包</h5>
<p>第二个是cmd可以是多个，它们可以用分号分开，可以用大括号括起来作为嵌套命令。下面是几个例子：</p>
<pre class="brush: bash; highlight: [12,21,31]; title: ; notranslate">$ cat pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam

# 对3行到第6行，执行命令/This/d
$ sed '3,6 {/This/d}' pets.txt
This is my cat
  my cat's name is betty
  my dog's name is frank
  my fish's name is george
This is my goat
  my goat's name is adam

# 对3行到第6行，匹配/This/成功后，再匹配/fish/，成功后执行d命令
$ sed '3,6 {/This/{/fish/d}}' pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
  my fish's name is george
This is my goat
  my goat's name is adam

# 从第一行到最后一行，如果匹配到This，则删除之；如果前面有空格，则去除空格
$ sed '1,${/This/d;s/^ *//g}' pets.txt
my cat's name is betty
my dog's name is frank
my fish's name is george
my goat's name is adam </pre>
<h5>Hold Space</h5>
<p>第三个我们再来看一下 Hold Space</p>
<p>接下来，我们需要了解一下Hold Space的概念，我们先来看四个命令：</p>
<p>g： 将hold space中的内容拷贝到pattern space中，原来pattern space里的内容清除<br />
G： 将hold space中的内容append到pattern space\n后<br />
h： 将pattern space中的内容拷贝到hold space中，原来的hold space里的内容被清除<br />
H： 将pattern space中的内容append到hold space\n后<br />
x： 交换pattern space和hold space的内容</p>
<p>这些命令有什么用？我们来看两个示例吧，用到的示例文件是：</p>
<pre class="brush: bash; title: ; notranslate">$ cat t.txt
one
two
three</pre>
<p>第一个示例：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ sed 'H;g' t.txt
one

one
two

one
two
three</pre>
<p>是不是有点没看懂，我作个图你就看懂了。</p>
<p><img class="aligncenter size-full wp-image-9118" alt="" src="http://coolshell.cn//wp-content/uploads/2013/02/sed_demo_00.jpg" width="592" height="404" /></p>
<p>第二个示例，反序了一个文件的行：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ sed '1!G;h;$!d' t.txt
three
two
one</pre>
<p>其中的 &#8217;1!G;h;$!d&#8217; 可拆解为三个命令</p>
<ul>
<li>1!G —— 只有第一行不执行G命令，将hold space中的内容append回到pattern space</li>
<li>h —— 第一行都执行h命令，将pattern space中的内容拷贝到hold space中</li>
<li>$!d —— 除了最后一行不执行d命令，其它行都执行d命令，删除当前行</li>
</ul>
<p>这个执行序列很难理解，做个图如下大家就明白了：</p>
<p><img class="aligncenter size-full wp-image-9110" alt="" src="http://coolshell.cn//wp-content/uploads/2013/02/sed_demo.jpg" width="623" height="316" /></p>
<p>就先说这么多吧，希望对大家有用。</p>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8619" data-post-type="none" >2012年11月23日 -- <a href="http://coolshell.cn/articles/8619.html" class="wp_rp_title">你可能不知道的Shell</a></li><li data-position="1" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="2" data-poid="in-7829" data-post-type="none" >2012年07月11日 -- <a href="http://coolshell.cn/articles/7829.html" class="wp_rp_title">28个Unix/Linux的命令行神器</a></li><li data-position="3" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="4" data-poid="in-7236" data-post-type="none" >2012年05月03日 -- <a href="http://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li data-position="5" data-poid="in-7490" data-post-type="none" >2012年06月20日 -- <a href="http://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li data-position="6" data-poid="in-9070" data-post-type="none" >2013年02月17日 -- <a href="http://coolshell.cn/articles/9070.html" class="wp_rp_title">AWK 简明教程</a></li><li data-position="7" data-poid="in-2822" data-post-type="none" >2010年08月24日 -- <a href="http://coolshell.cn/articles/2822.html" class="wp_rp_title">使用grep恢复被删文件内容</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9104.html/feed</wfw:commentRss>
		<slash:comments>85</slash:comments>
		</item>
		<item>
		<title>AWK 简明教程</title>
		<link>http://coolshell.cn/articles/9070.html</link>
		<comments>http://coolshell.cn/articles/9070.html#comments</comments>
		<pubDate>Sun, 17 Feb 2013 00:38:29 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[编程工具]]></category>
		<category><![CDATA[awk]]></category>
		<category><![CDATA[gawk]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[Unix]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=9070</guid>
		<description><![CDATA[有一些网友看了前两天的《Linux下应该知道的技巧》希望我能教教他们用awk和sed，所以，出现了这篇文章。我 [...]]]></description>
				<content:encoded><![CDATA[<p><img class="alignright size-full wp-image-9093" alt="" src="http://coolshell.cn//wp-content/uploads/2013/02/awk.jpg" width="350" height="279" />有一些网友看了前两天的《<a title="应该知道的Linux技巧" href="http://coolshell.cn/articles/8883.html" target="_blank">Linux下应该知道的技巧</a>》希望我能教教他们用awk和sed，所以，出现了这篇文章。我估计这些80后的年轻朋友可能对awk/sed这类上古神器有点陌生了，所以需要我这个老家伙来炒炒冷饭。<strong>况且，AWK是贝尔实验室1977年搞出来的文本出现神器，今年是蛇年，是AWK的本命年，而且年纪和我相仿，所以非常有必要为他写篇文章</strong>。</p>
<p>之所以叫AWK是因为其取了三位创始人 <a title="Alfred Aho" href="http://en.wikipedia.org/wiki/Alfred_Aho">Alfred Aho</a>，<a title="Peter J. Weinberger" href="http://en.wikipedia.org/wiki/Peter_J._Weinberger">Peter Weinberger</a>, 和 <a title="Brian Kernighan" href="http://en.wikipedia.org/wiki/Brian_Kernighan">Brian Kernighan</a> 的Family Name的首字符。要学AWK，就得提一提AWK的一本相当经典的书《<a href="http://plan9.bell-labs.com/cm/cs/awkbook/" rel="nofollow">The AWK Programming Language</a>》，它在<a href="http://book.douban.com/subject/1876898/" target="_blank">豆瓣上的评分</a>是9.4分！在<a href="http://www.amazon.cn/mn/detailApp/?asin=020107981X" target="_blank">亚马逊上居然卖1022.30元</a>。</p>
<p>我在这里的教程并不想面面俱到，本文和我之前的<a title="Go 语言简介（上）— 语法" href="http://coolshell.cn/articles/8460.html" target="_blank">Go语言简介</a>一样，全是示例，基本无废话。</p>
<p><strong>我只想达到两个目的：</strong></p>
<p style="text-align: left; padding-left: 30px;"><strong>1）你可以在乘坐公交地铁上下班，或是在坐马桶拉大便时读完（保证是一泡大便的工夫）。</strong></p>
<p style="text-align: left; padding-left: 30px;"><strong>2）我只想让这篇博文像一个火辣的脱衣舞女挑起你的兴趣，然后还要你自己去下工夫去撸。</strong></p>
<p>废话少说，我们开始脱吧（注：这里只是topless）。</p>
<h4>起步上台</h4>
<p>我从netstat命令中提取了如下信息作为用例：</p>
<p><span id="more-9070"></span></p>
<pre class="brush: bash; title: ; notranslate">$ cat netstat.txt
Proto Recv-Q Send-Q Local-Address          Foreign-Address             State
tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN
tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN
tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT
tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2
tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED
tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED
tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2
tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED
tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT
tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED
tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1
tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED
tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT
tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK
tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED
tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2
tcp        0      0 :::22                  :::*                        LISTEN
</pre>
<p>下面是最简单最常用的awk示例，其输出第1列和第4例，</p>
<ul>
<li>其中单引号中的被大括号括着的就是awk的语句，注意，其只能被单引号包含。</li>
<li>其中的$1..$n表示第几例。注：$0表示整个行。</li>
</ul>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '{print $1, $4}' netstat.txt
Proto Local-Address
tcp 0.0.0.0:3306
tcp 0.0.0.0:80
tcp 127.0.0.1:9000
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp coolshell.cn:80
tcp :::22</pre>
<p>我们再来看看awk的格式化输出，和C语言的printf没什么两样：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '{printf &quot;%-8s %-8s %-8s %-18s %-22s %-15s\n&quot;,$1,$2,$3,$4,$5,$6}' netstat.txt
Proto    Recv-Q   Send-Q   Local-Address      Foreign-Address        State
tcp      0        0        0.0.0.0:3306       0.0.0.0:*              LISTEN
tcp      0        0        0.0.0.0:80         0.0.0.0:*              LISTEN
tcp      0        0        127.0.0.1:9000     0.0.0.0:*              LISTEN
tcp      0        0        coolshell.cn:80    124.205.5.146:18245    TIME_WAIT
tcp      0        0        coolshell.cn:80    61.140.101.185:37538   FIN_WAIT2
tcp      0        0        coolshell.cn:80    110.194.134.189:1032   ESTABLISHED
tcp      0        0        coolshell.cn:80    123.169.124.111:49809  ESTABLISHED
tcp      0        0        coolshell.cn:80    116.234.127.77:11502   FIN_WAIT2
tcp      0        0        coolshell.cn:80    123.169.124.111:49829  ESTABLISHED
tcp      0        0        coolshell.cn:80    183.60.215.36:36970    TIME_WAIT
tcp      0        4166     coolshell.cn:80    61.148.242.38:30901    ESTABLISHED
tcp      0        1        coolshell.cn:80    124.152.181.209:26825  FIN_WAIT1
tcp      0        0        coolshell.cn:80    110.194.134.189:4796   ESTABLISHED
tcp      0        0        coolshell.cn:80    183.60.212.163:51082   TIME_WAIT
tcp      0        1        coolshell.cn:80    208.115.113.92:50601   LAST_ACK
tcp      0        0        coolshell.cn:80    123.169.124.111:49840  ESTABLISHED
tcp      0        0        coolshell.cn:80    117.136.20.85:50025    FIN_WAIT2
tcp      0        0        :::22              :::*                   LISTEN</pre>
<h4>脱掉外套</h4>
<h5>过滤记录</h5>
<p>我们再来看看如何过滤记录（下面过滤条件为：第三列的值为0 &amp;&amp; 第6列的值为LISTEN）</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '$3==0 &amp;&amp; $6==&quot;LISTEN&quot; ' netstat.txt
tcp        0      0 0.0.0.0:3306               0.0.0.0:*              LISTEN
tcp        0      0 0.0.0.0:80                 0.0.0.0:*              LISTEN
tcp        0      0 127.0.0.1:9000             0.0.0.0:*              LISTEN
tcp        0      0 :::22                      :::*                   LISTEN</pre>
<p>其中的“==”为比较运算符。其他比较运算符：!=, &gt;, &lt;, &gt;=, &lt;=</p>
<p>我们来看看各种过滤记录的方式：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk ' $3&gt;0 {print $0}' netstat.txt
Proto Recv-Q Send-Q Local-Address          Foreign-Address             State
tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED
tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1
tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK</pre>
<p>如果我们需要表头的话，我们可以引入内建变量NR：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '$3==0 &amp;&amp; $6==&quot;LISTEN&quot; || NR==1 ' netstat.txt
Proto Recv-Q Send-Q Local-Address          Foreign-Address             State
tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN
tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN
tcp        0      0 :::22                  :::*                        LISTEN</pre>
<p>再加上格式化输出：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '$3==0 &amp;&amp; $6==&quot;LISTEN&quot; || NR==1 {printf &quot;%-20s %-20s %s\n&quot;,$4,$5,$6}' netstat.txt
Local-Address        Foreign-Address      State
0.0.0.0:3306         0.0.0.0:*            LISTEN
0.0.0.0:80           0.0.0.0:*            LISTEN
127.0.0.1:9000       0.0.0.0:*            LISTEN
:::22                :::*                 LISTEN</pre>
<h5><strong>内建变量</strong></h5>
<p>说到了内建变量，我们可以来看看awk的一些内建变量：</p>
<table border="0" cellspacing="1" cellpadding="4">
<tbody>
<tr>
<td bgcolor="#ffffff">$0</td>
<td bgcolor="#ffffff">当前记录（这个变量中存放着整个行的内容）</td>
</tr>
<tr>
<td bgcolor="#ffffff">$1~$n</td>
<td bgcolor="#ffffff">当前记录的第n个字段，字段间由FS分隔</td>
</tr>
<tr>
<td bgcolor="#ffffff">FS</td>
<td bgcolor="#ffffff">输入字段分隔符 默认是空格或Tab</td>
</tr>
<tr>
<td bgcolor="#ffffff">NF</td>
<td bgcolor="#ffffff">当前记录中的字段个数，就是有多少列</td>
</tr>
<tr>
<td bgcolor="#ffffff">NR</td>
<td bgcolor="#ffffff">已经读出的记录数，就是行号，从1开始，如果有多个文件话，这个值也是不断累加中。</td>
</tr>
<tr>
<td bgcolor="#ffffff">FNR</td>
<td bgcolor="#ffffff">当前记录数，与NR不同的是，这个值会是各个文件自己的行号</td>
</tr>
<tr>
<td bgcolor="#ffffff">RS</td>
<td bgcolor="#ffffff">输入的记录分隔符， 默认为换行符</td>
</tr>
<tr>
<td bgcolor="#ffffff">OFS</td>
<td bgcolor="#ffffff">输出字段分隔符， 默认也是空格</td>
</tr>
<tr>
<td bgcolor="#ffffff">ORS</td>
<td bgcolor="#ffffff">输出的记录分隔符，默认为换行符</td>
</tr>
<tr>
<td bgcolor="#ffffff">FILENAME</td>
<td bgcolor="#ffffff">当前输入文件的名字</td>
</tr>
</tbody>
</table>
<p>怎么使用呢，比如：我们如果要输出行号：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '$3==0 &amp;&amp; $6==&quot;ESTABLISHED&quot; || NR==1 {printf &quot;%02s %s %-20s %-20s %s\n&quot;,NR, FNR, $4,$5,$6}' netstat.txt
01 1 Local-Address        Foreign-Address      State
07 7 coolshell.cn:80      110.194.134.189:1032 ESTABLISHED
08 8 coolshell.cn:80      123.169.124.111:49809 ESTABLISHED
10 10 coolshell.cn:80      123.169.124.111:49829 ESTABLISHED
14 14 coolshell.cn:80      110.194.134.189:4796 ESTABLISHED
17 17 coolshell.cn:80      123.169.124.111:49840 ESTABLISHED</pre>
<h5><strong>指定分隔符</strong></h5>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$  awk  'BEGIN{FS=&quot;:&quot;} {print $1,$3,$6}' /etc/passwd
root 0 /root
bin 1 /bin
daemon 2 /sbin
adm 3 /var/adm
lp 4 /var/spool/lpd
sync 5 /sbin
shutdown 6 /sbin
halt 7 /sbin</pre>
<p>上面的命令也等价于：（-F的意思就是指定分隔符）</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk  -F: '{print $1,$3,$6}' /etc/passwd</pre>
<p>注：如果你要指定多个分隔符，你可以这样来：</p>
<pre class="brush: bash; title: ; notranslate">awk -F '[;:]'</pre>
<p>再来看一个以\t作为分隔符输出的例子（下面使用了/etc/passwd文件，这个文件是以:分隔的）：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk  -F: '{print $1,$3,$6}' OFS=&quot;\t&quot; /etc/passwd
root    0       /root
bin     1       /bin
daemon  2       /sbin
adm     3       /var/adm
lp      4       /var/spool/lpd
sync    5       /sbin</pre>
<h4>脱掉衬衫</h4>
<h5>字符串匹配</h5>
<p>我们再来看几个字符串匹配的示例：</p>
<pre class="brush: bash; highlight: [1,8]; title: ; notranslate">$ awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS=&quot;\t&quot; netstat.txt
1       Local-Address   Foreign-Address State
6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2
9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2
13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1
18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2

$ $ awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS=&quot;\t&quot; netstat.txt
1       Local-Address   Foreign-Address State
5       coolshell.cn:80 124.205.5.146:18245     TIME_WAIT
6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2
9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2
11      coolshell.cn:80 183.60.215.36:36970     TIME_WAIT
13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1
15      coolshell.cn:80 183.60.212.163:51082    TIME_WAIT
18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2</pre>
<p>上面的第一个示例匹配FIN状态， 第二个示例匹配WAIT字样的状态。其实 ~ 表示模式开始。/ /中是模式。这就是一个正则表达式的匹配。</p>
<p>其实awk可以像grep一样的去匹配第一行，就像这样：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '/LISTEN/' netstat.txt
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN
tcp        0      0 :::22                   :::*                    LISTEN</pre>
<p>我们可以使用 “/FIN|TIME/” 来匹配 FIN 或者 TIME :</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}' OFS=&quot;\t&quot; netstat.txt
1       Local-Address   Foreign-Address State
5       coolshell.cn:80 124.205.5.146:18245     TIME_WAIT
6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2
9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2
11      coolshell.cn:80 183.60.215.36:36970     TIME_WAIT
13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1
15      coolshell.cn:80 183.60.212.163:51082    TIME_WAIT
18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2</pre>
<p>再来看看模式取反的例子：</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk '$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS=&quot;\t&quot; netstat.txt
1       Local-Address   Foreign-Address State
2       0.0.0.0:3306    0.0.0.0:*       LISTEN
3       0.0.0.0:80      0.0.0.0:*       LISTEN
4       127.0.0.1:9000  0.0.0.0:*       LISTEN
7       coolshell.cn:80 110.194.134.189:1032    ESTABLISHED
8       coolshell.cn:80 123.169.124.111:49809   ESTABLISHED
10      coolshell.cn:80 123.169.124.111:49829   ESTABLISHED
12      coolshell.cn:80 61.148.242.38:30901     ESTABLISHED
14      coolshell.cn:80 110.194.134.189:4796    ESTABLISHED
16      coolshell.cn:80 208.115.113.92:50601    LAST_ACK
17      coolshell.cn:80 123.169.124.111:49840   ESTABLISHED
19      :::22   :::*    LISTEN</pre>
<p>或是：</p>
<pre class="brush: bash; title: ; notranslate">awk '!/WAIT/' netstat.txt</pre>
<p><strong>折分文件</strong></p>
<p>awk拆分文件很简单，使用重定向就好了。下面这个例子，是按第6例分隔文件，相当的简单（其中的NR!=1表示不处理表头）。</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk 'NR!=1{print &gt; $6}' netstat.txt

$ ls
ESTABLISHED  FIN_WAIT1  FIN_WAIT2  LAST_ACK  LISTEN  netstat.txt  TIME_WAIT

$ cat ESTABLISHED
tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED
tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED
tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED
tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED
tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED
tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED

$ cat FIN_WAIT1
tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1

$ cat FIN_WAIT2
tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2
tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2
tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2

$ cat LAST_ACK
tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK

$ cat LISTEN
tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN
tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN
tcp        0      0 :::22                  :::*                        LISTEN

$ cat TIME_WAIT
tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT
tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT
tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT</pre>
<p>你也可以把指定的列输出到文件：</p>
<pre class="brush: bash; title: ; notranslate">awk 'NR!=1{print $4,$5 &gt; $6}' netstat.txt</pre>
<p>再复杂一点：（注意其中的if-else-if语句，可见awk其实是个脚本解释器）</p>
<pre class="brush: bash; highlight: [1,2,3]; title: ; notranslate">$ awk 'NR!=1{if($6 ~ /TIME|ESTABLISHED/) print &gt; &quot;1.txt&quot;;
else if($6 ~ /LISTEN/) print &gt; &quot;2.txt&quot;;
else print &gt; &quot;3.txt&quot; }' netstat.txt

$ ls ?.txt
1.txt  2.txt  3.txt

$ cat 1.txt
tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT
tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED
tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED
tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED
tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT
tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED
tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED
tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT
tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED

$ cat 2.txt
tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN
tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN
tcp        0      0 :::22                  :::*                        LISTEN

$ cat 3.txt
tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2
tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2
tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1
tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK
tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2</pre>
<h5>统计</h5>
<p>下面的命令计算所有的C文件，CPP文件和H文件的文件大小总和。</p>
<pre class="brush: bash; title: ; notranslate">$ ls -l  *.cpp *.c *.h | awk '{sum+=$5} END {print sum}'
2511401</pre>
<p>我们再来看一个统计各个connection状态的用法：（我们可以看到一些编程的影子了，大家都是程序员我就不解释了。注意其中的数组的用法）</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ awk 'NR!=1{a[$6]++;} END {for (i in a) print i &quot;, &quot; a[i];}' netstat.txt
TIME_WAIT, 3
FIN_WAIT1, 1
ESTABLISHED, 6
FIN_WAIT2, 3
LAST_ACK, 1
LISTEN, 4</pre>
<p>再来看看统计每个用户的进程的占了多少内存（注：sum的RSS那一列）</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">$ ps aux | awk 'NR!=1{a[$1]+=$6;} END { for(i in a) print i &quot;, &quot; a[i]&quot;KB&quot;;}'
dbus, 540KB
mysql, 99928KB
www, 3264924KB
root, 63644KB
hchen, 6020KB</pre>
<h4>脱掉内衣</h4>
<h5>awk脚本</h5>
<p>在上面我们可以看到一个END关键字。END的意思是“处理完所有的行的标识”，即然说到了END就有必要介绍一下BEGIN，这两个关键字意味着执行前和执行后的意思，语法如下：</p>
<ul>
<li>BEGIN{ 这里面放的是执行前的语句 }</li>
<li>END {这里面放的是处理完所有的行后要执行的语句 }</li>
<li>{这里面放的是处理每一行时要执行的语句}</li>
</ul>
<p>为了说清楚这个事，我们来看看下面的示例：</p>
<p>假设有这么一个文件（学生成绩表）：</p>
<pre class="brush: bash; title: ; notranslate">$ cat score.txt
Marry   2143 78 84 77
Jack    2321 66 78 45
Tom     2122 48 77 71
Mike    2537 87 97 95
Bob     2415 40 57 62</pre>
<p>我们的awk脚本如下（我没有写有命令行上是因为命令行上不易读，另外也在介绍另一种用法）：</p>
<pre class="brush: bash; title: ; notranslate">$ cat cal.awk
#!/bin/awk -f
#运行前
BEGIN {
    math = 0
    english = 0
    computer = 0

    printf &quot;NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL\n&quot;
    printf &quot;---------------------------------------------\n&quot;
}
#运行中
{
    math+=$3
    english+=$4
    computer+=$5
    printf &quot;%-6s %-6s %4d %8d %8d %8d\n&quot;, $1, $2, $3,$4,$5, $3+$4+$5
}
#运行后
END {
    printf &quot;---------------------------------------------\n&quot;
    printf &quot;  TOTAL:%10d %8d %8d \n&quot;, math, english, computer
    printf &quot;AVERAGE:%10.2f %8.2f %8.2f\n&quot;, math/NR, english/NR, computer/NR
}</pre>
<p>我们来看一下执行结果：（也可以这样运行 ./cal.awk score.txt）</p>
<pre class="brush: bash; title: ; notranslate">$ awk -f cal.awk score.txt
NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL
---------------------------------------------
Marry  2143     78       84       77      239
Jack   2321     66       78       45      189
Tom    2122     48       77       71      196
Mike   2537     87       97       95      279
Bob    2415     40       57       62      159
---------------------------------------------
  TOTAL:       319      393      350
AVERAGE:     63.80    78.60    70.00</pre>
<h5>环境变量</h5>
<p>即然说到了脚本，我们来看看怎么和环境变量交互：（使用-v参数和ENVIRON，使用ENVIRON的环境变量需要export）</p>
<pre class="brush: bash; highlight: [9]; title: ; notranslate">$ x=5

$ y=10
$ export y

$ echo $x $y
5 10

$ awk -v val=$x '{print $1, $2, $3, $4+val, $5+ENVIRON[&quot;y&quot;]}' OFS=&quot;\t&quot; score.txt
Marry   2143    78      89      87
Jack    2321    66      83      55
Tom     2122    48      82      81
Mike    2537    87      102     105
Bob     2415    40      62      72
</pre>
<h4>几个花活</h4>
<p>最后，我们再来看几个小例子：</p>
<pre class="brush: bash; title: ; notranslate">#从file文件中找出长度大于80的行
awk 'length&gt;80' file

#按连接数查看客户端IP
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr

#打印99乘法表
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i&lt;=NF;i++)printf(&quot;%dx%d=%d%s&quot;, i, NR, i*NR, i==NR?&quot;\n&quot;:&quot;\t&quot;)}' </pre>
<h4>自己撸吧</h4>
<p>关于其中的一些知识点可以参看<a href="http://www.gnu.org/software/gawk/manual/gawk.html" target="_blank">gawk的手册</a>：</p>
<ul>
<li>内建变量，参看：<a href="http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din-Variables" target="_blank">http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din-Variables</a></li>
<li>流控方面，参看：<a href="http://www.gnu.org/software/gawk/manual/gawk.html#Statements" target="_blank">http://www.gnu.org/software/gawk/manual/gawk.html#Statements</a></li>
<li>内建函数，参看：<a href="http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din" target="_blank">http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din</a></li>
<li>正则表达式，参看：<a href="http://www.gnu.org/software/gawk/manual/gawk.html#Regexp" target="_blank">http://www.gnu.org/software/gawk/manual/gawk.html#Regexp</a></li>
</ul>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8619" data-post-type="none" >2012年11月23日 -- <a href="http://coolshell.cn/articles/8619.html" class="wp_rp_title">你可能不知道的Shell</a></li><li data-position="1" data-poid="in-8883" data-post-type="none" >2013年01月09日 -- <a href="http://coolshell.cn/articles/8883.html" class="wp_rp_title">应该知道的Linux技巧</a></li><li data-position="2" data-poid="in-7829" data-post-type="none" >2012年07月11日 -- <a href="http://coolshell.cn/articles/7829.html" class="wp_rp_title">28个Unix/Linux的命令行神器</a></li><li data-position="3" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="4" data-poid="in-9410" data-post-type="none" >2013年04月26日 -- <a href="http://coolshell.cn/articles/9410.html" class="wp_rp_title">Unix考古记：一个“遗失”的shell</a></li><li data-position="5" data-poid="in-7236" data-post-type="none" >2012年05月03日 -- <a href="http://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li data-position="6" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="7" data-poid="in-7490" data-post-type="none" >2012年06月20日 -- <a href="http://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/9070.html/feed</wfw:commentRss>
		<slash:comments>107</slash:comments>
		</item>
		<item>
		<title>Linus：利用二级指针删除单向链表</title>
		<link>http://coolshell.cn/articles/8990.html</link>
		<comments>http://coolshell.cn/articles/8990.html#comments</comments>
		<pubDate>Mon, 04 Feb 2013 00:33:20 +0000</pubDate>
		<dc:creator>Leo</dc:creator>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linus Torvalds]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=8990</guid>
		<description><![CDATA[感谢网友full_of_bull投递此文（注：此文最初发表在这个这里，我对原文后半段修改了许多，并加入了插图） [...]]]></description>
				<content:encoded><![CDATA[<p><strong>感谢网友full_of_bull投递此文</strong>（注：此文最初发表在这个<a href="http://www.oldlinux.org/oldlinux/viewthread.php?tid=14575&amp;extra=page%3D1" target="_blank">这里</a>，我对原文后半段修改了许多，并加入了插图）</p>
<p>Linus大婶在<a href="http://meta.slashdot.org/story/12/10/11/0030249/linus-torvalds-answers-your-questions" target="_blank">slashdot</a>上回答一些编程爱好者的提问，其中一个人问他什么样的代码是他所喜好的，大婶表述了自己一些观点之后，举了一个指针的例子，解释了什么才是<strong>core low-level coding</strong>。</p>
<p>下面是Linus的教学原文及翻译——</p>
<p style="padding-left: 30px;">“At the opposite end of the spectrum, I actually wish more people understood the really core low-level kind of coding. Not big, complex stuff like the lockless name lookup, but simply good use of pointers-to-pointers etc. For example, I&#8217;ve seen too many people who delete a singly-linked list entry by keeping track of the &#8220;prev&#8221; entry, and then to delete the entry, doing something like。（在这段话的最后，我实际上希望更多的人了解什么是真正的核心底层代码。这并不像无锁文件名查询（注：可能是git源码里的设计）那样庞大、复杂，只是仅仅像诸如使用二级指针那样简单的技术。例如，我见过很多人在删除一个单项链表的时候，维护了一个&#8221;prev&#8221;表项指针，然后删除当前表项，就像这样）”</p>
<pre class="brush: cpp; title: ; notranslate">if (prev)
    prev-&gt;next = entry-&gt;next;
else
    list_head = entry-&gt;next;</pre>
<p style="padding-left: 30px;">and whenever I see code like that, I just go &#8220;This person doesn&#8217;t understand pointers&#8221;. And it&#8217;s sadly quite common.（当我看到这样的代码时，我就会想“这个人不了解指针”。令人难过的是这太常见了。）</p>
<p><span id="more-8990"></span></p>
<p style="padding-left: 30px;">People who understand pointers just use a &#8220;pointer to the entry pointer&#8221;, and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a &#8220;*pp = entry-&gt;next&#8221;. （了解指针的人会使用链表头的地址来初始化一个“指向节点指针的指针”。当遍历链表的时候，可以不用任何条件判断（注：指prev是否为链表头）就能移除某个节点，只要写)</p>
<pre class="brush: cpp; title: ; notranslate">*pp = entry-&gt;next</pre>
<p style="padding-left: 30px;">So there&#8217;s lots of pride in doing the small details right. It may not be big and important code, but I do like seeing code where people really thought about the details, and clearly also were thinking about the compiler being able to generate efficient code (rather than hoping that the compiler is so smart that it can make efficient code *despite* the state of the original source code). （纠正细节是令人自豪的事。也许这段代码并非庞大和重要，<strong>但我喜欢看那些注重代码细节的人写的代码，也就是清楚地了解如何才能编译出有效代码</strong>（而不是寄望于聪明的编译器来产生有效代码，即使是那些原始的汇编代码））。</p>
<p>Linus举了一个单向链表的例子，但给出的代码太短了，一般的人很难搞明白这两个代码后面的含义。正好，有个编程爱好者阅读了这段话，并给出了一个<a href="http://wordaligned.org/articles/two-star-programming" target="_blank">比较完整的代码</a>。他的话我就不翻译了，下面给出代码说明。</p>
<p>如果我们需要写一个remove_if(link*, rm_cond_func*)的函数，也就是传入一个单向链表，和一个自定义的是否删除的函数，然后返回处理后的链接。</p>
<p>这个代码不难，基本上所有的教科书都会提供下面的代码示例，而这种写法也是大公司的面试题<strong>标准</strong>模板：</p>
<pre class="brush: cpp; title: ; notranslate">typedef struct node
{
    struct node * next;
    ....
} node;

typedef bool (* remove_fn)(node const * v);

// Remove all nodes from the supplied list for which the
// supplied remove function returns true.
// Returns the new head of the list.
node * remove_if(node * head, remove_fn rm)
{
    for (node * prev = NULL, * curr = head; curr != NULL; )
    {
        node * const next = curr-&gt;next;
        if (rm(curr))
        {
            if (prev)
                prev-&gt;next = next;
            else
                head = next;
            free(curr);
        }
        else
            prev = curr;
        curr = next;
    }
    return head;
}</pre>
<p>这里remove_fn由调用查提供的一个是否删除当前实体结点的函数指针，其会判断删除条件是否成立。这段代码维护了两个节点指针prev和curr，<strong>标准的教科书写法——删除当前结点时，需要一个previous的指针，并且还要这里还需要做一个边界条件的判断——curr是否为链表头</strong>。于是，要删除一个节点（不是表头），只要将前一个节点的next指向当前节点的next指向的对象，即下一个节点（即：prev-&gt;next = curr-&gt;next），然后释放当前节点。</p>
<p>但在Linus看来，这是不懂指针的人的做法。那么，什么是core low-level coding呢？那就是<strong>有效地利用二级指针，将其作为管理和操作链表的首要选项。</strong>代码如下：</p>
<pre class="brush: cpp; highlight: [5,8,12]; title: ; notranslate">void remove_if(node ** head, remove_fn rm)
{
    for (node** curr = head; *curr; )
    {
        node * entry = *curr;
        if (rm(entry))
        {
            *curr = entry-&gt;next;
            free(entry);
        }
        else
            curr = &amp;entry-&gt;next;
    }
}</pre>
<p>同上一段代码有何改进呢？我们看到：<strong>不需要prev指针了，也不需要再去判断是否为链表头了，但是，<span style="color: #cc0000;">curr变成了一个指向指针的指针</span></strong>。这正是这段程序的精妙之处。（注意，我所highlight的那三行代码）</p>
<p>让我们来人肉跑一下这个代码，对于——</p>
<ul>
<li><strong>删除节点是表头</strong>的情况，输入参数中传入head的二级指针，在for循环里将其初始化curr，然后entry就是*head(*curr)，我们马上删除它，那么第8行就等效于*head = (*head)-&gt;next，就是删除表头的实现。</li>
</ul>
<ul>
<li><strong>删除节点不是表头</strong>的情况，对于上面的代码，我们可以看到——</li>
</ul>
<p style="padding-left: 30px;"><strong>1）<strong>（第12行）</strong>如果不删除当前结点 —— curr保存的是当前结点next指针的地址</strong>。</p>
<p style="padding-left: 30px;"><strong>2）（第5行） entry 保存了 *curr <strong>—— </strong>这意味着在下一次循环：entry就是prev-&gt;next指针所指向的内存。</strong></p>
<p style="padding-left: 30px;"><strong></strong><strong>3）（第8行）删除结点：*curr = entry-&gt;next; —— 于是：prev-&gt;next 指向了 entry -&gt; next;</strong></p>
<p>是不是很巧妙？我们可以只用一个二级指针来操作链表，对所有节点都一样。</p>
<p>如果你对上面的代码和描述理解上有困难的话，你可以看看下图的示意：</p>
<p><img class="aligncenter size-full wp-image-9018" alt="" src="http://coolshell.cn//wp-content/uploads/2013/02/linus_pointer_to_pointer.jpg" width="479" height="470" /></p>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-7886" data-post-type="none" >2012年07月13日 -- <a href="http://coolshell.cn/articles/7886.html" class="wp_rp_title">代码执行的效率</a></li><li data-position="1" data-poid="in-8275" data-post-type="none" >2012年09月14日 -- <a href="http://coolshell.cn/articles/8275.html" class="wp_rp_title">对九个超级程序员的采访</a></li><li data-position="2" data-poid="in-7992" data-post-type="none" >2012年08月06日 -- <a href="http://coolshell.cn/articles/7992.html" class="wp_rp_title">C++的坑真的多吗？</a></li><li data-position="3" data-poid="in-1278" data-post-type="none" >2009年08月16日 -- <a href="http://coolshell.cn/articles/1278.html" class="wp_rp_title">Linus Torvalds 语录 Top 10</a></li><li data-position="4" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="5" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="6" data-poid="in-2322" data-post-type="none" >2010年04月09日 -- <a href="http://coolshell.cn/articles/2322.html" class="wp_rp_title">Unix传奇(上篇)</a></li><li data-position="7" data-poid="in-4576" data-post-type="none" >2011年04月27日 -- <a href="http://coolshell.cn/articles/4576.html" class="wp_rp_title">Linux 2.6.39-rc3的一个插曲</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/8990.html/feed</wfw:commentRss>
		<slash:comments>109</slash:comments>
		</item>
		<item>
		<title>从面向对象的设计模式看软件设计</title>
		<link>http://coolshell.cn/articles/8961.html</link>
		<comments>http://coolshell.cn/articles/8961.html#comments</comments>
		<pubDate>Fri, 01 Feb 2013 00:15:59 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[Unix]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=8961</guid>
		<description><![CDATA[前些天发了一篇《如此理解面向对象编程》的文章，然后引起了大家的热议。然后我在微博上说了一句——“那23个经典的 [...]]]></description>
				<content:encoded><![CDATA[<p>前些天发了一篇《<a title="如此理解面向对象编程" href="http://coolshell.cn/articles/8745.html" target="_blank" rel="bookmark">如此理解面向对象编程</a>》的文章，然后引起了大家的热议。然后我在<a href="http://weibo.com/1401880315/z9wWHrrVR" target="_blank">微博上说</a>了一句——“<strong>那23个经典的设计模式和OO半毛钱关系没有，只不过人家用OO来实现罢了……OO的设计模式思想和Unix的设计思想基本没什么差别</strong>”，结果引来了一点点争议。所以，我写下这篇文章把我的观点说明一下。我希望这样可以让大家更容易地理解什么是设计模式。<strong>我顺便帮OO和 Unix/Linux搞搞基</strong>。</p>
<h4>什么是模式</h4>
<p>在正式说明GoF的那23个经典的设计模式其实和OO关系不大并和Unix的设计思想很相似的这个观点之前，让我先来说说什么是模式？设计模式的英文是Design Pattern，模式是Pattern的汉译。所谓Pattern就是一种规则，或是一种模型，或是一种习惯。Pattern这个东西到处都是，并不只有技术圏子里才有。比如：</p>
<ul>
<li>文章有文章的Pattern。如新闻有新闻的Pattern（第一段话简述了整个新闻），诗歌总是抒情的，论文总是死板的，讲稿总是高谈的，漫画总是幽默的，……</li>
<li><span style="line-height: 13px;">小说有小说的Pattern。比如，</span>
<ul>
<li><span style="line-height: 13px;">武侠小说必然要整个武林大会，整几个NB的武功和大师，分个正派和反派，还有一个或数个惊天阴谋，坏人总是要在一开始占尽优势，好人总是要力挽狂澜……</span></li>
<li><span style="line-height: 13px;">言情小说总是要有第三者，总是要有负心人，里面的女子总是要哭得死去活来，但又痴心不改，……</span></li>
</ul>
</li>
<li> 新闻联播的模式是：头10分钟领导很忙，中间10分钟人民很幸福，后10分钟国外很乱。中国政府官方宣传稿也模式也很明显，各种赞美，口号，胜利，总是要坚持个什么，团结个什么，迈向个什么，某某精神，某某思想，群众情绪稳定，不明真相，等等……</li>
<li>春节的模式是，回家，吃饺子，放个鞭炮，给压岁钱，同学聚会…… 同学聚会的模式基本上都是在饭桌上回忆一下校园时光，比较一下各自的当前处境，调戏一下女同学……</li>
<li>…… ……</li>
</ul>
<p>这就是Pattern，只要你细心观察，你会发现这世间有很多很多的Pattern。</p>
<p><span id="more-8961"></span></p>
<h4>GoF的23个设计模式</h4>
<p>《<a href="http://product.china-pub.com/25961" target="_blank">设计模式</a>》这本书中，GoF这四个人总结了23个经典的面向对象的设计模式，某中有5个创建模式，7个结构模式，11个行为模式。<strong>很多人都会觉得这是面向对象的设计模式，很多人也觉得非面向对象不能用这些模式。我觉得这是一种教条主义。</strong>就像《<a title="各种流行的编程风格" href="http://coolshell.cn/articles/2058.html" target="_blank">那些流行的编程方法</a>》中的“设计模式驱动型编程”一样，就像《<a title="如此理解面向对象编程" href="http://coolshell.cn/articles/8745.html" target="_blank">如此理解面向对象</a>》一样的那么的滑稽。</p>
<p>好了，回到我的论点——“<strong>GoF的这23个设计模式和OO关系不大，并且和Unix的设计思想基本一致，只不过GoF用OO实现了它们</strong>”，就像我上面说过的那些生活中的Pattern一样，只要你仔细思考，你会发现这23个设计模式在我们的生活和社会中也能有他们的身影。而且也一样可以用OO的方式实现之。</p>
<p>让我们来看看这23个经典的设计模式中的几个常用的模式：</p>
<p><strong>Factory 模式</strong>，这个模式可能是是个人都知道的模式。这个模式在现实社会中就像各种工厂一样，工厂跨界的不多，基本上都是在生产同一类的产品，有的生产汽车，有的生产电视，有的生产衣服，有的生产卫生纸……基本上来说，一个生产线上只有做同一类的东西。这和Factory模式很相似。编程中，像内存池，线程池，连接池等池化技术都是这个模式，当然，Factory给你的一个对象，而不单单只是资源，factory创建出来的对象都有同样的接口可以被多态调用。<strong>这其实和Unix把所有的硬件都factory成文件一样，并提供了read/write等文件操作来让你操作任意设备的I/O</strong>。</p>
<p><strong>Abstract Factor</strong>y：抽象工厂这个模式是创建一组有同一主题的不同的类。这个模式在现实社会当中也有很多例子，比如：</p>
<ul>
<li>移动公司的合约机计划，88套餐（通话100分钟，短信100条，彩信，20条，上网200M），128套餐（通话200分钟，短信150条，彩信50条，上网500M）……</li>
</ul>
<ul>
<li>家里的装修，总是要有厨卫，有门，有灯，有沙发，有茶几，有床，有衣柜，有电视，有冰箱，有洗衣机……，这些都是必需的，只是每个家庭里的具体装修不一样。</li>
</ul>
<ul>
<li>Diablo游戏中的Normal，Hard，Nightmare，Hell模式，这些模式的怪和场景和故事情况都差不多，就是每个场景的怪物和装备的属性不一样。或是WarCraft中的地图就是一个Abstract Factory模式(注：Warcraft的地图什么都能干)。这和学校中的小学，初中，高中，大学差不多，都是一样的学习环境，一样的教学方式，一样的教室，都要期中考和期末考，都有班长和科代表，就是学的东西的难度不一样，但基本上都是语文，英语，数，理，化，还有永远都有的政治课。学校就是一个抽象工厂。</li>
</ul>
<p>这就是抽象工厂的业务模型（或是：Business Pattern），你觉得是不是不一定非要用OO来实现这样的模式？（我们思考一下，我们会不会被先入为主了，觉得不会OO都不知道怎么实现了），不用OO，用相同格式但内容不同的配置文件是不是也能实现？在Unix下<strong>，抽象工厂这个模式在Unix下就像是/etc/rcX.d下的那些东西，1代表命令行单用户，2，代表命令行多用户，3代表命令行多用户完整模式启动，5代表图形界面启动，0代表关机，6代表重启，你要切换的话，init &lt;X&gt;就行了</strong>。</p>
<p><strong>Prototype模式</strong>，原型模式，复制一个类的实现。这个模式在现实中的例子也有很多：传真，复印，都是这个模式。<strong>Unix进程和Github项目的Fork就是一种。进程fork明显不是OO的模型</strong>（参看：<a title="一个fork的面试题" href="http://coolshell.cn/articles/7965.html" target="_blank">关于Fork的一道面试题</a>）。用非OO的方法同样可以实现这个模式。</p>
<p><strong>Singleton模式</strong>，单例模式。生活中，公司只有一个CEO，法律限制你只能有一个老婆，你只能有一个身份证号，一个TCP端口只能被一个进程使用，等等。软件开发方面，并不一定只有OO才能做到，你可以用一个全局变量，一个中心服务器，甚至可以使用行政手段来约束开发中不会出现多个实例。<strong>Unix下实现单例进程的一个最常用的实践是在进程启动的时候用“(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)”模式打开一个“锁文件”</strong>。</p>
<p><strong>Adapter模式</strong>，适配器模式。可以兼容欧洲美国中国的插头或插座，万能读卡器，可以播放各种格式多媒体文件的插放器，可以解析FTP/HTTP/HTTPS/等网络协议的浏览器，可以兼容各大银行的银联接口、支付宝、Paypal、VISA等银行接口，可以适配各种后端的解释器的Nginx或Apache，等等。用非OO的编程方式就是重新包装成一个标准接口。<strong>这个模式很像Unix下的/dev下的那些文件，操作系统把系统设备适配成文件，于是你就可以使用read/write来进行读写了</strong>。</p>
<p><strong>Bridge模式</strong>，桥接模式。这个模式用的更多，比如一个灯具可以接各种灯泡或灯管，一个电钻可以换上不同的钻头来适应不同的材料，一辆汽车可以随时更换不同的轮胎来适应不同的路面，你的桌面可以随时更换一个图片来适应你的心情，你的单反相机可以更换不同的镜头来拍不同的照片…… 桥接模式说白了就是组件化，模块化，可以自由拼装。在OO中，其主要是通过让业务类组合一个标准接口来完成，这在非OO的程序设计中用得实在是太多了，主要是通过回调函数或是标准接口来实现。这个也是Unix设计哲学中的主要思想。<strong>在Unix中，文件的权限使用的就是Bridge模式，标准接口是用户，用户组和其它，rwx三个模式，然后用 chmod/chown改一改，这文件就有不同的属主和属性了</strong>。</p>
<p><strong>Decorator模式</strong>，装饰模式。这个模式在生活中太多了，你给你的手机或电脑贴个什么，挂个什么，吃东西的时候加点什么佐料，多点肉还是多个蛋，一个Unix/Linux命令的各种参数是对这个命令的修饰，等等。<strong>我觉得这个模式在Unix中最经常的体现就是通过管道把命令连接起来来完成一个功能</strong>，比如：ps -elf  是列进程的，用管道 grep hchen就可以达到过滤的目的，grep的逻辑没有侵入ps中，grep 修饰了 ps，但是其组合起来完成了一个特定的功能。可见，这和OO没有什么关系。</p>
<p><strong>Facade模式，</strong>这个模式我们每个人从会编程的时候就在无意识地用这个模式了。这个模式就是把一大堆类拼装起来，并统一往外提供提口。在现实生活中这样的例子太多了，比如：旅行社把机票，酒店，景点，导游，司机，进店打了一个包叫旅行；IBM把主机，存储，OS，J2EE，DB，网络，流程打了个包叫企业级解决方案。Unix中最典型的一个例子就是用Shell脚本组合各种命令来创造一个新的功能，这是的Shell中的各种命令通过标准I/O这个接口进行组合交互。</p>
<p><strong>Proxy模式</strong>，代理模式。我们租个房，买个机票，打个官司，都少不了代理，人大代表代理了老百姓去行使政治权力。我们去饭馆里吃饭也是一种代理模式，因为我们只管吃就好了，洗菜做饭洗碗的工作都被Proxy帮你干了，于是你就省事多了。操作系统就是硬件的代理，CDN就是网站的代理，……使用代理你可以让事情变理更简单，也可以在代理层加入一些权限检查，这样可以让业务模块更关注业务，而把一些非业务的事情剥离出来交给代理以完成解耦。可见这个模式和OO没啥关系。<strong>Unix下这个模式最佳体现就是Shell，它代理了系统调用并提供UI</strong>。还有很多命令会帮你把/proc目录下的那些文件内容整理和显示出来。</p>
<p><strong>Chain of Responsibility模式</strong>，劫匪来抢银行，保安搞不定，就交给110，110搞不定就交给武警。有什么事件发生时的响应的Escalation Path，办公中的逐级审批。这个模式用一个函数指针数组或是栈结构就可以实现了。这个思想很像编程中的异常处理机制，一层一层地往上传递异常直到异常被捕捉。<strong>在Unix下，一个最简单的例子就是用 &amp;&amp; 或 || 来把命令拼起来，如：cmd1 &amp;&amp; cmd2  或 cmd3 || cmd4 ， 如果cmd1失败了，cmd2就不会执行，如果cmd3失败了，cmd4才会执行。</strong>如： cd lib &amp;&amp; rm -rf .o 或 ping -c1 coolshell.cn &amp;&amp; ssh haoel@coolshell.cn</p>
<p><strong>Command模式</strong>，这恐怕是软件里最多的模式了，比如：编译器里的Undo/Redo，宏录制。还有数据库的事务处理，线程池，设置向导，包括程序并行执行的指令集等等。这个模式主要是把一个对象的行为封装成一个一个的有相同接口的command，然后交给一个统一的命令执行器执行或管理这些命令。<strong>这个模式和我们的Unix/Linux机器启动时在/etc/init.d下的那些S和K开头的脚本很像，把各种daemon的启动和退出行为封装成一个脚本其支持reload/start/stop/status这样的命令，然后把他们按一定的规范做符号链接到/etc/init.d目录下，这样操作系统就会接管这些daemon的启动和退出</strong>。</p>
<p><strong>Observer模式</strong>，观察者模式，这个模式也叫pub-sub模式，很像我们用手机订阅手机报，微博的follow的信息流也是这样的一个模式。MVC中的C会sub V中的事件，用非OO的方式其实也是一个回调函数的事。在很多异步系统中，你需要知道最终的调用有没有成功，比如说调用支付宝的支付接口，你需要向支付宝注册一个回调的接口，以便支付宝回调你。<strong>Linux下的一些系统调用如epoll/aio/inotify/signal都是这种思路</strong>。</p>
<p><strong>Strategy 模式</strong>，策略模式，这个模式和Bridge模式很像，只不过Bridge是结构模式，其主要是用于对象的构造；而Strategy是行为模式，主要是用于对象的行为。策略模式很像浏览器里的各种插件，只要你装了某个插件，你就有某个功能。你可以安装多个插件来让你的浏览器有更多的功能（书本上的这个模式是你只能选用一个算法，当然，我们不用那么教条）。<strong>就像《<a title="你可能不知道的Shell" href="http://coolshell.cn/articles/8619.html" target="_blank">你可能不知道的Shell</a>》中的那个设置设置$EDITOR变量后可以按ctrl+x e启动编译器，或是用set -o vi或set -o emacs 来让自己的shell像vi或 emacs 一样，或是像find -exec或xargs一样的拼装命令</strong>。</p>
<p><strong>Bridge 和 Strategy是OO设计模式里的“Favor Composition Over Inheritance” 的典范，其实现了接口与实现分离的</strong>。Unix中的Shell就是一种，你可随意地更换不同的Shell。还有Emacs中的LISP驱动C，C实现了引擎，交给LISP实现逻辑。把程序分为前端和后端，通过socket专用应用协议进行通讯，前端实现策略，后端实现机制。再看看makefile把编译器和源代码的解耦，命令行输出这个接口可以把一个复杂的功能解耦并抽像成各种各样小而美的小功能命令，等等这样的例子，你会发现，还有大量的编程框架都会多少采用这样的思想，可以让你的软件像更换汽车零件一样方便。我在用<a title="用Unix的设计思想来应对多变的需求" href="http://coolshell.cn/articles/7236.html" target="_blank">Unix的设计思想来应对变更的需求</a>中说过灯具厂，灯泡厂，和开关厂的例子。</p>
<h4>后记</h4>
<p>因为写作仓促，上面的那些东西，可能会你让你觉得有些牵强，那么抱歉了，你可以帮我看看在生活中和 Unix里有没有更帅的例子。</p>
<p>不过，我们会发现上面OO搞出来的那么多模式在Unix下看来好像没有那么复杂，而且Unix下看起来并没有那么多模式，而且Unix中的设计模式无非就是这么几个关键词：<strong>单一，简洁，模块，拼装</strong>。我们再来看看OO设计的两大准则：<strong>1）钟情于组合而不是继承，2）依赖于接口而不是实现</strong>。还有S.O.L.I.D原则也一样（如果你仔细观察，你会发现SOLID原则在Unix下也是完美地体现）。你看，Unix和OO设计模式是不是完美的统一吗？</p>
<p>我有种强烈的感觉——<strong>Unix对这些所谓的OO的设计模式实现得更好</strong>。因为Unix就一条设计模式！再次推荐《<em><a href="http://book.douban.com/subject/5387401/" target="_blank">The Art of Unix Programming</a></em>》</p>
<p><img class="size-full wp-image-8967 aligncenter" alt="Unix Kiss" src="http://coolshell.cn//wp-content/uploads/2013/01/kiss.png" width="468" height="219" /></p>
<h4>餐后甜点</h4>
<p>我上面提到了《<em><a href="http://book.douban.com/subject/5387401/" target="_blank">The Art of Unix Programming</a></em>》，所以我有必要再谈谈这本书中我中毒最深的一章《模块性：保持清晰和简洁》中所谈到的胶合层。</p>
<p>胶合层这一节中说了，我们开发软件一般要么Top-Down，要么Bottom-Up，这两种方法都有好有不好。顶层一般是应用逻辑层，底层一般是原语层（我理解为技术沉淀层，或是技术基础层）。自顶向下的开发，你可能会因为开发到底层后发现底层可沉淀的东西越来越不爽（因为被可能被很多业务逻辑所侵入），如果自底向上的开发，你可能越到上层你越发现很多你下面干的基础上工作有很多用不上（比如干多了）。所以，最好的方式是同时进行，一会顶层，一会底层，来来回回的开发——说白了就是在开发中不断的重构，边开发边理解边沉淀。</p>
<p>无论怎么样，你会发现需要一层胶合层来胶合业务逻辑层和底层原语层（软件开发中的业务层和技术层的胶合），Unix的设计哲学认为，这层胶合层应该尽量地薄，胶合层越多，我们就只能在其中苦苦挣扎。</p>
<p>其实，<strong>胶合层原则就是分离原则上更为上层地体现，策略（业务逻辑）和机制（基础技术或原语）的清楚的分离。你可以看到，OO和Unix都是在做这样的分离。但是需要注意到的时，OO用抽象接口来做这个分离——很多OO的模式中，抽象层太多了，导致胶合层太过于复杂了，也就是说，OO鼓励了——“厚重地胶合和复杂层次”，反而增加了程序的复杂度（这种情况在恶化中）。而Unix采用的是薄的胶合层，薄地相当的优雅</strong>。（通过这段话的描述，我相信你会明白了《<a title="如此理解面向对象编程" href="http://coolshell.cn/articles/8745.html" target="_blank" rel="bookmark">如此理解面向对象编程</a>》中的个例子——为什么用OO来实现会比用非OO来实现更为地恶心——那就是因为OO胶合层太复杂了）</p>
<p><strong>OO的最大的问题就——接口复杂度太高，胶合层太多！</strong>（注：Unix编程艺术这本书里说了软件有三个复杂度：代码量、接口、实现，这三个东西构成了我们的软件复杂度）</p>
<h4>再送一个果盘</h4>
<p>大家一定记得《<a title="SteveY对Amazon和Google平台的长篇大论 - 60,581 人阅读" href="http://coolshell.cn/articles/5701.html">SteveY对Amazon和Google平台的长篇大论</a>》中Amazon中那个令人非常向往的SOA式的架构。因为以前在Amazon，有些话不好说。现在可以说了，我在Amazon里，我个人对这个服务化的架构相当的不待见，太复杂，复杂以乱七八糟，方向是好的，想法也是好的，但是这东西和OO一样，造成大量的接口复杂度，今天的Amazon，完全没人知道各个服务是怎么个调用的，一团乱麻（其内部并不像你看到的AWS那么的美妙。注：AWS是非常不错的，是相当好的设计）。</p>
<p><strong>那么我们怎么来解决SOA的接口复杂度问题？其实，Unix早就给出了答案——数据驱动编程</strong>（详见：《Unix编程艺术》的第9.1章），在我离开Amazon的时候，美国总部的Principle SDE们在吐槽今天Amazon的SOA架构，更好的架构应该是数据驱动式的。（今天还在Amazon的同学可以上内网boardcast上看看相关的Principle Talk视频）</p>
<p>（瞎扯一句：这本来是我想在2012年杭州QCon上的分享的一个主题，无奈当时被大会组织者给拒了，所以只好讲了一个《建一支小团队》，今天有多人还是不能明白甚至反感我的那个《小团队》的演讲，但是我相信那是必然的趋势，就像十年前大家在说“程序员只能干到30岁”时，当时的我我却毫不犹豫地相信十年后，30岁以上的有经验的老程序员一定会成为各个公司角逐和竟争的红人）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-7236" data-post-type="none" >2012年05月03日 -- <a href="http://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li data-position="1" data-poid="in-6950" data-post-type="none" >2012年03月26日 -- <a href="http://coolshell.cn/articles/6950.html" class="wp_rp_title">需求变化与IoC</a></li><li data-position="2" data-poid="in-4535" data-post-type="none" >2011年04月25日 -- <a href="http://coolshell.cn/articles/4535.html" class="wp_rp_title">一些软件设计的原则</a></li><li data-position="3" data-poid="in-8745" data-post-type="none" >2012年12月13日 -- <a href="http://coolshell.cn/articles/8745.html" class="wp_rp_title">如此理解面向对象编程</a></li><li data-position="4" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="5" data-poid="in-7829" data-post-type="none" >2012年07月11日 -- <a href="http://coolshell.cn/articles/7829.html" class="wp_rp_title">28个Unix/Linux的命令行神器</a></li><li data-position="6" data-poid="in-8619" data-post-type="none" >2012年11月23日 -- <a href="http://coolshell.cn/articles/8619.html" class="wp_rp_title">你可能不知道的Shell</a></li><li data-position="7" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/8961.html/feed</wfw:commentRss>
		<slash:comments>56</slash:comments>
		</item>
		<item>
		<title>应该知道的Linux技巧</title>
		<link>http://coolshell.cn/articles/8883.html</link>
		<comments>http://coolshell.cn/articles/8883.html#comments</comments>
		<pubDate>Wed, 09 Jan 2013 00:24:29 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[vim]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=8883</guid>
		<description><![CDATA[这篇文章来源于Quroa的一个问答《What are some time-saving tips that e [...]]]></description>
				<content:encoded><![CDATA[<p><img class="alignright size-full wp-image-8899" alt="" src="http://coolshell.cn//wp-content/uploads/2013/01/linux-bash-300x225.jpg" width="300" height="225" />这篇文章来源于Quroa的一个问答《<a href="http://www.quora.com/Linux/What-are-some-time-saving-tips-that-every-Linux-user-should-know#" target="_blank">What are some time-saving tips that every Linux user should know?</a>》—— Linux用户有哪些应该知道的提高效率的技巧。我觉得挺好的，总结得比较好，把其转过来，并加了一些自己的理解。 首先，我想告诉大家，<strong>在Unix/Linux下，最有效率技巧的不是操作图形界面，而是命令行操作，因为命令行意味着自动化</strong>。如果你看过《<a title="你可能不知道的Shell" href="http://coolshell.cn/articles/8619.html" target="_blank">你可能不知道的Shell</a>》以及《<a title="28个Unix/Linux的命令行神器" href="http://coolshell.cn/articles/7829.html" target="_blank">28个Unix/Linux的命令行神器</a>》你就会知道Linux有多强大，这个强大完全来自于命令行，于是，就算你不知道怎么去<a title="做个环保主义的程序员" href="http://coolshell.cn/articles/7186.html" target="_blank">做一个环保主义的程序员</a>，至少他们可以让你少熬点夜，从而有利于你的身体健康和性生活。下面是一个有点长的列表，正如作者所说，你并不需要知道所有的这些东西，但是如果你还在很沉重地在使用Linux的话，这些东西都值得你看一看。 （注：如果你想知道下面涉及到的命令的更多的用法，你一定要man一点。对于一些命令，你可以需要先yum或apt-get来安装一下，如果有什么问题，别忘了Google。如果你要Baidu的话，我仅代表这个地球上所有的生物包括微生物甚至细菌病毒和小强BS你到宇宙毁灭）</p>
<h4>基础</h4>
<ul>
<li><strong>学习 <a href="http://www.quora.com/Bash-shell" target="_blank">Bash</a> </strong>。你可以man bash来看看bash的东西，并不复杂也并不长。你用别的shell也行，但是bash是很强大的并且也是系统默认的。（学习zsh或tsch只会让你在很多情况下受到限制）</li>
</ul>
<ul>
<li><strong>学习 vim</strong> 。在Linux下，基本没有什么可与之竞争的编<del>译</del>辑器（就算你是一个Emacs或Eclipse的重度用户）。你可以看看《<a title="简明 Vim 练级攻略" href="http://coolshell.cn/articles/5426.html" target="_blank">简明vim攻略</a>》和 《<a title="游戏：VIM大冒险" href="http://coolshell.cn/articles/7166.html" target="_blank">Vim的冒险游戏</a>》以及《<a title="给程序员的VIM速查卡" href="http://coolshell.cn/articles/5479.html" target="_blank">给程序员的Vim速查卡</a>》还有《<a title="将vim变得简单:如何在vim中得到你最喜爱的IDE特性" href="http://coolshell.cn/articles/894.html" target="_blank">把Vim变成一个编程的IDE</a>》等等。</li>
</ul>
<ul>
<li><strong>了解 ssh</strong>。明白不需要口令的用户认证（通过ssh-agent, ssh-add），学会用ssh翻墙，用scp而不是ftp传文件，等等。你知道吗？scp 远端的时候，你可以按tab键来查看远端的目录和文件（当然，需要无口令的用户认证），这都是bash的功劳。</li>
</ul>
<p><span id="more-8883"></span></p>
<ul>
<li><strong>熟悉bash的作业管理</strong>，如： &amp;, Ctrl-Z, Ctrl-C, jobs, fg, bg, kill, 等等。当然，你也要知道Ctrl+\（SIGQUIT）和Ctrl+C （SIGINT）的区别。</li>
</ul>
<ul>
<li><strong>简单的文件管理</strong> ： ls 和 ls -l (你最好知道 &#8220;ls -l&#8221; 的每一列的意思), less, head, tail 和 tail -f, ln 和 ln -s (你知道明白hard link和soft link的不同和优缺点), chown, chmod, du (如果你想看看磁盘的大小 du -sk *), df, mount。当然，原作者忘了find命令。</li>
</ul>
<ul>
<li><strong>基础的网络管理</strong>： ip 或 ifconfig, dig。当然，原作者还忘了如netstat, ping, traceroute, 等</li>
</ul>
<ul>
<li><strong>理解正则表达式</strong>，还有grep/egrep的各种选项。比如： -o, -A, 和 -B 这些选项是很值得了解的。</li>
</ul>
<ul>
<li><strong>学习使用 apt-get 和 yum 来查找和安装软件</strong>（前者的经典分发包是Ubuntu，后者的经典分发包是Redhat），我还建议你试着从源码编译安装软件。</li>
</ul>
<p><b>日常</b></p>
<ul>
<li>在 bash 里，使用 Ctrl-R 而不是上下光标键来查找历史命令。</li>
</ul>
<ul>
<li>在 bash里，使用 Ctrl-W 来删除最后一个单词，使用 Ctrl-U 来删除一行。请man bash后查找Readline Key Bindings一节来看看bash的默认热键，比如：Alt-. 把上一次命令的最后一个参数打出来，而Alt-* 则列出你可以输入的命令。</li>
</ul>
<ul>
<li>回到上一次的工作目录： cd &#8211;  （回到home是 cd ~）</li>
</ul>
<ul>
<li>使用 xargs。这是一个很强大的命令。你可以使用-L来限定有多少个命令，也可以用-P来指定并行的进程数。如果你不知道你的命令会变成什么样，你可以使用xargs echo来看看会是什么样。当然， -I{} 也很好用。示例：</li>
</ul>
<blockquote><pre class="brush: bash; title: ; notranslate">find . -name \*.py | xargs grep some_function

cat hosts | xargs -I{} ssh root@{} hostname</pre>
</blockquote>
<ul>
<li>pstree -p 可以帮你显示进程树。（读过我的那篇《<a title="一个fork的面试题" href="http://coolshell.cn/articles/7965.html" target="_blank">一个fork的面试题</a>》的人应该都不陌生）</li>
</ul>
<ul>
<li>使用 pgrep 和 pkill 来找到或是kill 某个名字的进程。 (-f 选项很有用).</li>
</ul>
<ul>
<li>了解可以发给进程的信号。例如：要挂起一个进程，使用 kill -STOP [pid]. 使用 man 7 signal 来查看各种信号，使用kill -l 来查看数字和信号的对应表</li>
</ul>
<ul>
<li>使用 nohup 或  disown 如果你要让某个进程运行在后台。</li>
</ul>
<ul>
<li>使用netstat -lntp来看看有侦听在网络某端口的进程。当然，也可以使用 lsof。</li>
</ul>
<ul>
<li>在bash的脚本中，你可以使用 set -x 来debug输出。使用 set -e 来当有错误发生的时候abort执行。考虑使用 set -o pipefail 来限制错误。还可以使用trap来截获信号（如截获ctrl+c）。</li>
</ul>
<ul>
<li>在bash 脚本中，subshells (写在圆括号里的) 是一个很方便的方式来组合一些命令。一个常用的例子是临时地到另一个目录中，例如：</li>
</ul>
<blockquote><pre class="brush: bash; title: ; notranslate"># do something in current dir
(cd /some/other/dir; other-command)
# continue in original dir</pre>
</blockquote>
<ul>
<li>在 bash 中，注意那里有很多的变量展开。如：检查一个变量是否存在: ${name:?error message}。如果一个bash的脚本需要一个参数，也许就是这样一个表达式 input_file=${1:?usage: $0 input_file}。一个计算表达式： i=$(( (i + 1) % 5 ))。一个序列： {1..10}。 截断一个字符串： ${var%suffix} 和 ${var#prefix}。 示例： if var=foo.pdf, then echo ${var%.pdf}.txt prints &#8220;foo.txt&#8221;.</li>
</ul>
<ul>
<li>通过 &lt;(some command) 可以把某命令当成一个文件。示例：比较一个本地文件和远程文件 /etc/hosts： diff /etc/hosts &lt;(ssh somehost cat /etc/hosts)</li>
</ul>
<ul>
<li>了解什么叫 &#8220;<a href="http://zh.wikipedia.org/wiki/Here%E6%96%87%E6%A1%A3" target="_blank">here documents</a>&#8221; ，就是诸如 cat &lt;&lt;EOF 这样的东西。</li>
</ul>
<ul>
<li>在 bash中，使用重定向到标准输出和标准错误。如： some-command &gt;logfile 2&gt;&amp;1。另外，要确认某命令没有把某个打开了的文件句柄重定向给标准输入，最佳实践是加上 &#8220;&lt;/dev/null&#8221;，把/dev/null重定向到标准输入。</li>
</ul>
<ul>
<li>使用 man ascii 来查看 ASCII 表。</li>
</ul>
<ul>
<li>在远端的 ssh 会话里，使用 screen 或 dtach 来保存你的会话。（参看《<a title="28个Unix/Linux的命令行神器" href="http://coolshell.cn/articles/7829.html" target="_blank">28个Unix/Linux的命令行神器</a>》）</li>
</ul>
<ul>
<li>要来debug Web，试试curl 和 curl -I 或是 wget 。我觉得debug Web的利器是firebug，curl和wget是用来抓网页的，呵呵。</li>
</ul>
<ul>
<li>把 HTML 转成文本： lynx -dump -stdin</li>
</ul>
<ul>
<li>如果你要处理XML，使用 xmlstarlet</li>
</ul>
<ul>
<li>对于 Amazon S3， s3cmd 是一个很方便的命令（还有点不成熟）</li>
</ul>
<ul>
<li>在 ssh中，知道怎么来使用ssh隧道。通过 -L or -D (还有-R) ，翻墙神器。</li>
</ul>
<ul>
<li>你还可以对你的ssh 做点优化。比如，.ssh/config 包含着一些配置：避免链接被丢弃，链接新的host时不需要确认，转发认证，以前使用压缩（如果你要使用scp传文件）：</li>
</ul>
<blockquote><pre class="brush: bash; title: ; notranslate">TCPKeepAlive=yes
ServerAliveInterval=15
ServerAliveCountMax=6
StrictHostKeyChecking=no
Compression=yes
ForwardAgent=yes</pre>
</blockquote>
<ul>
<li>如果你有输了个命令行，但是你改变注意了，但你又不想删除它，因为你要在历史命令中找到它，但你也不想执行它。那么，你可以按下 Alt-# ，于是这个命令关就被加了一个#字符，于是就被注释掉了。</li>
</ul>
<p><b>数据处理 </b></p>
<ul>
<li>了解 sort 和 uniq 命令 (包括 uniq 的 -u 和 -d 选项).</li>
</ul>
<ul>
<li>了解用 cut, paste, 和 join 命令来操作文本文件。很多人忘了在cut前使用join。</li>
</ul>
<ul>
<li>如果你知道怎么用sort/uniq来做集合交集、并集、差集能很大地促进你的工作效率。假设有两个文本文件a和b已解被 uniq了，那么，用sort/uniq会是最快的方式，无论这两个文件有多大（sort不会被内存所限，你甚至可以使用-T选项，如果你的/tmp目录很小）</li>
</ul>
<blockquote><pre class="brush: bash; title: ; notranslate">cat a b | sort | uniq &gt; c   # c is a union b 并集

cat a b | sort | uniq -d &gt; c   # c is a intersect b 交集

cat a b b | sort | uniq -u &gt; c   # c is set difference a - b 差集</pre>
</blockquote>
<ul>
<li>了解和字符集相关的命令行工具，包括排序和性能。很多的Linux安装程序都会设置LANG 或是其它和字符集相关的环境变量。这些东西可能会让一些命令（如：sort）的执行性能慢N多倍（注：就算是你用UTF-8编码文本文件，你也可以很安全地使用ASCII来对其排序）。如果你想Disable那个i18n 并使用传统的基于byte的排序方法，那就设置export LC_ALL=C （实际上，你可以把其放在 .bashrc）。如果这设置这个变量，你的sort命令很有可能会是错的。</li>
</ul>
<ul>
<li>了解 awk 和 sed，并用他们来做一些简单的数据修改操作。例如：求第三列的数字之和： awk &#8216;{ x += $3 } END { print x }&#8217;。这可能会比Python快3倍，并比Python的代码少三倍。</li>
</ul>
<ul>
<li>使用 shuf 来打乱一个文件中的行或是选择文件中一个随机的行。</li>
</ul>
<ul>
<li>了解sort命令的选项。了解key是什么（-t和-k）。具体说来，你可以使用-k1,1来对第一列排序，-k1来对全行排序。</li>
</ul>
<ul>
<li>Stable sort (sort -s) 会很有用。例如：如果你要想对两例排序，先是以第二列，然后再以第一列，那么你可以这样： sort -k1,1 | sort -s -k2,2</li>
</ul>
<ul>
<li>我们知道，在bash命令行下，Tab键是用来做目录文件自动完成的事的。但是如果你想输入一个Tab字符（比如：你想在sort -t选项后输入&lt;tab&gt;字符），你可以先按Ctrl-V，然后再按Tab键，就可以输入&lt;tab&gt;字符了。当然，你也可以使用$&#8217;\t&#8217;。</li>
</ul>
<ul>
<li>如果你想查看二进制文件，你可以使用hd命令（在CentOS下是hexdump命令），如果你想编译二进制文件，你可以使用bvi命令（<a href="http://bvi.sourceforge.net/" target="_blank">http://bvi.sourceforge.net/</a> 墙）</li>
</ul>
<ul>
<li>另外，对于二进制文件，你可以使用strings（配合grep等）来查看二进制中的文本。</li>
</ul>
<ul>
<li>对于文本文件转码，你可以试一下 iconv。或是试试更强的 uconv 命令（这个命令支持更高级的Unicode编码）</li>
</ul>
<ul>
<li>如果你要分隔一个大文件，你可以使用split命令（split by size）和csplit命令（split by a pattern）。</li>
</ul>
<p><b>系统调试</b></p>
<ul>
<li>如果你想知道磁盘、CPU、或网络状态，你可以使用 iostat, netstat, top (或更好的 htop), 还有 dstat 命令。你可以很快地知道你的系统发生了什么事。关于这方面的命令，还有iftop, iotop等（参看《<a title="28个Unix/Linux的命令行神器" href="http://coolshell.cn/articles/7829.html" target="_blank">28个Unix/Linux的命令行神器</a>》）</li>
</ul>
<ul>
<li>要了解内存的状态，你可以使用free和vmstat命令。具体来说，你需要注意 “cached” 的值，这个值是Linux内核占用的内存。还有free的值。</li>
</ul>
<ul>
<li>Java 系统监控有一个小的技巧是，你可以使用kill -3 &lt;pid&gt; 发一个SIGQUIT的信号给JVM，可以把堆栈信息（包括垃圾回收的信息）dump到stderr/logs。</li>
</ul>
<ul>
<li>使用 mtr 会比使用 traceroute 要更容易定位一个网络问题。</li>
</ul>
<ul>
<li>如果你要找到哪个socket或进程在使用网络带宽，你可以使用 iftop 或 nethogs。</li>
</ul>
<ul>
<li>Apache的一个叫 ab 的工具是一个很有用的，用quick-and-dirty的方式来测试网站服务器的性能负载的工作。如果你需要更为复杂的测试，你可以试试 siege。</li>
</ul>
<ul>
<li>如果你要抓网络包的话，试试 wireshark 或 tshark。</li>
</ul>
<ul>
<li>了解 strace 和 ltrace。这两个命令可以让你查看进程的系统调用，这有助于你分析进程的hang在哪了，怎么crash和failed的。你还可以用其来做性能profile，使用 -c 选项，你可以使用-p选项来attach上任意一个进程。</li>
</ul>
<ul>
<li>了解用ldd命令来检查相关的动态链接库。注意：<a title="ldd 的一个安全问题" href="http://coolshell.cn/articles/1626.html" target="_blank">ldd的安全问题</a></li>
</ul>
<ul>
<li>使用gdb来调试一个正在运行的进程或分析core dump文件。参看我写的《<a title="GDB中应该知道的几个调试方法" href="http://coolshell.cn/articles/3643.html" target="_blank">GDB中应该知道的几个调试方法</a>》</li>
</ul>
<ul>
<li>学会到 /proc 目录中查看信息。这是一个Linux内核运行时记录的整个操作系统的运行统计和信息，比如： /proc/cpuinfo, /proc/xxx/cwd, /proc/xxx/exe, /proc/xxx/fd/, /proc/xxx/smaps.</li>
</ul>
<ul>
<li>如果你调试某个东西为什么出错时，sar命令会有用。它可以让你看看 CPU, 内存, 网络, 等的统计信息。</li>
</ul>
<ul>
<li>使用 dmesg 来查看一些硬件或驱动程序的信息或问题。</li>
</ul>
<p>作者最后加了一个免责声明：Disclaimer: Just because you <i>can</i> do something in bash, doesn&#8217;t necessarily mean you should. ;) （全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8619" data-post-type="none" >2012年11月23日 -- <a href="http://coolshell.cn/articles/8619.html" class="wp_rp_title">你可能不知道的Shell</a></li><li data-position="1" data-poid="in-7829" data-post-type="none" >2012年07月11日 -- <a href="http://coolshell.cn/articles/7829.html" class="wp_rp_title">28个Unix/Linux的命令行神器</a></li><li data-position="2" data-poid="in-7490" data-post-type="none" >2012年06月20日 -- <a href="http://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li data-position="3" data-poid="in-9070" data-post-type="none" >2013年02月17日 -- <a href="http://coolshell.cn/articles/9070.html" class="wp_rp_title">AWK 简明教程</a></li><li data-position="4" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="5" data-poid="in-7965" data-post-type="none" >2012年08月01日 -- <a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li data-position="6" data-poid="in-9410" data-post-type="none" >2013年04月26日 -- <a href="http://coolshell.cn/articles/9410.html" class="wp_rp_title">Unix考古记：一个“遗失”的shell</a></li><li data-position="7" data-poid="in-8239" data-post-type="none" >2012年09月07日 -- <a href="http://coolshell.cn/articles/8239.html" class="wp_rp_title">无锁队列的实现</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/8883.html/feed</wfw:commentRss>
		<slash:comments>124</slash:comments>
		</item>
		<item>
		<title>程序算法与人生选择</title>
		<link>http://coolshell.cn/articles/8790.html</link>
		<comments>http://coolshell.cn/articles/8790.html#comments</comments>
		<pubDate>Fri, 28 Dec 2012 01:00:50 +0000</pubDate>
		<dc:creator>陈皓</dc:creator>
				<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Job]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://coolshell.cn/?p=8790</guid>
		<description><![CDATA[每年一到要找工作的时候，我就能收到很多人给我发来的邮件，总是问我怎么选择他们的offer，去腾讯还是去豆瓣，去 [...]]]></description>
				<content:encoded><![CDATA[<p><img class="alignright  wp-image-8797" alt="" src="http://coolshell.cn//wp-content/uploads/2012/12/choice.jpg" width="281" height="207" />每年一到要找工作的时候，我就能收到很多人给我发来的邮件，总是问我怎么选择他们的offer，去腾讯还是去豆瓣，去外企还是去国内的企业，去创业还是去考研，来北京还是回老家，该不该去创新工场？该不该去thoughtworks？……等等，等等。今年从7月份到现在，我收到并回复了60多封这样的邮件。我更多帮他们整理思路，帮他们明白自己最想要的是什么。（注：我以后不再回复类似的邮件了）。</p>
<p>我深深地发现，对于我国这样从小被父母和老师安排各种事情长大的人，当有一天，父母和老师都跟不上的时候，我们几乎完全不知道怎么去做选择。而我最近也离开了亚马逊，换了一个工作。又正值年底，就像去年的那篇《<a title="三个事和三个问题" href="http://coolshell.cn/articles/6142.html" target="_blank">三个故事和三个问题</a>》一样，让我想到写一篇这样的文章。</p>
<h4>几个例子</h4>
<p>当我们在面对各种对选择的影响因子的时候，如：城市，公司规模，公司性质，薪水，项目，户口，技术，方向，眼界…… 你总会发现，你会在几个公司中纠结一些东西，举几个例子：</p>
<ul>
<li>某网友和我说，他们去上海腾讯，因为腾讯的规模很大，但却发现薪水待遇没有豆瓣高（低的还不是一点），如果以后要换工作的话，起薪点直接关系到了以后的高工资。我说那就去豆瓣吧，他说豆瓣在北京，污染那么严重，又没有户口，生存环境不好。我说去腾讯吧，他说腾讯最近组织调整，不稳定。我说那就去豆瓣吧，慢公司，发展很稳当。他说，豆瓣的盈利不清楚，而且用Python，自己不喜欢。我说，那就去腾讯吧，……</li>
</ul>
<ul>
<li>还有一网友和我说，他想回老家，因为老家的人脉关系比较好，能混得好。但又想留在大城市，因为大城市可以开眼界。</li>
</ul>
<p><span id="more-8790"></span></p>
<ul>
<li>另一网友和我说，他想进外企，练练英语，开开眼界，但是又怕在外企里当个螺丝钉，想法得不到实施。朋友拉他去创业，觉得创业挺好的，锻炼大，但是朋友做的那个不知道能不能做好。</li>
</ul>
<ul>
<li>还有一网友在创新工场的某团队和考研之间抉择，不知道去创新工场行不行，觉得那个项目一般，但是感觉那个团队挺有激情的，另一方面觉得自己的学历还不够，读个研应该能找到更好的工作。</li>
</ul>
<ul>
<li>还有一些朋友问题我应该学什么技术？不应该学什么技术？或是怎么学会学得最快，技术的路径应该是什么？有的说只做后端不做前端，有的说，只做算法研究，不做工程，等等，等等。因为他们觉得人生有限，术业有专攻。</li>
</ul>
<ul>
<li>等等，等等……</li>
</ul>
<p>我个人觉得，如果是非计算机科班出生的人不会做选择，不知道怎么走也罢了，但是我们计算机科班出生的人是学过算法的，<strong>懂算法的人应该是知道怎么做选择的</strong>。</p>
<h4></h4>
<h4>排序算法</h4>
<p>你不可能要所有的东西，所以你只能要你最重要的东西，你要知道什么东西最重要，你就需要对你心内的那些欲望和抱负有清楚的认识，不然，你就会在纠结中度过。</p>
<p>所以，在选择中纠结的人有必要参考一下排序算法。</p>
<ul>
<li>首先，你最需要参考的就是“冒泡排序”——这种算法的思路就是每次冒泡出一个最大的数。所以，你有必要问问你自己，面对那些影响你选择的因子，如果你只能要一个的话，你会要哪个？而剩下的都可以放弃。于是，当你把最大的数，一个一个冒泡出来的时候，并用这个决策因子来过滤选项的时候，你就能比较容易地知道知道你应该选什么了。<strong>这个算法告诉我们，人的杂念越少，就越容易做出选择。</strong></li>
</ul>
<ul>
<li>好吧，可能你已茫然到了怎么比较两个决策因子的大小，比如：你分不清楚，工资&gt;业务前景吗？业务前景&gt;能力提升吗？所以你完全没有办法进行冒泡法。那你，你不妨参考一个“快速排序”的思路——这个算法告诉我们，我们一开始并不需要找到最大的数，我们只需要把你价值观中的某个标准拿出来，然后，把可以满足这个价值的放到右边，不能的放到左边去。比如，你的标准是：工资大于5000元&amp;&amp;业务前景长于3年的公司，你可以用这个标准来过滤你的选项。然后，你可以再调整这个标准再继续递归下去。<strong>这个算法告诉我们，我们的选择标准越清晰，我们就越容易做出选择</strong>。</li>
</ul>
<p>这是排序算法中最经典的两个算法了，面试必考。相信你已烂熟于心中了。所以，我觉得你把这个算法应用于你的人生选择也应该不是什么问题。关于在于，你是否知道自己想要的是什么？</p>
<p>排序算法的核心思想就是，<strong>让你帮助你认清自己最需要的是什么，认清自己最想要的是什么，然后根据这个去做选择</strong>。</p>
<h4>贪婪算法</h4>
<p>所谓贪婪算法，是一种在每一步选择中都采取在当前状态下最好或最优（即最有利）的选择（注意：是当前状态下），从而希望导致结果是最好或最优的算法。贪婪算法最经典的一个例子就是<a title="Huffman 编码压缩算法" href="http://coolshell.cn/articles/7459.html" target="_blank">哈夫曼编码</a>。</p>
<p>对于人类来说，一般人在行为处事的时候都会使用到贪婪算法，</p>
<ul>
<li>比如在找零钱的时候，如果要找补36元，我们一般会按这样的顺序找钱：20元，10元，5元，1元。</li>
</ul>
<ul>
<li>或者我们在过十字路口的时候，要从到对角线的那个街区时，我们也会使用贪婪算法——哪边的绿灯先亮了我们就先过到那边去，然后再转身90度等红灯再过街。</li>
</ul>
<p>这样的例子有很多。对于选择中，大多数人都会选用贪婪算法，因为这是一个比较简单的算法，未来太复杂了，只能走一步看一步，在当前的状况下做出最利于自己的判断和选择即可。</p>
<p>有的人会贪婪薪水，有的人会贪婪做的项目，有的人会贪婪业务，有的人会贪婪职位，有的人会贪婪自己的兴趣……这些都没什么问题。贪婪算法并没有错，虽然不是全局最优解，但其可以让你找到局部最优解或是次优解。其实，有次优解也不错了。<strong>贪婪算法基本上是一种急功近利的算法，但是并不代表这种算法不好，如果贪婪的是一种长远和持续，又未尝不可呢？</strong>。</p>
<h4>动态规划</h4>
<p>但是我们知道，对于大部分的问题，贪婪法通常都不能找出最优解，因为他们一般没有测试所有可能的解。<strong>因为贪婪算法是一种短视的行为，只会跟据当前的形式做判断，也就是过早做决定</strong>，因而没法达到最佳解。</p>
<p>动态规划和贪婪算法的最大不同是，贪婪算法做出选择，不能回退。动态规划则会保存以前的运算结果，并根据以前的结果对当前进行选择，有回退功能。</p>
<p>动态规划算法至少告诉我们两个事：</p>
<p style="padding-left: 30px;">1）<strong>承前启后非常重要，</strong>当你准备去做遍历的时候，你的上次的经历不但能开启你以后的经历，而且还能为后面的经历所用。你的每一步都没有浪费。</p>
<p style="padding-left: 30px;">2）<strong>是否可以回退也很重要</strong>。这意思是——如果你面前有两个选择，一个是A公司一个是B公司，如果今天你错失了B公司，那到你明天还能不能找回来？</p>
<p>比如说：你有两个offer，一个是Yahoo，一个是Baidu，上述的第一点会让我们思考，Yahoo和Baidu谁能给我们开启更大的平台？上述的第二点告诉我们，是进入Yahoo后如果没有选好，是否还能回退到Baidu公司？还是进入Baidu公司后能容易回退到Yahoo公司？</p>
<h4><b>Dijkstra</b>最短路径</h4>
<p>最短路径是一个Greedy + DP的算法。相当经典。这个算法的大意如下：</p>
<p style="padding-left: 30px;">1）在初始化的时候，所有的结点都和我是无穷大，默认是达不到的。</p>
<p style="padding-left: 30px;">2）从离自己最近的结点开始贪婪。</p>
<p style="padding-left: 30px;">3）走过去，看看又能到达什么样的结点，计算并更新到所有目标点的距离。</p>
<p style="padding-left: 30px;">4）再贪婪与原点最短的结点，如此反复。</p>
<p>这个算法给我们带来了一些这样的启示：</p>
<ul>
<li>有朋友和我说过他想成为一个架构师，或是某技术领域的专家，并会踏踏实实的向这个目标前进，永不放弃。我还是鼓励了他，但我也告诉他了这个著名的算法，我说，这个算法告诉你，架构师或某领域的专家对你来说目前的距离是无穷大，他们放在心中，先看看你能够得着的东西。<strong>所谓踏实，并不是踏踏实实追求你的目标，而是踏踏实实把你够得着看得见的就在身边的东西干好。</strong>我还记得我刚参加工作，从老家出来的时候，从来没有想过要成为一个技术牛人，也从来没有想过我的博客会那么的有影响力，在做自己力所能及，看得见摸得着的事情，我就看见什么技术就学什么，学着学着就知道怎么学更轻松，怎么学更扎实，这也许就是我的最短路径。</li>
</ul>
<ul>
<li>有很多朋友问我要不要学C++，或是问我学Python还是学Ruby，是不是不用学前端，等等。这些朋友告诉我，他们不可能学习多个语言，学了不用也就忘了，而且术业有专攻。这并没有什么不对的，只是我个人觉得，学习一个东西没有必要只有两种状态，一种是不学，另一种是精通。了解一个技术其实花不了多少时间，我学C++的目的其实是为了更懂Java，学TCP/IP协议其实是为了更懂Socket编程，很多东西都是连通和相辅相成的，学好了C/C++/Unix/TCP等这些基础技术后，我发现到达别的技术路径一下缩短了（这就是为什么<a title="Go 语言简介（下）— 特性" href="http://coolshell.cn/articles/8489.html" target="_blank">我用两天时间就可以了解Go语言的原因</a>）。<strong>这就好像这个算法一样，算法效率不高，也许达到你的目标，你在一开始花了很长时间，遍历了很多地方，但是，这也许这就是你的最短路径</strong>。</li>
</ul>
<h4>算法就是Trade-Off</h4>
<p>你根本没有办法能得到所有你想得到的东西，<strong>任何的选择都意味着放弃</strong>——<strong>当你要去获得一个东西的时候，你总是需要放弃一些东西</strong>。<strong>人生本来就是一个跷跷板，一头上，另一头必然下</strong>。这和我们做软件设计或算法设计一样，用时间换空间，用空间换时间，还有CAP理论，总是有很多的Trade-Off，正如这个短语的原意一样——<strong>你总是要用某种东西去交易某种东西</strong>。</p>
<p>我们都在用某种东西在交易我们的未来，有的人用自己的努力，有的人用自己的思考，有的人用自己的年轻，有的人用自己的自由，有的人用自己的价值观，有的人用自己的道德…… …… 有的人在交换金钱，有的人在交换眼界，有的人在交换经历，有的人在交换地位，有的人在交换能力，有的人在交换自由，有的人在交换兴趣，有的人在交换虚荣心，在交换安逸享乐…… ……</p>
<p><strong>每个人有每个人的算法，每个算法都有每个算法的purpose，就算大家在用同样的算法，但是每个人算法中的那些变量、开关和条件都不一样，得到的结果也不一样。我们就是生活在Matrix里的一段程序，我们每个人的算法决定着我们每个人的选择，我们的选择决定了我们的人生</strong>。</p>
<p style="text-align: center;"><span style="color: #ff0000; font-size: 14px;" data-mce-mark="1"><strong>2012年就要过去了，祝大家新年快乐！</strong></span></p>
<div id="attachment_8798" class="wp-caption aligncenter" style="width: 650px"><img class="size-full wp-image-8798 " alt="插图来自电影 Life of Pi" src="http://coolshell.cn//wp-content/uploads/2012/12/life_of_pi_.jpg" width="640" height="275" /><p class="wp-caption-text">插图来自电影 Life of Pi</p></div>
<p>（全文完）
<div style="margin-top: 15px; font-size: 11px;color: #cc0000;">
<p align=center><strong>（转载本站文章请注明作者和出处 <a href="http://coolshell.cn/">酷壳 &#8211; CoolShell.cn</a> ，请勿用于任何商业用途）</strong></div>
<div style="text-align:center;padding:0px;font-size: 14px;margin-bottom: 50px;">——=== <b>访问 <a href=http://coolshell.cn/404/ target=_blank>酷壳404页面</a> 以支持公益事业</b> ===——</div>

<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp" style="visibility: visible"><li data-position="0" data-poid="in-8138" data-post-type="none" >2012年08月22日 -- <a href="http://coolshell.cn/articles/8138.html" class="wp_rp_title">为什么我反对纯算法面试题</a></li><li data-position="1" data-poid="in-7048" data-post-type="none" >2012年04月17日 -- <a href="http://coolshell.cn/articles/7048.html" class="wp_rp_title">挑战无处不在</a></li><li data-position="2" data-poid="in-6142" data-post-type="none" >2011年12月20日 -- <a href="http://coolshell.cn/articles/6142.html" class="wp_rp_title">三个事和三个问题</a></li><li data-position="3" data-poid="in-3231" data-post-type="none" >2010年10月26日 -- <a href="http://coolshell.cn/articles/3231.html" class="wp_rp_title">你和你的工作</a></li><li data-position="4" data-poid="in-6994" data-post-type="none" >2012年04月11日 -- <a href="http://coolshell.cn/articles/6994.html" class="wp_rp_title">我们需要专职的QA吗？</a></li><li data-position="5" data-poid="in-8170" data-post-type="none" >2012年08月27日 -- <a href="http://coolshell.cn/articles/8170.html" class="wp_rp_title">一次Ajax查错的经历</a></li><li data-position="6" data-poid="in-8088" data-post-type="none" >2012年08月16日 -- <a href="http://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li data-position="7" data-poid="in-7126" data-post-type="none" >2012年04月19日 -- <a href="http://coolshell.cn/articles/7126.html" class="wp_rp_title">这到底是谁之错？</a></li></ul><div class="wp_rp_footer"><a class="wp_rp_backlink" target="_blank" href="http://www.zemanta.com/?wp-related-posts">Zemanta</a></div></div></div>
]]></content:encoded>
			<wfw:commentRss>http://coolshell.cn/articles/8790.html/feed</wfw:commentRss>
		<slash:comments>174</slash:comments>
		</item>
	</channel>
</rss>

<!-- Dynamic page generated in 1.106 seconds. -->
<!-- Cached page generated by WP-Super-Cache on 2013-05-22 20:56:05 -->

<!-- Compression = gzip -->