tag:blogger.com,1999:blog-59465307047421309702024-03-06T16:20:07.273+08:00Jax 的工作紀錄除了在整理學習上的經驗,同時也能幫助其他需要的人Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.comBlogger68125tag:blogger.com,1999:blog-5946530704742130970.post-91470478122222424702022-08-01T11:37:00.002+08:002023-02-25T18:09:07.432+08:00[轉載] NLog Variables轉載自:<a href="https://www.uj5u.com/qiye/440792.html" target="_blank">當條件等于不作業時.net核心Nlog過濾器</a>
<p>我必須從我的啟動類傳遞變數值</p>
<pre class="cs:nogutter:nocontrols" name="code">
LogManager.Configuration.Variables["environment"] = "Development";
</pre>
<p>我在我的 nlog.config 檔案中添加了以下過濾器</p>
<pre class="xml:nogutter:nocontrols" name="code">
<rules>
<logger name="*" minlevel="Error" writeTo="logfile">
<filters>
<when condition="equals('${var:environment}', 'Development')" action="Ignore" />
</filters>
</logger>
</rules>
</pre>
<p>即使我將值作為 Development 傳遞,該訊息仍會被記錄而不是忽略。</p>
<p>但是,當我對它的作業值進行硬編碼時</p>
<p>您在 NLog中發現了一個錯誤,但如果您這樣做,它應該可以作業(也會更快):</p>
<pre class="xml:nogutter:nocontrols" name="code">
<rules>
<logger name="*" minlevel="Error" writeTo="logfile">
<filters defaultAction='log'>
<when condition="'${var:environment}' == 'Development'" action="Ignore" />
</filters>
</logger>
</rules>
</pre>
<p>您也可以用 <code>minLevel="${var:EnvironmentMinLevel:whenEmpty=Error}"</code> 處理,這比 <code><filters></code> 快得多</p>
<pre class="xml:nogutter:nocontrols" name="code">
<rules>
<logger name="*" minlevel="${var:EnvironmentMinLevel:whenEmpty=Error}" writeTo="logfile" />
</rules>
</pre>
<p>設定 Variables 要記得呼叫 Reconfig,或者在 config 中設定 <code>autoReload="true"</code></p>
<pre class="cs:nogutter:nocontrols" name="code">
NLog.LogManager.Configuration.Variables["EnvironmentMinLevel"] = "Off";
NLog.LogManager.ReconfigExistingLoggers();
</pre>
<p>另請參閱 <a href="https://github.com/NLog/NLog/wiki/Filtering-log-messages#semi-dynamic-routing-rules" target="_blank">https://github.com/NLog/NLog/wiki/Filtering-log-messages#semi-dynamic-routing-rules</a></p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-90018122299664482682022-07-15T12:52:00.000+08:002022-07-15T12:52:11.780+08:00[轉載] X-Y PROBLEM轉載自:<a href="https://coolshell.cn/articles/10804.html" target="_blank">酷壳 X-Y PROBLEM</a>
<p>对于X-Y Problem的意思如下:</p>
<ol>
<li>有人想解决问题X</li>
<li>他觉得Y可能是解决X问题的方法</li>
<li>但是他不知道Y应该怎么做</li>
<li>于是他去问别人Y应该怎么做?</li>
</ol>
<p>简而言之,<strong>没有去问怎么解决问题X,而是去问解决方案Y应该怎么去实现和操作</strong>。于是乎:</p>
<ol>
<li>热心的人们帮助并告诉这个人Y应该怎么搞,但是大家都觉得Y这个方案有点怪异。</li>
<li>在经过大量地讨论和浪费了大量的时间后,热心的人终于明白了原始的问题X是怎么一回事。</li>
<li>于是大家都发现,Y根本就不是用来解决X的合适的方案。</li>
</ol>
<p>X-Y Problem最大的严重的问题就是:<strong>在一个根本错误的方向上浪费他人大量的时间和精力</strong>!</p>
<h3>示例</h3>
<p>举个两个例子:</p>
<dl>
<dt>Q) 我怎么用Shell取得一个字符串的后3位字符?</dt>
<dd>A1) 如果这个字符的变量是$foo,你可以这样来 echo ${foo:-3}</dd>
<dd>A2) 为什么你要取后3位?你想干什么?</dd>
<dt>Q) 其实我就想取文件的扩展名</dt>
<dd>A1) 我靠,原来你要干这事,那我的方法不对,文件的扩展名并不保证一定有3位啊。</dd>
<dd>A1) 如果你的文件必然有扩展名的话,你可以这来样来:echo ${foo##*.}</dd>
</dl>
<p>再来一个示例:</p>
<dl>
<dt>Q)问一下大家,我如何得到一个文件的大小</dt>
<dd>A1) size = <code>ls -l $file | awk '{print $5}'</code></dd>
<dt>Q) 哦,要是这个文件名是个目录呢?</dt>
<dd>A2) 用du吧</dd>
<dd>A3) 不好意思,你到底是要文件的大小还是目录的大小?你到底要干什么?</dd>
<dt>Q) 我想把一个目录下的每个文件的每个块(第一个块有512个字节)拿出来做md5,并且计算他们的大小 ……</dt>
<dd>A1) 哦,你可以使用dd吧。</dd>
<dd>A2) dd不行吧。</dd>
<dd>A3) 你用md5来计算这些块的目的是什么?你究竟想干什么啊?</dd>
<dt>Q) 其实,我想写一个网盘,对于小文件就直接传输了,对于大文件我想分块做增量同步。</dt>
<dd>A2) 用rsync啊,你妹!</dd>
</dl>
<p><a href="http://www.perlmonks.org/index.pl?node_id=542341" target="_blank">这里有篇文章</a>说明了X-Y Problem的各种案例说明,我从其中摘出三个来让大家看看:</p>
<blockquote><p>你试图做X,并想到了用Y方案。所以你去问别人Y,但根本不提X。于是,你可以会错过本来可能有更好更适合的方案,除非你告诉大家X是什么。</p>
<p>— <i>from <a href="http://www.perlmonks.org/index.pl?node_id=430320">Re: How do I keep the command line from eating the backslashes?</a> by <a href="http://www.perlmonks.org/index.pl?node_id=163683">revdiablo</a></i></p></blockquote>
<blockquote><p>有些人问怎么做Y,但其它他想做的是X。他问怎么做Y是因为他觉得Y是最好搞定X的方法。 于是大家不断地回答“试试这个,试试那个”来帮助他,而他总是在说“这个有问题,那个有问题,因为……”。基本不同的情况,其它的方案可能会更好。</p>
<p>— <i>from <a href="http://www.perlmonks.org/index.pl?node_id=327963">Re: Re: Re: Re: regex to validate e-mail addresses and phone numbers</a> by <a href="http://www.perlmonks.org/index.pl?node_id=180961">Limbic~Region</a></i></p></blockquote>
<blockquote><p>X-Y Problem又叫“过早下结论”:提问者其实并不非常清楚想要解决的X问题,他猜测用Y可以搞定,于是他问大家如何实现Y。</p>
<p>— <i>from <a href="http://groups.google.com/groups?hl=en&selm=Pine.GHP.4.21.0009061210570.8800-100000@hpplus03.cern.ch"><Pine.GHP.4.21.0009061210570.8800-100000@hpplus03.cern.ch></a> by Alan J. Flavell</i></p></blockquote>
<p>其实这个问题在我之前的《<a title="你会问问题吗?" href="https://coolshell.cn/articles/3713.html" target="_blank">你会问问题吗</a>》里提到的那篇How To Ask Questions the Smart Way中的提到过,你可以<a href="http://www.beiww.com/doc/oss/smart-questions.html#id265951" target="_blank">移步去看一下</a>。</p>
<p>所以,我们在寻求别人帮助的时候,最好把我们想解决的问题和整个事情的来龙去脉说清楚。</p>
<h3>一些变种</h3>
<p>我们不要以为X-Y Problem就像上面那样的简单,我们不会出现,其实我们生活的这个世界有有各种X-Y Problem的变种。下面我个人觉得非常像XY Problem的总是:</p>
<ul>
<li>其一、大多数人有时候,非常容易把手段当目的,他们会用自己所喜欢的技术和方法来反推用户的需求,于是很有可能就会出现X-Y Problem – 也许解决用户需求最适合的技术方案是PC,但是我们要让他们用手机。</li>
<li>其二、产品经理有时候并不清楚他想解决的用户需求是什么,于是他觉得可能开发Y的功能能够满足用户,于是他提出了Y的需求让技术人员去做,但那根本不是解决X问题的最佳方案。</li>
<li>其三、因为公司或部门的一些战略安排,业务部门设计了相关的业务规划,然后这些业务规划更多的是公司想要的Y,而不是解决用户的X问题。</li>
<li>其四、对于个人的职业发展,X是成长为有更强的技能和能力,这个可以拥有比别人更强的竞争力,从而可以有更好的报酬,但确走向了Y:全身心地追逐KPI。</li>
<li>其五、本来我们想达成的X是做出更好和更有价值的产品,但最终走到了Y:通过各种手段提升安装量,点击量,在线量,用户量来衡量。</li>
<li>其六、很多团队Leader都喜欢制造信息不平等,并不告诉团队某个事情的来由,掩盖X,而直接把要做的Y告诉团队,导致团队并不真正地理解,而产生了很多时间和经历的浪费。</li>
</ul>
<p>所有的这些,在我心中都是X-Y Problem的变种,这是不是一种刻舟求剑的表现?</p>
<h3>参考</h3>
<ul>
<li><a href="http://meta.stackoverflow.com/questions/66377/what-is-the-xy-problem" target="_blank">StackOverflow: What is XY Problem?</a></li>
<li><a href="http://www.perlmonks.org/?node_id=542341" target="_blank">PerlMonks: XY Problem</a></li>
<li><a href="http://mywiki.wooledge.org/XyProblem" target="_blank">Greg’s Wiki</a></li>
</ul>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-7904583457729593852022-07-15T12:38:00.000+08:002022-07-15T12:38:39.289+08:00[轉載] CSS 变量教程轉載自:<a href="http://www.ruanyifeng.com/blog/2017/05/css-variables.html" target="_blank">阮一峰 CSS 变量教程</a>
<p>今年三月,微软<a href="https://www.neowin.net/news/css-custom-properties-are-coming-to-microsoft-edge-in-the-windows-10-creators-update" target="_blank">宣布</a> Edge 浏览器将支持 CSS 变量。</p>
<p>这个重要的 CSS 新功能,所有主要浏览器已经都支持了。本文全面介绍如何使用它,你会发现原生 CSS 从此变得异常强大。</p>
<h3>一、变量的声明</h3>
<p>声明变量的时候,变量名前面要加两根连词线(<code>--</code>)。</p>
<pre class="css:nogutter:nocontrols" name="code">
body {
--foo: #7F583F;
--bar: #F7EFD2;
}
</pre>
<p>上面代码中,<code>body</code>选择器里面声明了两个变量:<code>--foo</code>和<code>--bar</code>。</p>
<p>它们与<code>color</code>、<code>font-size</code>等正式属性没有什么不同,只是没有默认含义。所以 CSS 变量(CSS variable)又叫做<strong>"CSS 自定义属性"</strong>(CSS custom properties)。因为变量与自定义的 CSS 属性其实是一回事。</p>
<p>你可能会问,为什么选择两根连词线(<code>--</code>)表示变量?因为<code>$foo</code>被 Sass 用掉了,<code>@foo</code>被 Less 用掉了。为了不产生冲突,官方的 CSS 变量就改用两根连词线了。 </p>
<p>各种值都可以放入 CSS 变量。</p>
<pre class="css:nogutter:nocontrols" name="code">
:root{
--main-color: #4d4e53;
--main-bg: rgb(255, 255, 255);
--logo-border-color: rebeccapurple;
--header-height: 68px;
--content-padding: 10px 20px;
--base-line-height: 1.428571429;
--transition-duration: .35s;
--external-link: "external link";
--margin-top: calc(2vh + 20px);
}
</pre>
<p>变量名大小写敏感,<code>--header-color</code>和<code>--Header-Color</code>是两个不同变量。</p>
<h3>二、var() 函数</h3>
<p><code>var()</code>函数用于读取变量。</p>
<pre class="css:nogutter:nocontrols" name="code">
a {
color: var(--foo);
text-decoration-color: var(--bar);
}
</pre>
<p><code>var()</code>函数还可以使用第二个参数,表示变量的默认值。如果该变量不存在,就会使用这个默认值。</p>
<pre class="css:nogutter:nocontrols" name="code">
color: var(--foo, #7F583F);
</pre>
<p>第二个参数不处理内部的逗号或空格,都视作参数的一部分。</p>
<pre class="css:nogutter:nocontrols" name="code">
var(--font-stack, "Roboto", "Helvetica");
var(--pad, 10px 15px 20px);
</pre>
<p><code>var()</code>函数还可以用在变量的声明。</p>
<pre class="css:nogutter:nocontrols" name="code">
:root {
--primary-color: red;
--logo-text: var(--primary-color);
}
</pre>
<p>注意,变量值只能用作属性值,不能用作属性名。</p>
<pre class="css:nogutter:nocontrols" name="code">
.foo {
--side: margin-top;
/* 无效 */
var(--side): 20px;
}
</pre>
<p>上面代码中,变量<code>--side</code>用作属性名,这是无效的。</p>
<h3>三、变量值的类型</h3>
<p>如果变量值是一个字符串,可以与其他字符串拼接。</p>
<pre class="css:nogutter:nocontrols" name="code">
--bar: 'hello';
--foo: var(--bar)' world';
</pre>
<p>利用这一点,可以 debug(<a href="https://codepen.io/malyw/pen/oBWMOY" target="_blank">例子</a>)。</p>
<pre class="css:nogutter:nocontrols" name="code">
body:after {
content: '--screen-category : 'var(--screen-category);
}
</pre>
<p>如果变量值是数值,不能与数值单位直接连用。</p>
<pre class="css:nogutter:nocontrols" name="code">
.foo {
--gap: 20;
/* 无效 */
margin-top: var(--gap)px;
}
</pre>
<p>上面代码中,数值与单位直接写在一起,这是无效的。必须使用<code>calc()</code>函数,将它们连接。</p>
<pre class="css:nogutter:nocontrols" name="code">
.foo {
--gap: 20;
margin-top: calc(var(--gap) * 1px);
}
</pre>
<p>如果变量值带有单位,就不能写成字符串。</p>
<pre class="css:nogutter:nocontrols" name="code">
/* 无效 */
.foo {
--foo: '20px';
font-size: var(--foo);
}
/* 有效 */
.foo {
--foo: 20px;
font-size: var(--foo);
}
</pre>
<br/>
<h3>四、作用域</h3>
<p>同一个 CSS 变量,可以在多个选择器内声明。读取的时候,优先级最高的声明生效。这与 CSS 的"层叠"(cascade)规则是一致的。</p>
<p>下面是一个<a href="http://jsbin.com/buwahixoqo/edit?html,css,output" target="_blank">例子</a>。</p>
<pre class="xml:nogutter:nocontrols" name="code">
<style>
:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }
</style>
<p>蓝色</p>
<div>绿色</div>
<div id="alert">红色</div>
</pre>
<p>上面代码中,三个选择器都声明了<code>--color</code>变量。不同元素读取这个变量的时候,会采用优先级最高的规则,因此三段文字的颜色是不一样的。</p>
<p>这就是说,变量的作用域就是它所在的选择器的有效范围。</p>
<pre class="css:nogutter:nocontrols" name="code">
body {
--foo: #7F583F;
}
.content {
--bar: #F7EFD2;
}
</pre>
<p>上面代码中,变量<code>--foo</code>的作用域是<code>body</code>选择器的生效范围,<code>--bar</code>的作用域是<code>.content</code>选择器的生效范围。</p>
<p>由于这个原因,全局的变量通常放在根元素<code>:root</code>里面,确保任何选择器都可以读取它们。</p>
<pre class="css:nogutter:nocontrols" name="code">
:root {
--main-color: #06c;
}
</pre>
<br/>
<h3>五、响应式布局</h3>
<p>CSS 是动态的,页面的任何变化,都会导致采用的规则变化。</p>
<p>利用这个特点,可以在响应式布局的<code>media</code>命令里面声明变量,使得不同的屏幕宽度有不同的变量值。</p>
<pre class="css:nogutter:nocontrols" name="code">
body {
--primary: #7F583F;
--secondary: #F7EFD2;
}
a {
color: var(--primary);
text-decoration-color: var(--secondary);
}
@media screen and (min-width: 768px) {
body {
--primary: #F7EFD2;
--secondary: #7F583F;
}
}
</pre>
<br/>
<h3>六、兼容性处理</h3>
<p>对于不支持 CSS 变量的浏览器,可以采用下面的写法。</p>
<pre class="css:nogutter:nocontrols" name="code">
a {
color: #7F583F;
color: var(--primary);
}
</pre>
<p>也可以使用<code>@support</code>命令进行检测。</p>
<pre class="css:nogutter:nocontrols" name="code">
@supports ( (--a: 0)) {
/* supported */
}
@supports ( not (--a: 0)) {
/* not supported */
}
</pre>
<br/>
<h3>七、JavaScript 操作</h3>
<p>JavaScript 也可以检测浏览器是否支持 CSS 变量。</p>
<pre class="js:nogutter:nocontrols" name="code">
const isSupported =
window.CSS &&
window.CSS.supports &&
window.CSS.supports('--a', 0);
if (isSupported) {
/* supported */
} else {
/* not supported */
}
</pre>
<p>JavaScript 操作 CSS 变量的写法如下。</p>
<pre class="js:nogutter:nocontrols" name="code">
// 设置变量
document.body.style.setProperty('--primary', '#7F583F');
// 读取变量
document.body.style.getPropertyValue('--primary').trim();
// '#7F583F'
// 删除变量
document.body.style.removeProperty('--primary');
</pre>
<p>这意味着,JavaScript 可以将任意值存入样式表。下面是一个监听事件的例子,事件信息被存入 CSS 变量。</p>
<pre class="js:nogutter:nocontrols" name="code">
const docStyle = document.documentElement.style;
document.addEventListener('mousemove', (e) => {
docStyle.setProperty('--mouse-x', e.clientX);
docStyle.setProperty('--mouse-y', e.clientY);
});
</pre>
<p>那些对 CSS 无用的信息,也可以放入 CSS 变量。</p>
<pre class="css:nogutter:nocontrols" name="code">
--foo: if(x > 5) this.width = 10;
</pre>
<p>上面代码中,<code>--foo</code>的值在 CSS 里面是无效语句,但是可以被 JavaScript 读取。这意味着,可以把样式设置写在 CSS 变量中,让 JavaScript 读取。</p>
<p>所以,CSS 变量提供了 JavaScript 与 CSS 通信的一种途径。</p>
<h3>八、参考链接</h3>
<ul>
<li><a href="https://vgpena.github.io/winning-with-css-variables/" target="_blank">Winning with CSS Variables</a></li>
<li><a href="https://developers.google.com/web/updates/2016/02/css-variables-why-should-you-care" target="_blank">CSS Variables: Why Should You Care?</a></li>
<li><a href="https://www.smashingmagazine.com/2017/04/start-using-css-custom-properties/" target="_blank">It's Time To Start Using CSS Custom Properties</a></li>
<li><a href="https://philipwalton.com/articles/why-im-excited-about-native-css-variables/" target="_blank">Why I'm Excited About Native CSS Variables</a></li>
</ul>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-55213134146871729112022-07-15T11:21:00.004+08:002023-02-25T18:11:31.288+08:00[轉載] 跨域资源共享 CORS 详解轉載自:<a href="http://www.ruanyifeng.com/blog/2016/04/cors.html" target="_blank">阮一峰 跨域资源共享 CORS 详解</a>
<p>CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。</p>
<p>它允许浏览器向跨源服务器,发出<a href="https://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html" target="_blank"><code>XMLHttpRequest</code></a>请求,从而克服了AJAX只能<a href="https://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html" target="_blank">同源</a>使用的限制。</p>
<p>本文详细介绍CORS的内部机制。</p>
<h3>一、简介</h3>
<p>CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。</p>
<p>整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。</p>
<p>因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。</p>
<h3>二、两种请求</h3>
<p>浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。</p>
<p>只要同时满足以下两大条件,就属于简单请求。</p>
<dl>
<dt>(1) 请求方法是以下三种方法之一:</dt>
<dd>HEAD</dd>
<dd>GET</dd>
<dd>POST</dd>
<dt>(2)HTTP的头信息不超出以下几种字段:</dt>
<dd>Accept</dd>
<dd>Accept-Language</dd>
<dd>Content-Language</dd>
<dd>Last-Event-ID</dd>
<dd>Content-Type:只限于三个值<br/>= <code>application/x-www-form-urlencoded</code><br/>= <code>multipart/form-data</code><br/>= <code>text/plain</code></dd>
</dl>
<p>这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。</p>
<p>凡是不同时满足上面两个条件,就属于非简单请求。</p>
<p>浏览器对这两种请求的处理,是不一样的。</p>
<h3>三、简单请求</h3>
<br/>
<h4>3.1 基本流程</h4>
<p>对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个<code>Origin</code>字段。</p>
<p>下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个<code>Origin</code>字段。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
</pre>
<p>上面的头信息中,<code>Origin</code>字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。</p>
<p>如果<code>Origin</code>指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含<code>Access-Control-Allow-Origin</code>字段(详见下文),就知道出错了,从而抛出一个错误,被<code>XMLHttpRequest</code>的<code>onerror</code>回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。</p>
<p>如果<code>Origin</code>指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
</pre>
<p>上面的头信息之中,有三个与CORS请求相关的字段,都以<code>Access-Control-</code>开头。</p>
<dl>
<dt>(1)Access-Control-Allow-Origin</dt>
<dd>该字段是必须的。它的值要么是请求时<code>Origin</code>字段的值,要么是一个<code>*</code>,表示接受任意域名的请求。</dd>
<dt>(2)Access-Control-Allow-Credentials</dt>
<dd>该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为<code>true</code>,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为<code>true</code>,如果服务器不要浏览器发送Cookie,删除该字段即可。</dd>
<dt>(3)Access-Control-Expose-Headers</dt>
<dd>该字段可选。CORS请求时,<code>XMLHttpRequest</code>对象的<code>getResponseHeader()</code>方法只能拿到6个基本字段:<code>Cache-Control</code>、<code>Content-Language</code>、<code>Content-Type</code>、<code>Expires</code>、<code>Last-Modified</code>、<code>Pragma</code>。如果想拿到其他字段,就必须在<code>Access-Control-Expose-Headers</code>里面指定。上面的例子指定,<code>getResponseHeader('FooBar')</code>可以返回<code>FooBar</code>字段的值。</dd>
</dl>
<h4>3.2 withCredentials 属性</h4>
<p>上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定<code>Access-Control-Allow-Credentials</code>字段。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Credentials: true
</pre>
<p>另一方面,开发者必须在AJAX请求中打开<code>withCredentials</code>属性。</p>
<pre class="js:nogutter:nocontrols" name="code">
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
</pre>
<p>否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。</p>
<p>但是,如果省略<code>withCredentials</code>设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭<code>withCredentials</code>。</p>
<pre class="js:nogutter:nocontrols" name="code">
xhr.withCredentials = false;
</pre>
<p>需要注意的是,如果要发送Cookie,<code>Access-Control-Allow-Origin</code>就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的<code>document.cookie</code>也无法读取服务器域名下的Cookie。</p>
<h3>四、非简单请求</h3>
<br/>
<h4>4.1 预检请求</h4>
<p>非简单请求是那种对服务器有特殊要求的请求,比如请求方法是<code>PUT</code>或<code>DELETE</code>,或者<code>Content-Type</code>字段的类型是<code>application/json</code>。</p>
<p>非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。</p>
<p>浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的<code>XMLHttpRequest</code>请求,否则就报错。</p>
<p>下面是一段浏览器的JavaScript脚本。</p>
<pre class="js:nogutter:nocontrols" name="code">
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
</pre>
<p>上面代码中,HTTP请求的方法是<code>PUT</code>,并且发送一个自定义头信息<code>X-Custom-Header</code>。</p>
<p>浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
</pre>
<p>"预检"请求用的请求方法是<code>OPTIONS</code>,表示这个请求是用来询问的。头信息里面,关键字段是<code>Origin</code>,表示请求来自哪个源。</p>
<p>除了<code>Origin</code>字段,"预检"请求的头信息包括两个特殊字段。</p>
<dl>
<dt>(1)Access-Control-Request-Method</dt>
<dd>该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是<code>PUT</code>。</dd>
<dt>(2)Access-Control-Request-Headers</dt>
<dd>该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是<code>X-Custom-Header</code>。</dd>
</dl>
<h4>4.2 预检请求的回应</h4>
<p>服务器收到"预检"请求以后,检查了<code>Origin</code>、<code>Access-Control-Request-Method</code>和<code>Access-Control-Request-Headers</code>字段以后,确认允许跨源请求,就可以做出回应。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
</pre>
<p>上面的HTTP回应中,关键的是<code>Access-Control-Allow-Origin</code>字段,表示<code>http://api.bob.com</code>可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Origin: *
</pre>
<p>如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被<code>XMLHttpRequest</code>对象的<code>onerror</code>回调函数捕获。控制台会打印出如下的报错信息。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
</pre>
<p>服务器回应的其他CORS相关字段如下。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
</pre>
<dl>
<dt>(1)Access-Control-Allow-Methods</dt>
<dd>该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。</dd>
<dt>(2)Access-Control-Allow-Headers</dt>
<dd>如果浏览器请求包括<code>Access-Control-Request-Headers</code>字段,则<code>Access-Control-Allow-Headers</code>字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。</dd>
<dt>(3)Access-Control-Allow-Credentials</dt>
<dd>该字段与简单请求时的含义相同。</dd>
<dt>(4)Access-Control-Max-Age</dt>
<dd>该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。</dd>
</dl>
<h4>4.3 浏览器的正常请求和回应</h4>
<p>一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个<code>Origin</code>头信息字段。服务器的回应,也都会有一个<code>Access-Control-Allow-Origin</code>头信息字段。</p>
<p>下面是"预检"请求之后,浏览器的正常CORS请求。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
</pre>
<p>上面头信息的<code>Origin</code>字段是浏览器自动添加的。</p>
<p>下面是服务器正常的回应。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
</pre>
<p>上面头信息中,<code>Access-Control-Allow-Origin</code>字段是每次回应都必定包含的。</p>
<h3>五、与JSONP的比较</h3>
<p>CORS与JSONP的使用目的相同,但是比JSONP更强大。</p>
<p>JSONP只支持<code>GET</code>请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。</p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-557572642807075762015-04-27T17:49:00.000+08:002015-04-27T17:49:13.294+08:00[轉載] Spring 使用 Cache、整合 Ehcache轉載自:<a href="http://haohaoxuexi.iteye.com/blog/2123030" target="_blank">Spring使用Cache、整合Ehcache</a><br />
<br />
<h2>Spring 使用 Cache</h2><br />
從 3.1 開始,Spring 引入了對 Cache 的支持。其使用方法和原理都類似於 Spring 對事務管理的支持。Spring Cache 是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用 Spring Cache 的時候我們要保證我們緩存的方法對於相同的方法參數要有相同的返回結果。<br />
<br />
使用 Spring Cache 需要我們做兩方面的事:<br />
<ul><li>聲明某些方法使用緩存</li>
<li>配置 Spring 對 Cache 的支持</li>
</ul><br />
和 Spring 對事務管理的支持一樣,Spring 對 Cache 的支持也有基於注解和基於 XML 配置兩種方式。下面我們先來看看基於注解的方式。<br />
<br />
<br />
<h2>1 基於注解的支持</h2><br />
Spring 為我們提供了幾個注解來支持 Spring Cache。其核心主要是 <code>@Cacheable</code> 和 <code>@CacheEvict</code>。使用 <code>@Cacheable</code> 標記的方法在執行後 Spring Cache 將緩存其返回結果,而使用 <code>@CacheEvict</code> 標記的方法會在方法執行前或者執行後移除 Spring Cache 中的某些元素。下面我們將來詳細介紹一下 Spring 基於注解對 Cache 的支持所提供的幾個注解。<br />
<br />
<br />
<h2>1.1 @Cacheable</h2><br />
<code>@Cacheable</code> 可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的。對於一個支持緩存的方法,Spring 會在其被調用後將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法。Spring 在緩存方法的返回值時是以鍵值對進行緩存的,值就是方法的返回結果,至於鍵的話,Spring 又支持兩種策略,默認策略和自定義策略,這個稍後會進行說明。<strong style="color:red">需要注意的是當一個支持緩存的方法在對像內部被調用時是不會觸發緩存功能的。</strong><code>@Cacheable</code> 可以指定三個屬性,value、key 和 condition。<br />
<br />
<br />
<h2>1.1.1 value 屬性指定 Cache 名稱</h2><br />
value 屬性是必須指定的,其表示當前方法的返回值是會被緩存在哪個 Cache 上的,對應 Cache 的名稱。其可以是一個 Cache 也可以是多個 Cache,當需要指定多個 Cache 時其是一個陣列。<br />
<pre class="java:nogutter:nocontrols" name="code">@Cacheable("cache1") // Cache 是發生在 cache1 上的
public User find(Integer id) {
return null;
}
@Cacheable({"cache1", "cache2"}) // Cache 是發生在 cache1 和 cache2 上的
public User find(Integer id) {
return null;
}
</pre><br />
<br />
<h2>1.1.2 使用 key 屬性自定義 key</h2><br />
key 屬性是用來指定 Spring 緩存方法的返回結果時對應的 key 的。該屬性支持 SpringEL 表達式。當我們沒有指定該屬性時,Spring 將使用默認策略生成 key。我們這裡先來看看自定義策略,至於默認策略會在後文單獨介紹。<br />
<br />
自定義策略是指我們可以通過 Spring 的 EL 表達式來指定我們的 key。這裡的 EL 表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用 <code>"#參數名"</code> 或者 <code>"#p參數index"</code>。下面是幾個使用參數作為 key 的示例。<br />
<pre class="java:nogutter:nocontrols" name="code">@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
</pre><br />
除了上述使用方法參數作為 key 之外,Spring 還為我們提供了一個 root 對像可以用來生成 key。通過該 root 對像我們可以獲取到以下信息。<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>屬性名稱</th><th>描述</th><th>示例</th></tr>
<tr><td>methodName</td><td>當前方法名</td><td>#root.methodName</td></tr>
<tr><td>method</td><td>當前方法</td><td>#root.method.name</td></tr>
<tr><td>target</td><td>當前被調用的對像</td><td>#root.target</td></tr>
<tr><td>targetClass</td><td>當前被調用的對像的 class</td><td>#root.targetClass</td></tr>
<tr><td>args</td><td>當前方法參數組成的陣列</td><td>#root.args[0]</td></tr>
<tr><td>caches</td><td>當前被調用的方法使用的 Cache</td><td>#root.caches[0].name</td></tr>
</table><br />
當我們要使用 root 對像的屬性作為 key 時我們也可以將 <code>"#root"</code> 省略,因為 Spring 默認使用的就是 root 對像的屬性。如:<br />
<pre class="java:nogutter:nocontrols" name="code">@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
</pre><br />
<br />
<h2>1.1.3 condition 屬性指定發生的條件</h2><br />
有的時候我們可能並不希望緩存一個方法所有的返回結果。通過 condition 屬性可以實現這一功能。condition 屬性默認為空,表示將緩存所有的調用情形。其值是通過 SpringEL 表達式來指定的,當為 <code>true</code> 時表示進行緩存處理;當為 <code>false</code> 時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。如下示例表示只有當 user 的 id 為偶數時才會進行緩存。<br />
<pre class="java:nogutter:nocontrols" name="code">@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
</pre><br />
<br />
<h2>1.2 @CachePut</h2><br />
在支持 Spring Cache 的環境下,對於使用 <code>@Cacheable</code> 標注的方法,Spring 在每次執行前都會檢查 Cache 中是否存在相同 key 的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則才會執行並將返回結果存入指定的緩存中。<code>@CachePut</code> 也可以聲明一個方法支持緩存功能。與 <code>@Cacheable</code> 不同的是使用 <code>@CachePut</code> 標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。<br />
<br />
<code>@CachePut</code> 也可以標注在類上和方法上。使用 <code>@CachePut</code> 時我們可以指定的屬性跟 <code>@Cacheable</code> 是一樣的。<br />
<pre class="java:nogutter:nocontrols" name="code">@CachePut("users") // 每次都會執行方法,並將結果存入指定的緩存中
public User find(Integer id) {
return null;
}
</pre><br />
<br />
<h2>1.3 @CacheEvict</h2><br />
<code>@CacheEvict</code> 是用來標注在需要清除緩存元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。<code>@CacheEvict</code> 可以指定的屬性有 value、key、condition、allEntries 和 beforeInvocation。其中 value、key 和 condition 的語義與 <code>@Cacheable</code> 對應的屬性類似。即 value 表示清除操作是發生在哪些 Cache 上的(對應 Cache 的名稱);key 表示需要清除的是哪個 key,如未指定則會使用默認策略生成的 key;condition 表示清除操作發生的條件。下面我們來介紹一下新出現的兩個屬性 allEntries和 beforeInvocation。<br />
<br />
<br />
<h2>1.3.1 allEntries 屬性</h2><br />
allEntries 是 boolean 類型,表示是否需要清除緩存中的所有元素。默認為 false,表示不需要。當指定了 allEntries 為 true 時,Spring Cache 將忽略指定的 key。有的時候我們需要 Cache 一下清除所有的元素,這比一個一個清除元素更有效率。<br />
<pre class="java:nogutter:nocontrols" name="code">@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
</pre><br />
<br />
<h2>1.3.2 beforeInvocation 屬性</h2><br />
清除操作默認是在對應方法成功執行之後觸發的,即方法如果因為拋出異常而未能成功返回時也不會觸發清除操作。使用 beforeInvocation 可以改變觸發清除操作的時間,當我們指定該屬性值為 true 時,Spring 會在調用該方法之前清除緩存中的指定元素。<br />
<pre class="java:nogutter:nocontrols" name="code">@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
</pre><br />
其實除了使用 <code>@CacheEvict</code> 清除緩存元素外,當我們使用 Ehcache 作為實現時,我們也可以配置 Ehcache 自身的驅除策略,其是通過 Ehcache 的配置文件來指定的。由於 Ehcache 不是本文描述的重點,這裡就不多贅述了,想了解更多關於 Ehcache 的信息,請查看我關於 <a href="http://haohaoxuexi.iteye.com/category/319453" target="_blank">Ehcache 的專欄</a>。<br />
<br />
<br />
<h2>1.4 @Caching</h2><br />
<code>@Caching</code> 注解可以讓我們在一個方法或者類上同時指定多個 Spring Cache 相關的注解。其擁有三個屬性:cacheable、put 和 evict,分別用於指定 <code>@Cacheable</code>、<code>@CachePut</code> 和 <code>@CacheEvict</code>。<br />
<pre class="java:nogutter:nocontrols" name="code">@Caching(
cacheable = @Cacheable("users"),
evict = {
@CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true)
}
)
public User find(Integer id) {
return null;
}
</pre><br />
<br />
<h2>1.5 使用自定義注解</h2><br />
Spring 允許我們在配置可緩存的方法時使用自定義的注解,前提是自定義的注解上必須使用對應的注解進行標注。如我們有如下這麼一個使用 <code>@Cacheable</code> 進行標注的自定義注解。<br />
<pre class="java:nogutter:nocontrols" name="code">@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}
</pre><br />
那麼在我們需要緩存的方法上使用 <code>@MyCacheable</code> 進行標注也可以達到同樣的效果。<br />
<pre class="java:nogutter:nocontrols" name="code">@MyCacheable
public User findById(Integer id) {
System.out.println("find user by id: " + id);
User user = new User();
user.setId(id);
user.setName("Name" + id);
return user;
}
</pre><br />
<br />
<br />
<h2>2 配置 Spring 對 Cache 的支持</h2><br />
<h2>2.1 聲明對Cache的支持</h2><br />
<h2>2.1.1 基於注解</h2><br />
配置 Spring 對基於注解的 Cache 的支持,首先我們需要在 Spring 的配置文件中引入 cache 命名空間,其次通過 <code><cache:annotation-driven/></code> 就可以啟用 Spring 對基於注解的 Cache 的支持。<br />
<pre class="xml:nogutter:nocontrols" name="code"><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd"
>
<cache:annotation-driven/>
</beans>
</pre><br />
<code><cache:annotation-driven/></code> 有一個 cache-manager 屬性用來指定當前所使用的 CacheManager 對應的 bean 的名稱,默認是 cacheManager,所以當我們的 CacheManager 的 id 為 cacheManager 時我們可以不指定該參數,否則就需要我們指定了。<br />
<br />
<code><cache:annotation-driven/></code> 還可以指定一個 mode 屬性,可選值有 proxy 和 aspectj。默認是使用 proxy。<strong style="color:red">當 mode 為 proxy 時,只有緩存方法在外部被調用的時候 Spring Cache 才會發生作用</strong>,這也就意味著如果一個緩存方法在其聲明對像內部被調用時 Spring Cache 是不會發生作用的。而 mode 為 aspectj 時就不會有這種問題。另外<strong style="color:red">使用 proxy 時,只有 public 方法上的 <code>@Cacheable</code> 等標注才會起作用</strong>,如果需要非 public 方法上的方法也可以使用 Spring Cache 時把 mode 設置為 aspectj。<br />
<br />
此外,<code><cache:annotation-driven/></code> 還可以指定一個 proxy-target-class 屬性,表示是否要代理 class,默認為 false。我們前面提到的 <code>@Cacheable</code>、<code>@cacheEvict</code> 等也可以標注在接口上,這對於基於接口的代理來說是沒有什麼問題的,但是需要注意的是當我們設置 proxy-target-class 為 true 或者 mode 為 aspectj 時,是直接基於 class 進行操作的,定義在接口上的 <code>@Cacheable</code> 等 Cache 注解不會被識別到,那對應的 Spring Cache 也不會起作用了。<br />
<br />
需要注意的是 <code><cache:annotation-driven/></code> 只會去尋找定義在同一個 ApplicationContext 下的 <code>@Cacheable</code> 等緩存注解。<br />
<br />
<br />
<h2>2.1.2 基於 XML 配置</h2><br />
除了使用注解來聲明對 Cache 的支持外,Spring 還支持使用 XML 來聲明對 Cache 的支持。這主要是通過類似於 aop:advice 的 cache:advice 來進行的。在 cache 命名空間下定義了一個 cache:advice 元素用來定義一個對於 Cache 的 advice。其需要指定一個 cache-manager 屬性,默認為 cacheManager。 cache:advice 下面可以指定多個 cache:caching 元素,其有點類似於使用注解時的 <code>@Caching</code> 注解。cache:caching 元素下又可以指定 cache:cacheable、cache:cache-put 和 cache:cache-evict 元素,它們類似於使用注解時的 <code>@Cacheable</code>、<code>@CachePut</code> 和 <code>@CacheEvict</code>。下面來看一個示例:<br />
<pre class="xml:nogutter:nocontrols" name="code"><cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="users">
<cache:cacheable method="findById" key="#p0"/>
<cache:cacheable method="find" key="#user.id"/>
<cache:cache-evict method="deleteAll" all-entries="true"/>
</cache:caching>
</cache:advice>
</pre><br />
上面配置定義了一個名為 cacheAdvice 的 cache:advice,其中指定了將緩存 findById 方法和 find 方法到名為 users 的緩存中。這裡的方法還可以使用通配符 <code>"*"</code>,比如 <code>"find*"</code> 表示任何以 <code>"find"</code> 開始的方法。<br />
<br />
有了cache:advice 之後,我們還需要引入 aop 命名空間,然後通過 aop:config 指定定義好的 cacheAdvice 要應用在哪些 pointcut 上。如:<br />
<br />
<pre class="xml:nogutter:nocontrols" name="code"><aop:config proxy-target-class="false">
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>
</aop:config>
</pre><br />
上面的配置表示在調用 com.xxx.UserService 中任意公共方法時將使用 cacheAdvice 對應的 cache:advice 來進行 Spring Cache 處理。更多關於 Spring AOP 的內容不在本文討論範疇內。<br />
<br />
<br />
<h2>2.2 配置 CacheManager</h2><br />
CacheManager 是 Spring 定義的一個用來管理 Cache 的接口。Spring 自身已經為我們提供了兩種 CacheManager 的實現,一種是基於 Java API 的 ConcurrentMap,另一種是基於第三方 Cache 實現—— Ehcache,如果我們需要使用其它類型的緩存時,我們可以自己來實現 Spring 的 CacheManager 接口或 AbstractCacheManager 抽像類。下面分別來看看 Spring 已經為我們實現好了的兩種 CacheManager 的配置示例。<br />
<br />
<br />
<h2>2.2.1 基於 ConcurrentMap 的配置</h2><br />
<pre class="xml:nogutter:nocontrols" name="code"><bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>
</set>
</property>
</bean>
</pre><br />
上面的配置使用的是一個 SimpleCacheManager,其中包含一個名為 <code>"xxx"</code> 的 ConcurrentMapCache。<br />
<br />
<br />
<h2>2.2.2 基於 Ehcache 的配置</h2><br />
<pre class="xml:nogutter:nocontrols" name="code"><!-- Ehcache 實現 -->
<bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="/WEB-INF/ehcache.xml"/>
</bean>
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehCacheManagerFactory"/>
</bean>
</pre><br />
上面的配置使用了一個 Spring 提供的 EhCacheCacheManager 來生成一個 Spring 的 CacheManager,其接收一個 Ehcache 的 CacheManager,因為真正用來存入緩存數據的還是 Ehcache。Ehcache 的 CacheManager 是通過 Spring 提供的 EhCacheManagerFactoryBean 來生成的,其可以通過指定 ehcache 的配置文件位置來生成一個 Ehcache 的 CacheManager。若未指定則將按照 Ehcache 的默認規則取 classpath 根路徑下的 ehcache.xml 文件,若該文件也不存在,則獲取 Ehcache 對應 jar 包中的 ehcache-failsafe.xml 文件作為配置文件。更多關於 Ehcache 的內容這裡就不多說了,它不屬於本文討論的內容,欲了解更多關於 Ehcache 的內容可以參考我之前發布的 Ehcache 系列文章,也可以參考官方文檔等。<br />
<br />
<br />
<br />
<h2>3 鍵的生成策略</h2><br />
鍵的生成策略有兩種,一種是默認策略,一種是自定義策略。<br />
<br />
<br />
<h2>3.1 默認策略</h2><br />
默認的 key 生成策略是通過 KeyGenerator 生成的,其默認策略如下:<br />
<ul><li>如果方法沒有參數,則使用 0 作為 key</li>
<li>如果只有一個參數的話則使用該參數作為 key</li>
<li>如果參數多餘一個的話則使用所有參數的 hashCode 作為 key</li>
</ul><br />
如果我們需要指定自己的默認策略的話,那麼我們可以實現自己的 KeyGenerator,然後指定我們的 Spring Cache 使用的 KeyGenerator 為我們自己定義的 KeyGenerator。<br />
<br />
使用基於注解的配置時是通過 cache:annotation-driven 指定的.<br />
<pre class="xml:nogutter:nocontrols" name="code"><cache:annotation-driven key-generator="userKeyGenerator"/>
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
</pre><br />
而使用基於 XML 配置時是通過 cache:advice 來指定的。<br />
<pre class="xml:nogutter:nocontrols" name="code"><cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">
</cache:advice>
</pre><br />
需要注意的是此時我們所有的 Cache 使用的 Key 的默認生成策略都是同一個 KeyGenerator。<br />
<br />
<br />
<h2>3.1 自定義策略</h2><br />
自定義策略是指我們可以通過 Spring 的 EL 表達式來指定我們的 key。這裡的 EL 表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用 <code>"#參數名"</code> 或者 <code>"#p參數index"</code>。下面是幾個使用參數作為 key 的示例。<br />
<pre class="java:nogutter:nocontrols" name="code">@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
</pre><br />
除了上述使用方法參數作為 key 之外,Spring 還為我們提供了一個 root 對像可以用來生成 key。通過該 root 對像我們可以獲取到以下信息。<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>屬性名稱</th><th>描述</th><th>示例</th></tr>
<tr><td>methodName</td><td>當前方法名</td><td>#root.methodName</td></tr>
<tr><td>method</td><td>當前方法</td><td>#root.method.name</td></tr>
<tr><td>target</td><td>當前被調用的對像</td><td>#root.target</td></tr>
<tr><td>targetClass</td><td>當前被調用的對像的 class</td><td>#root.targetClass</td></tr>
<tr><td>args</td><td>當前方法參數組成的陣列</td><td>#root.args[0]</td></tr>
<tr><td>caches</td><td>當前被調用的方法使用的 Cache</td><td>#root.caches[0].name</td></tr>
</table><br />
當我們要使用 root 對像的屬性作為 key 時我們也可以將 <code>"#root"</code> 省略,因為 Spring 默認使用的就是 root 對像的屬性。如:<br />
<pre class="java:nogutter:nocontrols" name="code">@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
</pre><br />
<br />
<br />
<h2>4 Spring 單獨使用 Ehcache</h2><br />
前面介紹的內容是 Spring 內置的對 Cache 的支持,其實我們也可以通過 Spring 自己單獨的使用 Ehcache 的 CacheManager 或 Ehcache 對像。通過在 Application Context 中配置 EhCacheManagerFactoryBean 和 EhCacheFactoryBean,我們就可以把對應的 EhCache 的 CacheManager 和 Ehcache 對像注入到其它的 Spring bean 對像中進行使用。<br />
<br />
<br />
<h2>4.1 EhCacheManagerFactoryBean</h2><br />
EhCacheManagerFactoryBean 是 Spring 內置的一個可以產生 Ehcache 的 CacheManager 對像的 FactoryBean。其可以通過屬性 configLocation 指定用於創建 CacheManager 的 Ehcache 配置文件的路徑,通常是 ehcache.xml文件的路徑。如果沒有指定 configLocation,則將使用默認位置的配置文件創建 CacheManager,這是屬於 Ehcache 自身的邏輯,即如果在 classpath 根路徑下存在 ehcache.xml 文件,則直接使用該文件作為 Ehcache 的配置文件,否則將使用 ehcache-xxx.jar 中的 ehcache-failsafe.xml 文件作為配置文件來創建 Ehcache 的 CacheManager。此外,如果不希望創建的 CacheManager 使用默認的名稱(在 ehcache.xml 文件中定義的,或者是由 CacheManager 內部定義的),則可以通過 cacheManagerName 屬性進行指定。下面是一個配置 EhCacheManagerFactoryBean 的示例。<br />
<pre class="xml:nogutter:nocontrols" name="code"><!-- 定義 CacheManager -->
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置文件的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
<!-- 指定新建的 CacheManager 的名稱 -->
<property name="cacheManagerName" value="cacheManagerName"/>
</bean>
</pre><br />
<br />
<h2>4.2 EhCacheFactoryBean</h2><br />
EhCacheFactoryBean 是用來產生 Ehcache 的 Ehcache 對像的 FactoryBean。定義 EhcacheFactoryBean 時有兩個很重要的屬性我們可以來指定。一個是 cacheManager 屬性,其可以指定將用來獲取或創建 Ehcache 的 CacheManager 對像,若未指定則將通過 CacheManager.create() 獲取或創建默認的 CacheManager。另一個重要屬性是 cacheName,其表示當前 EhCacheFactoryBean 對應的是 CacheManager 中的哪一個 Ehcache 對像,若未指定默認使用 beanName 作為 cacheName。若 CacheManager 中不存在對應 cacheName 的 Ehcache 對像,則將使用 CacheManager 創建一個名為 cacheName 的 Cache 對像。此外我們還可以通過 EhCacheFactoryBean 的 timeToIdle、timeToLive 等屬性指定要創建的 Cache 的對應屬性,注意這些屬性只對 CacheManager 中不存在對應 Cache 時新建的 Cache 才起作用,對已經存在的 Cache 將不起作用,更多屬性設置請參考 Spring 的 API 文檔。此外還有幾個屬性是對不管是已經存在還是新創建的 Cache 都起作用的屬性:statisticsEnabled、sampledStatisticsEnabled、disabled、blocking 和 cacheEventListeners,其中前四個默認都是 false,最後一個表示為當前 Cache 指定 CacheEventListener。下面是一個定義 EhCacheFactoryBean 的示例。<br />
<pre class="xml:nogutter:nocontrols" name="code"><!-- 定義 CacheManager -->
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置文件的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
<!-- 指定新建的 CacheManager 的名稱 -->
<property name="cacheManagerName" value="cacheManagerName"/>
</bean>
<!-- 定義一個 Ehcache -->
<bean id="userCache"
class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheName" value="user"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
</pre><br />
<br />
(注:本文是基於 Spring 3.1.0 所寫)<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-81978445335236548722015-03-03T13:34:00.000+08:002015-03-03T13:34:49.453+08:00[轉載] Spring Collections (List, Set, Map, and Properties)轉載自:<a href="http://www.mkyong.com/spring/spring-collections-list-set-map-and-properties-example/" target="_blank">Spring Collections (List, Set, Map, and Properties) example</a><br />
<br />
Spring examples to show you how to inject values into collections type (List, Set, Map, and Properties). 4 major collection types are supported :<br />
<ul><li>List – <list/></li>
<li>Set – <set/></li>
<li>Map – <map/></li>
<li>Properties – <props/></li>
</ul><br />
<br />
<h2>Spring beans</h2>A Customer object, with four collection properties.<br />
<pre class="java:nogutter:nocontrols" name="code">package com.mkyong.common;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Customer
{
private List<Object> lists;
private Set<Object> sets;
private Map<Object, Object> maps;
private Properties pros;
//...
}
</pre>See different code snippets to declare collection in bean configuration file.<br />
<br />
<br />
<h2>1. List example</h2><pre class="xml:nogutter:nocontrols" name="code"><property name="lists">
<list>
<value>1</value>
<ref bean="PersonBean" />
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyongList" />
<property name="address" value="address" />
<property name="age" value="28" />
</bean>
</list>
</property>
</pre><br />
<br />
<h2>2. Set example</h2><pre class="xml:nogutter:nocontrols" name="code"><property name="sets">
<set>
<value>1</value>
<ref bean="PersonBean" />
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyongSet" />
<property name="address" value="address" />
<property name="age" value="28" />
</bean>
</set>
</property>
</pre><br />
<br />
<h2>3. Map example</h2><pre class="xml:nogutter:nocontrols" name="code"><property name="maps">
<map>
<entry key="Key 1" value="1" />
<entry key="Key 2" value-ref="PersonBean" />
<entry key="Key 3">
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyongMap" />
<property name="address" value="address" />
<property name="age" value="28" />
</bean>
</entry>
</map>
</property>
</pre><br />
<br />
<h2>4. Properties example</h2><pre class="xml:nogutter:nocontrols" name="code"><property name="pros">
<props>
<prop key="admin">admin@nospam.com</prop>
<prop key="support">support@nospam.com</prop>
</props>
</property>
</pre><br />
<br />
<h2>Full Spring’s bean configuration file.</h2><pre class="xml:nogutter:nocontrols" name="code"><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="CustomerBean" class="com.mkyong.common.Customer">
<!-- java.util.List -->
<property name="lists">
<list>
<value>1</value>
<ref bean="PersonBean" />
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyongList" />
<property name="address" value="address" />
<property name="age" value="28" />
</bean>
</list>
</property>
<!-- java.util.Set -->
<property name="sets">
<set>
<value>1</value>
<ref bean="PersonBean" />
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyongSet" />
<property name="address" value="address" />
<property name="age" value="28" />
</bean>
</set>
</property>
<!-- java.util.Map -->
<property name="maps">
<map>
<entry key="Key 1" value="1" />
<entry key="Key 2" value-ref="PersonBean" />
<entry key="Key 3">
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyongMap" />
<property name="address" value="address" />
<property name="age" value="28" />
</bean>
</entry>
</map>
</property>
<!-- java.util.Properties -->
<property name="pros">
<props>
<prop key="admin">admin@nospam.com</prop>
<prop key="support">support@nospam.com</prop>
</props>
</property>
</bean>
<bean id="PersonBean" class="com.mkyong.common.Person">
<property name="name" value="mkyong1" />
<property name="address" value="address 1" />
<property name="age" value="28" />
</bean>
</beans>
</pre><br />
Run it…<br />
<pre class="java:nogutter:nocontrols" name="code">package com.mkyong.common;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main( String[] args ) {
ApplicationContext context
= new ClassPathXmlApplicationContext("SpringBeans.xml");
Customer cust = (Customer)context.getBean("CustomerBean");
System.out.println(cust);
}
}
</pre><br />
Output<br />
<pre class="none:nogutter:nocontrols" name="code">Customer [
lists=[
1,
Person [address=address 1, age=28, name=mkyong1],
Person [address=address, age=28, name=mkyongList]
],
maps={
key 1=1,
key 2=Person [address=address 1, age=28, name=mkyong1],
key 3=Person [address=address, age=28, name=mkyongMap]
},
pros={
admin=admin@nospam.com,
support=support@nospam.com
},
sets=[
1,
Person [address=address 1, age=28, name=mkyong1],
Person [address=address, age=28, name=mkyongSet]
]
]
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-87755819874058657482015-03-01T18:10:00.000+08:002015-03-01T18:10:23.760+08:00[轉載] Make 命令教程轉載自:<a href="http://www.ruanyifeng.com/blog/2015/02/make.html" target="_blank">Make 命令教程 - 阮一峰的网络日志</a><br />
<br />
代碼變成可執行文件,叫做<a target="_blank" href="http://www.ruanyifeng.com/blog/2014/11/compiler.html">編譯</a>(compile);先編譯這個,還是先編譯那個(即編譯的安排),叫做<a target="_blank" href="http://en.wikipedia.org/wiki/Software_build">構建</a>(build)。<br />
<br />
<a target="_blank" href="http://en.wikipedia.org/wiki/Make_%28software%29">Make</a> 是最常用的構建工具,誕生於 1977 年,主要用於 C 語言的項目。但是實際上 ,任何只要某個文件有變化,就要重新構建的項目,都可以用 Make 構建。<br />
<br />
本文介紹 Make 命令的用法,從簡單的講起,不需要任何基礎,只要會使用命令行,就能看懂。我的參考資料主要是Isaac Schlueter的<a target="_blank" href="https://gist.github.com/isaacs/62a2d1825d04437c6f08">《Makefile文件教程》</a>和<a target="_blank" href="https://www.gnu.org/software/make/manual/make.html">《GNU Make手冊》</a>。<br />
<br />
<br />
<h1>一、Make 的概念</h1><br />
Make 這個詞,英語的意思是"制作"。Make 命令直接用了這個意思,就是要做出某個文件。比如,要做出文件 a.txt,就可以執行下面的命令。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make a.txt
</pre><br />
但是,如果你真的輸入這條命令,它並不會起作用。因為 Make 命令本身並不知道,如何做出 a.txt,需要有人告訴它,如何調用其他命令完成這個目標。<br />
<br />
比如,假設文件 a.txt 依賴於 b.txt 和 c.txt ,是後面兩個文件連接(<code>cat</code> 命令)的產物。那麼,make 需要知道下面的規則。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">a.txt: b.txt c.txt
cat b.txt c.txt > a.txt
</pre><br />
也就是說,make a.txt 這條命令的背後,實際上分成兩步:第一步,確認 b.txt 和 c.txt 必須已經存在,第二步使用 <code>cat</code> 命令 將這個兩個文件合並,輸出為新文件。<br />
<br />
像這樣的規則,都寫在一個叫做 <b>Makefile</b> 的文件中,Make 命令依賴這個文件進行構建。Makefile 文件也可以寫為 <b>makefile</b>, 或者用命令行參數指定為其他文件名。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make -f rules.txt
# 或者
$ make --file=rules.txt
</pre><br />
上面代碼指定 make 命令依據 rules.txt 文件中的規則,進行構建。<br />
<br />
總之,make 只是一個根據指定的 Shell 命令進行構建的工具。它的規則很簡單,你規定要構建哪個文件、它依賴哪些源文件,當那些文件有變動時,如何重新構建它。<br />
<br />
<br />
<br />
<h1>二、Makefile 文件的格式</h1><br />
構建規則都寫在 Makefile 文件裡面,要學會如何 Make 命令,就必須學會如何編寫 Makefile 文件。<br />
<br />
<br />
<h2>2.1 概述</h2><br />
Makefile 文件由一系列規則(rules)構成。每條規則的形式如下。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code"><target> : <prerequisites>
[tab] <commands>
</pre><br />
上面第一行冒號前面的部分,叫做"目標"(target),冒號後面的部分叫做"前置條件"(prerequisites);第二行必須由一個tab鍵起首,後面跟著"命令"(commands)。<br />
<br />
"目標"是必需的,不可省略;"前置條件"和"命令"都是可選的,但是兩者之中必須至少存在一個。<br />
<br />
每條規則就明確兩件事:構建目標的前置條件是什麼,以及如何構建。下面就詳細講解,每條規則的這三個組成部分。<br />
<br />
<br />
<h2>2.2 目標(target)</h2><br />
一個目標(target)就構成一條規則。目標通常是文件名,指明Make命令所要構建的對像,比如上文的 a.txt 。目標可以是一個文件名,也可以是多個文件名,之間用空格分隔。<br />
<br />
除了文件名,目標還可以是某個操作的名字,這稱為"偽目標"(phony target)。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">clean:
rm *.o
</pre><br />
上面代碼的目標是 clean,它不是文件名,而是一個操作的名字,屬於"偽目標 ",作用是刪除對像文件。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make clean
</pre><br />
但是,如果當前目錄中,正好有一個文件叫做 clean,那麼這個命令不會執行。因為 Make 發現 clean 文件已經存在,就認為沒有必要重新構建了,就不會執行指定的 <code>rm</code> 命令。<br />
<br />
為了避免這種情況,可以明確聲明 clean 是"偽目標",寫法如下。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">.PHONY: clean
clean:
rm *.o temp
</pre><br />
聲明 clean 是"偽目標"之後,make 就不會去檢查是否存在一個叫做 clean 的文件,而是每次運行都執行對應的命令。像 .PHONY 這樣的內置目標名還有不少,可以查看<a target="_blank" href="http://www.gnu.org/software/make/manual/html_node/Special-Targets.html#Special-Targets">手冊</a>。<br />
<br />
如果 Make 命令運行時沒有指定目標,默認會執行 Makefile 文件的第一個目標。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make
</pre><br />
上面代碼執行 Makefile 文件的第一個目標。<br />
<br />
<br />
<h2>2.3 前置條件(prerequisites)</h2><br />
前置條件通常是一組文件名,之間用空格分隔。它指定了"目標"是否重新構建的判斷標准:只要有一個前置文件不存在,或者有過更新(前置文件的 last-modification 時間戳比目標的時間戳新),"目標"就需要重新構建。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">result.txt: source.txt
cp source.txt result.txt
</pre><br />
上面代碼中,構建 result.txt 的前置條件是 source.txt 。如果當前目錄中,source.txt 已經存在,那麼 <code>make result.txt</code> 可以正常運行,否則必須再寫一條規則,來生成 source.txt 。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">source.txt:
echo "this is the source" > source.txt
</pre><br />
上面代碼中,source.txt 後面沒有前置條件,就意味著它跟其他文件都無關,只要這個文件還不存在,每次調用 <code>make source.txt</code>,它都會生成。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make result.txt
$ make result.txt
</pre><br />
上面命令連續執行兩次 <code>make result.txt</code>。第一次執行會先新建 source.txt,然後再新建 result.txt。第二次執行,Make 發現 source.txt 沒有變動(時間戳晚於 result.txt),就不會執行任何操作,result.txt 也不會重新生成。<br />
<br />
如果需要生成多個文件,往往采用下面的寫法。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">source: file1 file2 file3
</pre><br />
上面代碼中,source 是一個偽目標,只有三個前置文件,沒有任何對應的命令。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make source
</pre><br />
執行 <code>make source</code> 命令後,就會一次性生成 file1,file2,file3 三個文件。這比下面的寫法要方便很多。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make file1
$ make file2
$ make file3
</pre><br />
<br />
<h2>2.4 命令(commands)</h2><br />
命令(commands)表示如何更新目標文件,由一行或多行的 Shell 命令組成。它是構建"目標"的具體指令,它的運行結果通常就是生成目標文件。<br />
<br />
每行命令之前必須有一個 tab 鍵。如果想用其他鍵,可以用內置變量 .RECIPEPREFIX 聲明。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">.RECIPEPREFIX = >
all:
> echo Hello, world
</pre><br />
上面代碼用 .RECIPEPREFIX 指定,大於號(>)替代 tab 鍵。所以,每一行命令的起首變成了大於號,而不是 tab 鍵。<br />
<br />
需要注意的是,每行命令在一個單獨的 shell 中執行。這些 Shell 之間沒有繼承關系。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">var-lost:
export foo=bar
echo "foo=[$$foo]"
</pre><br />
上面代碼執行後(<code>make var-lost</code>),取不到 foo 的值。因為兩行命令在兩個不同的進程執行。一個解決辦法是將兩行命令寫在一行,中間用分號分隔。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">var-kept:
export foo=bar; echo "foo=[$$foo]"
</pre><br />
另一個解決辦法是在換行符前加反斜杠轉義。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">var-kept:
export foo=bar; \
echo "foo=[$$foo]"
</pre><br />
最後一個方法是加上 <code>.ONESHELL:</code> 命令。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">.ONESHELL:
var-kept:
export foo=bar;
echo "foo=[$$foo]"
</pre><br />
<br />
<br />
<h1>三、Makefile文件的語法</h1><br />
<h2>3.1 注釋</h2><br />
井號(#)在 Makefile 中表示注釋。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code"># 这是注释
result.txt: source.txt
# 这是注释
cp source.txt result.txt # 这也是注释
</pre><br />
<br />
<h2>3.2 回聲(echoing)</h2><br />
正常情況下,make 會打印每條命令,然後再執行,這就叫做回聲(echoing)。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">test:
# 这是测试
</pre><br />
執行上面的規則,會得到下面的結果。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$ make test
# 这是测试
</pre><br />
在命令的前面加上 <code>@</code>,就可以關閉回聲。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">test:
@# 这是测试
</pre><br />
現在再執行 <code>make test</code>,就不會有任何輸出。<br />
<br />
由於在構建過程中,需要了解當前在執行哪條命令,所以通常只在注釋和純顯示的 <code>echo</code> 命令前面加上 <code>@</code>。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">test:
@# 这是测试
@echo TODO
</pre><br />
<br />
<h2>3.3 通配符</h2><br />
通配符(wildcard)用來指定一組符合條件的文件名。Makefile 的通配符與 Bash 一致,主要有星號(*)、問號(?)和 [...] 。比如, <code>*.o</code> 表示所有後綴名為 o 的文件。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">clean:
rm -f *.o
</pre><br />
<br />
<h2>3.4 模式匹配</h2><br />
Make 命令允許對文件名,進行類似正則運算的匹配,主要用到的匹配符是 <code>%</code>。比如,假定當前目錄下有 f1.c 和 f2.c 兩個源碼文件,需要將它們編譯為對應的對像文件。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">%.o: %.c
</pre><br />
等同於下面的寫法。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">f1.o: f1.c
f2.o: f2.c
</pre><br />
使用匹配符 <code>%</code>,可以將大量同類型的文件,只用一條規則就完成構建。<br />
<br />
<br />
<h2>3.5 變量和賦值符</h2><br />
Makefile 允許使用等號自定義變量。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">txt = Hello World
test:
@echo $(txt)
</pre><br />
上面代碼中,變量 txt 等於 Hello World。調用時,變量需要放在 $( ) 之中。<br />
<br />
調用Shell變量,需要在美元符號前,再加一個美元符號,這是因為Make命令會對美元符號轉義。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">test:
@echo $$HOME
</pre><br />
有時,變量的值可能指向另一個變量。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">v1 = $(v2)
</pre><br />
上面代碼中,變量 v1 的值是另一個變量 v2。這時會產生一個問題,v1 的值到底在定義時擴展(靜態擴展),還是在運行時擴展(動態擴展)?如果 v2 的值是動態的,這兩種擴展方式的結果可能會差異很大。<br />
<br />
為了解決類似問題,Makefile 一共提供了四個賦值運算符 (=、:=、?=、+=),它們的區別請看 <a target="_blank" href="http://stackoverflow.com/questions/448910/makefile-variable-assignment">StackOverflow</a>。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">VARIABLE = value
# 在执行时扩展,允许递归扩展。
VARIABLE := value
# 在定义时扩展。
VARIABLE ?= value
# 只有在该变量为空时才设置值。
VARIABLE += value
# 将值追加到变量的尾端。
</pre><br />
<br />
<h2>3.6 內置變量(Implicit Variables)</h2><br />
Make命令提供一系列內置變量,比如,<code>$(CC)</code> 指向當前使用的編譯器,<code>$(MAKE)</code> 指向當前使用的Make工具。這主要是為了跨平台的兼容性,詳細的內置變量清單見<a target="_blank" href="https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html">手冊</a>。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">output:
$(CC) -o output input.c
</pre><br />
<br />
<h2>3.7 自動變量(Automatic Variables)</h2><br />
Make 命令還提供一些自動變量,它們的值與當前規則有關。主要有以下幾個。<br />
<br />
<br />
<strong>(1)$@</strong><br />
<br />
$@指代當前目標,就是Make命令當前構建的那個目標。比如,<code>make foo</code>的 $@ 就指代foo。 <br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">a.txt b.txt:
touch $@
</pre><br />
等同於下面的寫法。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">a.txt:
touch a.txt
b.txt:
touch b.txt
</pre><br />
<br />
<strong>(2)$<</strong><br />
<br />
$< 指代第一個前置條件。比如,規則為 t: p1 p2,那麼$< 就指代p1。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">a.txt: b.txt c.txt
cp $< $@
</pre><br />
等同於下面的寫法。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">a.txt: b.txt c.txt
cp b.txt a.txt
</pre><br />
<br />
<strong>(3)$?</strong><br />
<br />
$? 指代比目標更新的所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,其中 p2 的時間戳比 t 新,$?就指代p2。<br />
<br />
<br />
<strong>(4)$^</strong><br />
<br />
$^ 指代所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,那麼 $^ 就指代 p1 p2 。<br />
<br />
<br />
<strong>(5)$*</strong><br />
<br />
$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。<br />
<br />
<br />
<strong>(6)$(@D) 和 $(@F)</strong><br />
<br />
$(@D) 和 $(@F) 分別指向 $@ 的目錄名和文件名。比如,$@是 src/input.c,那麼$(@D) 的值為 src ,$(@F) 的值為 input.c。<br />
<br />
<br />
<strong>(7)$(<D) 和 $(<F)</strong><br />
<br />
$(<D) 和 $(<F) 分別指向 $< 的目錄名和文件名。 <br />
<br />
所有的自動變量清單,請看<a target="_blank" href="https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html">手冊</a>。下面是自動變量的一個例子。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">dest/%.txt: src/%.txt
@[ -d dest ] || mkdir dest
cp $< $@
</pre><br />
上面代碼將 src 目錄下的 txt 文件,拷貝到 dest 目錄下。首先判斷 dest 目錄是否存在,如果不存在就新建,然後,$< 指代前置文件(src/%.txt), $@ 指代目標文件(dest/%.txt)。<br />
<br />
<br />
<h2>3.8 判斷和循環</h2><br />
Makefile使用 Bash 語法,完成判斷和循環。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
</pre><br />
上面代碼判斷當前編譯器是否 gcc ,然後指定不同的庫文件。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
# 等同于
all:
for i in one two three; do \
echo $i; \
done
</pre><br />
上面代碼的運行結果。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">one
two
three
</pre><br />
<br />
<h2>3.9 函數</h2><br />
Makefile 還可以使用函數,格式如下。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$(function arguments)
# 或者
${function arguments}
</pre><br />
Makefile提供了許多<a target="_blank" href="http://www.gnu.org/software/make/manual/html_node/Functions.html">內置函數</a>,可供調用。下面是幾個常用的內置函數。<br />
<br />
<br />
<strong>(1)shell 函數</strong><br />
<br />
shell 函數用來執行 shell 命令<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">srcfiles := $(shell echo src/{00..99}.txt)
</pre><br />
<br />
<strong>(2)wildcard 函數</strong><br />
<br />
wildcard 函數用來在 Makefile 中,替換 Bash 的通配符。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">srcfiles := $(wildcard src/*.txt)
</pre><br />
<br />
<strong>(3)subst 函數</strong><br />
<br />
subst 函數用來文本替換,格式如下。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$(subst from,to,text)
</pre><br />
下面的例子將字符串 "feet on the street" 替換成 "fEEt on the strEEt"。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$(subst ee,EE,feet on the street)
</pre><br />
下面是一個稍微復雜的例子。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">comma:= ,
empty:=
# space变量用两个空变量作为标识符,当中是一个空格
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now `a,b,c'.
</pre><br />
<br />
<strong>(4)patsubst 函數</strong><br />
<br />
patsubst 函數用於模式匹配的替換,格式如下。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$(patsubst pattern,replacement,text)
</pre><br />
下面的例子將文件名 "x.c.c bar.c",替換成 "x.c.o bar.o"。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">$(patsubst %.c,%.o,x.c.c bar.c)
</pre><br />
<br />
<strong>(5)替換後綴名</strong><br />
<br />
替換後綴名函數的寫法是:變量名 + 冒號 + 後綴名替換規則。它實際上 patsubst 函數的一種簡寫形式。<br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">min: $(OUTPUT:.js=.min.js)
</pre><br />
上面代碼的意思是,將變量 OUTPUT 中的後綴名 .js 全部替換成 .min.js 。<br />
<br />
<br />
<h1>四、Makefile 的實例</h1><br />
<br />
<strong>(1)執行多個目標</strong><br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
</pre><br />
上面代碼可以調用不同目標,刪除不同後綴名的文件,也可以調用一個目標(cleanall),刪除所有指定類型的文件。<br />
<br />
<br />
<strong>(2)編譯C語言項目</strong><br />
<br />
<pre class="sh:nogutter:nocontrols" name="code">edit : main.o kbd.o command.o display.o
cc -o edit main.o kbd.o command.o display.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h
cc -c display.c
clean :
rm edit main.o kbd.o command.o display.o
.PHONY: edit clean
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-51245005324441978602015-02-26T16:15:00.001+08:002015-02-26T16:35:29.948+08:00[轉載] Java 內部類別轉載自:<a href="http://openhome.cc/Gossip/Java/InnerClass.html" target="_blank">Java Gossip: 內部類別</a><br />
<br />
可以在類別中再定義類別,稱之為內部類別(Inner class),初學者暫時不會使用到內部類別,在這邊先簡單介紹語法,雖然會無聊一些,不過之後章節就會看到相關應用。<br />
<br />
內部類別可以定義在類別區塊之中。例如以下程式片段建立了非靜態的內部類別:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">class Some {
class Other {
}
}
</pre><br />
雖然實務上很少看到接下來的寫法,不過要使用 <code>Some</code> 中的 <code>Other</code> 類別,必須先建立實例 <code>Some</code> 實例。例如:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">Some s = new Some();
Some.Other o = s.new Other();
</pre><br />
內部類別也可以使用 <b>public</b>、<b>protected</b> 或 <b>private</b> 宣告。例如:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">class Some {
private class Other {
}
}
</pre><br />
內部類別本身可以存取外部類別的成員,通常非靜態內部類別會宣告為 <b>private</b>,這類內部類別是輔助類別中某些操作而設計,外部不用知道內部類別的存在。<br />
<br />
內部類別也可以宣告為 <b>static</b>。例如:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">class Some {
static class Other {
}
}
</pre><br />
一個被宣告為 static 的內部類別,通常是將外部類別當作名稱空間。你可以如下建立類別實例:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">Some.Other o = new Some.Other();
</pre><br />
被宣告為 <b>static</b> 的內部類別,雖然將外部類別當作名稱空間,但算是個獨立類別,它可以存取外部類別 <b>static</b> 成員,但不可存取外部類別非 <b>static</b> 成員。例如:<br />
<img src="http://2.bp.blogspot.com/-R99-VO01ZWc/VO7aUCENdCI/AAAAAAAANpo/vHAvwetaCzk/s550/InnerClass-1.PNG" /><br />
<br />
方法中也可以宣告類別,這通常是輔助方法中演算之用,方法外無法使用。例如:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">class Some {
public void doSome() {
class Other {
}
}
}
</pre><br />
實務上比較少看到在方法中定義具名的內部類別,倒很常看到方法中定義匿名內部類別(Anonymous inner class)並直接實例化,這跟類別繼承或介面實作有關,以下先看一下語法,細節留到談到繼承與介面時再作討論:<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">Object o = new Object() {
public String toString() {
return "無聊的語法示範而已";
}
};
</pre><br />
如果要稍微解釋一下,這個語法定義了一個沒有名稱的類別,它繼承 <code>Object</code> 類別,並重新定義(Override)了 <code>toString()</code> 方法,<b>new</b> 表示實例化這個沒有名稱的類別。匿名類別語法本身,在某些場合有時有些囉嗦,JDK 8 提出了 Lambda,有一部份目的是用來解決匿名類別語法囉嗦的問題,之後會再討論。<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-43582761593150662342015-02-26T16:07:00.001+08:002015-02-26T16:07:43.331+08:00[轉載] Java 動態代理轉載自:<a href="http://openhome.cc/Gossip/JavaEssence/Proxy.html" target="_blank">Java Essence: 動態代理</a><br />
<br />
來看一個最簡單的例子,當您需要在執行某些方法時留下日誌訊息,直覺的,您可能會如下撰寫:<br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
import java.util.logging.*;
public class HelloSpeaker {
private Logger logger =
Logger.getLogger(this.getClass().getName());
public void hello(String name) {
// 方法執行開始時留下日誌
logger.log(Level.INFO, "hello method starts....");
// 程式主要功能
System.out.println("Hello, " + name);
// 方法執行完畢前留下日誌
logger.log(Level.INFO, "hello method ends....");
}
}
</pre><br />
<br />
在 <code>HelloSpeaker</code> 類別中,當執行 <code>hello()</code> 方法時,你希望該方法執行開始與執行完畢時都能留下日誌,最簡單的作法就是如以上的程式設計,在 方法執行的前後加上日誌動作,然而記錄的這幾行程式碼橫切入(Cross-cutting)<code>HelloSpeaker</code> 類別中,對於 <code>HelloSpeaker</code> 來說,日誌的這幾個動作並不屬於 <code>HelloSpeaker</code> 商務邏輯(顯示"Hello"等文字),這使得 <code>HelloSpeaker</code> 增加了額外的職責。<br />
<br />
想想如果程式中這種日誌的動作到處都有需求,以上的寫法勢必造成你必須到處撰寫這些日誌動作的程式碼,這將使得維護日誌程式碼的困難度加大。如果需要的服 務(Service)不只有日誌動作,有一些非物件本身職責的相關動作也混入了物件之中(例如權限檢查、交易管理等等),會使得物件的負擔更形加重,甚至 混淆了物件本身該負有的職責,物件本身的職責所佔的程式碼,或許還小於這些與物件職責不相關的動作或服務的程式碼。<br />
<br />
另一方面,使用以上的寫法,若你有一日不再需要日誌(或權限檢查、交易管理等)的服務,那麼你將需要修改所有留下日誌動作的程式碼,你無法簡單的就將這些相關服務從即有的程式中移去。<br />
<br />
可以使用代理(Proxy)機制來解決這個問題,在這邊討論兩種代理方式:<br />
<ul><li><b>靜態代理(Static proxy)</b></li>
<li><b>動態代理(Dynamic proxy)</b></li>
</ul><br />
在靜態代理的實現中,代理物件與被代理的物件都必須實現同一個介面,在代理物件中可以實現記錄等相關服務,並在需要的時候再呼叫被代理的物件,如此被代理物件當中就可以僅保留商務相關職責。<br />
<br />
舉個實際的例子來說,首先定義一個 <code>IHello</code> 介面:<br />
<em>IHello.java</em><br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
public interface IHello {
public void hello(String name);
}
</pre><br />
<br />
然後讓實現商務邏輯的 <code>HelloSpeaker</code> 類別要實現 <code>IHello</code> 介面,例如:<br />
<em>HelloSpeaker.java</em><br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
</pre><br />
<br />
可以看到,在 <code>HelloSpeaker</code> 類別中現在沒有任何日誌的程式碼插入其中,日誌服務的實現將被放至代理物件之中,代理物件同樣也要實現 <code>IHello</code> 介面,例如:<br />
<em>HelloProxy.java</em><br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
import java.util.logging.*;
public class HelloProxy implements IHello {
private Logger logger =
Logger.getLogger(this.getClass().getName());
private IHello helloObject;
public HelloProxy(IHello helloObject) {
this.helloObject = helloObject;
}
public void hello(String name) {
// 日誌服務
log("hello method starts....");
// 執行商務邏輯
helloObject.hello(name);
// 日誌服務
log("hello method ends....");
}
private void log(String msg) {
logger.log(Level.INFO, msg);
}
}
</pre><br />
<br />
在 <code>HelloProxy</code> 類別的 <code>hello()</code> 方法中,真正實現商務邏輯前後可以安排記錄服務,可以實際撰寫一個測試程式來看看如何使用代理物件。<br />
<em>ProxyDemo.java</em><br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
public class ProxyDemo {
public static void main(String[] args) {
IHello proxy =
new HelloProxy(new HelloSpeaker());
proxy.hello("Justin");
}
}
</pre><br />
<br />
程式中呼叫執行的是代理物件,建構代理物件時必須給它一個被代理物件,記得在操作取回的代理物件時,必須轉換操作介面為 IHello 介面。<br />
<br />
代理物件 <code>HelloProxy</code> 將代理真正的 <code>HelloSpeaker</code> 來執行 <code>hello()</code>,並在其前後加上日誌的動作,這使得我們的 <code>HelloSpeaker</code> 在撰寫時不必介入日誌動作,<code>HelloSpeaker</code> 可以專心於它的職責。<br />
<br />
在 JDK 1.3 之後加入了可協助開發動態代理功能的 API 等相關類別,您不必為特定物件與方法撰寫特定的代理物件,使用動態代理,可以使得一個處理者 (Handler)服務於各個物件,首先,一個處理者的類別設計必須實作 <code>java.lang.reflect.InvocationHandler</code> 介面, 以實例來進行說明,例如設計一個 <code>LogHandler</code> 類別:<br />
<em>LogHandler.java</em><br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
import java.util.logging.*;
import java.lang.reflect.*;
public class LogHandler implements InvocationHandler {
private Logger logger =
Logger.getLogger(this.getClass().getName());
private Object delegate;
public Object bind(Object delegate) {
this.delegate = delegate;
return Proxy.newProxyInstance(
delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(),
this
);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
Object result = null;
try {
log("method starts..." + method);
result = method.invoke(delegate, args);
logger.log(Level.INFO, "method ends..." + method);
} catch (Exception e){
log(e.toString());
}
return result;
}
private void log(String message) {
logger.log(Level.INFO, message);
}
}
</pre><br />
<br />
主要的概念是使用 <code>Proxy.newProxyInstance()</code> 靜態方法建立一個代理物件(底層會使用 Native 的方式生成代理物件的 Class 實例),建立代理物件時必須告知所要代理的介面,之後您可以操作所 建立的代理物件,在每次操作時會呼叫 <code>InvocationHandler</code> 的 <code>invoke()</code> 方法,<code>invoke()</code> 方法會傳入被代理物件的方法名稱與執行 參數,實際上要執行的方法交由 <code>method.invoke()</code>,您在 <code>method.invoke()</code> 前後加上記錄動作,<code>method.invoke()</code> 傳 回的物件是實際方法執行過後的回傳結果。<br />
<br />
接下來撰寫一個測試的程式,您要使用 <code>LogHandler</code> 的 <code>bind()</code> 方法來綁定被代理物件,如下所示:<br />
<em>ProxyDemo.java</em><br />
<pre class="java:nogutter:nocontrols" name="code">package cc.openhome;
public class ProxyDemo {
public static void main(String[] args) {
LogHandler logHandler = new LogHandler();
IHello helloProxy =
(IHello) logHandler.bind(new HelloSpeaker());
helloProxy.hello("Justin");
}
}
</pre><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-87873507994584505492015-02-26T15:44:00.000+08:002015-02-26T15:44:09.024+08:00[轉載] Java 型態通配字元轉載自:<a href="http://openhome.cc/Gossip/JavaEssence/WildCard.html" target="_blank">Java Essence: 我只收這種東西</a><br />
<br />
如果你定義了以下的類別:<br />
<pre class="java:nogutter:nocontrols" name="code">class Node<T> {
T value;
Node<T> next;
Node(T value, Node<T> next) {
this.value = value;
this.next = next;
}
}
</pre><br />
<br />
如果在以下的例子中:<br />
<pre class="java:nogutter:nocontrols" name="code">class Fruit {}
class Apple extends Fruit {
@Override
public String toString() {
return "Apple";
}
}
class Banana extends Fruit {
@Override
public String toString() {
return "Banana";
}
}
public class Main {
public static void main(String[] args) {
Node<Apple> apple = new Node<Apple>(new Apple(), null);
Node<Fruit> fruit = apple; // 編譯錯誤,incompatible types
}
}
</pre><br />
<br />
<br />
在範例中,apple 的型態是 <code>Node<Apple></code>,而 fruit 的型態為 <code>Node<Fruit></code>,你將 apple 所參考的物件 給 fruit 參考,那麼 <code>Node<Apple></code> 該是一種 <code>Node<Fruit></code> 呢?在上例中編譯器給你的答案為「不是」!<br />
<br />
如 果 B 是 A 的子型態,而 <code>Node<B></code> 被視為一種 Node<A> 型態,則稱 Node 具有<b>共變性(Covariance)</b>或有<b>彈性的(flexible)</b>。如 果 <code>Node<A></code> 被視為一種 <code>Node<B></code> 型態,則稱 Node 具有<b>逆變性(Contravariance)</b>。如果不具共變性或逆變性,則 Node 是<b>不可變 的(nonvariant)</b>或<b>嚴謹的(rigid)</b>。<br />
<br />
Java 的泛型不支援<b>共變性</b>,不過可以使用型態通配字元 <b>?</b> 與 <b>extends</b> 來宣告變數,使其達到類似共變性,例如:<br />
<pre class="java:nogutter:nocontrols" name="code">public class Main {
public static void main(String[] args) {
Node<Apple> apple = new Node<Apple>(new Apple(), null);
Node<? extends Fruit> fruit = apple; // 類似共變性效果
}
}
</pre><br />
<br />
一個實際應用的例子是:<br />
<pre class="java:nogutter:nocontrols" name="code">public class Main {
public static void main(String[] args) {
Node<Apple> apple1 = new Node<Apple>(new Apple(), null);
Node<Apple> apple2 = new Node<Apple>(new Apple(), apple1);
Node<Apple> apple3 = new Node<Apple>(new Apple(), apple2);
Node<Banana> banana1 = new Node<Banana>(new Banana(), null);
Node<Banana> banana2 = new Node<Banana>(new Banana(), banana1);
show(apple3);
show(banana2);
}
static void show(Node<? extends Fruit> n) {
Node<? extends Fruit> node = n;
do {
System.out.println(node.value);
node = node.next;
} while(node != null);
}
}
</pre><br />
<br />
<br />
你的目的是可以顯示所有的水果節點,由於 <code>show()</code> 方法使用型態通配字元宣告參數,使得 n 具備類似共變性的效果,因此 <code>show()</code> 方法就可以顯示 <code>Node<Apple></code> 也可以顯示 <code>Node<Banana></code>。<br />
<br />
Java 的泛型亦不支援<b>逆變性</b>,不過可以使用型態通配字元 <b>?</b> 與 <b>super</b> 來宣告變數,使其達到類似逆變性,例如:<br />
<pre class="java:nogutter:nocontrols" name="code">public class Main {
public static void main(String[] args) {
Node<Fruit> fruit = new Node<Fruit>(new Fruit(), null);
Node<? super Apple> apple = fruit;
Node<? super Banana> banana = fruit;
}
}
</pre><br />
<br />
一個實際應用的例子如下:<br />
<pre class="java:nogutter:nocontrols" name="code">class Fruit {
int price;
int weight;
Fruit(int price, int weight) {
this.price = price;
this.weight = weight;
}
}
class Apple extends Fruit {
Apple(int price, int weight) {
super(price, weight);
}
}
class Banana extends Fruit {
Banana(int price, int weight) {
super(price, weight);
}
}
interface Comparator<T> {
int compare(T t1, T t2);
}
class Basket<T> {
private T[] things;
Basket(T... things) {
this.things = things;
}
void sort(Comparator<? super T> comparator) {
// 作一些排序
}
}
</pre><br />
<br />
籃子(Basket)中可以置放各種物品,並可以傳入一個比較器(Comparator)進行排序。假設你分別在兩個籃子中放置了蘋果(Apple)與香蕉(Banana):<br />
<pre class="java:nogutter:nocontrols" name="code">public class Main {
public static void main(String[] args) {
Comparator<Fruit> comparator = new Comparator<Fruit>() {
public int compare(Fruit f1, Fruit f2) {
return f1.price - f2.price;
}
};
Basket<Apple> b1 = new Basket<Apple>(
new Apple(20, 100), new Apple(25, 150)
);
Basket<Banana> b2 = new Basket<Banana>(
new Banana(30, 200), new Banana(25, 250)
);
b1.sort(comparator);
b2.sort(comparator);
}
}
</pre><br />
<br />
現在 b1 的型態為 <code>Basket<Apple></code>,而 b2 的型態為 <code>Basket<Banana></code>,你可以如上實作一個水果(Fruit)比較器,比較水果的價格進行排序,這可以同時適用於 <code>Basket<Apple></code> 與 <code>Basket<Banana></code>。<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-77978543049829428202015-02-23T00:00:00.000+08:002015-03-03T11:54:21.139+08:00[轉載] Java 字符串格式化:String.format()方法的使用轉載自:<a href="http://kgd1120.iteye.com/blog/1293633" target="_blank">java字符串格式化:String.format()方法的使用 - kgd1120 - ITeye技术网站</a><br />
<br />
<h2>常規類型的格式化</h2><br />
String 類的 format() 方法用於創建格式化的字符串以及連接多個字符串對像。熟悉 C 語言的讀者應該記得 C 語言的 sprintf() 方法,兩者有類似之處。format() 方法有兩種重載形式。<br />
<br />
<code>format(String format, Object... args)</code><br />
該方法使用指定的字符串格式和參數生成格式化的新字符串。 新字符串始終使用本地語言環境。例如當前日期信息在中國語言環境中的表現形式為 “2007-10-27”,但是在其他國家有不同的表現形式。<br />
<ul><li><code>format</code>:字符串格式。</li>
<li><code>args...</code>:字符串格式中由格式說明符引用的參數。如果還有格式說明符以外的參數,則忽略這些額外的參數。參數的數目是可變的,可以為 0。</li>
</ul><br />
<code>format(Locale locale, String format, Object... args)</code><br />
該方法使用指定的語言環境、字符串格式和參數生成一個格式化的新字符串。新字符串始終使用指定的語言環境。<br />
<ul><li><code>locale</code>:指定的語言環境。</li>
<li><code>format</code>:字符串格式。</li>
<li><code>args...</code>:字符串格式中由格式說明符引用的參數。如果還有格式說明符以外的參數,則忽略這些額外的參數。參數的數目是可變的,可以為 0。</li>
</ul><br />
format() 方法中的字符串格式參數有很多種轉換符選項,例如:日期、整數、浮點數等:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>轉換符</th><th>說明</th><th>示例</th></tr>
<tr><td><b>%s</b></td><td>字符串類型</td><td>"mingrisoft"</td></tr>
<tr><td><b>%c</b></td><td>字符類型</td><td>'m'</td></tr>
<tr><td><b>%b</b></td><td>布爾類型</td><td>true</td></tr>
<tr><td><b>%d</b></td><td>整數類型(十進制)</td><td>99</td></tr>
<tr><td><b>%x</b></td><td>整數類型(十六進制)</td><td>FF</td></tr>
<tr><td><b>%o</b></td><td>整數類型(八進制)</td><td>77</td></tr>
<tr><td><b>%f</b></td><td>浮點類型</td><td>99.99</td></tr>
<tr><td><b>%a</b></td><td>十六進制浮點類型</td><td>FF.35AE</td></tr>
<tr><td><b>%e</b></td><td>指數類型</td><td>9.38e+5</td></tr>
<tr><td><b>%g</b></td><td>通用浮點類型(f和e類型中較短的)</td><td></td></tr>
<tr><td><b>%h</b></td><td>散列碼</td><td></td></tr>
<tr><td><b>%%</b></td><td>百分比類型</td><td>%</td></tr>
<tr><td><b>%n</b></td><td>換行符</td><td></td></tr>
<tr><td><b>%tx</b></td><td>日期與時間類型(x代表不同的日期與時間轉換符)</td><td></td></tr>
</table><br />
<br />
使用各種轉換符實現不同數據類型到字符串的轉換:<br />
<pre class="java:nocontrols" name="code">public static void main(String[] args) {
String str=null;
// 格式化字符串
str=String.format("Hi,%s", "飛龍");
// 輸出字符串變量str的內容
System.out.println(str);
System.out.printf("字母a的大寫是:%c %n", 'A');
System.out.printf("3>7的結果是:%b %n", 3>7);
System.out.printf("100的一半是:%d %n", 100/2);
System.out.printf("100的16進制數是:%x %n", 100);
System.out.printf("100的8進制數是:%o %n", 100);
System.out.printf("50元的書打8.5折扣是:%f 元%n", 50*0.85);
System.out.printf("上面價格的16進制數是:%a %n", 50*0.85);
System.out.printf("上面價格的指數表示:%e %n", 50*0.85);
System.out.printf(
"上面價格的指數和浮點數結果的長度較短的是:%g %n", 50*0.85
);
System.out.printf("上面的折扣是%d%% %n", 85);
System.out.printf("字母A的散列碼是:%h %n", 'A');
}
</pre><br />
輸出以下結果:<br />
<pre class="none:nocontrols" name="code">Hi,飛龍
字母a的大寫是:A
3>7的結果是:false
100的一半是:50
100的16進制數是:64
100的8進制數是:144
50元的書打8.5折扣是:42.500000 元
上面價格的16進制數是:0x1.54p5
上面價格的指數表示:4.250000e+01
上面價格的指數和浮點數結果的長度較短的是:42.5000
上面的折扣是85%
字母A的散列碼是:41
</pre><br />
<br />
這些字符串格式參數不但可以靈活將其他數據類型轉換成字符串,而且可以與各種標誌組合在一起,生成各種格式的字符串:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>標誌</th><th>說明</th><th>示例</th><th>結果</th></tr>
<tr><td><b>+</b></td><td>為正數或者負數添加符號</td><td>("%+d",15)</td><td>+15</td></tr>
<tr><td><b>−</b></td><td>左對齊</td><td>("%-5d",15)</td><td>|15 |</td></tr>
<tr><td><b>0</b></td><td>數字前面補0</td><td>("%04d", 99)</td><td>0099</td></tr>
<tr><td><b>空格</b></td><td>在整數之前添加指定數量的空格</td><td>("% 4d", 99)</td><td>| 99|</td></tr>
<tr><td><b>,</b></td><td>以“,”對數字分組</td><td>("%,f", 9999.99)</td><td>9,999.990000</td></tr>
<tr><td><b>(</b></td><td>使用括號包含負數</td><td>("%(f", -99.99)</td><td>(99.990000)</td></tr>
<tr><td><b>#</b></td><td>如果是浮點數則包含小數點,<br />
如果是16進制或8進制則添加0x或0</td><td>("%#x", 99)<br />
("%#o", 99)</td><td>0x63<br />
0143</td></tr>
<tr><td><b><</b></td><td>格式化前一個轉換符所描述的參數</td><td>("%f 和 %<3.2f", 99.45)</td><td>99.450000 和 99.45</td></tr>
<tr><td><b>$</b></td><td>被格式化的參數索引</td><td>("%1$d,%2$s", 99,"abc")</td><td>99,abc</td></tr>
</table><br />
<br />
使用幾種常用的轉換符組合標誌實現字符串的格式化:<br />
<pre class="java:nocontrols" name="code">public static void main(String[] args) {
String str=null;
// 格式化字符串
str=String.format("格式參數$的使用:%1$d,%2$s", 99,"abc");
// 輸出字符串變量
System.out.println(str);
System.out.printf("顯示正負數的符號:%+d與%d%n", 99,-99);
System.out.printf("最牛的編號是:%03d%n", 7);
System.out.printf("Tab鍵的效果是:% 8d%n", 7);
System.out.printf("整數分組的效果是:%,d%n", 9989997);
System.out.printf("一本書的價格是:%2.2f元%n", 49.8);
}
</pre><br />
輸出以下結果:<br />
<pre class="none:nocontrols" name="code">格式參數$的使用:99,abc
顯示正負數的符號:+99與-99
最牛的編號是:007
Tab鍵的效果是: 7
整數分組的效果是:9,989,997
一本書的價格是:49.80元
</pre><br />
<br />
<br />
<hr/><h2>日期和時間字符串格式化</h2><br />
在程序界面中經常需要顯示時間和日期,但是其顯示的 格式經常不盡人意,需要編寫大量的代碼經過各種算法才得到理想的日期與時間格式。字符串格式中還有%tx轉換符沒有詳細介紹,它是專門用來格式化日期和時 間的。%tx轉換符中的x代表另外的處理日期和時間格式的轉換符,它們的組合能夠將日期和時間格式化成多種格式。<br />
<br />
<br />
<strong>常見日期時間格式化</strong><br />
<br />
格式化日期與時間的轉換符定義了各種格式化日期字符串的方式,其中最常用的日期和時間的組合格式如下:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>轉換符</th><th>說明</th><th>示例</th></tr>
<tr><td><b>c</b></td><td>包括全部日期和時間信息</td><td>星期六 十月 27 14:21:20 CST 2007</td></tr>
<tr><td><b>F</b></td><td>“年-月-日”格式</td><td>2007-10-27</td></tr>
<tr><td><b>D</b></td><td>“月/日/年”格式</td><td>10/27/07</td></tr>
<tr><td><b>r</b></td><td>“HH:MM:SS PM”格式(12時制)</td><td>02:25:51 下午</td></tr>
<tr><td><b>T</b></td><td>“HH:MM:SS”格式(24時制)</td><td>14:28:16</td></tr>
<tr><td><b>R</b></td><td>“HH:MM”格式(24時制)</td><td>14:28</td></tr>
</table><br />
<br />
使用轉換符格式化當前日期和時間:<br />
<pre class="java:nocontrols" name="code">public static void main(String[] args) {
// 創建日期對像
Date date=new Date();
// 格式化輸出日期或時間
System.out.printf("全部日期和時間信息:%tc%n",date);
System.out.printf("年-月-日格式:%tF%n",date);
System.out.printf("月/日/年格式:%tD%n",date);
System.out.printf("HH:MM:SS PM格式(12時制):%tr%n",date);
System.out.printf("HH:MM:SS格式(24時制):%tT%n",date);
System.out.printf("HH:MM格式(24時制):%tR",date);
System.out.printf("年-月-日 HH:MM:SS格式:%1$tF %1$tT%n",date);
}
</pre><br />
輸出以下結果:<br />
<pre class="none:nocontrols" name="code">全部日期和時間信息:星期日十月28 13:53:24 CST 2007
年-月-日格式:2007-10-28
月/日/年格式:10/28/07
HH:MM:SS PM格式(12時制):01:53:24 下午
HH:MM:SS格式(24時制):13:53:24
HH:MM格式(24時制):13:53
年-月-日 HH:MM:SS格式:2007-10-28 13:53:24
</pre><br />
<br />
<strong>格式化日期字符串</strong><br />
<br />
定義日期格式的轉換符可以使日期通過指定的轉換符生成新字符串:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>轉換符</th><th>說明</th><th>示例</th></tr>
<tr><td><b>b</b> 或 <b>h</b></td><td>月份簡稱</td><td>中:十月<br />
英:Oct</td></tr>
<tr><td><b>B</b></td><td>月份全稱</td><td>中:十月<br />
英:October</td></tr>
<tr><td><b>a</b></td><td>星期的簡稱</td><td>中:星期六<br />
英:Sat</td></tr>
<tr><td><b>A</b></td><td>星期的全稱</td><td>中:星期六<br />
英:Saturday</td></tr>
<tr><td><b>C</b></td><td>年的前兩位數字(前補0至2位)</td><td>20</td></tr>
<tr><td><b>y</b></td><td>年的後兩位數字(前補0至2位)</td><td>07</td></tr>
<tr><td><b>Y</b></td><td>4位數字的年份(前補0至4位)</td><td>2007</td></tr>
<tr><td><b>j</b></td><td>一年中的天數(即年的第幾天)</td><td>300</td></tr>
<tr><td><b>m</b></td><td>兩位數字的月份(前補0至2位)</td><td>10</td></tr>
<tr><td><b>d</b></td><td>兩位數字的日(前補0至2位)</td><td>27</td></tr>
<tr><td><b>e</b></td><td>月份的日</td><td>5</td></tr>
</table><br />
<br />
使用各種轉換符格式化當前系統的日期:<br />
<pre class="java:nocontrols" name="code">public static void main(String[] args) {
// 創建日期對像
Date date=new Date();
// 格式化日期字符串
String str=String.format(Locale.US,"英文月份簡稱:%tb",date);
// 輸出字符串內容
System.out.println(str);
System.out.printf("本地月份簡稱:%tb%n",date);
str=String.format(Locale.US,"英文月份全稱:%tB",date);
System.out.println(str);
System.out.printf("本地月份全稱:%tB%n",date);
str=String.format(Locale.US,"英文星期的簡稱:%ta",date);
System.out.println(str);
System.out.printf("本地星期的簡稱:%tA%n",date);
System.out.printf("年的前兩位數字(前補0至2位):%tC%n",date);
System.out.printf("年的後兩位數字(前補0至2位):%ty%n",date);
System.out.printf("一年中的天數(即年的第幾天):%tj%n",date);
System.out.printf("兩位數字的月份(前補0至2位):%tm%n",date);
System.out.printf("兩位數字的日(前補0至2位):%td%n",date);
System.out.printf("月份的日:%te",date);
}
</pre><br />
輸出以下結果:<br />
<pre class="none:nocontrols" name="code">英文月份簡稱:Oct
本地月份簡稱:十月
英文月份全稱:October
本地月份全稱:十月
英文星期的簡稱:Sun
本地星期的簡稱:星期日
年的前兩位數字(前補0至2位):20
年的後兩位數字(前補0至2位):07
一年中的天數(即年的第幾天):301
兩位數字的月份(前補0至2位):10
兩位數字的日(前補0至2位):28
月份的日:28
</pre><br />
<br />
<strong>格式化時間字符串</strong><br />
<br />
和日期格式轉換符相比,時間格式的轉換符要更多、更精確。它可以將時間格式化成時、分、秒甚至時毫秒等單位:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>轉換符</th><th>說明</th><th>示例</th></tr>
<tr><td><b>H</b></td><td>2位數字24時制的小時(前補0至2位)</td><td>15</td></tr>
<tr><td><b>I</b></td><td>2位數字12時制的小時(前補0至2位)</td><td>03</td></tr>
<tr><td><b>k</b></td><td>2位數字24時制的小時</td><td>15</td></tr>
<tr><td><b>l</b></td><td>2位數字12時制的小時</td><td>3</td></tr>
<tr><td><b>M</b></td><td>2位數字的分鐘(前補0至2位)</td><td>03</td></tr>
<tr><td><b>S</b></td><td>2位數字的秒(前補0至2位)</td><td>09</td></tr>
<tr><td><b>L</b></td><td>3位數字的毫秒(前補0至3位)</td><td>015</td></tr>
<tr><td><b>N</b></td><td>9位數字的毫秒數(前補0至9位)</td><td>562000000</td></tr>
<tr><td><b>p</b></td><td>小寫字母的上午或下午標記</td><td>中:下午<br />
英:pm</td></tr>
<tr><td><b>z</b></td><td>相對於GMT的RFC822時區的偏移量</td><td>+0800</td></tr>
<tr><td><b>Z</b></td><td>時區縮寫字符串</td><td>CST</td></tr>
<tr><td><b>s</b></td><td>1970-1-1 00:00:00 到現在所經過的秒數</td><td>1193468128</td></tr>
<tr><td><b>Q</b></td><td>1970-1-1 00:00:00 到現在所經過的毫秒數</td><td>1193468128984</td></tr>
</table><br />
<br />
使用各種轉換符格式化當前系統的時間:<br />
<pre class="java:nocontrols" name="code">public static void main(String[] args) {
// 創建日期對像
Date date=new Date();
System.out.printf("2位數字24時制的小時(前補0至2位):%tH%n",date);
System.out.printf("2位數字12時制的小時(前補0至2位):%tI%n",date);
System.out.printf("2位數字24時制的小時:%tk%n",date);
System.out.printf("2位數字12時制的小時:%tl%n",date);
System.out.printf("2位數字的分鐘(前補0至2位):%tM%n",date);
System.out.printf("2位數字的秒(前補0至2位):%tS%n",date);
System.out.printf("3位數字的毫秒(前補0至3位):%tL%n",date);
System.out.printf("9位數字的毫秒數(前補0至9位):%tN%n",date);
String str=String.format(
Locale.US,"小寫字母的上午或下午標記(英):%tp",date
);
// 輸出字符串變量str的內容
System.out.println(str);
System.out.printf ("小寫字母的上午或下午標記(中):%tp%n",date);
System.out.printf("相對於GMT的RFC822時區的偏移量:%tz%n",date);
System.out.printf("時區縮寫字符串:%tZ%n",date);
System.out.printf("1970-1-1 00:00:00 到現在所經過的秒數:%ts%n",date);
System.out.printf("1970-1-1 00:00:00 到現在所經過的毫秒數:%tQ%n",date);
}
</pre><br />
輸出以下結果:<br />
<pre class="none:nocontrols" name="code">2位數字24時制的小時(前補0至2位):15
2位數字12時制的小時(前補0至2位):03
2位數字24時制的小時:15
2位數字12時制的小時:3
2位數字的分鐘(前補0至2位):24
2位數字的秒(前補0至2位):56
3位數字的毫秒(前補0至3位):828
9位數字的毫秒數(前補0至9位):828000000
小寫字母的上午或下午標記(英):pm
小寫字母的上午或下午標記(中):下午
相對於GMT的RFC822時區的偏移量:+0800
時區縮寫字符串:CST
1970-1-1 00:00:00到現在所經過的秒數:1193556296
1970-1-1 00:00:00到現在所經過的毫秒數:1193556296828
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com1tag:blogger.com,1999:blog-5946530704742130970.post-10906470392954903882015-02-22T21:55:00.000+08:002015-02-24T15:46:01.902+08:00[轉載] Java Gossip: wait()、notify()轉載自:<a href="http://openhome.cc/Gossip/JavaGossip-V2/WaitNotify.htm" target="_blank">Java Gossip: wait()、notify()</a><br />
<br />
<b><code>wait()</code>、<code>notify()</code> 與 <code>notifyAll()</code> 是由 Object 所提供的方法</b>,您在定義自己的類別時會繼承下來(記得 Java 中所有的物件最頂層都繼承自 Object),<code>wait()</code>、<code>notify()</code> 與 <code> notifyAll()</code> 都被宣告為 "<code>final</code>",所以您無法重新定義它們,透過這三個方法您可以控制執行緒是否為 Runnable 狀態。<br />
<br />
<b>您必須在同步化的方法或區塊中呼叫 <code>wait()</code> 方法</b>,當物件的 <code>wait()</code> 方法被調用,目前的執行緒會被放入物件的<b>「等待集合」(Wait set)</b>中, 執行緒會釋放物件的鎖定,其它的執行緒可以競爭鎖定,取得鎖定的執行緒可以執行同步化區塊;被放在等待集中的執行緒將不參與執行緒的排班,<code>wait()</code> 可 以指定等待的時間,如果指定時間的話,則時間到之後執行緒會再度加入排班,如果指定時間 0 或不指定,則執行緒會持續等待,直到有被<b>中斷(interrupt)</b>或是被<b>告知(notify)</b>可以參與排班。<br />
<br />
當物件的 <code>notify()</code> 被調用,它會從物件的等待集中選出「一個」執行緒加入排班,被選出的執行緒是隨機的,被選出的執行緒會與其它正在執行的執行緒共 同競爭對物件的鎖定;如果您呼叫 <code>notifyAll()</code>,則「所有」在等待集中的執行緒都會被喚醒,這些執行緒會與其它正在執行的執行緒共同競爭對物件的 鎖定。<br />
<br />
簡單的說,當執行緒呼叫到物件的 <code>wait()</code> 方法時,表示它要先讓出物件的被同步區使用權並等待通知,或是等待一段指定的時間,直到被通知或時間到時再從 等待點開始執行,這就好比您要叫某人作事,作到一半時某人叫您等候通知(或等候1分鐘之類的),當您被通知(或時間到時)某人會繼續為您服務。<br />
<br />
說明 <code>wait()</code>、<code>notify()</code> 或 <code>notifyAll()</code> 的應用最常見的一個例子,就是<b>生產者(Producer)</b>與<b>消費者(Consumer)</b>的 例子,如果生產者會將產品交給店員,而消費者從店員處取走產品,店員一次只能持有固定數量產品,如果生產者生產了過多的產品,店員叫生產者等一下 (wait),如果店中有空位放產品了再通知(notify)生產者繼續生產,如果店中沒有產品了,店員會告訴消費者等一下(wait),如果店中有產品 了再通知(notify)消費者來取走產品。<br />
<br />
<br />
以下舉一個最簡單的:生產者每次生產一個 <code>int</code> 整數交給在店員上,而消費者從店員處取走整數,店員一次只能持有一個整數。<br />
<br />
<br />
以程式實例來看,首先是生產者:<br />
<b>Producer.java</b><br />
<pre class="java:nocontrols" name="code">package onlyfun.caterpillar;
public class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("生產者開始生產整數......");
// 生產1到10的整數
for(int product = 1; product <= 10; product++) {
try {
// 暫停隨機時間
Thread.sleep((int) (Math.random() * 3000));
}
catch(InterruptedException e) {
e.printStackTrace();
}
// 將產品交給店員
clerk.setProduct(product);
}
}
}
</pre><br />
<br />
再來是消費者:<br />
<b>Consumer.java</b><br />
<pre class="java:nocontrols" name="code">package onlyfun.caterpillar;
public class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("消費者開始消耗整數......");
// 消耗10個整數
for(int i = 1; i <= 10; i++) {
try {
// 等待隨機時間
Thread.sleep((int) (Math.random() * 3000));
}
catch(InterruptedException e) {
e.printStackTrace();
}
// 從店員處取走整數
clerk.getProduct();
}
}
}
</pre><br />
<br />
生產者將產品放至店員,而消費者從店員處取走產品,所以店員來決定誰必須等待並等候通知。<br />
<b>Clerk.java</b><br />
<pre class="java:nocontrols" name="code">package onlyfun.caterpillar;
public class Clerk {
// -1 表示目前沒有產品
private int product = -1;
// 這個方法由生產者呼叫
public synchronized void setProduct(int product) {
while(this.product != -1) {
try {
// 目前店員沒有空間收產品,請稍候!
wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
this.product = product;
System.out.printf("生產者設定 (%d)%n", this.product);
// 通知等待區中的一個消費者可以繼續工作了
notify();
}
// 這個方法由消費者呼叫
public synchronized int getProduct() {
while(this.product == -1) {
try {
// 缺貨了,請稍候!
wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
int p = this.product;
System.out.printf(
"消費者取走 (%d)%n", this.product);
this.product = -1;
// 通知等待區中的一個生產者可以繼續工作了
notify();
return p;
}
}
</pre><br />
<br />
根據 <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Object.html#wait%28long%29" target="_blank">規格書中所說明</a> ,執行緒也有可能在未經 <code>notify()</code>、<code>interrupt()</code> 或逾時的情況下自動甦醒(spurious wakeup),雖然這種情況實務上很少發生,但應用程式應考量這種情況,你必須持續檢測這種情況,因而 <code>wait()</code> 必須總是在迴圈中執行,例如:<br />
<pre class="java:nogutter:nocontrols" name="code">synchronized (obj) {
while (執行條件不成立時)
obj.wait(timeout);
... // 執行一些動作進行判斷
}
</pre><br />
<br />
使用這麼一個程式來測試:<br />
<b>WaitNotifyDemo.java</b><br />
<pre class="java:nocontrols" name="code">package onlyfun.caterpillar;
public class WaitNotifyDemo {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Thread producerThread = new Thread(
new Producer(clerk)
);
Thread consumerThread = new Thread(
new Consumer(clerk)
);
producerThread.start();
consumerThread.start();
}
}
</pre><br />
<br />
執行結果:<br />
<pre class="none:nocontrols" name="code">生產者開始生產整數......
消費者開始消耗整數......
生產者設定 (1)
消費者取走 (1)
生產者設定 (2)
消費者取走 (2)
生產者設定 (3)
消費者取走 (3)
生產者設定 (4)
消費者取走 (4)
生產者設定 (5)
消費者取走 (5)
生產者設定 (6)
消費者取走 (6)
生產者設定 (7)
消費者取走 (7)
生產者設定 (8)
消費者取走 (8)
生產者設定 (9)
消費者取走 (9)
生產者設定 (10)
消費者取走 (10)
</pre><br />
<br />
生產者會生產10個整數,而消費者會消耗10個整數,由於店員處只能放置一個整數,所以每生產一個就消耗一個,其結果如上所示是無誤的。<br />
<br />
如果一個執行緒進入物件的等待集中,您可以中斷它的等待,這時將會發生InterruptedException例外物件,interrupt()方法可用來進行這項工作。Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-61767065967947805202015-02-22T21:32:00.000+08:002015-02-24T15:44:23.643+08:00[轉載] Java 類靜態域,非靜態域,構造函數的初始化順序轉載自:<a href="http://ini.iteye.com/blog/2007835" target="_blank">java类静态域、块,非静态域、块,构造函数的初始化顺序 - 艾妮 - ITeye技术网站</a><br />
<br />
面試的時候,經常會遇到這樣的考題:給你兩個類的程式,它們之間是繼承的關系,每個類裡只有建構子方法和一些變數,建構子裡可能還有一段程式對變數值進行了某種運算,另外還有一些將變數值輸出到控制台的程式,然後讓我們判斷輸出的<br />
結果。這實際上是在考查我們對於繼承情況下類的初始化順序的了解。<br />
<br />
我們大家都知道,對於靜態變數、靜態初始化、變數、初始化、建構子,它們的初始化順序以此是:<br />
<code>(靜態變數、靜態初始化) > (變數、初始化) > 建構子</code><br />
<br />
我們也可以通過下面的測試程式來驗證這一點:<br />
<pre class="java" name="code">public class InitialOrderTest {
// 靜態變數
public static String staticField = "靜態變數";
// 變數
public String field = "變數";
// 靜態初始化
static {
System.out.println(staticField);
System.out.println("靜態初始化");
}
// 初始化
{
System.out.println(field);
System.out.println("初始化");
}
// 建構子
public InitialOrderTest() {
System.out.println("建構子");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}
</pre><br />
運行以上程式,我們會得到如下的輸出結果:<br />
<pre class="none:nocontrols" name="code">靜態變數
靜態初始化
變數
初始化
建構子
</pre><br />
這與上文中說的完全符合。<br />
<br />
<br />
<br />
那麼對於繼承情況下又會怎樣呢?我們仍然以一段測試程式來獲取最終結果:<br />
<pre class="java" name="code">class Parent {
// 靜態變數
public static String p_StaticField = "父--靜態變數";
// 變數
public String p_Field = "父--變數";
// 靜態初始化
static {
System.out.println(p_StaticField);
System.out.println("父--靜態初始化");
}
// 初始化
{
System.out.println(p_Field);
System.out.println("父--初始化");
}
// 建構子
public Parent() {
System.out.println("父--建構子");
}
}
public class SubClass extends Parent {
// 靜態變數
public static String s_StaticField = "子--靜態變數";
// 變數
public String s_Field = "子--變數";
// 靜態初始化
static {
System.out.println(s_StaticField);
System.out.println("子--靜態初始化");
}
// 初始化
{
System.out.println(s_Field);
System.out.println("子--初始化");
}
// 建構子
public SubClass() {
System.out.println("子--建構子");
}
public static void main(String[] args) {
new SubClass();
}
}
</pre><br />
運行一下上面的程式,結果馬上呈現在我們的眼前:<br />
<pre class="none:nocontrols" name="code">父--靜態變數
父--靜態初始化
子--靜態變數
子--靜態初始化
父--變數
父--初始化
父--建構子
子--變數
子--初始化
子--建構子
</pre><br />
現在,結果已經不言自明了。大家可能會注意到一點,那就是,並不是父類完全初始化完畢後才進行子類的初始化,實際上子類的靜態變數和靜態初始化的初始化是在父類的變數、初始化和建構子初始化之前就完成了。那麼對於靜態變數和靜態初始化之間、變數和初始化之間的先後順序又是怎樣呢?是否靜態變數總是先於靜態初始化,變數總是先於初始化就被初始化了呢?實際上這取決於它們在類中出現的先後順序。<br />
<br />
<br />
<br />
我們以靜態變數和靜態初始化為例來進行說明。 同樣,我們還是寫一個類來進行測試:<br />
<pre class="java" name="code">public class TestOrder {
// 靜態變數
public static TestA a = new TestA();
// 靜態初始化
static {
System.out.println("靜態初始化");
}
// 靜態變數
public static TestB b = new TestB();
public static void main(String[] args) {
new TestOrder();
}
}
class TestA {
public TestA() {
System.out.println("TestA");
}
}
class TestB {
public TestB() {
System.out.println("TestB");
}
}
</pre><br />
運行上面的程式,會得到如下的結果:<br />
<pre class="none:nocontrols" name="code">TestA
靜態初始化
TestB
</pre><br />
大家可以隨意改變變數 a、變數 b 以及靜態初始化的前後位置,就會發現輸出結果隨著它們在類中出現的前後順序而改變,這就說明靜態變數和靜態初始化是依照他們在類中的定義順序進行初始化的。同樣,變數和初始化也遵循這個規律。了解了繼承情況下類的初始化順序之後,如何判斷最終輸出結果就迎刃而解了。<br />
<br />
<br />
<br />
測試函數:<br />
<pre class="java" name="code">public class TestStaticCon {
public static int a = 0;
static {
a = 10;
System.out.println("靜態初始化 a = " + a);
}
{
a = 8;
System.out.println("初始化 a = " + a);
}
public TestStaticCon() {
this(a);
System.out.println("無參數建構子 a = " + a);
}
public TestStaticCon(int n) {
System.out.println("參數建構子 n = " + n);
System.out.println("參數建構子 a = " + a);
}
public static void main(String[] args) {
new TestStaticCon();
}
}
</pre><br />
運行結果:<br />
<pre class="none:nocontrols" name="code">靜態初始化 a = 10
初始化 a = 8
參數建構子 n = 10
參數建構子 a = 8
無參數建構子 a = 8
</pre><br />
<br />
結論:<br />
靜態初始化是在類加載時自動執行的,非靜態初始化是在創建對像時自動執行的程式,不創建對像不執行該類的非靜態初始化。且執行順序為<br />
<code>靜態初始化 -> 非靜態初始化 -> 建構子</code><br />
<br />
<br />
擴展( 靜態初始化 與 靜態方法 ):<br />
一般情況下,如果有些程式必須在項目啟動的時候就執行的時候,需要使用靜態初始化,這種程式是主動執行的。<br />
需要在項目啟動的時候就初始化,在不創建對像的情況下,其他程序來調用的時候,需要使用靜態方法,這種程式是被動執行的。<br />
兩者的區別就是:靜態初始化是自動執行的。靜態方法是被調用的時候才執行的。<br />
<br />
<br />
作用:<br />
靜態初始化可用來初始化一些項目最常用的變數或類別靜態方法可用作不創建物件也可能需要執行的程式。<br />
<br />
<br />
<br />
阿裡筆試題:<br />
<pre class="java" name="code">public class Test1 {
public static int k = 0;
public static Test1 t1 = new Test1("obj t1");
public static Test1 t2 = new Test1("obj t2");
public static int i = print("var I");
public static int n = 99;
public int j = print("var J");
static{
print("static");
}
{
print("init");
}
public Test1(String str){
print(str);
++i;
++n;
}
public static int print(String str){
k++;
System.out.printf("%-3s: %-8s i=%-4d n=%d", k, str, i, n);
++n;
return ++i;
}
public static void main(String[] args) {
new Test1("main");
}
}
</pre><br />
運行結果:<br />
<pre class="none:nocontrols" name="code">1 : var J i=0 n=0
2 : init i=1 n=1
3 : obj t1 i=2 n=2
4 : var J i=4 n=4
5 : init i=5 n=5
6 : obj t2 i=6 n=6
7 : var I i=8 n=8
8 : static i=9 n=99
9 : var J i=10 n=100
10 : init i=11 n=101
11 : main i=12 n=102
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-5197760974359813492015-02-21T23:37:00.000+08:002015-02-24T15:43:14.686+08:00[轉載] Java中常用的加密方法轉載自:<a href="http://www.iteye.com/topic/1122076" target="_blank">Java中常用的加密方法(JDK) - Java - language - ITeye论坛</a><br />
<br />
加密,是以某種特殊的演算法改變原有的信息數據,使得未授權的用戶即使獲得了已加密的信息,但因不知解密的方法,仍然無法了解信息的內容。大體上分為雙向加密和單向加密,而雙向加密又分為對稱加密和非對稱加密(有些資料將加密直接分為對稱加密和非對稱加密)。<br />
<br />
雙向加密大體意思就是明文加密後形成密文,可以通過演算法還原成明文。而單向加密只是對信息進行了摘要計算,不能通過演算法生成明文,單向加密從嚴格意思上說不能算是加密的一種,應該算是摘要演算法吧。具體區分可以參考:<br />
(本人解釋不清呢 …… )<br />
<a href="http://security.group.iteye.com/group/wiki/1710-one-way-encryption-algorithm" target="_blank">http://security.group.iteye.com/group/wiki/1710-one-way-encryption-algorithm</a><br />
<br />
<br />
<h1>一、雙向加密</h1><br />
<h2>(一)、對稱加密</h2><br />
采用單鑰密碼系統的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。<br />
需要對加密和解密使用相同密鑰的加密演算法。由於其速度,對稱性加密通常在消息發送方需要加密大量數據時使用。對稱性加密也稱為密鑰加密。<br />
所謂對稱,就是采用這種加密方法的雙方使用方式用同樣的密鑰進行加密和解密。密鑰是控制加密及解密過程的指令。<br />
<br />
演算法是一組規則,規定如何進行加密和解密。因此對稱式加密本身不是安全的。 <br />
常用的對稱加密有:DES、IDEA、RC2、RC4、SKIPJACK、RC5、AES 演算法等<br />
<br />
對稱加密一般 Java 類中中定義成員<br />
<pre class="java:nogutter:nocontrols" name="code">// KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
private KeyGenerator keygen;
// SecretKey 負責保存對稱密鑰
private SecretKey deskey;
// Cipher負責完成加密或解密工作
private Cipher c;
// 該字節數組負責保存加密的結果
private byte[] cipherByte;
</pre><br />
<br />
在構造函數中初始化<br />
<pre class="java:nogutter:nocontrols" name="code">Security.addProvider(new com.sun.crypto.provider.SunJCE());
// 實例化支持 DES 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
keygen = KeyGenerator.getInstance("DES");//
// 生成密鑰
deskey = keygen.generateKey();
// 生成 Cipher 物件,指定其支持的 DES 演算法
c = Cipher.getInstance("DES");
</pre><br />
<br />
<strong>1. DES</strong><br />
演算法為密碼體制中的對稱密碼體制,又被成為美國數據加密標准,是 1972 年美國 IBM 公司研制的對稱密碼體制加密演算法。 明文按 64 位進行分組, 密鑰長 64 位,密鑰事實上是 56 位參與 DES 運算(第 8、16、24、32、40、48、56、64 位是校驗位, 使得每個密鑰都有奇數個 1)分組後的明文組和 56 位的密鑰按位替代或交換的方法形成密文組的加密方法。<br />
<br />
<pre class="java" name="code">import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class EncrypDES {
// KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
private KeyGenerator keygen;
// SecretKey 負責保存對稱密鑰
private SecretKey deskey;
// Cipher 負責完成加密或解密工作
private Cipher c;
// 該字節數組負責保存加密的結果
private byte[] cipherByte;
public EncrypDES()
throws NoSuchAlgorithmException, NoSuchPaddingException
{
Security.addProvider(new com.sun.crypto.provider.SunJCE());
// 實例化支持 DES 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
keygen = KeyGenerator.getInstance("DES");
// 生成密鑰
deskey = keygen.generateKey();
// 生成 Cipher 物件,指定其支持的 DES 演算法
c = Cipher.getInstance("DES");
}
/**
* 對字符串加密
*
* @param str
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public byte[] Encrytor(String str)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// 根據密鑰,對 Cipher 物件進行初始化,ENCRYPT_MODE 表示加密模式
c.init(Cipher.ENCRYPT_MODE, deskey);
byte[] src = str.getBytes();
// 加密,結果保存進 cipherByte
cipherByte = c.doFinal(src);
return cipherByte;
}
/**
* 對字符串解密
*
* @param buff
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public byte[] Decryptor(byte[] buff)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// 根據密鑰,對 Cipher 物件進行初始化,DECRYPT_MODE 表示加密模式
c.init(Cipher.DECRYPT_MODE, deskey);
cipherByte = c.doFinal(buff);
return cipherByte;
}
/**
* @param args
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeyException
*/
public static void main(String[] args) throws Exception {
EncrypDES de1 = new EncrypDES();
String msg ="郭XX-搞笑相聲全集";
byte[] encontent = de1.Encrytor(msg);
byte[] decontent = de1.Decryptor(encontent);
System.out.println("明文是:" + msg);
System.out.println("加密後:" + new String(encontent));
System.out.println("解密後:" + new String(decontent));
}
}
</pre><br />
<br />
<strong>2. 3DES</strong><br />
又稱 Triple DES,是 DES 加密演算法的一種模式,它使用 3 條 56 位的密鑰對 3DES<br />
數據進行三次加密。數據加密標准(DES)是美國的一種由來已久的加密標准,它使用對稱密鑰加密法,並於 1981 年被 ANSI 組織規範為 ANSI X.3.92。DES 使用 56 位密鑰和密碼塊的方法,而在密碼塊的方法中,文本被分成 64 位大小的文本塊然後再進行加密。比起最初的 DES,3DES 更為安全。 <br />
3DES(即Triple DES)是 DES 向 AES 過渡的加密演算法(1999年,NIST 將 3-DES 指定為過渡的加密標准),是 DES 的一個更安全的變形。它以 DES 為基本模塊,通過組合分組方法設計出分組加密演算法,其具體實現如下:<br />
設 Ek() 和 Dk() 代表 DES 演算法的加密和解密過程,K 代表 DES 演算法使用的密鑰,P 代表明文,C 代表密文,<br />
<br />
3DES 加密過程為:<code>C=Ek3(Dk2(Ek1(P)))</code><br />
3DES 解密過程為:<code>P=Dk1((EK2(Dk3(C)))</code><br />
<br />
<pre class="java" name="code">import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class EncrypDES3 {
// KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
private KeyGenerator keygen;
// SecretKey 負責保存對稱密鑰
private SecretKey deskey;
// Cipher 負責完成加密或解密工作
private Cipher c;
// 該字節數組負責保存加密的結果
private byte[] cipherByte;
public EncrypDES3()
throws NoSuchAlgorithmException, NoSuchPaddingException
{
Security.addProvider(new com.sun.crypto.provider.SunJCE());
// 實例化支持 DESede 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
keygen = KeyGenerator.getInstance("DESede");
// 生成密鑰
deskey = keygen.generateKey();
// 生成 Cipher 物件,指定其支持的 DESede 演算法
c = Cipher.getInstance("DESede");
}
/**
* 對字符串加密
*
* @param str
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public byte[] Encrytor(String str)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// 根據密鑰,對 Cipher 物件進行初始化,ENCRYPT_MODE 表示加密模式
c.init(Cipher.ENCRYPT_MODE, deskey);
byte[] src = str.getBytes();
// 加密,結果保存進 cipherByte
cipherByte = c.doFinal(src);
return cipherByte;
}
/**
* 對字符串解密
*
* @param buff
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public byte[] Decryptor(byte[] buff)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// 根據密鑰,對 Cipher 物件進行初始化,DECRYPT_MODE 表示加密模式
c.init(Cipher.DECRYPT_MODE, deskey);
cipherByte = c.doFinal(buff);
return cipherByte;
}
/**
* @param args
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeyException
*/
public static void main(String[] args) throws Exception {
EncrypDES3 des = new EncrypDES3();
String msg ="郭XX-搞笑相聲全集";
byte[] encontent = des.Encrytor(msg);
byte[] decontent = des.Decryptor(encontent);
System.out.println("明文是:" + msg);
System.out.println("加密後:" + new String(encontent));
System.out.println("解密後:" + new String(decontent));
}
}
</pre><br />
<br />
<strong>3. AES</strong><br />
密碼學中的高級加密標准(Advanced Encryption Standard,AES),又稱 高級加密標准<br />
Rijndael 加密法,是美國聯邦政府采用的一種區塊加密標准。這個標准用來替代原先的 DES,已經被多方分析且廣為全世界所使用。經過五年的甄選流程,高級加密標准由美國國家標准與技術研究院(NIST)於 2001 年 11 月 26 日發布於 FIPS PUB 197,並在 2002 年 5 月 26 日成為有效的標准。2006 年高級加密標准已然成為對稱密鑰加密中最流行的演算法之一。<br />
該演算法為比利時密碼學家 Joan Daemen 和 Vincent Rijmen 所設計,結合兩位作者的名字,以 Rijndael 之命名之,投稿高級加密標准的甄選流程。(Rijdael 的發音近於 "Rhinedoll")<br />
<br />
<pre class="java" name="code">import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class EncrypAES {
// KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
private KeyGenerator keygen;
// SecretKey 負責保存對稱密鑰
private SecretKey deskey;
// Cipher 負責完成加密或解密工作
private Cipher c;
//該字節數組負責保存加密的結果
private byte[] cipherByte;
public EncrypAES()
throws NoSuchAlgorithmException, NoSuchPaddingException
{
Security.addProvider(new com.sun.crypto.provider.SunJCE());
// 實例化支持 AES 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
keygen = KeyGenerator.getInstance("AES");
//生成密鑰
deskey = keygen.generateKey();
//生成 Cipher 物件,指定其支持的 AES 演算法
c = Cipher.getInstance("AES");
}
/**
* 對字符串加密
*
* @param str
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public byte[] Encrytor(String str)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// 根據密鑰,對 Cipher 物件進行初始化,ENCRYPT_MODE 表示加密模式
c.init(Cipher.ENCRYPT_MODE, deskey);
byte[] src = str.getBytes();
// 加密,結果保存進 cipherByte
cipherByte = c.doFinal(src);
return cipherByte;
}
/**
* 對字符串解密
*
* @param buff
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public byte[] Decryptor(byte[] buff)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// 根據密鑰,對 Cipher 物件進行初始化,DECRYPT_MODE 表示加密模式
c.init(Cipher.DECRYPT_MODE, deskey);
cipherByte = c.doFinal(buff);
return cipherByte;
}
/**
* @param args
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeyException
*/
public static void main(String[] args) throws Exception {
EncrypAES de1 = new EncrypAES();
String msg ="郭XX-搞笑相聲全集";
byte[] encontent = de1.Encrytor(msg);
byte[] decontent = de1.Decryptor(encontent);
System.out.println("明文是:" + msg);
System.out.println("加密後:" + new String(encontent));
System.out.println("解密後:" + new String(decontent));
}
}
</pre><br />
<br />
<h2>(二)、非對稱加密</h2><br />
1976 年,美國學者 Dime 和 Henman 為解決信息公開傳送和密鑰管理問題,提出一種新的密鑰交換協議,允許在不安全的媒體上的通訊雙方交換信息,安全地達成一致的密鑰,這就是“公開密鑰系統”。相對於“對稱加密演算法”這種方法也叫做“非對稱加密演算法”。 與對稱加密演算法不同,非對稱加密演算法需要兩個密鑰:公開密鑰(publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密;如果用私有密鑰對數據進行加密,那麼只有用對應的公開密鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種演算法叫作非對稱加密演算法。<br />
<br />
<strong>1. RSA</strong><br />
公鑰加密演算法是 1977 年由 Ron Rivest、Adi Shamirh 和 LenAdleman 在(美國麻省理工學院)開發的。RSA 取名來自開發他們三者的名字。RSA 是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前為止已知的所有密碼攻擊,已被 ISO 推薦為公鑰數據加密標准。RSA 演算法基於一個十分簡單的數論事實:將兩個大素數相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。<br />
<br />
<pre class="java" name="code">import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class EncrypRSA {
/**
* 加密
* @param publicKey
* @param srcBytes
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
protected byte[] encrypt(RSAPublicKey publicKey,byte[] srcBytes)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
if(publicKey == null){ return null; }
// Cipher 負責完成加密或解密工作,基於 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 根據公鑰,對 Cipher 物件進行初始化
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] resultBytes = cipher.doFinal(srcBytes);
return resultBytes;
}
/**
* 解密
* @param privateKey
* @param srcBytes
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
protected byte[] decrypt(RSAPrivateKey privateKey,byte[] srcBytes)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
if(privateKey == null){ return null; }
// Cipher負責完成加密或解密工作,基於RSA
Cipher cipher = Cipher.getInstance("RSA");
// 根據公鑰,對 Cipher 物件進行初始化
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] resultBytes = cipher.doFinal(srcBytes);
return resultBytes;
}
/**
* @param args
* @throws NoSuchAlgorithmException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
*/
public static void main(String[] args)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException
{
EncrypRSA rsa = new EncrypRSA();
String msg = "郭XX-精品相聲";
// KeyPairGenerator 類用於生成公鑰和私鑰對,基於 RSA 演算法生成物件
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密鑰對生成器,密鑰大小為 1024 位
keyPairGen.initialize(1024);
// 生成一個密鑰對,保存在 keyPair 中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私鑰
RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
// 得到公鑰
RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();
// 用公鑰加密
byte[] srcBytes = msg.getBytes();
byte[] resultBytes = rsa.encrypt(publicKey, srcBytes);
// 用私鑰解密
byte[] decBytes = rsa.decrypt(privateKey, resultBytes);
System.out.println("明文是:" + msg);
System.out.println("加密後是:" + new String(resultBytes));
System.out.println("解密後是:" + new String(decBytes));
}
}
</pre><br />
<br />
<strong>2. DSA</strong><br />
Digital Signature Algorithm (DSA)是 Schnorr 和 ElGamal 簽名演算法的變種,被美國 NIST 作為 DSS(DigitalSignature Standard)。(感覺有點復雜,沒有附代碼)<br />
詳見<a href="http://63938525.iteye.com/blog/1051565" target="_blank">http://63938525.iteye.com/blog/1051565</a><br />
<br />
<br />
<br />
<h2>(三)、題外話 MySQL加密解密函數</h2><br />
MySQL有兩個函數來支持這種類型的加密,分別叫做 ENCODE() 和 DECODE()。<br />
下面是一個簡單的實例:<br />
<pre class="sql:nogutter:nocontrols" name="code">mysql> INSERT INTO users (username,password) VALUES ('joe',ENCODE('guessme','abr'));
Query OK, 1 row affected (0.14 sec)
</pre><br />
其中,Joe 的密碼是 guessme,它通過密鑰 abracadabra 被加密。要注意的是,加密完的結果是一個二進制字符串,如下所示:<br />
<br />
提示:雖然 ENCODE() 和DECODE() 這兩個函數能夠滿足大多數的要求,但是有的時候您希望使用強度更高的加密手段。在這種情況下,您可以使用 AES_ENCRYPT() 和 AES_DECRYPT() 函數,它們的工作方式是相同的,但是加密強度更高。<br />
<br />
單向加密與雙向加密不同,一旦數據被加密就沒有辦法顛倒這一過程。因此密碼的驗證包括對用戶輸入內容的重新加密,並將它與保存的密文進行比對,看是否匹配。一種簡單的單向加密方式是MD5校驗碼。MySQL 的 MD5() 函數會為您的數據創建一個“指紋”並將它保存起來,供驗證測試使用。下面就是如何使用它的一個簡單例子:<br />
<pre class="sql:nogutter:nocontrols" name="code">mysql> INSERT INTO users (username,password) VALUES ('joe',MD5('guessme'));
Query OK, 1 row affected (0.00 sec)
</pre><br />
<br />
<br />
或者,您考慮一下使用 ENCRYPT() 函數,它使用系統底層的 crypt() 系統調用來完成加密。這個函數有兩個參數:一個是要被加密的字符串,另一個是雙(或者多)字符的“salt”。它然後會用 salt 加密字符串;這個 salt 然後可以被用來再次加密用戶輸入的內容,並將它與先前加密的字符串進行比對。下面一個例子說明了如何使用它:<br />
<pre class="sql:nogutter:nocontrols" name="code">mysql> INSERT INTO users (username,password) VALUES('joe', ENCRYPT('guessme','ab'));
Query OK, 1 row affected (0.00 sec)
</pre><br />
PS:ENCRYPT() 只能用在 UNIX、LINIX 系統上,因為它需要用到底層的 crypt() 庫。<br />
<br />
<br />
<br />
<hr /><h1>二、單向加密(信息摘要)</h1><br />
Java 一般需要獲取物件 MessageDigest 來實現單項加密(信息摘要)。<br />
<br />
<strong>1. MD5</strong><br />
即 Message-Digest Algorithm 5(信息-摘要演算法 5),用於確保信息傳輸完整一致。是計算機廣泛使用的雜湊演算法之一(又譯摘要演算法、哈希演算法),主流編程語言普遍已有MD5實現。將數據(如漢字)運算為另一固定長度值,是雜湊演算法的基礎原理,MD5 的前身有 MD2、MD3 和 MD4。MD5 的作用是讓大容量信息在用數字簽名軟件簽署私人密鑰前被"壓縮"成一種保密的格式(就是把一個任意長度的字節串變換成一定長的十六進制數字串)。<br />
除了 MD5 以外,其中比較有名的還有 sha-1、RIPEMD 以及 Haval 等<br />
<br />
<pre class="java" name="code">import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class EncrypMD5 {
public byte[] eccrypt(String info) throws
NoSuchAlgorithmException
{
// 根據 MD5 演算法生成 MessageDigest 物件
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] srcBytes = info.getBytes();
// 使用 srcBytes 更新摘要
md5.update(srcBytes);
// 完成哈希計算,得到 result
byte[] resultBytes = md5.digest();
return resultBytes;
}
public static void main(String args[]) throws
NoSuchAlgorithmException
{
String msg = "郭XX-精品相聲技術";
EncrypMD5 md5 = new EncrypMD5();
byte[] resultBytes = md5.eccrypt(msg);
System.out.println("密文是:" + new String(resultBytes));
System.out.println("明文是:" + msg);
}
}
</pre><br />
<br />
<strong>2. SHA</strong><br />
是一種數據加密演算法,該演算法經過加密專家多年來的發展和改進已日益完善,現在已成為公認的最安全的散列演算法之一,並被廣泛使用。該演算法的思想是接收一段明文,然後以一種不可逆的方式將它轉換成一段(通常更小)密文,也可以簡單的理解為取一串輸入碼(稱為預映射或信息),並把它們轉化為長度較短、位數固定的輸出序列即散列值(也稱為信息摘要或信息認證代碼)的過程。散列函數值可以說時對明文的一種“指紋”或是“摘要”所以對散列值的數字簽名就可以視為對此明文的數字簽名。<br />
<br />
<pre class="java" name="code">import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class EncrypSHA {
public byte[] eccrypt(String info) throws
NoSuchAlgorithmException
{
MessageDigest sha = MessageDigest.getInstance("SHA");
byte[] srcBytes = info.getBytes();
// 使用 srcBytes 更新摘要
sha.update(srcBytes);
// 完成哈希計算,得到 result
byte[] resultBytes = sha.digest();
return resultBytes;
}
/**
* @param args
* @throws NoSuchAlgorithmException
*/
public static void main(String[] args) throws
NoSuchAlgorithmException
{
String msg = "郭XX-精品相聲技術";
EncrypSHA sha = new EncrypSHA();
byte[] resultBytes = sha.eccrypt(msg);
System.out.println("明文是:" + msg);
System.out.println("密文是:" + new String(resultBytes));
}
}
</pre><br />
<br />
附件中是以上幾種的源代碼,附帶額外的兩種使用方式。<br />
<br />
增加一種關於文件的哈希演算法源代碼:<br />
<br />
<pre class="java" name="code">import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
public class FileHashUtil {
public static final char[] hexChar = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
public static final String[] hashTypes = new String[] {
"MD2", "MD5", "SHA1", "SHA-256", "SHA-384", "SHA-512"
};
public void MD5File(String fileName) throws Exception{
//String fileName = args[0];
System.out.println("需要獲取 hash 的文件為: " + fileName);
java.util.List<MessageDigest> mds = new java.util.ArrayList<>();
for (String hashType : hashTypes) {
MessageDigest md = MessageDigest.getInstance(hashType);
mds.add(md);
}
InputStream fis = null;
try {
fis = new FileInputStream(fileName);
byte[] buffer = new byte[1024];
int numRead = 0;
while ((numRead = fis.read(buffer)) > 0) {
for (MessageDigest md : mds) {
md.update(buffer, 0, numRead);
}
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (fis != null) { fis.close(); }
}
for (MessageDigest md : mds) {
System.out.println(
md.getAlgorithm() +
" == " +
toHexString(md.digest())
);
}
}
public static void main(String[] args) throws Exception {
String[] fileName = new String[] {
"D:/hapfish/ShellFolder.java",
"D:/hapfish/ShellFolder - 副本.java",
"E:/ShellFolder - 副本.java",
"E:/ShellFolder.txt",
"D:/hapfish/ShellFolder.jpg",
"E:/ShellFolder增加字符.txt",
"D:/hapfish/birosoft.jar"
};
FileHashUtil files = new FileHashUtil();
for(int i=0;i<fileName.length;i++){
files.MD5File(fileName[i]);
}
}
public static String toHexString(byte[] b) {
StringBuilder sb = new StringBuilder(b.length * 2);
for (int i = 0; i < b.length; i++) {
sb.append(hexChar[(b[i] & 0xf0) >>> 4]);
sb.append(hexChar[b[i] & 0x0f]);
}
return sb.toString();
}
}
</pre><br />
<br />
運行說明<br />
<pre class="java" name="code">"D:/hapfish/ShellFolder.java",
"D:/hapfish/ShellFolder - 副本.java",
"E:/ShellFolder - 副本.java",
"E:/ShellFolder.txt",
"D:/hapfish/ShellFolder.jpg",
以上五個文件是同一文件經過復制、改擴展名的,最後計算哈希結果是一致的。
"E:/ShellFolder增加字符.txt" 增加了幾個字符串,就不一樣了
"D:/hapfish/birosoft.jar" 完全不相關的另外一個文件
</pre><br />
<br />
運行結果:<br />
<pre class="none:nogutter" name="code">需要獲取 hash 的文件為: D:/hapfish/ShellFolder.java
MD2 == 3a755a99c5e407005cd45ebd856b4649
MD5 == 5d08d440fa911d1e418c69a90b83cd86
SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: D:/hapfish/ShellFolder - 副本.java
MD2 == 3a755a99c5e407005cd45ebd856b4649
MD5 == 5d08d440fa911d1e418c69a90b83cd86
SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: E:/ShellFolder - 副本.java
MD2 == 3a755a99c5e407005cd45ebd856b4649
MD5 == 5d08d440fa911d1e418c69a90b83cd86
SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: E:/ShellFolder.txt
MD2 == 3a755a99c5e407005cd45ebd856b4649
MD5 == 5d08d440fa911d1e418c69a90b83cd86
SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: D:/hapfish/ShellFolder.jpg
MD2 == 3a755a99c5e407005cd45ebd856b4649
MD5 == 5d08d440fa911d1e418c69a90b83cd86
SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: E:/ShellFolder增加字符.txt
MD2 == f2717c24c6c0e110457bd17221c9ca6c
MD5 == c49e353a7c4c26bd7ccb5e90917c230f
SHA1 == 477c8a9e465bfaa4be42d35c032a17f7e6b42b97
SHA-256 == 9fa18adaf242ebcdc6563922d84c2a163c82e1a24db2eb2b73978ed1f354a8a3
SHA-384 == 4eee8f8e6d64d21c15dc01fa049f4d12a3b8e1d94d87763fe0bea75ab5ea8432fa8251289ece45ee39fe3d36b3c3020c
SHA-512 == e852ec0ff77250be497389d2f5a1818c18bb66106b9905c4ee26fe0d256eb3b77e0ce9a28a84e4b67e4332ba37ec3aa7518148e3a682318c0fc34c391f45c201
需要獲取 hash 的文件為: D:/hapfish/birosoft.jar
MD2 == 38c5e1404718916dec59c33cafc909b3
MD5 == dc3e2cc4fb3949cf3660e0f5f8c3fba3
SHA1 == cde3dc25498afc5a563af0bb0eb54dc45f71bb28
SHA-256 == adf6a961c70c6ea677dff066fc5d896fb0beb4dd442ca0eb619ae1d1b04291e5
SHA-384 == fe7c6b754893c53ebd82bb53703fb5cc32115c9a38f98072f73def90729b271ee3c5c78e258bd9ff5ee5476193c2178b
SHA-512 == a15376f327256a6e049dfbdc5c2ad3a98bffccc6fa92ee01ff53db6b04471ca0f45ca28f76ff4a6911b57825afa046671299141f2499d71f1dac618c92385491
</pre><br />
<br />
最後,把運行結果貼出來有點占空間,主要為了說明表述自己的猜想。一般來說同一哈希演算法對同一文件(鏡像、擴展名被修改)所產生的結果應該是一致的。<br />
<br />
因此有個猜想,在 baidu 文庫、騰訊的群共享上傳時,先會判斷是否有相同文件,從某種可能上來說也采用了對文件的哈希演算法,畢竟從本地運算一個哈希演算法後獲得的數值要比把整個文件傳過去比較實惠得多。而且字符串的比較也是很方便的。<br />
<br />
對於某一種哈希演算法,存在一種可能:就是兩個不同的文件,計算出來的哈希值可能是一樣的。當然為了保險,可以用兩種甚至更多的哈希演算法,只有在每種演算法獲得的哈希值都相同時,才能判斷是同一個文件。<br />
如果我們也對用戶上傳的文件進行哈希計算的話,就可以節省資源,同樣的文件按理說可以減少上傳次數……Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com1tag:blogger.com,1999:blog-5946530704742130970.post-24551212532478429882015-02-21T22:09:00.000+08:002015-02-21T23:15:23.103+08:00[轉載] 淺談 Java 中的 Set、List、Map 的區別轉載自:<a href="http://www.javaroots.com/2013/09/print-tables-details-in-schema-jdbc.html" target="_blank">浅谈Java中的Set、List、Map的区别 - 51CTO.COM</a><br />
<br />
就學習經驗,淺談 Java 中的 Set,List,Map 的區別,對 Java 的集合的理解是想對於陣列:<br />
<br />
陣列是大小固定的,並且同一個陣列只能存放類型一樣的數據(基本類型/引用類型),Java 集合可以存儲和操作數目不固定的一組數據。所有的 Java 集合都位於 java.util包中!Java 集合只能存放引用類型的的數據,不能存放基本數據類型。<br />
<br />
<br />
<strong>Java 集合主要分為三種類型:</strong><br />
<ul><li>Set(集)</li>
<li>List(列表)</li>
<li>Map(映射)</li>
</ul><br />
<br />
<hr /><br />
<h2>Collection 介面 :</h2><br />
Collection 是最基本的集合介面,聲明了適用於 Java 集合(只包括 Set 和 List)的通用方法。Set 和 List 都繼承了 Conllection<br />
<br />
Collection 介面的方法:<br />
<ul><li><code>boolean add(Object o)</code>向集合中加入一個物件的引用</li>
<li><code>void clear()</code> 刪除集合中所有的物件,即不再持有這些物件的引用</li>
<li><code>boolean isEmpty()</code> 判斷集合是否為空</li>
<li><code>boolean contains(Object o)</code> 判斷集合中是否持有特定物件的引用</li>
<li><code>Iterartor iterator()</code> 返回一個 Iterator 物件,可以用來遍歷集合中的元素</li>
<li><code>boolean remove(Object o)</code> 從集合中刪除一個物件的引用</li>
<li><code>int size()</code> 返回集合中元素的數目</li>
<li><code>Object[] toArray()</code> 返回一個陣列,該陣列中包括集合中的所有元素</li>
</ul><br />
關於:Iterator() 和 toArray() 方法都用於集合的所有的元素,前者返回一個 Iterator 物件,後者返回一個包含集合中所有元素的陣列。<br />
<br />
Iterator 介面聲明了如下方法:<br />
<ul><li><code>hasNext()</code> 判斷集合中元素是否遍歷完畢,如果沒有,就返回 true</li>
<li><code>next()</code> 返回下一個元素</li>
<li><code>remove()</code> 從集合中刪除上一個有 next() 方法返回的元素。</li>
</ul><br />
<br />
<hr /><h2>Set(集合):</h2><br />
Set 是最簡單的一種集合。集合中的物件不按特定的方式排序,並且沒有重復物件。 <br />
<br />
Set 介面主要實現了兩個實現類:<br />
<ul><li><code>HashSet</code> 按照哈希算法來存取集合中的物件,存取速度比較快。</li>
<li><code>TreeSet</code> 實現了 SortedSet 介面,能夠對集合中的物件進行排序。</li>
</ul><br />
Set 的用法:存放的是物件的引用,沒有重復物件<br />
<pre class="java:nogutter:nocontrols" name="code">Set set=new HashSet();
String s1=new String("hello");
String s2=s1;
String s3=new String("world");
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set.size()); //打印集合中对象的数目 为 2。
</pre><br />
Set 的 add()方法是如何判斷物件是否已經存放在集合中?<br />
<pre class="java:nogutter:nocontrols" name="code">boolean isExists=false;
Iterator iterator=set.iterator();
while(it.hasNext()) {
String oldStr=it.next();
if(newStr.equals(oldStr)){
isExists=true;
}
}
</pre><br />
<br />
<hr /><h2>List(列表):</h2><br />
List的特征是其元素以線性方式存儲,集合中可以存放重復物件。<br />
<br />
List介面主要實現類包括:<br />
<ul><li><code>ArrayList</code> 代表長度可以改變得陣列。可以對元素進行隨機的訪問,向 ArrayList() 中插入與刪除元素的速度慢。</li>
<li><code>LinkedList</code> 在實現中采用鏈表數據結構。插入和刪除速度快,訪問速度慢。</li>
</ul><br />
對於 List 的隨機訪問來說,就是只隨機來檢索位於特定位置的元素。 List 的 get(int index) 方法放回集合中由參數 index 指定的索引位置的物件,下標從“0” 開始。最基本的兩種檢索集合中的所有物件的方法:<br />
<br />
1: for 循環和 get()方法:<br />
<pre class="java:nogutter:nocontrols" name="code">for(int i=0; i < list.size(); i++){
System.out.println(list.get(i));
}
</pre><br />
2: 使用 迭代器(Iterator):<br />
<pre class="java:nogutter:nocontrols" name="code">Iterator it = list.iterator();
while(it.hashNext){
System.out.println(it.next);
}
</pre><br />
<br />
<hr /><h2>Map(映射):</h2><br />
Map 是一種把鍵物件和值物件映射的集合,它的每一個元素都包含一對鍵物件和值物件。 Map 沒有繼承於 Collection 介面從 Map 集合中檢索元素時,只要給出鍵物件,就會返回對應的值物件。<br />
<br />
Map 的常用方法:<br />
<ul><li><code>Object put(Object key, Object value)</code> 向集合中加入元素</li>
<li><code>Object remove(Object key)</code> 刪除與KEY相關的元素</li>
<li><code>void putAll(Map t)</code> 將來自特定映像的所有元素添加給該映像</li>
<li><code>void clear()</code> 從映像中刪除所有映射</li>
<li><code>boolean containsKey(Object key)</code> 判斷映像中是否存在關鍵字 key</li>
<li><code>boolean containsValue(Object value)</code> 判斷映像中是否存在值 value</li>
<li><code>int size()</code> 返回當前映像中映射的數量</li>
<li><code>boolean isEmpty()</code> 判斷映像中是否有任何映射</li>
<li><code>Object get(Object key)</code> 獲得與關鍵字 key 相關的值 。Map 集合中的鍵物件不允許重復,也就說任意兩個鍵物件通過 equals() 方法比較的結果都是 false,但是可以將任意多個鍵獨享映射到同一個值物件上。</li>
</ul><br />
<br />
<hr /><h2>Collections 集合實用類:</h2>Collections 提供了供 Java 集合實用的靜態方法。<br />
<br />
<br />
<hr /><h2>List 的功能方法</h2><br />
實際上有兩種 List:<br />
一種是基本的 ArrayList,其優點在於隨機訪問元素,另一種是更強大的 LinkedList,它並不是為快速隨機訪問設計的,而是具有一套更通用的方法。<br />
<dl><dt>List</dt>
<dd>次序是 List 最重要的特點:它保證維護元素特定的順序。List 為 Collection 添加了許多方法,使得能夠向 List 中間插入與移除元素(這只推薦 LinkedList 使用)。一個 List 可以生成 ListIterator,使用它可以從兩個方向遍歷 List,也可以從 List 中間插入和移除元 素。</dd>
<dt>ArrayList</dt>
<dd>由陣列實現的 List。允許對元素進行快速隨機訪問,但是向 List 中間插入與移除元素的速度很慢。ListIterator 只應該用來由後向前遍歷 ArrayList,而不是用來插入和移除元素。因為那比 LinkedList 開銷要大很多。</dd>
<dt>LinkedList</dt>
<dd>對順序訪問進行了優化,向 List 中間插入與刪除的開銷並不大。隨機訪問則相對較慢(使用 ArrayList 代替)。還具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何介面或基類中定義過)使得 LinkedList 可以當作堆棧、隊列和雙向隊列使用。</dd> </dl><br />
<br />
<hr /><h2>Set的功能方法</h2><br />
Set 具有與 Collection 完全一樣的介面,因此沒有任何額外的功能,不像前面有兩個不同的 List。實際上 Set 就是 Collection,只是行為不同(這是繼承與多態思想的典型應用:表現不同的行為)。Set 不保存重復的元素(至於如何判斷元素相同則較為負責)<br />
<br />
Set:存入 Set 的每個元素都必須是唯一的,因為 Set 不保存重復元素。加入 Set 的元素必須定義 equals() 方法以確保物件的唯一性。Set 與 Collection 有完全一樣的介面。Set 介面不保證維護元素的次序。<br />
<dl><dt>HashSet</dt>
<dd>為快速查找設計的 Set。存入 HashSet 的物件必須定義 hashCode()。</dd>
<dt>TreeSet</dt>
<dd>保存次序的 Set,底層為樹結構。使用它可以從 Set 中提取有序的序列。</dd>
<dt>LinkedHashSet</dt>
<dd>具有 HashSet 的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。於是在使用迭代器遍歷 Set 時,結果會按元素插入的次序顯示。</dd> </dl><br />
<br />
<hr /><h2>Map的功能方法 </h2><br />
方法 put(Object key, Object value) 添加一個“值”(想要得東西)和與“值”相關聯的“鍵”(key)(使用它來查找)。方法 get(Object key) 返回與給定“鍵”相關聯的“值”。可以用 containsKey() 和 containsValue() 測試 Map 中是否包含某個“鍵”或“值”。 標准的 Java 類庫中包含了幾種不同的 Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它們都有同樣的基本介面Map,但是行為、效率、排序策略、保存物件的生命周期和判定“鍵”等價的策略等各不相同。<br />
<br />
執行效率是 Map 的一個大問題。看看 get() 要做哪些事,就會明白為什麼在 ArrayList 中搜索“鍵”是相當慢的。而這正是 HashMap 提高速度的地方。HashMap 使用了特殊的值,稱為“散列碼”(hash code),來取代對鍵的緩慢搜索。“散列碼”是“相對唯一”用以代表物件的 int 值,它是通過將該物件的某些信息進行轉換而生成的。所有 Java 物件都 能產生散列碼,因為 hashCode() 是定義在基類 Object 中的方法。<br />
<dl><dt>HashMap</dt>
<dd>Map 基於散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量 capacity 和負載因子 load factor,以調整容器的性能。HashMap 就是使用物件的 hashCode() 進行快速查詢的。此方法能夠顯著提高性能。</dd>
<dt>LinkedHashMap</dt>
<dd>類似於 HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比 HashMap 慢一點。而在迭代訪問時發而更快,因為它使用鏈表維護內部次序。</dd>
<dt>TreeMap</dt>
<dd> 基於紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由 Comparabel 或 Comparator 決定)。TreeMap 的特點在 於,你得到的結果是經過排序的。TreeMap 是唯一的帶有 subMap() 方法的 Map,它可以返回一個子樹。</dd>
<dt>WeakHashMao</dt>
<dd>弱鍵(weak key)Map,Map 中使用的物件也被允許釋放: 這是為解決特殊問題設計的。如果沒有 map 之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。</dd>
<dt>IdentifyHashMap</dt>
<dd>使用 == 代替 equals() 對“鍵”作比較的 hash map。專為解決特殊問題而設計。</dd> </dl><br />
<br />
<hr /><h2>總結:</h2><br />
Java 集合的基本用法,都歸納了,上面這些是平常最常用的 Java 集合,具體的其他的,還要參考JDK幫助文檔了,呵呵 關於 Map的應用,還有很多,具體就是這個,Collections 提供了很多 List / Map 實用的方法,對平常開發非常有用。<br />
<br />
List 按物件進入的順序保存物件,不做排序或編輯操作。Set 對每個物件只接受一次,並使用自己內部的排序方法(通常,你只關心某個元素是否屬於 Set,而不關心它的順序,否則應該使用 List)。Map 同樣對每個元素保存一份,但這是基於"key"的,Map 也有內置的排序,因而不關心元素添加的順序。如果添加元素的順序對你很重要,應該使用 LinkedHashSet 或者 LinkedHashMap.<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com3tag:blogger.com,1999:blog-5946530704742130970.post-23506363444405207432015-02-21T18:10:00.000+08:002015-02-24T15:47:45.289+08:00[轉載] Get Tables And Columns Details In Schema Using JDBC轉載自:<a href="http://www.javaroots.com/2013/09/print-tables-details-in-schema-jdbc.html" target="_blank">JavaRoots: Get Tables And Columns Details In Schema Using JDBC</a><br />
<br />
This is a sample program written using java and JDBC API , to print all the tables in a database schema . It also prints columns available in the particular table.<br />
<br />
<pre class="java:nogutter:nocontrols" name="code">package com.datamigration.main;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.datamigration.db.DataBase;
/**
* @author Abhishek Somani
*/
public class PrintTable {
public static String SCHEMA_NAME = "${YOUR_SCHEMA_NAME}";
public static void main(String[] args) {
// create and setup your database and get db connection
DataBase db = new DataBase();
db.init();
try {
Connection con = db.getConnection();
DatabaseMetaData metaData = con.getMetaData();
String tableType[] = { "TABLE" };
StringBuilder builder = new StringBuilder();
ResultSet result = metaData.getTables(
null, SCHEMA_NAME, null, tableType
);
while (result.next()) {
String tableName = result.getString(3);
builder.append(tableName + "( ");
ResultSet columns = metaData.getColumns(
null, null, tableName, null
);
while (columns.next()) {
String columnName = columns.getString(4);
builder.append(columnName);
builder.append(",");
}
builder.deleteCharAt(builder.lastIndexOf(","));
builder.append(" )");
builder.append("\n");
builder.append("----------------");
builder.append("\n");
}
System.out.println(builder.toString());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
</pre><br />
This program will print tables along with colmuns like this :<br />
<pre class="none:nogutter:nocontrols" name="code">TABLE1( ID,NAME,STATUS,CREATED_AT,UPDATED_AT)
-----------------------------------
TABLE2( ID,NAME,STATUS,CREATED_AT,UPDATED_AT)
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-34530681631212133812015-02-21T17:47:00.001+08:002015-02-26T09:45:09.046+08:00[轉載] [JSP] Expression Language (EL)轉載自:<a href="http://www.w3cschool.cc/jsp/jsp-expression-language.html" target="_blank">JSP 表达式语言 | w3cschool菜鸟教程</a><br />
<br />
JSP 表達式語言(EL)使得訪問存儲在 JavaBean 中的數據變得非常簡單。JSP EL 既可以用來創建算術表達式也可以用來創建邏輯表達式。在 JSP EL 表達式內可以使用整型數,浮點數,字符串,常量 true、false,還有 null。<br />
<br />
<br />
<h2>一個簡單的語法</h2><br />
典型的,當您需要在JSP標簽中指定一個屬性值時,只需要簡單地使用字符串即可:<br />
<pre class="xml:nogutter:nocontrols" name="code"><jsp:setProperty name="box" property="perimeter" value="100"/>
</pre><br />
JSP EL 允許您指定一個表達式來表示屬性值。一個簡單的表達式語法如下:<br />
<pre class="xml:nogutter:nocontrols" name="code">${expr}
</pre><br />
其中,expr 指的是表達式。在 JSP EL 中通用的操作符是"."和"[]"。這兩個操作符允許您通過內嵌的 JSP 物件訪問各種各樣的 JavaBean 屬性。<br />
<br />
舉例來說,上面的 <code><jsp:setProperty></code> 標簽可以使用表達式語言改寫成如下形式:<br />
<pre class="xml:nogutter:nocontrols" name="code"><jsp:setProperty name="box" property="perimeter"
value="${2*box.width+2*box.height}"/>
</pre><br />
當 JSP 編譯器在屬性中見到"${}"格式後,它會產生代碼來計算這個表達式,並且產生一個替代品來代替表達式的值。<br />
<br />
您也可以在標簽的模板文本中使用表達式語言。<br />
比如 <code><jsp:text></code> 標簽簡單地將其主體中的文本插入到 JSP 輸出中:<br />
<pre class="xml:nogutter:nocontrols" name="code"><jsp:text>
<h1>Hello JSP!</h1>
</jsp:text>
</pre><br />
現在,在 <code><jsp:text></code> 標簽主體中使用表達式,就像這樣:<br />
<pre class="xml:nogutter:nocontrols" name="code"><jsp:text>
Box Perimeter is: ${2*box.width + 2*box.height}
</jsp:text>
</pre><br />
在 EL 表達式中可以使用圓括號來組織子表達式。<br />
比如 <code>${(1 + 2) * 3}</code> 等於9,但是 <code>${1 + (2 * 3)}</code> 等於7。<br />
<br />
想要停用對 EL 表達式的評估的話,需要使用 page 指令將 isELIgnored 屬性值設為 true:<br />
<pre class="xml:nogutter:nocontrols" name="code"><%@ page isELIgnored ="true|false" %>
</pre><br />
這樣,EL 表達式就會被忽略。若設為 false,則容器將會計算 EL 表達式。<br />
<br />
<br />
<hr/><h2>EL 中的基礎操作符</h2><br />
EL表達式支持大部分Java所提供的算術和邏輯操作符:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>操作符</th><th>描述</th></tr>
<tr><td>.</td><td>訪問一個 Bean 屬性或者一個映射條目</td></tr>
<tr><td>[]</td><td>訪問一個數組或者鏈表的元素</td></tr>
<tr><td>()</td><td>組織一個子表達式以改變優先級</td></tr>
<tr><td>+</td><td>加</td></tr>
<tr><td>-</td><td>減或負</td></tr>
<tr><td>*</td><td>乘</td></tr>
<tr><td>/ 或 div</td><td>除</td></tr>
<tr><td>% 或 mod</td><td>取模</td></tr>
<tr><td>== 或 eq</td><td>測試是否相等</td></tr>
<tr><td>!= 或 ne</td><td>測試是否不等</td></tr>
<tr><td>< 或 lt</td><td>測試是否小於</td></tr>
<tr><td>> 或 gt</td><td>測試是否大於</td></tr>
<tr><td><= 或 le</td><td>測試是否小於等於</td></tr>
<tr><td>>= 或 gt</td><td>測試是否大於等於</td></tr>
<tr><td>&& 或 and</td><td>測試邏輯與</td></tr>
<tr><td>|| 或 or</td><td>測試邏輯或</td></tr>
<tr><td>! 或 not</td><td>測試取反</td></tr>
<tr><td>empty</td><td>測試是否空值</td></tr>
</table><br />
<br />
<hr/><h2>JSP EL 中的函數</h2><br />
JSP EL 允許您在表達式中使用函數。這些函數必須被定義在自定義標簽庫中。函數的使用語法如下:<br />
<pre class="xml:nogutter:nocontrols" name="code">${ns:func(param1, param2, ...)}
</pre><br />
ns 指的是命名空間(namespace),func 指的是函數的名稱,param1 指的是第一個參數,param2 指的是第二個參數,以此類推。比如,有函數 <code>fn:length</code>,在 JSTL 庫中定義,可以像下面這樣來獲取一個字符串的長度: <br />
<pre class="xml:nogutter:nocontrols" name="code">${fn:length("Get my length")}
</pre><br />
要使用任何標簽庫中的函數,您需要將這些庫安裝在服務器中,然後使用 <code><taglib></code> 標簽在JSP文件中包含這些庫。<br />
<br />
<br />
<hr/><h2>JSP EL 隱含物件</h2><br />
JSP EL支持下表列出的隱含物件:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th>隱含物件</th><th>描述</th></tr>
<tr><td>pageScope</td><td>page 作用域</td></tr>
<tr><td>requestScope</td><td>request 作用域</td></tr>
<tr><td>sessionScope</td><td>session 作用域</td></tr>
<tr><td>applicationScope</td><td>application 作用域</td></tr>
<tr><td>param</td><td>Request 物件的參數,字符串</td></tr>
<tr><td>paramValues</td><td>Request 物件的參數,字符串集合</td></tr>
<tr><td>header</td><td>HTTP 信息頭,字符串</td></tr>
<tr><td>headerValues</td><td>HTTP 信息頭,字符串集合</td></tr>
<tr><td>initParam</td><td>上下文初始化參數</td></tr>
<tr><td>cookie</td><td>Cookie 值</td></tr>
<tr><td>pageContext</td><td>當前頁面的 pageContext</td></tr>
</table><br />
您可以在表達式中使用這些物件,就像使用變量一樣。接下來會給出幾個例子來更好的理解這個概念。<br />
<br />
<br />
<hr/><h2>pageContext 物件</h2><br />
pageContext 物件是 JSP 中 pageContext 物件的引用。通過 pageContext 物件,您可以訪問 request 物件。比如,訪問 request 物件傳入的查詢字符串,就像這樣:<br />
<pre class="xml:nogutter:nocontrols" name="code">${pageContext.request.queryString}
</pre><br />
<br />
<hr/><h2>Scope 物件</h2><br />
pageScope,requestScope,sessionScope,applicationScope 變量用來訪問存儲在各個作用域層次的變量。<br />
<br />
舉例來說,如果您需要顯式訪問在 applicationScope 層的 box 變量,可以這樣來訪問:<br />
<pre class="xml:nogutter:nocontrols" name="code">${applicationScope.box}
</pre><br />
<br />
<hr/><h2>param 和 paramValues 物件</h2><br />
param 和 paramValues 物件用來訪問參數值,通過使用 <code>request.getParameter</code> 方法和 <code>request.getParameterValues</code> 方法。<br />
<br />
舉例來說,訪問一個名為 order 的參數,可以這樣使用表達式:<code>${param.order}</code> 或者 <code>${param["order"]}</code>。<br />
<br />
接下來的例子表明了如何訪問request中的username參數:<br />
<pre class="xml:nogutter:nocontrols" name="code"><%@ page import="java.io.*,java.util.*" %>
<%
String title = "Accessing Request Param";
%>
<html>
<head>
<title><% out.print(title); %></title>
</head>
<body>
<center>
<h1><% out.print(title); %></h1>
</center>
<div align="center">
<p>${param["username"]}</p>
</div>
</body>
</html>
</pre><br />
param 物件返回單一的字符串,而 paramValues 物件則返回一個字符串數組。<br />
<br />
<br />
<hr/><h2>header 和 headerValues 物件</h2><br />
header 和 headerValues 物件用來訪問信息頭,通過使用 <code>request.getHeader</code> 方法和 <code>request.getHeaders</code> 方法。<br />
<br />
舉例來說,要訪問一個名為 user-agent 的信息頭,可以這樣使用表達式:<code>${header.user-agent}</code> 或者 <code>${header["user-agent"]}</code>。<br />
<br />
接下來的例子表明了如何訪問 user-agent 信息頭:<br />
<pre class="xml:nogutter:nocontrols" name="code"><%@ page import="java.io.*,java.util.*" %>
<%
String title = "User Agent Example";
%>
<html>
<head>
<title><% out.print(title); %></title>
</head>
<body>
<center>
<h1><% out.print(title); %></h1>
</center>
<div align="center">
<p>${header["user-agent"]}</p>
</div>
</body>
</html>
</pre><br />
運行結果如下:<br />
<blockquote>User Agent Example<br />
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; HPNTDF; .NET4.0C; InfoPath.2)<br />
</blockquote><br />
header 物件返回單一值,而 headerValues 則返回一個字符串數組。Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-36254828965237148632015-02-21T11:23:00.000+08:002015-02-21T11:23:13.209+08:00[轉載] [Java] BigDecimal的四則運算轉載自:<a href="http://bluebottle.idv.tw/?p=4795" target="_blank">[Java] BigDecimal的四則運算</a><br />
<br />
最近因為需要計算報表中的房物出租率,會用到BigDecimal的運算,目前只知道int, double, float等基本型態的我,連BigDecimal是三小都不太清楚,後來查了許多資料才知道原來BigDecimal可以用在精確的數學計算上面,許多商業用報表都需要用到BigDecimal型別。<br />
<br />
<strong>BigDecimal有兩個較常用到的方法:</strong><br />
<ol><li><code>BigDecimal(double val)</code><br/>Translates a double into a BigDecimal.</li>
<li><code>BigDecimal(String val)</code><br/>Translates the String repre sentation of a BigDecimal into a BigDecimal.</li>
</ol><br />
API將上述方法解釋的非常清楚,而我們如果要精確計算,一定得把數值轉型成String型別,否則得到的結果會是有問題的。簡單來說,若要使用BigDecimal做加法計算必須有以下步驟:<br />
<ol><li>需要先將兩個浮點數(double)轉為String,並分別宣告為BigDecimal</li>
<li>在其中一個浮點數使用add方法,傳入另一個浮點數作為參數</li>
<li>作為運算結果的參數(答案)也須宣告為BigDecimal</li>
<li>最後把運算的結果(BigDecimal)再轉換為浮點數</li>
</ol><br />
在BigDecimal的運算中,加為add, 減為sub,乘為multiply,除法為divide。在做運算之前需針對每一個變數new BigDecimal物件,舉例如下:<br />
<pre class="java:nogutter:nocontrols" name="code">// 精確的加法運算,v1 為加數,v2 為被加數,return v1 + v2
public static double add(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
</pre><br />
在運算之後若需四捨五入該怎麼辦呢?舉例如下:<br />
<pre class="java:nogutter:nocontrols" name="code">// v 為需要四捨五入的數字,scale 為小數點後面要保留幾位數,return 四捨五入後的結果
public static double round(double v,int scale){
if(scale < 0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero"
);
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
</pre><br />
完整範例請參考以下文字:<br />
<pre class="java:nogutter:nocontrols" name="code">import java.math.BigDecimal;
public class Testtest {
public static void main(String[]args){
// 宣告第一個需要運算的數值
BigDecimal bigNumber = new BigDecimal("89.1234567890123456789");
// 宣告第二個需要運算的數值
BigDecimal bigRate = new BigDecimal(1000);
// 宣告運算後的答案為 bigResult
BigDecimal bigResult = new BigDecimal(0);
// bigResult 為 bigNumBer * bigRate
bigResult = bigNumber.multiply(bigRate);
// 印出 bigResult
System.out.println(bigResult.toString());
// 將 bigNumber 四捨五入變為 double 型態
double dData = bigNumber.doubleValue();
// 印出 dDate
System.out.println(dData);
// 宣告 data2 為 bigNumber/bigRate 並四捨五入至小數點第二位
double data2 = bigNumber
.divide(bigRate,2,BigDecimal.ROUND_HALF_UP)
.doubleValue();
// 印出 data2
System.out.println(data2);
}
}
</pre><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-85072958064986502822015-01-31T21:02:00.001+08:002015-01-31T21:36:21.047+08:00[轉載] PHP 配置open_basedir,让各虚拟站点独立运行轉載自:<a href="http://www.cnblogs.com/flashlm/archive/2009/11/11/php_open_basedir_seprate_site.html" target="_blank">PHP 配置open_basedir,让各虚拟站点独立运行 - canbeing - 博客园</a><br />
<br />
open_basedir 可将用户访问文件的活动范围限制在指定的区域,通常是其家目录的路径,也可用符号"."来代表当前目录。open_basedir 也可以同时设置多个目录, 在 Windows 中用分号(;)分隔目录,在任何其它系统中用冒号(:)分隔目录。当其作用于 Apache 模块时,父目录中的 open_basedir 路径自动被继承。<br />
<br />
以下以 Linux 系统下的配置为例:<br />
<br />
<strong>方法一:在 php.ini 里配置</strong><br />
<code>open_basedir = .:/tmp/</code><br />
<br />
<strong>方法二:在 Apache 配置的 VirtualHost 里设置</strong><br />
<code>php_admin_value open_basedir .:/tmp/</code><br />
<br />
<strong>方法三:在 Apache 配置的 Direcotry .htaccess 里设置</strong><br />
<code>php_admin_value open_basedir .:/tmp/</code><br />
<br />
<br />
<strong>关于三个配置方法的解释:</strong><br />
<ol><li>方法二的优先级高于方法一,也就是说方法二会覆盖方法一<br/>方法三的优先级高于方法二,也就是说方法三会覆盖方法二</li>
<li>配置目录里加了 <code>/tmp/</code> 是因为php默认的临时文件(如上传的文件、session 等)会放在该目录,所以一般需要添加该目录,否则部分功能将无法使用</li>
<li>配置目录里加了 <code>.</code> 是指运行php文件的当前目录,这样做可以避免每个站点一个一个设置</li>
<li>如果站点还使用了站点目录外的文件,需要单独在对应 VirtualHost 设置该目录</li>
</ol>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-60980528615287115212015-01-30T17:03:00.000+08:002015-01-30T17:03:48.226+08:00[轉載] Java 8 新特性详解轉載自: <a href="https://chou.it/2014/03/java-8-new-features/" target="_blank">Java 8 新特性详解 | Lex Chou的博客</a><br /><br />本文翻译自 <a href="http://winterbe.com/posts/2014/03/16/java-8-tutorial/" target="_blank">http://winterbe.com/posts/2014/03/16/java-8-tutorial/</a>,加了一些自己的认识。<br /><blockquote>
“Java is still not dead—and people are starting to figure that out.”</blockquote>
<br /><br />本教程将 Java 8 的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda 表达式,方法引用以及多重 Annotation,之后你将会学到最新的 API 上的改进,比如流,函数式接口,Map 以及全新的日期 API。<br />本教程将用带注释的简单代码来描述新特性,你将看不到大片吓人的文字。<br /><br /><br /><h1>
接口的默认方法</h1>
<br />Java 8 允许我们给接口添加一个非抽象的方法实现,只需要使用 <i>default</i> 关键字即可,这个特征又叫做<b>扩展方法</b>,示例如下:<br /><pre class="java:nogutter:nocontrols" name="code">
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}</pre>
<br /><br />Formula 接口在拥有 calculate 方法之外同时还定义了 sqrt 方法,实现了 Formula 接口的子类只需要实现一个 calculate 方法,默认方法 sqrt 将在子类上可以直接使用。<br /><pre class="java:nogutter:nocontrols" name="code">
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0</pre>
<br /><br />文中的 formula 被实现为一个匿名类的实例,该代码非常容易理解,6 行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。<br /><b>译者注:</b> 在 Java 中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在 C++ 中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做 mixin。新的 Java 8 的这个特新在编译器实现的角度上来说更加接近 Scala 的 trait。 在 C# 中也有名为<b>扩展方法</b>的概念,允许给已存在的类型扩展方法,和 Java 8 的这个在语义上有差别。<br /><br /><br /><h1>
Lambda 表达式</h1>
<br />首先看看在老版本的Java中是如何排列字符串的:<br /><pre class="java:nogutter:nocontrols" name="code">
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});</pre>
<br /><br />只需要给静态方法 Collections.sort 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 sort 方法。<br />在 Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8 提供了更简洁的语法,lambda 表达式:<br /><pre class="java:nogutter:nocontrols" name="code">
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});</pre>
<br /><br />看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:<br /><pre class="java:nogutter:nocontrols" name="code">
Collections.sort(names, (String a, String b) -> b.compareTo(a));</pre>
<br /><br />对于函数体只有一行代码的,你可以去掉大括号 {} 以及 return 关键字,但是你还可以写得更短点:<br /><pre class="java:nogutter:nocontrols" name="code">
Collections.sort(names, (a, b) -> b.compareTo(a));</pre>
<br /><br />Java 编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看 lambda 表达式还能作出什么更方便的东西来:<br /><br /><br /><h1>
函数式接口</h1>
<br />Lambda 表达式是如何在 java 的类型系统中表示的呢?每一个 lambda 表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的 lambda 表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。<br />我们可以将 lambda 表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。<br />示例如下:<br /><pre class="java:nogutter:nocontrols" name="code">
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123</pre>
<br /><br />需要注意如果 @FunctionalInterface 如果没有指定,上面的代码也是对的。<br /><b>译者注</b> 将 lambda 表达式映射到一个单方法的接口上,这种做法在 Java 8 之前就有别的语言实现,比如 Rhino JavaScript 解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个 function,Rhino 解释器会自动做一个单接口的实例到 function 的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的 addEventListener 第二个参数 EventListener。<br /><br /><br /><h1>
方法与构造函数引用</h1>
<br />前一节中的代码还可以通过静态方法引用来表示:<br /><pre class="java:nogutter:nocontrols" name="code">
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123</pre>
<br /><br />Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:<br /><pre class="java:nogutter:nocontrols" name="code">
converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"</pre>
<br /><br />接下来看看构造函数是如何使用 :: 关键字来引用的,首先我们定义一个包含多个构造函数的简单类:<br /><pre class="java:nogutter:nocontrols" name="code">
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}</pre>
<br /><br />接下来我们指定一个用来创建 Person 对象的对象工厂接口:<br /><pre class="java:nogutter:nocontrols" name="code">
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}</pre>
<br /><br />这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:<br /><pre class="java:nogutter:nocontrols" name="code">
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");</pre>
<br /><br />我们只需要使用 Person::new 来获取 Person 类构造函数的引用,Java 编译器会自动根据 PersonFactory.create 方法的签名来选择合适的构造函数。<br /><br /><br /><h1>
Lambda 作用域</h1>
<br />在 lambda 表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了 final 的外层局部变量,或者实例的字段以及静态变量。<br /><br /><h2>
访问局部变量</h2>
<br />我们可以直接在 lambda 表达式中访问外层的局部变量:<br /><pre class="java:nogutter:nocontrols" name="code">
final int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3</pre>
<br /><br />但是和匿名对象不同的是,这里的变量 num 可以不用声明为 final,该代码同样正确:<br /><pre class="java:nogutter:nocontrols" name="code">
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3</pre>
<br /><br />不过这里的 num 必须不可被后面的代码修改(即隐性的具有 final 的语义),例如下面的就无法编译:<br /><pre class="java:nogutter:nocontrols" name="code">
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
num = 3;</pre>
<br />在 lambda 表达式中试图修改 num 同样是不允许的。<br /><br /><h2>
访问对象字段与静态变量</h2>
<br />和本地变量不同的是,lambda 内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:<br /><pre class="java:nogutter:nocontrols" name="code">
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}</pre>
<br /><br /><h2>
访问接口的默认方法</h2>
<br />还记得第一节中的 formula 例子么,接口 Formula 定义了一个默认方法 sqrt 可以直接被 formula 的实例包括匿名对象访问到,但是在 lambda 表达式中这个是不行的。<br />Lambda 表达式中是无法访问到默认方法的,以下代码将无法编译:<br /><pre class="java:nogutter:nocontrols" name="code">
Formula formula = (a) -> sqrt( a * 100);
Built-in Functional Interfaces</pre>
<br /><br />JDK 1.8 API 包含了很多内建的函数式接口,在老 Java 中常用到的比如 Comparator 或者 Runnable 接口,这些接口都增加了 @FunctionalInterface 注解以便能用在 lambda 上。<br />Java 8 API 同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到 lambda 上使用的。<br /><br /><h2>
Predicate 接口</h2>
<br />Predicate 接口只有一个参数,返回 boolean 类型。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):<br /><pre class="java:nogutter:nocontrols" name="code">
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();</pre>
<br /><br /><h2>
Function 接口</h2>
<br />Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):<br /><pre class="java:nogutter:nocontrols" name="code">
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"</pre>
<br /><br /><h2>
Supplier 接口</h2>
<br />Supplier 接口返回一个任意范型的值,和 Function 接口不同的是该接口没有任何参数<br /><pre class="java:nogutter:nocontrols" name="code">
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person</pre>
<br /><br /><h2>
Consumer 接口</h2>
<br />Consumer 接口表示执行在单个参数上的操作。<br /><pre class="java:nogutter:nocontrols" name="code">
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));</pre>
<br /><br /><h2>
Comparator 接口</h2>
<br />Comparator 是老 Java 中的经典接口, Java 8 在此之上添加了多种默认方法:<br /><pre class="java:nogutter:nocontrols" name="code">
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0</pre>
<br /><br /><h2>
Optional 接口</h2>
<br />Optional 不是函数是接口,这是个用来防止 NullPointerException 异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:<br />Optional 被定义为一个简单的容器,其值可能是 null 或者不是 null。在 Java 8 之前一般某个函数应该返回非空对象但是偶尔却可能返回了 null,而在 Java 8 中,不推荐你返回 null 而是返回 Optional。<br /><pre class="java:nogutter:nocontrols" name="code">
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent( (s) -> System.out.println(s.charAt(0)) ); // "b"</pre>
<br /><br /><h2>
Stream 接口</h2>
<br />java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回 Stream 本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。<br />首先看看 Stream 是怎么用,首先创建实例代码的用到的数据 List:<br /><pre class="java:nogutter:nocontrols" name="code">
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");</pre>
<br /><br />Java 8 扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个 Stream。下面几节将详细解释常用的Stream操作:<br /><br /><br /><h2>
Filter 过滤</h2>
<br />过滤通过一个 predicate 接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他 Stream 操作(比如 forEach)。forEach 需要一个函数来对过滤后的元素依次执行。forEach 是一个最终操作,所以我们不能在 forEach 之后来执行其他 Stream 操作。<br /><pre class="java:nogutter:nocontrols" name="code">
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"</pre>
<br /><br /><h2>
Sort 排序</h2>
<br />排序是一个中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。<br /><pre class="java:nogutter:nocontrols" name="code">
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"</pre>
<br /><br />需要注意的是,排序只创建了一个排列好后的 Stream,而不会影响原有的数据源,排序之后原数据 stringCollection 是不会被修改的:<br /><pre class="java:nogutter:nocontrols" name="code">
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1</pre>
<br /><br /><h2>
Map 映射</h2>
<br />中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过 map 来讲对象转换成其他类型,map 返回的 Stream 类型是根据你 map 传递进去的函数的返回值决定的。<br /><pre class="java:nogutter:nocontrols" name="code">
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"</pre>
<br /><br /><h2>
Match 匹配</h2>
<br />Stream 提供了多种匹配操作,允许检测指定的 Predicate 是否匹配整个 Stream。所有的匹配操作都是最终操作,并返回一个 boolean 类型的值。<br /><pre class="java:nogutter:nocontrols" name="code">
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true</pre>
<br /><br /><h2>
Count 计数</h2>
<br />计数是一个最终操作,返回 Stream 中元素的个数,返回值类型是 long。<br /><pre class="java:nogutter:nocontrols" name="code">
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3</pre>
<br /><br /><h2>
Reduce 规约</h2>
<br />这是一个最终操作,允许通过指定的函数来讲 stream 中的多个元素规约为一个元素,规越后的结果是通过 Optional 接口表示的:<br /><pre class="java:nogutter:nocontrols" name="code">
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"</pre>
<br /><br /><h2>
并行 Streams</h2>
<br />前面提到过 Stream 有串行和并行两种,串行 Stream 上的操作是在一个线程中依次完成,而并行 Stream 则是在多个线程上同时执行。<br />下面的例子展示了是如何通过并行 Stream 来提升性能:<br />首先我们创建一个没有重复元素的大表:<br /><pre class="java:nogutter:nocontrols" name="code">
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}</pre>
<br /><br />然后我们计算一下排序这个 Stream 要耗时多久,<br />串行排序:<br /><pre class="java:nogutter:nocontrols" name="code">
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// 串行耗时: 899 ms</pre>
<br /><br />并行排序:<br /><pre class="java:nogutter:nocontrols" name="code">
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
// 并行排序耗时: 472 ms</pre>
<br /><br />上面两个代码几乎是一样的,但是并行版的快了 50% 之多,唯一需要做的改动就是将 stream() 改为 parallelStream()。<br /><br /><h2>
Map</h2>
<br />前面提到过,Map 类型不支持 stream,不过Map提供了一些新的有用的方法来处理一些日常任务。<br /><pre class="java:nogutter:nocontrols" name="code">
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));</pre>
<br /><br />以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而 forEach 则接收一个 Consumer 接口来对 map 里的每一个键值对进行操作。<br />下面的例子展示了 map 上的其他有用的函数:<br /><pre class="java:nogutter:nocontrols" name="code">
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33</pre>
<br /><br />接下来展示如何在 Map 里删除一个键值全都匹配的项:<br /><pre class="java:nogutter:nocontrols" name="code">
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null</pre>
<br /><br />另外一个有用的方法:<br /><pre class="java:nogutter:nocontrols" name="code">
map.getOrDefault(42, "not found"); // not found</pre>
<br /><br />对 Map 的元素做合并也变得很容易了:<br /><pre class="java:nogutter:nocontrols" name="code">
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat</pre>
<br /><br />Merge 做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到 map 中。<br /><br /><br /><br /><h1>
Date API</h1>
<br />Java 8 在包 java.time 下包含了一组全新的时间日期 API。新的日期 API 和开源的 Joda-Time 库差不多,但又不完全一样,下面的例子展示了这组新 API 里最重要的一些部分:<br /><br /><h2>
Clock 时钟</h2>
<br />Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用 Instant 类来表示,Instant 类也可以用来创建老的 java.util.Date 对象。<br /><pre class="java:nogutter:nocontrols" name="code">
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date</pre>
<br /><br /><h2>
Timezones 时区</h2>
<br />在新 API 中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法 <i>of</i> 来获取到。 时区定义了到 UTS 时间的时间差,在 Instant 时间点对象到本地日期对象之间转换的时候是极其重要的。<br /><pre class="java:nogutter:nocontrols" name="code">
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]</pre>
<br /><br /><h2>
LocalTime 本地时间</h2>
<br />LocalTime 定义了一个没有时区信息的时间,例如 晚上 10 点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:<br /><pre class="java:nogutter:nocontrols" name="code">
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239</pre>
<br /><br />LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。<br /><pre class="java:nogutter:nocontrols" name="code">
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37</pre>
<br /><br /><h2>
LocalDate 本地日期</h2>
<br />LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和 LocalTime 基本一致。下面的例子展示了如何给 Date 对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。<br /><pre class="java:nogutter:nocontrols" name="code">
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY</pre>
<br /><br />从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单:<br /><pre class="java:nogutter:nocontrols" name="code">
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24</pre>
<br /><br /><h2>
LocalDateTime 本地日期时间</h2>
<br />LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime 还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。<br /><pre class="java:nogutter:nocontrols" name="code">
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439</pre>
<br /><br />只要附加上时区信息,就可以将其转换为一个时间点 Instant 对象,Instant 时间点对象可以很容易的转换为老式的 java.util.Date。<br /><pre class="java:nogutter:nocontrols" name="code">
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014</pre>
<br /><br />格式化 LocalDateTime 和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:<br /><pre class="java:nogutter:nocontrols" name="code">
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13</pre>
<br /><br />和 java.text.NumberFormat 不一样的是新版的 DateTimeFormatter 是不可变的,所以它是线程安全的。<br />关于时间日期格式的详细信息 <a href="http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html" target="_blank">DateTimeFormatter</a><br /><br /><br /><h1>
Annotation 注解</h1>
<br />在 Java 8 中支持多重注解了,先看个例子来理解一下是什么意思。<br />首先定义一个包装类 Hints 注解用来放置一组具体的 Hint 注解:<br /><pre class="java:nogutter:nocontrols" name="code">
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}</pre>
<br /><br />Java 8 允许我们把同一个类型的注解使用多次,只需要给该注解标注一下 @Repeatable 即可。<br />例 1: 使用包装类当容器来存多个注解(老方法)<br /><pre class="java:nogutter:nocontrols" name="code">
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}</pre>
<br /><br />例 2:使用多重注解(新方法)<br /><pre class="java:nogutter:nocontrols" name="code">
@Hint("hint1")
@Hint("hint2")
class Person {}</pre>
<br /><br />第二个例子里 java 编译器会隐性的帮你定义好 @Hints 注解,了解这一点有助于你用反射来获取这些信息:<br /><pre class="java:nogutter:nocontrols" name="code">
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length); // 2</pre>
<br /><br />即便我们没有在 Person 类上定义 @Hints 注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints 注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的 @Hint 注解。<br />另外 Java 8 的注解还增加到两种新的 target 上了:<br /><pre class="java:nogutter:nocontrols" name="code">
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}</pre>
<br /><br />关于 Java 8 的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8 里还有很多很有用的东西,比如 Arrays.parallelSort, StampedLock 和 CompletableFuture 等等。<br />希望该教程能对你有用. 本教程的完整的代码<a href="https://github.com/winterbe/java8-tutorial" target="_blank">放在GitHub上</a>。<a href="https://chou.it/" target="_blank">译者的博客</a>。<br />Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com1tag:blogger.com,1999:blog-5946530704742130970.post-52279852424499773042014-10-02T09:48:00.000+08:002014-10-02T09:48:42.537+08:00[轉載] Superpower your JavaScript with 10 quick tips - For Beginners轉載自:<a target="_blank" href="http://www.htmlxprs.com/post/14/superpower-your-javascript-with-10-quick-tips">Superpower your JavaScript with 10 quick tips - For Beginners</a><br />
<br />
Who doesn't love to write better code? In this article I will list 10 simple and quick JavaScript tips that will help beginners improve their code quality. You might know some of these already. In case you didn't feel free to buy me a beer later. <br />
<br />
<br />
<h3>#1 - Use && and || Operators Like a Boss</h3><br />
Well, JavaScript gives you two awesome logical operators : <code>&&</code> and <code>||</code>. In other languages like Java or C++ they always return a boolean value. But things get a bit interesting when it comes to JavaScript. The <code>&&</code> and <code>||</code> operators return the last evaluated operand. Have a look at the following<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">function getMeBeers(count){
if(count){
return count;
}
else{
return 1;
}
}
</pre><br />
Well, our function is simple and returns the required number of beers. If you specify no argument, it simply gives you a single beer. Here, you can use <code>||</code> to refactor the code as following:<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">function getMeBeers(count){
return count || 1;
}
</pre><br />
In case of <code>||</code> if the first operand is falsy, JavaScript will evaluate the next operand. So, if no count is specified while calling the function, 1 will be returned. On the other hand, if count is specified then it will be returned (as the first operand is truthy in this case).<br />
<br />
<strong>Note:</strong> <code>null</code>,<code> false</code>,<code>0</code> (the number), <code>undefined</code>, <code>NaN</code>, and <code>''</code> (empty string) are falsy values. Any other value is considered truthy.<br />
<br />
Now if you want to allow adults only, you can use <code>&&</code> as following:<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">function getMeBeers(age, count){
return (age>=18) && (count || 1);
}
</pre><br />
Instead of : <br />
<br />
<pre class="js:nogutter:nocontrols" name="code">function getMeBeers(age, count){
if(age>=18){
return count || 1;
}
}</pre><br />
In the above case if age < 18, JavaScript won't even bother evaluating the next operand. So, our function will return. But if the age is actually >= 18, the first operand will evaluate to <code>true</code> and then the next operand will be evaluated.<br />
<br />
<blockquote>By the way you should be a careful here. If you pass count as 0, the function will return 1 (as 0 is falsy). So, in a real world app you should be careful while handling numeric values.</blockquote><br />
<br />
<br />
<h3>#2 - Use === and !== Instead of == and !=</h3><br />
The operators <code>==</code> and <code>!=</code> do automatic type conversion, if needed. But <code>===</code> and <code>!==</code> will consider both value and type while comparing and won't do any automatic type conversion. So, to reliably compare two values for equality/non-equality always use <code>===</code> and <code>!==</code>. <br />
<br />
<pre class="js:nogutter:nocontrols" name="code">10 == '10' //true
10 === '10' //false
10 === 10 //true
</pre><br />
<br />
<br />
<h3>#3 - Use Strict mode while writing JS</h3><br />
Strict mode was introduced in ECMA 5. It allows you to put a function or an entire script into strict operating context. The benefits are:<br />
<br />
<ol><li>It eliminates some of the silent JavaScript errors by throwing the errors explicitly. </li>
<li>It throws exceptions when relatively unsafe actions take place.</li>
<li>Sometimes strict mode code can run faster than non strict mode code.</li>
</ol><br />
<pre class="xml:nogutter:nocontrols" name="code"><script type="text/javascript">
'use strict'
//Strict mode code goes here
</script>
</pre><br />
Or,<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">function strictCode(){
'use strict'
//This is a strict mode function
}
</pre><br />
As currently all the major browsers <a href="http://caniuse.com/#feat=use-strict" target="_blank">support</a> this feature you should start using strict mode.<br />
<br />
<br />
<br />
<h3>#4 - Use splice() to remove an array element</h3><br />
If you want to remove an element from an array use <code>splice()</code> instead of <code>delete</code>. <code>delete</code> sets the particular array item to <code>undefined</code> while <code>splice()</code> actually removes the item. <br />
<br />
<pre class="js:nogutter:nocontrols" name="code">var array=[1,2,3];
delete array[1]; // now array= [1,undefined,3]
array.splice(1,1); //now array=[1,3]
</pre><br />
<br />
<br />
<h3>#5 - Use [] to create new array</h3><br />
Use <code>[]</code> to create a new array. Writing <code>var a=[1,2,3]</code> will create a 3-element array whereas <code>new Array(N)</code> will create a physically empty array of N logical length.<br />
<br />
Similarly, use <code>var a={}</code> to create an object rather than <code>new Object()</code>. The former one is compact, readable and takes less space. You can also instantly populate the object like:<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">var a = {
name: 'John Doe',
email: 'john@doe.com'
}
</pre><br />
<br />
<br />
<h3>#6 - Use String.link() to create Hyperlinks</h3><br />
Many times in JavaScript you will need to generate HTML anchors on the fly. For that you will need some concatenation which may look ugly and messy. <br />
<br />
<pre class="js:nogutter:nocontrols" name="code">var text="Cool JS Tips";
var url="http://www.htmlxprs.com";
var block='<a href="'+url+'">'+ text +'</a>';
</pre><br />
Instead, how about this: <br />
<br />
<pre class="js:nogutter:nocontrols" name="code">var block=text.link(url); // <a href="http://www.htmlxprs.com">Cool JS Tips</a>
</pre><br />
<br />
<br />
<h3>#7 - Cache the length while looping</h3><br />
While looping through a JavaScript array you can cache the length so that the overall performance is better:<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">for (var i=0,length=array.length; i<length; i++){
//awesome code goes here
}
</pre><br />
Be careful while creating an inner loop. You need to name the length variable differently in the inner one. <br />
<br />
<br />
<br />
<h3>#8 - Put 'var' before your variable name</h3><br />
While creating variables don't forget to use the <code>var</code> keyword. If you forget then the variable gets added to the global scope which is definitely a bad thing.<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">var localVar=12; //this is a local variable
globalVar=12; //variable goes to 'window' global
</pre><br />
<br />
<br />
<h3>#9 - Use Closures and Self Executing Functions</h3><br />
Closures, if used carefully and precisely, can take your JS code to a whole new level. There are plenty of tutorials available which can teach you about <a href="http://stackoverflow.com/questions/111102/how-do-javascript-closures-work" target="_blank">closures</a>. I will just give an example of closures and self executing functions for creating modular code:<br />
<br />
<pre class="js:nogutter:nocontrols" name="code">var car=(function(){
var _name='Benz Velo';
return {
getName: function(){
return _name;
}
}
})(); //function created and invoked
console.log(car.getName()); //Logs Benz Velo
</pre><br />
As you see we just created a function and immediately invoked it. These are called self executing functions or Immediately Invoked Function Expression (IIFE). The function returns an object which is stored in variable <code>car</code>. We also used closure because the object being returned can access the variable <code>_name</code> defined by the parent function. This is helpful for creating modules and emulating private variables.<br />
<br />
<br />
<br />
<h3>#10 - There is always room for Optimization</h3><br />
Use jsHint or jsLint to check your code quality and improve further. Also don't forget to minify and concatenate your JS files while deploying your app. Finally, use <a href="http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/" target="_blank">SourceMaps</a> to make debugging a breeze.<br />
<br />
<br />
<br />
<h3>Conclusion</h3><br />
Tips can be unlimited. But I hope these 10 quick tips will help you write better code and superpower your JS-Foo. If you loved this you will love our newsletter as well. Don't forget to subscribe.<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-59681471593501217402013-08-25T16:43:00.000+08:002013-08-25T16:43:32.725+08:00[轉載] 字符串匹配的 Boyer-Moore 演算法轉載自:<a target="_blank" href="http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html">字符串匹配的Boyer-Moore算法 - 阮一峰的网络日志</a><br />
<br />
上一篇文章,我介绍了<a target="_blank" href="http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html">KMP算法</a>。<br />
<br />
但是,它并不是效率最高的算法,实际采用并不多。各种文本编辑器的"查找"功能(Ctrl+F),大多采用<a target="_blank" href="http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm">Boyer-Moore算法</a>。<br />
<br />
<img src="https://lh5.googleusercontent.com/-cj-BzPEpKys/Uhm_WXiaFnI/AAAAAAAAMPE/pKpNPHJ_CTo/s550/bg2013050301.jpg" style="border:none;" /><br />
<br />
Boyer-Moore算法不仅效率高,而且构思巧妙,容易理解。1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了这种算法。<br />
<br />
下面,我根据Moore教授自己的<a target="_blank" href="http://www.cs.utexas.edu/~moore/best-ideas/string-searching/fstrpos-example.html">例子</a>来解释这种算法。<br />
<br />
<br />
<h3>1.</h3><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA7mZngN70WjsHTljQxjUtgi8SlnAeGGQ5DaUMxVoqpqbkolZX-jTxABtH4l9FZCY4f2NU4Siv_foQufrh95Dz7O6fctzDgqRwBvAM2wpxojLi_3WZUMuVe5kdNQnaLrphoOuTDbFEfBul/s550/bg2013050302.png" style="border:none;" /><br />
<br />
假定字符串为"HERE IS A SIMPLE EXAMPLE",搜索词为"EXAMPLE"。<br />
<br />
<br />
<br />
<h3>2.</h3><img src="https://lh3.googleusercontent.com/-50fKsYPIo6o/Uhm_WS8g--I/AAAAAAAAMPA/PW3VXJi7dy4/s550/bg2013050303.png" style="border:none;" /><br />
<br />
首先,"字符串"与"搜索词"头部对齐,从尾部开始比较。<br />
<br />
这是一个很聪明的想法,因为如果尾部字符不匹配,那么只要一次比较,就可以知道前7个字符(整体上)肯定不是要找的结果。<br />
<br />
我们看到,"S"与"E"不匹配。这时,<strong>"S"就被称为"坏字符"(bad character),即不匹配的字符。</strong>我们还发现,"S"不包含在搜索词"EXAMPLE"之中,这意味着可以把搜索词直接移到"S"的后一位。<br />
<br />
<br />
<br />
<h3>3.</h3><img src="https://lh4.googleusercontent.com/-P-goa4avPp8/Uhm_WwcXKaI/AAAAAAAAMPQ/2RZ9ieAIg9I/s550/bg2013050304.png" style="border:none;" /><br />
<br />
依然从尾部开始比较,发现"P"与"E"不匹配,所以"P"是"坏字符"。但是,"P"包含在搜索词"EXAMPLE"之中。所以,将搜索词后移两位,两个"P"对齐。<br />
<br />
<br />
<br />
<h3>4.</h3><img src="https://lh5.googleusercontent.com/-b_EfsHVnk5Q/Uhm_XEE7qQI/AAAAAAAAMPk/PCdBfqnZF5o/s550/bg2013050305.png" style="border:none;" /><br />
<br />
我们由此总结出<strong>"坏字符规则"</strong>:<br />
<br />
<blockquote>后移位数 = 坏字符的位置 - 搜索词中的上一次出现位置</blockquote><br />
如果"坏字符"不包含在搜索词之中,则上一次出现位置为 -1。<br />
<br />
以"P"为例,它作为"坏字符",出现在搜索词的第6位(从0开始编号),在搜索词中的上一次出现位置为4,所以后移 6 - 4 = 2位。再以前面第二步的"S"为例,它出现在第6位,上一次出现位置是 -1(即未出现),则整个搜索词后移 6 - (-1) = 7位。<br />
<br />
<br />
<br />
<h3>5.</h3><img src="https://lh4.googleusercontent.com/-0zgkPwRsT1E/Uhm_XDhy_MI/AAAAAAAAMPc/V4clsLFS62g/s550/bg2013050306.png" style="border:none;" /><br />
<br />
依然从尾部开始比较,"E"与"E"匹配。<br />
<br />
<br />
<br />
<h3>6.</h3><img src="https://lh3.googleusercontent.com/-pBuQM1N7Np8/Uhm_XV5cU5I/AAAAAAAAMPw/ARF5MP8pFco/s550/bg2013050307.png" style="border:none;" /><br />
<br />
比较前面一位,"LE"与"LE"匹配。<br />
<br />
<br />
<br />
<h3>7.</h3><img src="https://lh3.googleusercontent.com/-EyE3Zfy_pa4/Uhm_XzJKkyI/AAAAAAAAMQE/IAtporFTSUk/s550/bg2013050308.png" style="border:none;" /><br />
<br />
比较前面一位,"PLE"与"PLE"匹配。<br />
<br />
<br />
<br />
<h3>8.</h3><img src="https://lh3.googleusercontent.com/-Sr9RTs7fS-Y/Uhm_YEoZkBI/AAAAAAAAMP0/DiDA2GEi1zc/s550/bg2013050309.png" style="border:none;" /><br />
<br />
比较前面一位,"MPLE"与"MPLE"匹配。<strong>我们把这种情况称为"好后缀"(good suffix),即所有尾部匹配的字符串。</strong>注意,"MPLE"、"PLE"、"LE"、"E"都是好后缀。<br />
<br />
<br />
<br />
<h3>9.</h3><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga33uWqMSPSCsQK6m6TGMwCGgujhWl2GwRTBBGjT-aRi-LLypF3QmCc2fxQEiNLwY9VWuLhpelVqQCwOWNqVqxL5a2C0MkmUOQOfIoje6WEBJRpIWlTkYq11CxNCeuToqIY0_zoTmbzW8G/s550/bg2013050310.png" style="border:none;" /><br />
<br />
比较前一位,发现"I"与"A"不匹配。所以,"I"是"坏字符"。<br />
<br />
<br />
<br />
<h3>10.</h3><img src="https://lh4.googleusercontent.com/-hv9P6LnBcLI/Uhm_YSVRb3I/AAAAAAAAMQQ/ZfbVSB58WVs/s550/bg2013050311.png" style="border:none;" /><br />
<br />
根据"坏字符规则",此时搜索词应该后移 2 - (-1)= 3 位。问题是,此时有没有更好的移法?<br />
<br />
<br />
<br />
<h3>11.</h3><img src="https://lh3.googleusercontent.com/-Sr9RTs7fS-Y/Uhm_YEoZkBI/AAAAAAAAMP0/DiDA2GEi1zc/s550/bg2013050309.png" style="border:none;" /><br />
<br />
我们知道,此时存在"好后缀"。所以,可以采用<strong>"好后缀规则"</strong>:<br />
<br />
<blockquote>后移位数 = 好后缀的位置 - 搜索词中的上一次出现位置</blockquote><br />
举例来说,如果字符串"ABCDAB"的后一个"AB"是"好后缀"。那么它的位置是5(从0开始计算,取最后的"B"的值),在"搜索词中的上一次出现位置"是1(第一个"B"的位置),所以后移 5 - 1 = 4位,前一个"AB"移到后一个"AB"的位置。<br />
<br />
再举一个例子,如果字符串"ABCDEF"的"EF"是好后缀,则"EF"的位置是5 ,上一次出现的位置是 -1(即未出现),所以后移 5 - (-1) = 6位,即整个字符串移到"F"的后一位。<br />
<br />
这个规则有三个注意点:<br />
<br />
<blockquote><ol><li>"好后缀"的位置以最后一个字符为准。假定"ABCDEF"的"EF"是好后缀,则它的位置以"F"为准,即5(从0开始计算)。</li>
<li>如果"好后缀"在搜索词中只出现一次,则它的上一次出现位置为 -1。比如,"EF"在"ABCDEF"之中只出现一次,则它的上一次出现位置为-1(即未出现)。</li>
<li>如果"好后缀"有多个,则除了最长的那个"好后缀",其他"好后缀"的上一次出现位置必须在头部。比如,假定"BABCDAB"的"好后缀"是"DAB"、"AB"、"B",请问这时"好后缀"的上一次出现位置是什么?回答是,此时采用的好后缀是"B",它的上一次出现位置是头部,即第0位。这个规则也可以这样表达:如果最长的那个"好后缀"只出现一次,则可以把搜索词改写成如下形式进行位置计算"(DA)BABCDAB",即虚拟加入最前面的"DA"。</li>
</ol></blockquote><br />
回到上文的这个例子。此时,所有的"好后缀"(MPLE、PLE、LE、E)之中,只有"E"在"EXAMPLE"还出现在头部,所以后移 6 - 0 = 6位。 <br />
<br />
<br />
<br />
<h3>12.</h3><img src="https://lh5.googleusercontent.com/-VSB9EwuQq2E/Uhm_YzCskfI/AAAAAAAAMQI/UAfcNWHT420/s550/bg2013050312.png" style="border:none;" /><br />
<br />
可以看到,"坏字符规则"只能移3位,"好后缀规则"可以移6位。所以,<strong>Boyer-Moore算法的基本思想是,每次后移这两个规则之中的较大值。</strong><br />
<br />
更巧妙的是,这两个规则的移动位数,只与搜索词有关,与原字符串无关。因此,可以预先计算生成《坏字符规则表》和《好后缀规则表》。使用时,只要查表比较一下就可以了。<br />
<br />
<br />
<br />
<h3>13.</h3><img src="https://lh4.googleusercontent.com/-noMwtArDrEQ/Uhm_ZZ0lAeI/AAAAAAAAMQc/hkMrYYwgL2o/s550/bg2013050313.png" style="border:none;" /><br />
<br />
继续从尾部开始比较,"P"与"E"不匹配,因此"P"是"坏字符"。根据"坏字符规则",后移 6 - 4 = 2位。<br />
<br />
<br />
<br />
<h3>14.</h3><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_S2LQBdFViamHc4pfRR3d2JxJLAkYMlbGb1qCQvU3TvhzAqc2N7Z7tlySWOkyhIJuY757AVy4C6-ntK6JhuUN6Tm9huFzfF7yZriF-dEBI9CYBrenl4IhSGV47EBcQEhUNJJ9CFImqGkI/s550/bg2013050314.png" style="border:none;" /><br />
<br />
从尾部开始逐位比较,发现全部匹配,于是搜索结束。如果还要继续查找(即找出全部匹配),则根据"好后缀规则",后移 6 - 0 = 6位,即头部的"E"移到尾部的"E"的位置。<br />
<br />
(完)<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-73330877161185483482013-08-25T16:07:00.000+08:002013-09-22T20:19:21.622+08:00[轉載] 虚数的意义轉載自:<a target="_blank" href="http://www.ruanyifeng.com/blog/2012/09/imaginary_number.html">虚数的意义 - 阮一峰的网络日志</a><br />
<br />
有人在<a target="_blank" href="http://math.stackexchange.com/questions/199676/what-are-imaginary-numbers">Stack Exchange</a>问了一个问题:<br />
<br />
<blockquote>"我一直觉得虚数(imaginary number)很难懂。<br />
<br />
中学老师说,虚数就是-1的平方根。<br />
<br />
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzaZkSRdUmaEjjkeYfMDGJXX5abu_NWfZjcczvjelPfHoCYwEtf7x1kamq55fqeA_Pg-8fSa1bO4gujxfF1Mp8I66GBzzbUae4X-Wp8mgB5l68fgdTUAqdOZveAFosFQ7AhTxsXLsv0JPe/s550/chart.png" style="border:none;" /><br />
<br />
可是,什么数的平方等于-1呢?计算器直接显示出错!<br />
<br />
直到今天,我也没有搞懂。谁能解释,虚数到底是什么?<br />
<br />
它有什么用?"<br />
</blockquote><br />
帖子的下面,很多人给出了自己的解释,还推荐了一篇非常棒的文章<a target="_blank" href="http://betterexplained.com/articles/a-visual-intuitive-guide-to-imaginary-numbers/">《虚数的图解》</a>。我读后恍然大悟,醍醐灌顶,原来虚数这么简单,一点也不奇怪和难懂!<br />
<br />
下面,我就用自己的语言,讲述我所理解的虚数。<br />
<br />
<br />
<br />
<hr /><h3>一、什么是虚数?</h3><br />
首先,假设有一根数轴,上面有两个反向的点:+1和-1。<br />
<br />
<img src="https://lh6.googleusercontent.com/-9i0-vthAwa4/Uhm4DO8InhI/AAAAAAAAMNU/PfXm56D9j6c/s550/bg2012092401.png" style="border:none;" /><br />
<br />
这根数轴的正向部分,可以绕原点旋转。显然,逆时针旋转180度,+1就会变成-1。<br />
<br />
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQR7AwsUC83eTrg8Sps3lZ6SIq6lIsplgku4EH1B6FSb7_a5l-QqoKsE7eMXKIm_qv6e5E1lG-AnFowLbw20gZ8eMeQ5TfAujrCZr0021mzM7vdRhehdrlnkl58S5dfIisQ8MwXkxd5t0/s550/bg2012092402.png" style="border:none;" /><br />
<br />
这相当于两次逆时针旋转90度。<br />
<br />
<img src="https://lh6.googleusercontent.com/-Y7p7NhtqHsw/Uhm4DFGgf7I/AAAAAAAAMNY/buuCCnmTEzk/s550/bg2012092403.png" style="border:none;" /><br />
<br />
因此,我们可以得到下面的关系式:<br />
<br />
<blockquote>(+1) * (逆时针旋转90度) * (逆时针旋转90度) = (-1)</blockquote><br />
如果把+1消去,这个式子就变为:<br />
<br />
<blockquote>(逆时针旋转90度)^2 = (-1)</blockquote><br />
将"逆时针旋转90度"记为 i :<br />
<br />
<blockquote>i^2 = (-1)</blockquote><br />
这个式子很眼熟,它就是虚数的定义公式。<br />
<br />
所以,我们可以知道,<strong>虚数 i 就是逆时针旋转90度,i 不是一个数,而是一个旋转量。</strong><br />
<br />
<br />
<br />
<hr /><h3>二、复数的定义</h3><br />
既然 i 表示旋转量,我们就可以用 i ,表示任何实数的旋转状态。<br />
<br />
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinJOH61MT64tP6rLfQ3BJ4WMDge3mkoqz8WJudits64NjirP2j9dUaoPx18Ib-cOW6L_jPrIABRBExqO-wnIFWnTwIvrERERnV66s8AkcYb5UOUe25Yxktl5GhHgzRz7UZVb3iiLnSPsf7/s550/bg2012092404.png" style="border:none;" /><br />
<br />
将实数轴看作横轴,虚数轴看作纵轴,就构成了一个二维平面。旋转到某一个角度的任何正实数,必然唯一对应这个平面中的某个点。<br />
<br />
只要确定横坐标和纵坐标,比如( 1 , i ),就可以确定某个实数的旋转量(45度)。<br />
<br />
数学家用一种特殊的表示方法,表示这个二维坐标:用 + 号把横坐标和纵坐标连接起来。比如,把 ( 1 , i ) 表示成 1 + i 。<strong>这种表示方法就叫做复数(complex number),其中 1 称为实数部,i 称为虚数部。</strong><br />
<br />
为什么要把二维坐标表示成这样呢,下一节告诉你原因。<br />
<br />
<br />
<br />
<hr /><h3>三、虚数的作用:加法</h3><br />
虚数的引入,大大方便了涉及到旋转的计算。<br />
<br />
<img src="https://lh4.googleusercontent.com/-_MpG44yTN2g/Uhm4EAA6vsI/AAAAAAAAMN0/aTU0EzBN4JY/s550/bg2012092405.png" style="border:none;" /><br />
<br />
比如,物理学需要计算"力的合成"。假定一个力是 3 + i ,另一个力是 1 + 3i ,请问它们的合成力是多少?<br />
<br />
<img src="https://lh6.googleusercontent.com/-ASXsUPrTpf4/Uhm4EBv80iI/AAAAAAAAMOE/rE2f9yygqzw/s550/bg2012092406.png" style="border:none;" /><br />
<br />
根据"平行四边形法则",你马上得到,合成力就是 ( 3 + i ) + ( 1 + 3i ) = ( 4 + 4i )。<br />
<br />
这就是虚数加法的物理意义。<br />
<br />
<br />
<br />
<hr /><h3>四、虚数的作用:乘法</h3><br />
如果涉及到旋转角度的改变,处理起来更方便。<br />
<br />
<img src="https://lh6.googleusercontent.com/-Y8wdswRAi1c/Uhm4EYJHjJI/AAAAAAAAMN4/eujo8ZlK_gk/s550/bg2012092407.png" style="border:none;" /><br />
<br />
比如,一条船的航向是 3 + 4i 。<br />
<br />
如果该船的航向,逆时针增加45度,请问新航向是多少?<br />
<br />
<img src="https://lh6.googleusercontent.com/-_mPUhQR52fI/Uhm4Ems6P_I/AAAAAAAAMOI/t-IXaCwgygw/s550/bg2012092408.png" style="border:none;" /><br />
<br />
45度的航向就是 1 + i 。计算新航向,只要把这两个航向 3 + 4i 与 1 + i 相乘就可以了(原因在下一节解释):<br />
<br />
<blockquote>( 3 + 4i ) * ( 1 + i ) = ( -1 + 7i )</blockquote><br />
所以,该船的新航向是 -1 + 7i 。<br />
<br />
如果航向逆时针增加90度,就更简单了。因为90度的航向就是 i ,所以新航向等于:<br />
<br />
<blockquote>( 3 + 4i ) * i = ( -4 + 3i )</blockquote><br />
这就是虚数乘法的物理意义:改变旋转角度。<br />
<br />
<br />
<br />
<hr /><h3>五、虚数乘法的数学证明</h3><br />
为什么一个复数改变旋转角度,只要做乘法就可以了?<br />
<br />
下面就是它的数学证明,实际上很简单。<br />
<br />
<img src="https://lh3.googleusercontent.com/-rJ16vKJA2K4/Uhm4FHxJhsI/AAAAAAAAMOU/bnrZXgnBErg/s550/bg2012092409.png" style="border:none;" /><br />
<br />
任何复数 a + bi,都可以改写成旋转半径 r 与横轴夹角 θ 的形式。<br />
<br />
假定现有两个复数 a + bi 和 c + di,可以将它们改写如下:<br />
<br />
<blockquote>a + bi = r1 * ( cosα + isinα )<br />
<br />
c + di = r2 * ( cosβ + isinβ )<br />
</blockquote><br />
<br />
这两个复数相乘,( a + bi )( c + di ) 就相当于<br />
<br />
<blockquote>r1 * r2 * ( cosα + isinα ) * ( cosβ + isinβ )</blockquote><br />
<br />
展开后面的乘式,得到<br />
<br />
<blockquote>cosα * cosβ - sinα * sinβ + i( cosα * sinβ + sinα * cosβ )</blockquote><br />
<br />
根据三角函数公式,上面的式子就等于<br />
<br />
<blockquote>cos(α+β) + isin(α+β)</blockquote><br />
<br />
所以,<br />
<br />
<blockquote>( a + bi )( c + di ) = r1 * r2 * ( cos(α+β) + isin(α+β) )</blockquote><br />
<br />
这就证明了,两个复数相乘,就等于旋转半径相乘、旋转角度相加。<br />
<br />
(完)<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-23030989327272617592013-08-25T13:56:00.000+08:002014-10-08T13:29:31.867+08:00[轉載] 調校 SQL 以徹底改善應用程式效能轉載自:<a target="_blank" href="http://blog.xuite.net/j2ee/code/15120677">調校 SQL 以徹底改善應用程式效能</a><br />
<br />
有些程式員在撰寫前端的應用程式時,會透過各種 OOP 語言將存取資料庫的 SQL 陳述式串接起來,卻忽略了 SQL 語法的效能問題。版工曾聽過某半導體大廠的新進程式員,所兜出來的一段 PL/SQL 跑了好幾分鐘還跑不完;想當然爾,即使他前端的 AJAX 用得再漂亮,程式效能頂多也只是差強人意而已。以下是版工整理出的一些簡單心得,讓長年鑽究 ASP.NET / JSP / AJAX 等前端應用程式,卻無暇研究 SQL 語法的程式員,避免踩到一些 SQL 的效能地雷。<br />
<br />
<br />
<strong>1、資料庫設計與規劃</strong><br />
<ul><li>Primary Key 欄位的長度儘量小,能用 small integer 就不要用 integer。例如員工資料表,若能用員工編號當主鍵,就不要用身分證字號。</li>
<li>一般欄位亦同。若該資料表要存放的資料不會超過 3 萬筆,用 small integer 即可,不必用 integer。</li>
<li>文字資料欄位若長度固定,如:身分證字號,就不要用 varchar 或 nvarchar,應該用 char 或 nchar。</li>
<li>文字資料欄位若長度不固定,如:地址,則應該用 varchar 或 nvarchar。除了可節省儲存空間外,存取磁碟時也會較有效率。</li>
<li>設計欄位時,若其值可有可無,最好也給一個預設值,並設成「不允許 NULL」(一般欄位預設為「允許 NULL」)。因為 SQL Server 在存放和查詢有 NULL 的資料表時,會花費額外的運算動作 [2]。</li>
<li>若一個資料表的欄位過多,應垂直切割成兩個以上的資料表,並用同名的 Primary Key 一對多連結起來,如:Northwind 的 Orders、Order Details 資料表。以避免在存取資料時,以叢集索引掃描時會載入過多的資料,或修改資料時造成互相鎖定或鎖定過久。</li>
</ul><br />
<br />
<hr /><strong>2、適當地建立索引</strong><br />
<ul><li>記得自行幫 Foreign Key 欄位建立索引,即使是很少被 JOIN 的資料表亦然。</li>
<li>替常被查詢或排序的欄位建立索引,如:常被當作 WHERE 子句條件的欄位。</li>
<li>用來建立索引的欄位,長度不宜過長,不要用超過 20 個位元組的欄位,如:地址。</li>
<li>不要替內容重複性高的欄位建立索引,如:性別;反之,若重複性低的欄位則適合建立索引,如:姓名。</li>
<li>不要替使用率低的欄位建立索引。</li>
<li>不宜替過多欄位建立索引,否則反而會影響到新增、修改、刪除的效能,尤其是以線上交易 (OLTP) 為主的網站資料庫。</li>
<li>若資料表存放的資料很少,就不必刻意建立索引。否則可能資料庫沿著索引樹狀結構去搜尋索引中的資料,反而比掃描整個資料表還慢。</li>
<li>若查詢時符合條件的資料很多,則透過「非叢集索引」搜尋的效能,可能反而不如整個資料表逐筆掃描。</li>
<li>建立「叢集索引」的欄位選擇至為重要,會影響到整個索引結構的效能。要用來建立「叢集索引」的欄位,務必選擇「整數」型別 (鍵值會較小)、唯一、不可為 NULL。</li>
</ul><br />
<br />
<hr /><strong>3、適當地使用索引</strong><br />
<ul><li>有些書籍會提到,使用 LIKE、% 做模糊查詢時,即使您已替某個欄位建立索引 (如下方例子的 CustomerID),但以常數字元開頭才會使用到索引,若以萬用字元 (%) 開頭則不會使用索引,如下所示:<br />
<pre class="sql:nogutter:nocontrols" name="code">USE Northwind;
GO
SELECT * FROM Orders WHERE CustomerID LIKE 'D%'; --使用索引
SELECT * FROM Orders WHERE CustomerID LIKE '%D'; --不使用索引
</pre><br />
執行完成後按 Ctrl+L,可檢閱如下圖的「執行計畫」。<br />
<br />
<img src="https://lh3.googleusercontent.com/-KBw56MgqA20/UhmXa87iZmI/AAAAAAAANHg/8VzDH18EwyM/s512/mssql-optimization-1.jpg" style="border:none;" /><br />
圖 1 可看出「查詢最佳化程式」有使用到索引做搜尋<br />
<br />
<img src="https://lh3.googleusercontent.com/-VfW8IGvmdn0/UhmXa8a2ClI/AAAAAAAANHg/X2XWjCDM5DA/s512/mssql-optimization-2.jpg" style="border:none;" /><br />
圖 2 在此的叢集索引掃描,並未直接使用索引,效能上幾乎只等於掃描整個資料表<br />
<br />
但經版工反覆測試,這種語法是否會使用到索引,抑或會逐筆掃描,並非絕對的。仍要看所下的查詢關鍵字,以及欄位內儲存的資料內容而定。但對於儲存資料筆數龐大的資料表,最好還是少用 LIKE 做模糊查詢。<br />
<br />
</li>
<li>以下的運算子會造成「負向查詢」,常會讓「查詢最佳化程式」無法有效地使用索引,最好能用其他運算子和語法改寫 (經版工測試,並非有負向運算子,就絕對無法使用索引):<br />
<code>NOT 、 != 、 <> 、 !> 、 !< 、 NOT EXISTS 、 NOT IN 、 NOT LIKE</code></li>
<li>避免讓 WHERE 子句中的欄位,去做字串串接或數字運算,否則可能導致「查詢最佳化程式」無法直接使用索引,而改採叢集索引掃描 (經版工測試並非絕對)。</li>
<li>資料表中的資料,會依照「叢集索引」欄位的順序存放,因此當您下 BETWEEN、GROUP BY、ORDER BY 時若有包含「叢集索引」欄位,由於資料已在資料表中排序好,因此可提升查詢速度。</li>
<li>若使用「複合索引」,要注意索引順序上的第一個欄位,才適合當作過濾條件。</li>
</ul><br />
<br />
<hr /><strong>4、避免在 WHERE 子句中對欄位使用函數</strong><br />
<br />
對欄位使用函數,也等於對欄位做運算或串接的動作,一樣可能會讓「查詢最佳化程式」無法有效地使用索引。但真正對效能影響最重大的,是當您的資料表內若有 10 萬筆資料,則在查詢時就需要呼叫函數 10 萬次,這點才是真正的效能殺手。程式員應注意,在系統開發初期可能感覺不出差異,但當系統上線且資料持續累積後,這些語法細節所造成的效能問題就會逐步浮現。<br />
<br />
<pre class="sql:nogutter:nocontrols" name="code">SELECT * FROM Orders WHERE DATEPART(yyyy, OrderDate) = 1996 AND DATEPART(mm, OrderDate)=7
-- 可改成
SELECT * FROM Orders WHERE OrderDate BETWEEN '19960701' AND '19960731'
</pre><br />
<pre class="sql:nogutter:nocontrols" name="code">SELECT * FROM Orders WHERE SUBSTRING(CustomerID, 1, 1) = 'D'
-- 可改成
SELECT * FROM Orders WHERE CustomerID LIKE 'D%'
</pre><br />
注意當您在下 UPDATE、DELETE 陳述式時,若有採用 WHERE 子句,也應符合上述原則。<br />
<br />
<br />
<hr /><strong>5、AND 與 OR 的使用</strong><br />
<br />
在 AND 運算中,「只要有一個」條件有用到索引 (如下方的 CustomerID),即可大幅提升查詢速度,如下圖 3 所示:<br />
<br />
<pre class="sql:nogutter:nocontrols" name="code">SELECT * FROM Orders WHERE CustomerID='VINET' AND Freight=32.3800
--使用索引,會出現下圖 3 的畫面
</pre><br />
<pre class="sql:nogutter:nocontrols" name="code">SELECT * FROM Orders WHERE Freight=32.3800
--不使用索引,會出現上圖 2 的畫面
</pre><br />
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigWdMoBqfL-DZyFX9iHyM6ExH9ciZB8u86JuWTJJSLnoJ7auH2UUgh93GyPxpwrJrMb8-M_0Fswvw-UUbLv2TB2fCEqpZB-b_uQ51Xu3Clafxj4YY4OUVus9TPilPhdX6LDqsgCfVx7JE8/s512/mssql-optimization-3.jpg" style="border:none;" /><br />
圖 3<br />
<br />
但在 OR 運算中,則要「所有的」條件都有可用的索引,才能使用索引來提升查詢速度。因此 OR 運算子的使用必須特別小心。<br />
<br />
若您將上方 AND 的範例,邏輯運算子改成 OR 的話,如下所示:<br />
<br />
<pre class="sql:nogutter:nocontrols" name="code">SELECT * FROM Orders WHERE CustomerID='VINET' OR Freight=32.3800
</pre><br />
由於無法有效地使用索引,也會出現圖 2 的畫面。<br />
<br />
在使用 OR 運算子時,只要有一個條件 (欄位) 沒有可用的索引,則其他所有的條件 (欄位) 都有索引也沒用,只能如圖 2 般,把整個資料表或整個叢集索引都掃描過,以逐筆比對是否有符合條件的資料。<br />
<br />
<br />
據網路上文件的說法 [1],上述的 OR 運算陳述式,我們還可用 UNION 聯集適當地改善,如下:<br />
<br />
<pre class="sql:nogutter:nocontrols" name="code">SELECT * FROM Orders WHERE CustomerID='VINET'
UNION
SELECT * FROM Orders WHERE Freight=32.3800
</pre><br />
此時您再按 Ctrl+L 檢閱「執行計畫」,會發現上半段的查詢會使用索引,但下半段仍用叢集索引掃描,對效能不無小補。<br />
<br />
<br />
<hr /><strong>6、適當地使用子查詢</strong><br />
<br />
相較於「子查詢 (Subquery)」,若能用 JOIN 完成的查詢,一般會比較建議使用後者。原因除了 JOIN 的語法較容易理解外,在多數的情況下,JOIN 的效能也會比子查詢較佳;但這並非絕對,也有的情況可能剛好相反。<br />
<br />
我們知道子查詢可分為「獨立子查詢」和「關聯子查詢」兩種,前者指子查詢的內容可單獨執行,後者則無法單獨執行,亦即外層查詢的「每一次」查詢動作都需要引用內層查詢的資料,或內層查詢的「每一次」查詢動作都需要參考外層查詢的資料。<br />
<br />
以下我們看一個比較極端的例子 [2]。若我們希望所有查詢出來的資料,都能另外給一個自動編號,版工我在之前的文章「用 SQL Server 2005 新增的 ROW_NUMBER 函數撰寫 GridView 分頁」中有介紹過,可用 SQL Server 2005 中新增的 ROW_NUMBER 函數輕易地達成,且 ROW_NUMBER 函數還能再加上「分群 (PARTITION BY)」等功能,而且執行效能極佳。<br />
<br />
<img src="https://lh6.googleusercontent.com/-kuVFN51A_XQ/UhmXbQA3D5I/AAAAAAAANHg/TSuJR5s2sMA/s512/mssql-optimization-4.jpg" style="border:none;" /><br />
圖 4 將 Orders 資料表的 830 筆資料都撈出來,並在右側給一組自動編號<br />
<br />
現在我們要如上圖 4 般,將 Northwind 中 Orders 資料表的 830 筆資料都撈出來,並自動給一組編號,若用 ROW_NUMBER 函數的寫法如下所示,而且效能極佳,只要 2 ms (毫秒),亦即千分之二秒。<br />
<br />
<pre class="sql:nogutter:nocontrols" name="code">SET STATISTICS TIME ON
SELECT OrderID, ROW_NUMBER() OVER(ORDER BY OrderID) AS 編號
FROM dbo.Orders
</pre><br />
但如果是在舊版的 SQL Server 2000 中,我們可能得用以下的「子查詢」寫法:<br />
<br />
<pre class="sql:nogutter:nocontrols" name="code">SET STATISTICS TIME ON
SELECT OrderID,
(SELECT COUNT(*) FROM dbo.Orders AS 內圈
WHERE 內圈.OrderID <= 外圈.OrderID) AS 編號
FROM dbo.Orders AS 外圈
ORDER BY 編號
</pre><br />
但這種舊寫法,會像先前所提到的,外層查詢的「每一次」查詢動作都需要引用內層查詢的資料。以上方例子而言,外層查詢的每一筆資料,都要等內層查詢「掃描整個資料表」並作比對和計數,因此 830 筆資料每一筆都要重複掃描整個資料表 830 次,所耗用的時間也因此爆增至 170 ms。<br />
<br />
若您用相同的寫法,去查詢 AdventureWorks 資料庫中,有 31,465 筆資料的 Sales.SalesOrderHeader 資料表,用 ROW_NUMBER 函數要 677 ms,還不到 1 秒鐘;但用子查詢的話,居然要高達 225,735 ms,將近快 4 分鐘的時間。<br />
<br />
雖然這是較極端的範例,但由此可知子查詢的撰寫,在使用上不可不慎,尤其是「關聯子查詢」。程式員在程式開發初期、資料量還很少時感受不到此種 SQL 語法的重大陷阱;但等到系統上線幾個月或一兩年後,可能就會有反應遲緩的現象。<br />
<br />
<br />
<hr /><strong>7、其他查詢技巧</strong><br />
<ul><li>DISTINCT、ORDER BY 語法,會讓資料庫做額外的計算。此外聯集的使用,若沒有要剔除重複資料的需求,使用 UNION ALL 會比 UNION 更佳,因為後者會加入類似 DISTINCT 的演算法。</li>
<li>在 SQL Server 2005 版本中,存取資料庫物件時,最好明確指定該物件的「結構描述 (Schema)」,也就是使用兩節式名稱。否則若呼叫者的預設 Schema 不是 dbo,則 SQL Server 在執行時,會先尋找該使用者預設 Schema 所搭配的物件,找不到的話才會轉而使用預設的 dbo,會多耗費尋找的時間。例如若要執行一個叫做 dbo.mySP1 的 Stored Procedure,應使用以下的兩節式名稱:<br />
<pre class="sql:nogutter:nocontrols" name="code">EXEC dbo.mySP1
</pre></li>
</ul><br />
<br />
<hr /><strong>8、儘可能用 Stored Procedure 取代前端應用程式直接存取資料表</strong><br />
<br />
Stored Procedure 除了經過事先編譯、效能較好以外,亦可節省 SQL 陳述式傳遞的頻寬,也方便商業邏輯的重複使用。再搭配自訂函數和 View 的使用,將來若要修改資料表結構、重新切割或反正規化時亦較方便。<br />
<br />
<br />
<hr /><strong>9、儘可能在資料來源層,就先過濾資料</strong><br />
<br />
使用 SELECT 語法時,儘量避免傳回所有的資料至前端而不設定 WHERE 等過濾條件。雖然 ASP.NET 中 SqlDataSource、ObjectDataSource 控制項的 FilterExpression 可再做篩選,GridView 控制項的 SortExpression 可再做排序,但會多消耗掉資料庫的系統資源、Web server 的記憶體和網路頻寬。最好還是在資料庫和資料來源層,就先用 SQL 條件式篩選出所要的資料。<br />
<br />
<br />
<hr /><strong>結論:</strong><br />
<br />
本文的觀念,不管是寫 SQL statement、Stored Procedure、自訂函數或 View 皆然。本文只是挑出程式員較容易犯的 SQL 語法效能問題,以期能在短時間瀏覽過本文後,在寫 ADO.NET 程式時能修正以往隨興的 SQL 撰寫習慣。文中提到的幾點,只不過是 SQL 語法效能議題的入門篇。後續有時間的話,版工會再補充在本帖的回應留言,或另開新主題。<br />
<br />
<br />
<hr /><strong>參考文件:</strong><br />
<br />
[1] SQL查詢最佳化 (網際烏托邦):<br />
<a target="_blank" href="http://www.ithome.com.tw/plog/index.php?op=ViewArticle&articleId=5421&blogId=620">http://www.ithome.com.tw/plog/index.php?op=ViewArticle&articleId=5421&blogId=620</a><br />
<br />
<strong>參考書籍:</strong><br />
<br />
[2] SQL Server 2005 Performance Tuning 效能調校:<br />
作者:胡百敬、姚巧枚、劉承修<br />
出版社:悅知出版社<br />
<a target="_blank" href="http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9789866761225&sid=41966">http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9789866761225&sid=41966</a><br />
<br />
[3] SQL Server 2005 完全實戰:<br />
作者:章立民<br />
出版社:碁峰出版社<br />
<br />
<strong>相關文件:</strong><br />
<br />
[4] 臺大醫院資料庫分割疏失,系統幾近停擺 (ITHome):<br />
<a target="_blank" href="http://www.ithome.com.tw/itadm/article.php?c=43597">http://www.ithome.com.tw/itadm/article.php?c=43597</a><br />
<br />
[5] 當DataGrid遇見100萬筆資料:<br />
<a target="_blank" href="http://blog.sina.com.tw/4907/article.php?pbgid=4907&entryid=3921">http://blog.sina.com.tw/4907/article.php?pbgid=4907&entryid=3921</a><br />
<br />
[6] ASP.NET 2.0 GridView 範例集 - 「4-8-4、GridView的效能」:<br />
<a target="_blank" href="http://blog.csdn.net/Code6421/archive/2007/12/22/1958167.aspx">http://blog.csdn.net/Code6421/archive/2007/12/22/1958167.aspx</a><br />
<br />
[7] 有關開啟頁面時,一次載入數千筆資料的效能問題:<br />
<a target="_blank" href="http://www.blueshop.com.tw:80/board/show.asp?subcde=BRD200709141021458MV">http://www.blueshop.com.tw:80/board/show.asp?subcde=BRD200709141021458MV</a><br />
<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-51159845162601336772013-08-17T12:27:00.000+08:002013-08-20T20:25:49.563+08:00[轉載] 公司職稱中英文對照表轉載自:<a target="_blank" href="http://wywu.pixnet.net/blog/post/26354355">公司職稱中英文對照表</a><br />
<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"><th width="25%">中文職稱</th><th>英文職稱</th><th>英文縮寫 </th></tr>
<tr><td>董事長 / 會長</td><td>Chairman</td><td> </td></tr>
<tr><td>副董事長 / 副會長</td><td>Vice Chairman</td><td> </td></tr>
<tr><td>監事</td><td>Supervisor</td><td> </td></tr>
<tr><td>常務監事</td><td>Managing Supervisor</td><td> </td></tr>
<tr><td>董事 / 理事 / 總監</td><td>Director</td><td> </td></tr>
<tr><td>董事總經理</td><td>Managing Director</td><td>MD</td></tr>
<tr><td>執行董事 / 常務董事</td><td>Executive Director</td><td>ED</td></tr>
<tr><td>副董事 / 助理董事</td><td>Associate Director</td><td>AD</td></tr>
<tr><td>執行長</td><td>Chief Executive</td><td> </td></tr>
<tr><td>執行長</td><td>Chief Executive Officer</td><td>CEO</td></tr>
<tr><td>財務長</td><td>Chief Financial Officer</td><td>CFO</td></tr>
<tr><td>資訊長</td><td>Chief Information Officer</td><td>CIO</td></tr>
<tr><td>知識長</td><td>Chief Knowledge Officer</td><td>CKO</td></tr>
<tr><td>營運長</td><td>Chief Operating Officer</td><td>COO</td></tr>
<tr><td>技術長</td><td>Chief Technology Officer</td><td>CTO</td></tr>
<tr><td>管理長</td><td>Chief Administrative Officer</td><td>CAO</td></tr>
<tr><td>行銷長</td><td>Chief Marketing Officer</td><td>CMO</td></tr>
<tr><td>投資長</td><td>Chief Investment Officer</td><td>CIO</td></tr>
<tr><td>總裁 / 社長</td><td>President</td><td> </td></tr>
<tr><td>執行副總裁</td><td>Executive Vice President</td><td>EVP</td></tr>
<tr><td>資深副總裁</td><td>Senior Vice President</td><td>SVP</td></tr>
<tr></tr>
<tr><td>副總裁 / 副社長</td><td>Vice President</td><td>VP</td></tr>
<tr><td>協理副總裁</td><td>Associate Vice President</td><td>AVP</td></tr>
<tr><td>助理副總裁 / 協理</td><td>Assistant Vice President</td><td>AVP</td></tr>
<tr><td>總經理</td><td>General Manager</td><td>GM</td></tr>
<tr><td>副總經理</td><td>Vice General Manager / Deputy General Manage</td><td>VGM / DGM</td></tr>
<tr><td>副總經理 / 協理</td><td>Associate General Manager / Assistant General Manager</td><td>AGM</td></tr>
<tr><td>執行助理</td><td>Executive Assistant</td><td> </td></tr>
<tr><td>特別助理</td><td>Special Assistant</td><td> </td></tr>
<tr><td>顧問</td><td>Adviser / Consultant</td><td> </td></tr>
<tr><td>處長 / 協理</td><td>Director</td><td> </td></tr>
<tr><td>副處長</td><td>Vice Director / Deputy Director</td><td> </td></tr>
<tr><td>資深經理</td><td>Senior Manager</td><td> </td></tr>
<tr><td>經理</td><td>Manager</td><td> </td></tr>
<tr><td>副理 / 襄理</td><td>Deputy Manager / Assistant Manager</td><td> </td></tr>
<tr><td>襄理</td><td>Junior Manager / Senior Officer</td><td> </td></tr>
<tr><td>部門經理 / 課長</td><td>Section Manager</td><td> </td></tr>
<tr><td>部門副理 / 副課長</td><td>Deputy Section Manager</td><td> </td></tr>
<tr><td>廠長</td><td>Factory Manager / Plant Manager / Factory Director</td><td> </td></tr>
<tr><td>副廠長</td><td>Deputy Factory Manager</td><td> </td></tr>
<tr><td>專案經理</td><td>Project Manager</td><td>PM</td></tr>
<tr><td>主任 / 組長</td><td>Supervisor / Director / Chief</td><td> </td></tr>
<tr><td>業務主任</td><td>Sales Executive</td><td> </td></tr>
<tr><td>專案專員 / 業務專員</td><td>Account Officer</td><td>AO</td></tr>
<tr><td>專案執行</td><td>Account Executive</td><td>AE</td></tr>
<tr><td>領班 / 組長</td><td>Team Leader</td><td> </td></tr>
<tr><td>專員</td><td>Specialist (Officer)</td><td> </td></tr>
<tr><td>儲備幹部</td><td>Management Associate</td><td>MA</td></tr>
<tr><td>儲備幹部</td><td>Management Trainee</td><td>MT</td></tr>
<tr><td>代表</td><td>Representative</td><td> </td></tr>
<tr><td>管理師</td><td>Administrator</td><td> </td></tr>
<tr><td>會計</td><td>Accountant</td><td> </td></tr>
<tr><td>稽核</td><td>Auditor</td><td> </td></tr>
<tr><td>工程師</td><td>Engineer</td><td> </td></tr>
<tr><td>研究員 / 分析師</td><td>Analyst</td><td> </td></tr>
<tr><td>秘書</td><td>Secretary</td><td> </td></tr>
<tr><td>辦事員 / 事務員</td><td>Clerk</td><td> </td></tr>
<tr><td>職員</td><td>Staff</td><td> </td></tr>
<tr><td>作業員</td><td>Operator</td><td> </td></tr>
<tr><td>技術員</td><td>Technician</td><td> </td></tr>
<tr><td>助理</td><td>Assistant</td><td> </td></tr>
</table>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0