﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>语源科技BlogJava-学习知识</title><link>http://www.blogjava.net/linlin-2000/</link><description>让知识来充实大脑</description><language>zh-cn</language><lastBuildDate>Sun, 12 Apr 2026 06:10:56 GMT</lastBuildDate><pubDate>Sun, 12 Apr 2026 06:10:56 GMT</pubDate><ttl>60</ttl><item><title>字符，字节和编码</title><link>http://www.blogjava.net/linlin-2000/archive/2006/04/26/43250.html</link><dc:creator>00</dc:creator><author>00</author><pubDate>Wed, 26 Apr 2006 05:08:00 GMT</pubDate><guid>http://www.blogjava.net/linlin-2000/archive/2006/04/26/43250.html</guid><wfw:comment>http://www.blogjava.net/linlin-2000/comments/43250.html</wfw:comment><comments>http://www.blogjava.net/linlin-2000/archive/2006/04/26/43250.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/linlin-2000/comments/commentRss/43250.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/linlin-2000/services/trackbacks/43250.html</trackback:ping><description><![CDATA[转载自<a href="http://www.regexlab.com/zh/encoding.htm">http://www.regexlab.com/zh/encoding.htm</a><br /><h2><a name="main"></a>字符，字节和编码</h2><p><font size="1">[原创文章，转载请保留或注明出处：<a href="http://www.regexlab.com/zh/encoding.htm">http://www.regexlab.com/zh/encoding.htm</a>]</font></p><p>级别：初级</p><blockquote><p>摘要：本文介绍了字符与编码的发展过程，相关概念的正确理解。举例说明了一些实际应用中，编码的实现方法。然后，本文讲述了通常对字符与编码的几种误解，由于这些误解而导致乱码产生的原因，以及消除乱码的办法。本文的内容涵盖了“中文问题”，“乱码问题”。</p></blockquote><h4><a name="intro"></a>引言</h4><p>“字符与编码”是一个被经常讨论的话题。即使这样，时常出现的乱码仍然困扰着大家。虽然我们有很多的办法可以用来消除乱码，但我们并不一定理解这些办法的内在原理。而有的乱码产生的原因，实际上由于底层代码本身有问题所导致的。因此，不仅是初学者会对字符编码感到模糊，有的底层开发人员同样对字符编码缺乏准确的理解。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h4><a name="develop"></a>1. 编码问题的由来，相关概念的理解</h4><h5>1.1 字符与编码的发展</h5><p>从计算机对多国语言的支持角度看，大致可以分为三个阶段：</p><table cellspacing="0" cellpadding="3" border="0"><tbody><tr><td class="top_1">　</td><td class="top_2" nowrap="" align="middle"><b>系统内码</b></td><td class="top_2" align="middle"><b>说明</b></td><td class="top_2" align="middle"><b>系统</b></td></tr><tr><td class="con_1" nowrap="">阶段一</td><td class="con_2" nowrap="" align="middle">ASCII</td><td class="con_2">计算机刚开始只支持英语，其它语言不能够在计算机上存储和显示。</td><td class="con_2">英文 DOS</td></tr><tr><td class="con_1" nowrap="">阶段二</td><td class="con_2" nowrap="" align="middle">ANSI编码<br />（本地化）</td><td class="con_2">为使计算机支持更多语言，通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如：汉字 '中' 在中文操作系统中，使用 [0xD6,0xD0] 这两个字节存储。<br /><br />不同的国家和地区制定了不同的标准，由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式，称为<b> ANSI 编码</b>。在简体中文系统下，ANSI 编码代表 GB2312 编码，在日文操作系统下，ANSI 编码代表 JIS 编码。<br /><br />不同 ANSI 编码之间互不兼容，当信息在国际间交流时，无法将属于两种语言的文字，存储在同一段<b> ANSI 编码</b>的文本中。</td><td class="con_2">中文 DOS，中文 Windows 95/98，日文 Windows 95/98</td></tr><tr><td class="bot_1" nowrap="">阶段三</td><td class="bot_2" nowrap="" align="middle">UNICODE<br />（国际化）</td><td class="bot_2">为了使国际间信息交流更加方便，国际组织制定了 <b>UNICODE 字符集</b>，为各种语言中的每一个字符设定了统一并且唯一的数字编号，以满足跨语言、跨平台进行文本转换、处理的要求。</td><td class="bot_2">Windows NT/2000/XP，Linux，Java</td></tr></tbody></table><p>字符串在内存中的存放方法：</p><p>在 ASCII 阶段，<b>单字节字符串</b>使用一个字节存放一个字符（SBCS）。比如，"Bob123" 在内存中为：</p><table style="FONT-SIZE: 80%; COLOR: #000080" cellspacing="5" cellpadding="0" border="0"><tbody><tr><td>42</td><td>6F</td><td>62</td><td>31</td><td>32</td><td>33</td><td>00</td></tr><tr><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td></tr><tr><td align="middle">B</td><td align="middle">o</td><td align="middle">b</td><td align="middle">1</td><td align="middle">2</td><td align="middle">3</td><td align="middle">\0</td></tr></tbody></table><p>在使用 ANSI 编码支持多种语言阶段，每个字符使用一个字节或多个字节来表示（MBCS），因此，这种方式存放的字符也被称作<b>多字节字符</b>。比如，"中文123" 在中文 Windows 95 内存中为7个字节，每个汉字占2个字节，每个英文和数字字符占1个字节：</p><table style="FONT-SIZE: 80%; COLOR: #000080" cellspacing="5" cellpadding="0" border="0"><tbody><tr><td>D6</td><td>D0</td><td>CE</td><td>C4</td><td>31</td><td>32</td><td>33</td><td>00</td></tr><tr><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#000080"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td></tr><tr><td align="middle" colspan="2">中</td><td align="middle" colspan="2">文</td><td align="middle">1</td><td align="middle">2</td><td align="middle">3</td><td align="middle">\0</td></tr></tbody></table><p>在 UNICODE 被采用之后，计算机存放字符串时，改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节（16 位）来存放一个序号（DBCS），因此，这种方式存放的字符也被称作<b>宽字节字符</b>。比如，字符串 "中文123" 在 Windows 2000 下，内存中实际存放的是 5 个序号：</p><table style="FONT-SIZE: 80%; COLOR: #000080" cellspacing="5" cellpadding="0" border="0"><tbody><tr><td valign="bottom">2D</td><td valign="bottom">4E</td><td valign="bottom">87</td><td valign="bottom">65</td><td valign="bottom">31</td><td valign="bottom">00</td><td valign="bottom">32</td><td valign="bottom">00</td><td valign="bottom">33</td><td valign="bottom">00</td><td valign="bottom">00</td><td valign="bottom">00</td><td><font color="#808080">     ← 在 x86 CPU 中，低字节在前</font></td></tr><tr><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td bgcolor="#ff0000" colspan="2"><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td><td><img height="1" src="http://www.regexlab.com/images/spacer.gif" width="1" border="0" /></td></tr><tr><td align="middle" colspan="2">中</td><td align="middle" colspan="2">文</td><td align="middle" colspan="2">1</td><td align="middle" colspan="2">2</td><td align="middle" colspan="2">3</td><td align="middle" colspan="2">\0</td><td align="middle">　</td></tr></tbody></table><p>一共占 10 个字节。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5><a name="concept"></a>1.2 字符，字节，字符串</h5><p>理解编码的关键，是要把字符的概念和字节的概念理解准确。这两个概念容易混淆，我们在此做一下区分：</p><table cellspacing="0" cellpadding="3" border="0"><tbody><tr><td class="top_1">　</td><td class="top_2" align="middle"><b>概念描述</b></td><td class="top_2" align="middle"><b>举例</b></td></tr><tr><td class="con_1" nowrap="" align="middle">字符</td><td class="con_2">人们使用的记号，抽象意义上的一个符号。</td><td class="con_2">'1', '中', 'a', '$', '￥', ……</td></tr><tr><td class="con_1" nowrap="" align="middle">字节</td><td class="con_2">计算机中存储数据的单元，一个8位的二进制数，是一个很具体的存储空间。</td><td class="con_2">0x01, 0x45, 0xFA, ……</td></tr><tr><td class="con_1" nowrap="" align="middle">ANSI<br />字符串</td><td class="con_2">在内存中，如果“字符”是以 <b>ANSI 编码</b>形式存在的，一个字符可能使用一个字节或多个字节来表示，那么我们称这种字符串为 <b>ANSI 字符串</b>或者<b>多字节字符串</b>。</td><td class="con_2">"中文123"<br /><span class="rem"><font color="#339933">（占7字节）</font></span></td></tr><tr><td class="bot_1" nowrap="" align="middle">UNICODE<br />字符串</td><td class="bot_2">在内存中，如果“字符”是以在 UNICODE 中的序号存在的，那么我们称这种字符串为 <b>UNICODE 字符串</b>或者<b>宽字节字符串</b>。</td><td class="bot_2">L"中文123"<br /><span class="rem"><font color="#339933">（占10字节）</font></span></td></tr></tbody></table><p>由于不同 ANSI 编码所规定的标准是不相同的，因此，对于一个给定的<b>多字节字符串</b>，我们必须知道它采用的是哪一种编码规则，才能够知道它包含了哪些“字符”。而对于 <b>UNICODE 字符串</b>来说，不管在什么环境下，它所代表的“字符”内容总是不变的。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>1.3 字符集与编码</h5><p>各个国家和地区所制定的不同 ANSI 编码标准中，都只规定了各自语言所需的“字符”。比如：汉字标准（GB2312）中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义：</p><ol><li>使用哪些字符。也就是说哪些汉字，字母和符号会被收入标准中。所包含“字符”的集合就叫做“<b>字符集</b>”。 
</li><li>规定每个“字符”分别用一个字节还是多个字节存储，用哪些字节来存储，这个规定就叫做“<b>编码</b>”。 </li></ol><p>各个国家和地区在制定编码标准的时候，“字符的集合”和“编码”一般都是同时制定的。因此，平常我们所说的“字符集”，比如：GB2312, GBK, JIS 等，除了有“字符的集合”这层含义外，同时也包含了“编码”的含义。</p><p>“<b>UNICODE 字符集</b>”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种，比如：UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h4><a name="implement"></a>2. 字符与编码在程序中的实现</h4><h5>2.1 程序中的字符与字节</h5><p>在 C++ 和 Java 中，用来代表“字符”和“字节”的数据类型，以及进行编码的方法：</p><table cellspacing="0" cellpadding="3" border="0"><tbody><tr><td class="top_1" align="middle"><b>类型或操作</b></td><td class="top_2" align="middle"><b>C++</b></td><td class="top_2" align="middle"><b>Java</b></td></tr><tr><td class="con_1" nowrap="" align="middle">字符</td><td class="con_2">wchar_t</td><td class="con_2">char<font color="#ff0000"><sup> *</sup></font></td></tr><tr><td class="con_1" nowrap="" align="middle">字节</td><td class="con_2">char</td><td class="con_2">byte</td></tr><tr><td class="con_1" nowrap="" align="middle">ANSI 字符串</td><td class="con_2">char[]</td><td class="con_2">byte[]</td></tr><tr><td class="con_1" nowrap="" align="middle">UNICODE 字符串</td><td class="con_2">wchar_t[]</td><td class="con_2">String</td></tr><tr><td class="con_1" nowrap="" align="middle">字节串→字符串</td><td class="con_2">mbstowcs(), MultiByteToWideChar() <font color="#ff0000"><sup>*</sup></font></td><td class="con_2">string = new String(bytes, "encoding")</td></tr><tr><td class="bot_1" nowrap="" align="middle">字符串→字节串</td><td class="bot_2">wcstombs(), WideCharToMultiByte()</td><td class="bot_2">bytes = string.getBytes("encoding")</td></tr></tbody></table><p>以上需要注意几点：</p><ol><li>Java 中的 char 代表一个“UNICODE 字符（宽字节字符）”，而 C++ 中的 char 代表一个字节。 
</li><li>MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函数。 </li></ol><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>2.2 C++ 中相关实现方法</h5><p>声明一段字符串常量：</p><table cellspacing="0" cellpadding="6" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><span class="rem"><font color="#339933">// ANSI 字符串，内容长度 7 字节</font></span><span class="key"><br /><font color="#0000ff">char</font></span>     sz[<span class="number"><font color="#6d66a5">20</font></span>] = <span class="string"><font color="#ff00ff">"中文123"</font></span>;<br /><br /><font color="#339933"><span class="rem">// UNICODE 字符串，内容长度 5 个 wchar_t（10 字节）</span><br /></font>wchar_t wsz[<span class="number"><font color="#6d66a5">20</font></span>] = L<span class="string"><font color="#ff00ff">"\x4E2D\x6587\x0031\x0032\x0033"</font></span>;</td></tr></tbody></table><p>UNICODE 字符串的 I/O 操作，字符与字节的转换操作：</p><table cellspacing="0" cellpadding="6" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><span class="rem"><font color="#339933">// 运行时设定当前 ANSI 编码，VC 格式<br /></font></span>setlocale(LC_ALL, <span class="string"><font color="#ff00ff">".936"</font></span>);<br /><br /><font color="#339933"><span class="rem">// GCC 中格式</span><br /></font>setlocale(LC_ALL, <span class="string"><font color="#ff00ff">"zh_CN.GBK"</font></span>);<br /><br /><font color="#339933"><span class="rem">// Visual C++ 中使用小写 %s，按照 setlocale 指定编码输出到文件<br />// GCC 中使用大写 %S</span><br /></font>fwprintf(fp, L<span class="string"><font color="#ff00ff">"%s\n"</font></span>, wsz);<br /><br /><font color="#339933"><span class="rem">// 把 UNICODE 字符串按照 setlocale 指定的编码转换成字节</span><br /></font>wcstombs(sz, wsz, <span class="number"><font color="#6d66a5">20</font></span>);<span class="rem"><br /><font color="#339933">// 把字节串按照 setlocale 指定的编码转换成 UNICODE 字符串<br /></font></span>mbstowcs(wsz, sz, <span class="number"><font color="#6d66a5">20</font></span>);</td></tr></tbody></table><p>在 Visual C++ 中，UNICODE 字符串常量有更简单的表示方法。如果源程序的编码与当前默认 ANSI 编码不符，则需要使用 #pragma setlocale，告诉编译器源程序使用的编码：</p><table cellspacing="0" cellpadding="6" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><span class="rem"><font color="#339933">// 如果源程序的编码与当前默认 ANSI 编码不一致，<br />// 则需要此行，编译时用来指明当前源程序使用的编码</font></span><span class="key"><br /><font color="#0000ff">#pragma setlocale</font></span>(<span class="string"><font color="#ff00ff">".936"</font></span>)<br /><br /><font color="#339933"><span class="rem">// UNICODE 字符串常量，内容长度 10 字节</span><br /></font>wchar_t wsz[<span class="number"><font color="#6d66a5">20</font></span>] = L<span class="string"><font color="#ff00ff">"中文123"</font></span>;</td></tr></tbody></table><p>以上需要注意 #pragma setlocale 与 setlocale(LC_ALL, "") 的作用是不同的，#pragma setlocale 在编译时起作用，setlocale() 在运行时起作用。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>2.3 Java 中相关实现方法</h5><p>字符串类 String 中的内容是 UNICODE 字符串：</p><table cellspacing="0" cellpadding="6" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><span class="rem"><font color="#339933">// Java 代码，直接写中文</font></span><span class="pw"><br /><font color="#ff0000">String</font></span> string = <span class="string"><font color="#ff00ff">"中文123"</font></span>;<br /><br /><font color="#339933"><span class="rem">// 得到长度为 5，因为是 5 个字符</span><br /></font><span class="pw"><font color="#ff0000">System</font></span>.out.println(string.length());</td></tr></tbody></table><p>字符串 I/O 操作，字符与字节转换操作。在 Java 包 java.io.* 中，以“Stream”结尾的类一般是用来操作“字节串”的类，以“Reader”，“Writer”结尾的类一般是用来操作“字符串”的类。</p><table cellspacing="0" cellpadding="6" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><span class="rem"><font color="#339933">// 字符串与字节串间相互转化<br /><br />// 按照 GB2312 得到字节（得到多字节字符串）</font></span><span class="key"><br /><font color="#0000ff">byte</font></span> [] bytes = string.getBytes(<span class="string"><font color="#ff00ff">"GB2312"</font></span>);<br /><br /><font color="#339933"><span class="rem">// 从字节按照 GB2312 得到 UNICODE 字符串</span><br /></font>string = <span class="key"><font color="#0000ff">new</font></span><span class="pw"><font color="#ff0000">String</font></span>(bytes, <span class="string"><font color="#ff00ff">"GB2312"</font></span>);<br /><br /><font color="#339933"><span class="rem">// 要将 String 按照某种编码写入文本文件，有两种方法：<br /><br />// 第一种办法：用 Stream 类写入已经按照指定编码转化好的字节串</span><br /></font>OutputStream os = <span class="key"><font color="#0000ff">new</font></span> FileOutputStream(<span class="string"><font color="#ff00ff">"1.txt"</font></span>);<br />os.write(bytes);<br />os.close();<br /><br /><font color="#339933"><span class="rem">// 第二种办法：构造指定编码的 Writer 来写入字符串</span><br /></font>Writer ow = <span class="key"><font color="#0000ff">new</font></span> OutputStreamWriter(<span class="key"><font color="#0000ff">new</font></span> FileOutputStream(<span class="string"><font color="#ff00ff">"2.txt"</font></span>), <span class="string"><font color="#ff00ff">"GB2312"</font></span>);<br />ow.write(string);<br />ow.close();<br /><br /><span class="rem"><font color="#339933">/* 最后得到的 1.txt 和 2.txt 都是 7 个字节 */</font></span></td></tr></tbody></table><p>如果 java 的源程序编码与当前默认 ANSI 编码不符，则在编译的时候，需要指明一下源程序的编码。比如：</p><table cellspacing="0" cellpadding="6" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code">E:\&gt;javac <font color="#ff0000">-encoding BIG5</font> Hello.java</td></tr></tbody></table><p>以上需要注意区分源程序的编码与 I/O 操作的编码，前者是在编译时起作用，后者是在运行时起作用。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h4><a name="misunderstand"></a>3. 几种误解，以及乱码产生的原因和解决办法</h4><h5>3.1 容易产生的误解</h5><table cellspacing="0" cellpadding="3" border="0"><tbody><tr><td class="top_1">　</td><td class="top_2" align="middle"><b>对编码的误解</b></td></tr><tr><td class="con_1" nowrap="" align="middle">误解一</td><td class="con_2">在将“字节串”转化成“UNICODE 字符串”时，比如在读取文本文件时，或者通过网络传输文本时，容易将“字节串”简单地作为<b>单字节字符串</b>，采用每“一个字节”就是“一个字符”的方法进行转化。<br /><br />而实际上，在非英文的环境中，应该将“字节串”作为 ANSI 字符串，采用适当的编码来得到 UNICODE 字符串，有可能“多个字节”才能得到“一个字符”。<br /><br />通常，一直在英文环境下做开发的程序员们，容易有这种误解。</td></tr><tr><td class="bot_1" nowrap="" align="middle">误解二</td><td class="bot_2">在 DOS，Windows 98 等非 UNICODE 环境下，字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串，必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维：“字符串的编码”。<br /><br />当 UNICODE 被支持后，Java 中的 String 是以字符的“序号”来存储的，不是以“某种编码的字节”来存储的，因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时，或者，将一个“字节串”当成一个 ANSI 字符串时，才有编码的概念。<br /><br />不少的人都有这个误解。</td></tr></tbody></table><p>第一种误解，往往是导致乱码产生的原因。第二种误解，往往导致本来容易纠正的乱码问题变得更复杂。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>3.2 常用的编码简介</h5><p>简单介绍一下常用的编码规则，为后边的章节做一个准备。在这里，我们根据编码规则的特点，把所有的编码分成三类：</p><table cellspacing="0" cellpadding="3" border="0"><tbody><tr><td class="top_1" align="middle"><b>分类</b></td><td class="top_2" align="middle"><b>编码标准</b></td><td class="top_2" align="middle"><b>说明</b></td></tr><tr><td class="con_1" nowrap="" align="middle">单字节字符编码</td><td class="con_2">ISO-8859-1</td><td class="con_2">最简单的编码规则，每一个字节直接作为一个 UNICODE 字符。比如，[0xD6, 0xD0] 这两个字节，通过 iso-8859-1 转化为字符串时，将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符，即 "ÖÐ"。<br /><br />反之，将 UNICODE 字符串通过 iso-8859-1 转化为字节串时，只能正常转化 0~255 范围的字符。</td></tr><tr><td class="con_1" nowrap="" align="middle">ANSI 编码</td><td class="con_2">GB2312,<br />BIG5,<br />Shift_JIS,<br />ISO-8859-2 ……</td><td class="con_2">把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时，根据各自编码的规定，一个 UNICODE 字符可能转化成一个字节或多个字节。<br /><br />反之，将字节串转化成字符串时，也可能多个字节转化成一个字符。比如，[0xD6, 0xD0] 这两个字节，通过 GB2312 转化为字符串时，将得到 [0x4E2D] 一个字符，即 '中' 字。<br /><br />“ANSI 编码”的特点：<br />1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。<br />2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。</td></tr><tr><td class="bot_1" nowrap="" align="middle">UNICODE 编码</td><td class="bot_2">UTF-8,<br />UTF-16, UnicodeBig ……</td><td class="bot_2">与“ANSI 编码”类似的，把字符串通过 UNICODE 编码转化成“字节串”时，一个 UNICODE 字符可能转化成一个字节或多个字节。<br /><br />与“ANSI 编码”不同的是：<br />1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字符。<br />2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计算得到的。</td></tr></tbody></table><p>在这里，我们可以看到，前面所讲的“误解一”，即采用每“一个字节”就是“一个字符”的转化方法，实际上也就等同于采用 iso-8859-1 进行转化。因此，我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作，得到原始的“字节串”。然后再使用正确的 ANSI 编码，比如 string = new String(bytes, "GB2312")，来得到正确的“UNICODE 字符串”。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5><a name="instances"></a>3.3 非 UNICODE 程序在不同语言环境间移植时的乱码</h5><p>非 UNICODE 程序中的字符串，都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发时的语言环境不同，将会导致 ANSI 字符串的显示失败。</p><p>比如，在日文环境下开发的非 UNICODE 的日文程序界面，拿到中文环境下运行时，界面上将显示乱码。如果这个日文程序界面改为采用 UNICODE 来记录字符串，那么当在中文环境下运行时，界面上将可以显示正常的日文。</p><p>由于客观原因，有时候我们必须在中文操作系统下运行非 UNICODE 的日文软件，这时我们可以采用一些工具，比如，南极星，AppLocale 等，暂时的模拟不同的语言环境。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>3.4 网页提交字符串</h5><p>当页面中的表单提交字符串时，首先把字符串按照当前页面的编码，转化成字节串。然后再将每个字节转化成 "%XX" 的格式提交到 Web 服务器。比如，一个编码为 GB2312 的页面，提交 "中" 这个字符串时，提交给服务器的内容为 "%D6%D0"。</p><p>在服务器端，Web 服务器把收到的 "%D6%D0" 转化成 [0xD6, 0xD0] 两个字节，然后再根据 GB2312 编码规则得到 "中" 字。</p><p>在 Tomcat 服务器中，request.getParameter() 得到乱码时，常常是因为前面提到的“误解一”造成的。默认情况下，当提交 "%D6%D0" 给 Tomcat 服务器时，request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符，而不是返回一个 "中" 字符。因此，我们需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字节串，再用 string = new String(bytes, "GB2312") 重新得到正确的字符串 "中"。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>3.5 从数据库读取字符串</h5><p>通过数据库客户端（比如 ODBC 或 JDBC）从数据库服务器中读取字符串时，客户端需要从服务器获知所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时，客户端负责将字节流按照正确的编码转化成 UNICODE 字符串。</p><p>如果从数据库读取字符串时得到乱码，而数据库中存放的数据又是正确的，那么往往还是因为前面提到的“误解一”造成的。解决的办法还是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法，重新得到原始的字节串，再重新使用正确的编码转化成字符串。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h5>3.6 电子邮件中的字符串</h5><p>当一段 Text 或者 HTML 通过电子邮件传送时，发送的内容首先通过一种指定的<b>字符编码</b>转化成“字节串”，然后再把“字节串”通过一种指定的<b>传输编码</b>（Content-Transfer-Encoding）进行转化得到另一串“字节串”。比如，打开一封电子邮件源代码，可以看到类似的内容：</p><table cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code">Content-Type: text/plain;<br />        <font color="#ff0000">charset="gb2312"</font><br /><font color="#ff0000">Content-Transfer-Encoding: base64</font><br /><br />sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==</td></tr></tbody></table><p>最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 两种。在对二进制文件或者中文文本进行转化时，Base64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行转化时，Quoted-Printable 得到的“字节串”比 Base64 更短。</p><p>邮件的标题，用了一种更简短的格式来标注“字符编码”和“传输编码”。比如，标题内容为 "中"，则在邮件源代码中表示为：</p><table cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><font color="#339933"><span class="rem">// 正确的标题格式</span><br /></font>Subject: <span style="BACKGROUND-COLOR: #ffff00">=?</span>GB2312<span style="BACKGROUND-COLOR: #ffff00">?B?</span>1tA=<span style="BACKGROUND-COLOR: #ffff00">?=</span></td></tr></tbody></table><p>其中，</p><ul><li>第一个“=?”与“?”中间的部分指定了字符编码，在这个例子中指定的是 GB2312。 
</li><li>“?”与“?”中间的“B”代表 Base64。如果是“Q”则代表 Quoted-Printable。 
</li><li>最后“?”与“?=”之间的部分，就是经过 GB2312 转化成字节串，再经过 Base64 转化后的标题内容。 </li></ul><p>如果“传输编码”改为 Quoted-Printable，同样，如果标题内容为 "中"：</p><table cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><font color="#339933"><span class="rem">// 正确的标题格式</span><br /></font>Subject: <span style="BACKGROUND-COLOR: #ffff00">=?</span>GB2312<span style="BACKGROUND-COLOR: #ffff00">?Q?</span>=D6=D0<span style="BACKGROUND-COLOR: #ffff00">?=</span></td></tr></tbody></table><p>如果阅读邮件时出现乱码，一般是因为“字符编码”或“传输编码”指定有误，或者是没有指定。比如，有的发邮件组件在发送邮件时，标题 "中"：</p><table cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td class="code"><font color="#339933"><span class="rem">// 错误的标题格式</span><br /></font>Subject: <span style="BACKGROUND-COLOR: #ffff00">=?</span><font color="#ff0000">ISO-8859-1</font><span style="BACKGROUND-COLOR: #ffff00">?Q?</span>=D6=D0<span style="BACKGROUND-COLOR: #ffff00">?=</span></td></tr></tbody></table><p>这样的表示，实际上是明确指明了标题为 [0x00D6, 0x00D0]，即 "ÖÐ"，而不是 "中"。</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td align="right" width="100%"><img height="1" alt="" src="http://www.regexlab.com/images/blue_rule.gif" width="100%" border="0" /></td></tr><tr valign="top"><td align="right" width="100%"><table cellspacing="0" cellpadding="0"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.regexlab.com/images/u_bold.gif" width="16" border="0" /></td><td valign="top" align="right"><p><a href="http://www.regexlab.com/zh/encoding.htm#main">回页首</a></p></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table><h4><a name="correct"></a>4. 几种错误理解的纠正</h4><h5>误解：“ISO-8859-1 是国际编码？”</h5><p>非也。iso-8859-1 只是单字节字符集中最简单的一种，也就是“字节编号”与“UNICODE 字符编号”一致的那种编码规则。当我们要把一个“字节串”转化成“字符串”，而又不知道它是哪一种 ANSI 编码时，先暂时地把“每一个字节”作为“一个字符”进行转化，不会造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节串。</p><h5>误解：“Java 中，怎样知道某个字符串的内码？”</h5><p>Java 中，字符串类 java.lang.String 处理的是 UNICODE 字符串，不是 ANSI 字符串。我们只需要把字符串作为“抽象的符号的串”来看待。因此不存在字符串的内码的问题。</p><img src ="http://www.blogjava.net/linlin-2000/aggbug/43250.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/linlin-2000/" target="_blank">00</a> 2006-04-26 13:08 <a href="http://www.blogjava.net/linlin-2000/archive/2006/04/26/43250.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入理解abstract class和interface </title><link>http://www.blogjava.net/linlin-2000/archive/2006/04/06/39557.html</link><dc:creator>00</dc:creator><author>00</author><pubDate>Thu, 06 Apr 2006 04:30:00 GMT</pubDate><guid>http://www.blogjava.net/linlin-2000/archive/2006/04/06/39557.html</guid><wfw:comment>http://www.blogjava.net/linlin-2000/comments/39557.html</wfw:comment><comments>http://www.blogjava.net/linlin-2000/archive/2006/04/06/39557.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/linlin-2000/comments/commentRss/39557.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/linlin-2000/services/trackbacks/39557.html</trackback:ping><description><![CDATA[
		<div class="postTitle"> </div>
		<strong>转载自 <a href="http://bbs.java.ccidnet.com/htm_data/2/0603/1080.html">http://bbs.java.ccidnet.com/htm_data/2/0603/1080.html</a><br /><!----><br /></strong>
		<span class="tpc_content">
				<font size="2">abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制，正是由于这两种机制的存在，才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性，甚至可以相互替换，因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实，两者之间还是有很大的区别的，对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析，试图给开发者提供一个在二者之间进行选择的依据。 <br /><br />理解抽象类 <br /><br />abstract class和interface在Java语言中都是用来进行抽象类（本文中的抽象类并非从abstract class翻译而来，它表示的是一个抽象体，而abstract class为Java语言中用于定义抽象类的一种方法，请读者注意区分）定义的，那么什么是抽象类，使用抽象类能为我们带来什么好处呢？ <br /><br />在面向对象的概念中，我们知道所有的对象都是通过类来描绘的，但是反过来却不是这样。并不是所有的类都是用来描绘对象的，如果一个类中没有包含足够的信息来描绘一个具体的对象，这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念，是对一系列看上去不同，但是本质上相同的具体概念的抽象。比如：如果我们进行一个图形编辑软件的开发，就会发现问题领域存在着圆、三角形这样一些具体概念，它们是不同的，但是它们又都属于形状这样一个概念，形状这个概念在问题领域是不存在的，它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念，所以用以表征抽象概念的抽象类是不能够实例化的。 <br /><br />在面向对象领域，抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述，但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类，而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体，因此它可以是不允许修改的；同时，通过从这个抽象体派生，也可扩展此模块的行为功能。熟悉OCP的读者一定知道，为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle)，抽象类是其中的关键所在。 <br /><br />从语法定义层面看abstract class和interface <br /><br />在语法层面，Java语言对于abstract class和interface给出了不同的定义方式，下面以定义一个名为Demo的抽象类为例来说明这种不同。 <br />使用abstract class的方式定义Demo抽象类的方式如下： <br /><br />abstract class Demo { <br />abstract void method1(); <br />abstract void method2(); <br />… <br />}<br /><br />使用interface的方式定义Demo抽象类的方式如下： <br /><br />interface Demo { <br />void method1(); <br />void method2(); <br />…<br />} <br /><br />在abstract class方式中，Demo可以有自己的数据成员，也可以有非abstarct的成员方法，而在interface方式的实现中，Demo只能够有静态的不能被修改的数据成员（也就是必须是static final的，不过在interface中一般不定义数据成员），所有的成员方法都是abstract的。从某种意义上说，interface是一种特殊形式的abstract class。 <br /><br />从编程的角度来看，abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。 <br /><br />首先，abstract class在Java语言中表示的是一种继承关系，一个类只能使用一次继承关系。但是，一个类却可以实现多个interface。也许，这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。 <br /><br />其次，在abstract class的定义中，我们可以赋予方法的默认行为。但是在interface的定义中，方法却不能拥有默认行为，为了绕过这个限制，必须使用委托，但是这会 增加一些复杂性，有时会造成很大的麻烦。 <br /><br />在抽象类中不能定义默认行为还存在另一个比较严重的问题，那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面（一般通过abstract class或者interface来表示）以适应新的情况（比如，添加新的方法或者给已用的方法中添加新的参数）时，就会非常的麻烦，可能要花费很多的时间（对于派生类很多的情况，尤为如此）。但是如果界面是通过abstract class来实现的，那么可能就只需要修改定义在abstract class中的默认行为就可以了。 <br /><br />同样，如果不能在抽象类中定义默认行为，就会导致同样的方法实现出现在该抽象类的每一个派生类中，违反了"one rule，one place"原则，造成代码重复，同样不利于以后的维护。因此，在abstract class和interface间进行选择时要非常的小心。 <br /><br />从设计理念层面看abstract class和interface <br /><br />上面主要从语法定义和编程的角度论述了abstract class和interface的区别，这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面：abstract class和interface所反映出的设计理念，来分析一下二者的区别。作者认为，从这个层面进行分析才能理解二者概念的本质所在。 <br /><br />前面已经提到过，abstarct class在Java语言中体现了一种继承关系，要想使得继承关系合理，父类和派生类之间必须存在"is a"关系，即父类和派生类在概念本质上应该是相同的（参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述，有兴趣的读者可以参考）。对于interface 来说则不然，并不要求interface的实现者和interface定义在概念本质上是一致的，仅仅是实现了interface定义的契约而已。为了使论述便于理解，下面将通过一个简单的实例进行说明。 <br /><br />考虑这样一个例子，假设在我们的问题领域中有一个关于Door的抽象概念，该Door具有执行两个动作open和close，此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型，定义方式分别如下所示： <br /><br />使用abstract class方式定义Door： <br /><br />abstract class Door { <br />abstract void open(); <br />abstract void close()； <br />}<br /><br />使用interface方式定义Door： <br /><br />interface Door { <br />void open(); <br />void close(); <br />}<br /><br />其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 <br /><br />如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢（在本例中，主要是为了展示abstract class和interface反映在设计理念上的区别，其他方面无关的问题都做了简化或者忽略）？下面将罗列出可能的解决方案，并从设计理念层面对这些不同的方案进行分析。 <br /><br />解决方案一： <br /><br />简单的在Door的定义中增加一个alarm方法，如下： <br /><br />abstract class Door { <br />abstract void open(); <br />abstract void close()； <br />abstract void alarm(); <br />}<br /><br />或者 <br /><br />interface Door { <br />void open(); <br />void close(); <br />void alarm(); <br />}<br /><br />那么具有报警功能的AlarmDoor的定义方式如下： <br /><br />class AlarmDoor extends Door { <br />void open() { … } <br />void close() { … } <br />void alarm() { … } <br />}<br /><br />或者 <br /><br />class AlarmDoor implements Door {<br />void open() { … }<br />void close() { … }<br />void alarm() { … }<br />} <br /><br />这种方法违反了面向对象设计中的一个核心原则ISP（Interface Segregation Priciple），在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变（比如：修改alarm方法的参数）而改变，反之依然。 <br /><br />解决方案二： <br /><br />既然open、close和alarm属于两个不同的概念，根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有：这两个概念都使用abstract class方式定义；两个概念都使用interface方式定义；一个概念使用abstract class方式定义，另一个概念使用interface方式定义。 <br /><br />显然，由于Java语言不支持多重继承，所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的，但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。 <br /><br />如果两个概念都使用interface方式来定义，那么就反映出两个问题：1、我们可能没有理解清楚问题领域，AlarmDoor在概念本质上到底是Door还是报警器？2、如果我们对于问题领域的理解没有问题，比如：我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的，那么我们在实现时就没有能够正确的揭示我们的设计意图，因为在这两个概念的定义上（均使用interface方式定义）反映不出上述含义。<br /><br />如果我们对于问题领域的理解是：AlarmDoor在概念本质上是Door，同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢？前面已经说过，abstract class在Java语言中表示一种继承关系，而继承关系在本质上是"is a"关系。所以对于Door这个概念，我们应该使用abstarct class方式来定义。另外，AlarmDoor又具有报警功能，说明它又能够完成报警概念中定义的行为，所以报警概念可以通过interface方式定义。如下所示： <br /><br />abstract class Door {<br />abstract void open();<br />abstract void close()；<br />}<br />interface Alarm {<br />void alarm();<br />}<br />class AlarmDoor extends Door implements Alarm {<br />void open() { … }<br />void close() { … }<br />void alarm() { … }<br />}<br /><br />这种实现方式基本上能够明确的反映出我们对于问题领域的理解，正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系，interface表示的是"like a"关系，大家在选择时可以作为一个依据，当然这是建立在对问题领域的理解上的，比如：如果我们认为AlarmDoor在概念本质上是报警器，同时又具有Door的功能，那么上述的定义方式就要反过来了。<br /><br />结论<br /><br />abstract class和interface是Java语言中的两种定义抽象类的方式，它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理，因为它们表现了概念间的不同的关系（虽然都能够实现需求的功能）。这其实也是语言的一种的惯用法，希望读者朋友能够细细体会。</font>
		</span>
		<br />
<img src ="http://www.blogjava.net/linlin-2000/aggbug/39557.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/linlin-2000/" target="_blank">00</a> 2006-04-06 12:30 <a href="http://www.blogjava.net/linlin-2000/archive/2006/04/06/39557.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>