Java字符编码解码

时间:2021-09-07 15:45:01

http://blog.csdn.net/geli_hero

字符集基础:

Character set(字符集) 
         字符的集合,也就是,带有特殊语义的符号。字母“A”是一个字符。“%”也是一个字符。没有内在数字价值,与 ASC II ,Unicode,甚至是电脑也没有任何的直接联系。在电脑产生前的很长一段时间内,符号就已经存在了。 
Coded character set(编码字符集) 
         一个数值赋给一个字符的集合。把代码赋值给字符,这样它们就可以用特定的字符编码集表达数字的结果。其他的编码字符集可以赋不同的数值到同一个字符上。字符集映射通常是由标准组织确定的,例如 USASCII ,ISO 8859 -1,Unicode (ISO 10646 -1) ,以及 JIS X0201。 
Character-encoding scheme(字符编码方案) 
         编码字符集成员到八位字节(8 bit 字节)的映射。编码方案定义了如何把字符编码的序列表达为字节序列。字符编码的数值不需要与编码字节相同,也不需要是一对一或一对多个的关系。原则上,把字符集编码和解码近似视为对象的序列化和反序列化。 


通常字符数据编码是用于网络传输或文件存储。编码方案不是字符集,它是映射;但是因为它们之间的紧密联系,大部分编码都与一个独立的字符集相关联。例如,UTF -8,
仅用来编码 Unicode字符集。尽管如此,用一个编码方案处理多个字符集还是可能发生的。例如,EUC 可以对几个亚洲语言的字符进行编码。 
图6-1 是使用 UTF -8 编码方案将 Unicode字符序列编码为字节序列的图形表达式。UTF -8把小于 0x80 的字符代码值编码成一个单字节值(标准 ASC II )。所有其他的 Unicode字符都被编码成 2 到6 个字节的多字节序列(http://www.ietf.org/rfc/rfc2279.txt )。

Charset(字符集) 
       术语 charset 是在RFC2278(http://ietf.org/rfc/rfc2278.txt) 中定义的。它是编码字符集 和字符编码方案的集合。java.nio.charset 包的类是 Charset,它封装字符集抽取。

Java字符编码解码

Unicode是16-位字符编码。它试着把全世界所有语言的字符集统一到一个独立的、全面的映射中。它赢得了一席之地,但是目前仍有许多其他字符编码正在被广泛的使用。
大部分的操作系统在 I/O 与文件存储方面仍是以字节为导向的,所以无论使用何种编码,Unicode或其他编码,在字节序列和字符集编码之间仍需要进行转化。 
由java.nio.charset 包组成的类满足了这个需求。这不是 Java 平台第一次处理字符集编码,但是它是最系统、最全面、以及最灵活的解决方式。java.nio.charset.spi包提供服务器供给接口(SPI),使编码器和解码器可以根据需要选择插入。 


字符集:在JVM 启动时确定默认值,取决于潜在的操作系统环境、区域设置、和/或JVM配置。如果您需要一个指定的字符集,最安全的办法是明确的命名它。不要假设默认部署与您的开发环境相同。字符集名称不区分大小写,也就是,当比较字符集名称时认为大写字母和小写字母相同。互联网名称分配机构(IANA )维护所有正式注册的字符集名称。


示例6-1 演示了通过不同的 Charset实现如何把字符翻译成字节序列。 
 
示例6 -1. 使用标准字符集编码

[java]  view plain copy
  1. package com.ronsoft.books.nio.charset;  
  2.   
  3. import java.nio.charset.Charset;  
  4. import java.nio.ByteBuffer;  
  5.   
  6. /** 
  7.  * Charset encoding test. Run the same input string, which contains some 
  8.  * non-ascii characters, through several Charset encoders and dump out the hex 
  9.  * values of the resulting byte sequences. 
  10.  *  
  11.  * @author Ron Hitchens (ron@ronsoft.com) 
  12.  */  
  13. public class EncodeTest {  
  14.     public static void main(String[] argv) throws Exception {  
  15.         // This is the character sequence to encode  
  16.         String input = " \u00bfMa\u00f1ana?";  
  17.         // the list of charsets to encode with  
  18.         String[] charsetNames = { "US-ASCII""ISO-8859-1""UTF-8",  
  19.                 "UTF-16BE""UTF-16LE""UTF-16" // , "X-ROT13"  
  20.         };  
  21.         for (int i = 0; i < charsetNames.length; i++) {  
  22.             doEncode(Charset.forName(charsetNames[i]), input);  
  23.         }  
  24.     }  
  25.   
  26.     /** 
  27.      * For a given Charset and input string, encode the chars and print out the 
  28.      * resulting byte encoding in a readable form. 
  29.      */  
  30.     private static void doEncode(Charset cs, String input) {  
  31.         ByteBuffer bb = cs.encode(input);  
  32.         System.out.println("Charset: " + cs.name());  
  33.         System.out.println("  Input: " + input);  
  34.         System.out.println("Encoded: ");  
  35.         for (int i = 0; bb.hasRemaining(); i++) {  
  36.             int b = bb.get();  
  37.             int ival = ((int) b) & 0xff;  
  38.             char c = (char) ival;  
  39.             // Keep tabular alignment pretty  
  40.             if (i < 10)  
  41.                 System.out.print(" ");  
  42.             // Print index number  
  43.             System.out.print("  " + i + ": ");  
  44.             // Better formatted output is coming someday...  
  45.             if (ival < 16)  
  46.                 System.out.print("0");  
  47.             // Print the hex value of the byte  
  48.             System.out.print(Integer.toHexString(ival));  
  49.             // If the byte seems to be the value of a  
  50.             // printable character, print it. No guarantee  
  51.             // it will be.  
  52.             if (Character.isWhitespace(c) || Character.isISOControl(c)) {  
  53.                 System.out.println("");  
  54.             } else {  
  55.                 System.out.println(" (" + c + ")");  
  56.             }  
  57.         }  
  58.         System.out.println("");  
  59.     }  
  60. }  
结果:
[java]  view plain copy
  1. Charset: US-ASCII  
  2.   Input:  ?Ma?ana?  
  3. Encoded:   
  4.    020  
  5.    1: 3f (?)  
  6.    2: 4d (M)  
  7.    361 (a)  
  8.    4: 3f (?)  
  9.    561 (a)  
  10.    6: 6e (n)  
  11.    761 (a)  
  12.    8: 3f (?)  
  13.   
  14. Charset: ISO-8859-1  
  15.   Input:  ?Ma?ana?  
  16. Encoded:   
  17.    020  
  18.    1: bf (?)  
  19.    2: 4d (M)  
  20.    361 (a)  
  21.    4: f1 (?)  
  22.    561 (a)  
  23.    6: 6e (n)  
  24.    761 (a)  
  25.    8: 3f (?)  
  26.   
  27. Charset: UTF-8  
  28.   Input:  ?Ma?ana?  
  29. Encoded:   
  30.    020  
  31.    1: c2 (?)  
  32.    2: bf (?)  
  33.    3: 4d (M)  
  34.    461 (a)  
  35.    5: c3 (?)  
  36.    6: b1 (±)  
  37.    761 (a)  
  38.    8: 6e (n)  
  39.    961 (a)  
  40.   10: 3f (?)  
  41.   
  42. Charset: UTF-16BE  
  43.   Input:  ?Ma?ana?  
  44. Encoded:   
  45.    000  
  46.    120  
  47.    200  
  48.    3: bf (?)  
  49.    400  
  50.    5: 4d (M)  
  51.    600  
  52.    761 (a)  
  53.    800  
  54.    9: f1 (?)  
  55.   1000  
  56.   1161 (a)  
  57.   1200  
  58.   13: 6e (n)  
  59.   1400  
  60.   1561 (a)  
  61.   1600  
  62.   17: 3f (?)  
  63.   
  64. Charset: UTF-16LE  
  65.   Input:  ?Ma?ana?  
  66. Encoded:   
  67.    020  
  68.    100  
  69.    2: bf (?)  
  70.    300  
  71.    4: 4d (M)  
  72.    500  
  73.    661 (a)  
  74.    700  
  75.    8: f1 (?)  
  76.    900  
  77.   1061 (a)  
  78.   1100  
  79.   12: 6e (n)  
  80.   1300  
  81.   1461 (a)  
  82.   1500  
  83.   16: 3f (?)  
  84.   1700  
  85.   
  86. Charset: UTF-16  
  87.   Input:  ?Ma?ana?  
  88. Encoded:   
  89.    0: fe (?)  
  90.    1: ff (?)  
  91.    200  
  92.    320  
  93.    400  
  94.    5: bf (?)  
  95.    600  
  96.    7: 4d (M)  
  97.    800  
  98.    961 (a)  
  99.   1000  
  100.   11: f1 (?)  
  101.   1200  
  102.   1361 (a)  
  103.   1400  
  104.   15: 6e (n)  
  105.   1600  
  106.   1761 (a)  
  107.   1800  
  108.   19: 3f (?)  

字符集类:
[java]  view plain copy
  1. package java.nio.charset;   
  2. public abstract class Charset implements Comparable   
  3. {   
  4.         public static boolean isSupported (String charsetName)   
  5.         public static Charset forName (String charsetName)   
  6.         public static SortedMap availableCharsets()    
  7.         public final String name()    
  8.         public final Set aliases()   
  9.         public String displayName()   
  10.         public String displayName (Locale locale)    
  11.         public final boolean isRegistered()    
  12.         public boolean canEncode()    
  13.         public abstract CharsetEncoder newEncoder();    
  14.         public final ByteBuffer encode (CharBuffer cb)    
  15.         public final ByteBuffer encode (String str)    
  16.         public abstract CharsetDecoder newDecoder();    
  17.         public final CharBuffer decode (ByteBuffer bb)    
  18.         public abstract boolean contains (Charset cs);   
  19.         public final boolean equals (Object ob)   
  20.         public final int compareTo (Object ob)    
  21.         public final int hashCode()   
  22.         public final String toString()    
  23. }  


那么Charset对象需要满足几个条件: 
 
  字符集的规范名称应与在 IANA 注册的名称相符。 
  如果IANA 用同一个字符集注册了多个名称,对象返回的规范名称应该与 IANA 注册中的MIME -首选名称相符。 
  如果字符集名称从注册中移除,那么当前的规范名称应保留为别名。 
  如果字符集没有在 IANA 注册,它的规范名称必须以“X -”或“x-”开头。 

大多数情况下,只有 JVM卖家才会关注这些规则。然而,如果您打算以您自己的字符集作为应用的一部分,那么了解这些不该做的事情将对您很有帮助。针对 isRegistered() 您应该返回 false 并以“X -”开头命名您的字符集。




字符集比较:

[java]  view plain copy
  1. public abstract class Charset implements Comparable   
  2. {   
  3.         // This is a partial API listing   
  4.         public abstract boolean contains (Charset cs);    
  5.         public final boolean equals (Object ob)   
  6.         public final int compareTo (Object ob)    
  7.         public final int hashCode()   
  8.         public final String toString()    
  9. }  
回想一下,字符集是由字符的编码集与该字符集的编码方案组成的。与普通的集合类似,一个字符集可能是另一个字符集的子集。一个字符集(C 1)包含另一个(C 2),表示在C 2 中表达的每个字符都可以在 C 1 中进行相同的表达。每个字符集都被认为是包含其本身。如果这个包含关系成立,那么您在 C 2(被包含的子集)中编码的任意流在 C 1 中也一定可以编码,无需任何替换。




字符集编码器:字符集是由一个编码字符集和一个相关编码方案组成的。CharsetEncoder 和CharsetDecoder 类实现转换方案。

[java]  view plain copy
  1. float averageBytesPerChar()   
  2.           Returns the average number of bytes that will be produced for each character of input.   
  3.  boolean canEncode(char c)   
  4.           Tells whether or not this encoder can encode the given character.   
  5.  boolean canEncode(CharSequence cs)   
  6.           Tells whether or not this encoder can encode the given character sequence.   
  7.  Charset charset()   
  8.           Returns the charset that created this encoder.   
  9.  ByteBuffer encode(CharBuffer in)   
  10.           Convenience method that encodes the remaining content of a single input character buffer into a newly-allocated byte buffer.   
  11.  CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput)   
  12.           Encodes as many characters as possible from the given input buffer, writing the results to the given output buffer.   
  13. protected abstract  CoderResult encodeLoop(CharBuffer in, ByteBuffer out)   
  14.           Encodes one or more characters into one or more bytes.   
  15.  CoderResult flush(ByteBuffer out)   
  16.           Flushes this encoder.   
  17. protected  CoderResult implFlush(ByteBuffer out)   
  18.           Flushes this encoder.   
  19. protected  void implOnMalformedInput(CodingErrorAction newAction)   
  20.           Reports a change to this encoder's malformed-input action.   
  21. protected  void implOnUnmappableCharacter(CodingErrorAction newAction)   
  22.           Reports a change to this encoder's unmappable-character action.   
  23. protected  void implReplaceWith(byte[] newReplacement)   
  24.           Reports a change to this encoder's replacement value.   
  25. protected  void implReset()   
  26.           Resets this encoder, clearing any charset-specific internal state.   
  27.  boolean isLegalReplacement(byte[] repl)   
  28.           Tells whether or not the given byte array is a legal replacement value for this encoder.   
  29.  CodingErrorAction malformedInputAction()   
  30.           Returns this encoder's current action for malformed-input errors.   
  31.  float maxBytesPerChar()   
  32.           Returns the maximum number of bytes that will be produced for each character of input.   
  33.  CharsetEncoder onMalformedInput(CodingErrorAction newAction)   
  34.           Changes this encoder's action for malformed-input errors.   
  35.  CharsetEncoder onUnmappableCharacter(CodingErrorAction newAction)   
  36.           Changes this encoder's action for unmappable-character errors.   
  37.  byte[] replacement()   
  38.           Returns this encoder's replacement value.   
  39.  CharsetEncoder replaceWith(byte[] newReplacement)   
  40.           Changes this encoder's replacement value.   
  41.  CharsetEncoder reset()   
  42.           Resets this encoder, clearing any internal state.   
  43.  CodingErrorAction unmappableCharacterAction()   
  44.           Returns this encoder's current action for unmappable-character errors.   



CharsetEncoder 对象是一个状态转换引擎:字符进去,字节出来。一些编码器的调用可能需要完成转换。编码器存储在调用之间转换的状态。

关于 CharsetEncoder API 的一个注意事项:首先,越简单的encode() 形式越方便,在重新分配的 ByteBuffer中您提供的 CharBuffer 的编码集所有的编码于一身。这是当您在 Charset类上直接调用 encode() 时最后调用的方法。

Underflow(下溢)

Overflow (上溢)

Malformed input(有缺陷的输入)

Unmappable character (无映射字符) 


编码时,如果编码器遭遇了有缺陷的或不能映射的输入,返回结果对象。您也可以检测独立的字符,或者字符序列,来确定它们是否能被编码。下面是检测能否进行编码的方法:

[java]  view plain copy
  1. package java.nio.charset;   
  2. public abstract class CharsetEncoder    
  3. {   
  4.          // This is a partial API listing    
  5.         public boolean canEncode (char c)    
  6.         public boolean canEncode (CharSequence cs)   
  7. }  


CodingErrorAction 定义了三个公共域: 
 
REPORT (报告) 
       创建 CharsetEncoder 时的默认行为。这个行为表示编码错误应该通过返回 CoderResult 对象
报告,前面提到过。 
 
IGNORE (忽略) 
         表示应忽略编码错误并且如果位置不对的话任何错误的输入都应中止。 
 
REPLACE(替换) 
         通过中止错误的输入并输出针对该 CharsetEncoder 定义的当前的替换字节序列处理编码错误。



记住,字符集编码把字符转化成字节序列,为以后的解码做准备。如果替换序列不能被解码成有效的字符序列,编码字节序列变为无效。



CoderResult类:CoderResult 对象是由 CharsetEncoder 和CharsetDecoder 对象返回的: 

[java]  view plain copy
  1. package java.nio.charset;   
  2. public class CoderResult {   
  3.         public static final CoderResult OVERFLOW   
  4.         public static final CoderResult UNDERFLOW    
  5.         public boolean isUnderflow()    
  6.         public boolean isOverflow()   
  7. <span style="white-space:pre">  </span>public boolean isError()   
  8.         public boolean isMalformed()    
  9.         public boolean isUnmappable()   
  10.         public int length()    
  11.         public static CoderResult malformedForLength (int length)     
  12.         public static CoderResult unmappableForLength (int length)    
  13. <span style="white-space:pre">  </span>public void throwException() throws CharacterCodingException   
  14. }   



字符集解码器:字符集解码器是编码器的逆转。通过特殊的编码方案把字节编码转化成 16-位Unicode字符的序列。与 CharsetEncoder 类似的, CharsetDecoder 是状态转换引擎。两个都不是线程安全的,因为调用它们的方法的同时也会改变它们的状态,并且这些状态会被保留下来。

[java]  view plain copy
  1.  float averageCharsPerByte()   
  2.           Returns the average number of characters that will be produced for each byte of input.   
  3.  Charset charset()   
  4.           Returns the charset that created this decoder.   
  5.  CharBuffer decode(ByteBuffer in)   
  6.           Convenience method that decodes the remaining content of a single input byte buffer into a newly-allocated character buffer.   
  7.  CoderResult decode(ByteBuffer in, CharBuffer out, boolean endOfInput)   
  8.           Decodes as many bytes as possible from the given input buffer, writing the results to the given output buffer.   
  9. protected abstract  CoderResult decodeLoop(ByteBuffer in, CharBuffer out)   
  10.           Decodes one or more bytes into one or more characters.   
  11.  Charset detectedCharset()   
  12.           Retrieves the charset that was detected by this decoder  (optional operation).   
  13.  CoderResult flush(CharBuffer out)   
  14.           Flushes this decoder.   
  15. protected  CoderResult implFlush(CharBuffer out)   
  16.           Flushes this decoder.   
  17. protected  void implOnMalformedInput(CodingErrorAction newAction)   
  18.           Reports a change to this decoder's malformed-input action.   
  19. protected  void implOnUnmappableCharacter(CodingErrorAction newAction)   
  20.           Reports a change to this decoder's unmappable-character action.   
  21. protected  void implReplaceWith(String newReplacement)   
  22.           Reports a change to this decoder's replacement value.   
  23. protected  void implReset()   
  24.           Resets this decoder, clearing any charset-specific internal state.   
  25.  boolean isAutoDetecting()   
  26.           Tells whether or not this decoder implements an auto-detecting charset.   
  27.  boolean isCharsetDetected()   
  28.           Tells whether or not this decoder has yet detected a charset  (optional operation).   
  29.  CodingErrorAction malformedInputAction()   
  30.           Returns this decoder's current action for malformed-input errors.   
  31.  float maxCharsPerByte()   
  32.           Returns the maximum number of characters that will be produced for each byte of input.   
  33.  CharsetDecoder onMalformedInput(CodingErrorAction newAction)   
  34.           Changes this decoder's action for malformed-input errors.   
  35.  CharsetDecoder onUnmappableCharacter(CodingErrorAction newAction)   
  36.           Changes this decoder's action for unmappable-character errors.   
  37.  String replacement()   
  38.           Returns this decoder's replacement value.   
  39.  CharsetDecoder replaceWith(String newReplacement)   
  40.           Changes this decoder's replacement value.   
  41.  CharsetDecoder reset()   
  42.           Resets this decoder, clearing any internal state.   
  43.  CodingErrorAction unmappableCharacterAction()   
  44.           Returns this decoder's current action for unmappable-character errors.   


实际完成解码的方法上:
[java]  view plain copy
  1. package java.nio.charset;   
  2. public abstract class CharsetDecoder   
  3. {   
  4.         // This is a partial API listing   
  5.         public final CharsetDecoder reset()    
  6.         public final CharBuffer decode (ByteBuffer in)      
  7.                throws CharacterCodingException   
  8.         public final CoderResult decode (ByteBuffer in, CharBuffer out,      
  9.                boolean endOfInput)   
  10.         public final CoderResult flush (CharBuffer out)   
  11. }   

解码处理和编码类似,包含相同的基本步骤: 
 
1.   复位解码器,通过调用 reset() ,把解码器放在一个已知的状态准备用来接收输入。 
2.   把endOfInput 设置成 false 不调用或多次调用 decode(),供给字节到解码引擎中。随着解码的进行,字符将被添加到给定的 CharBuffer 中。 
3.   把endOfInput 设置成 true 调用一次 decode(),通知解码器已经提供了所有的输入。 
4.   调用flush() ,确保所有的解码字符都已经发送给输出。 


示例6-2 说明了如何对表示字符集编码的字节流进行编码。 
 
示例6 -2.  字符集解码 

[java]  view plain copy
  1. package com.ronsoft.books.nio.charset;  
  2.   
  3. import java.nio.*;  
  4. import java.nio.charset.*;  
  5. import java.nio.channels.*;  
  6. import java.io.*;  
  7.   
  8. /** 
  9.  * Test charset decoding. 
  10.  *  
  11.  * @author Ron Hitchens (ron@ronsoft.com) 
  12.  */  
  13. public class CharsetDecode {  
  14.     /** 
  15.      * Test charset decoding in the general case, detecting and handling buffer 
  16.      * under/overflow and flushing the decoder state at end of input. This code 
  17.      * reads from stdin and decodes the ASCII-encoded byte stream to chars. The 
  18.      * decoded chars are written to stdout. This is effectively a 'cat' for 
  19.      * input ascii files, but another charset encoding could be used by simply 
  20.      * specifying it on the command line. 
  21.      */  
  22.     public static void main(String[] argv) throws IOException {  
  23.         // Default charset is standard ASCII  
  24.         String charsetName = "ISO-8859-1";  
  25.         // Charset name can be specified on the command line  
  26.         if (argv.length > 0) {  
  27.             charsetName = argv[0];  
  28.         }  
  29.         // Wrap a Channel around stdin, wrap a channel around stdout,  
  30.         // find the named Charset and pass them to the deco de method.  
  31.         // If the named charset is not valid, an exception of type  
  32.         // UnsupportedCharsetException will be thrown.  
  33.         decodeChannel(Channels.newChannel(System.in), new OutputStreamWriter(  
  34.                 System.out), Charset.forName(charsetName));  
  35.     }  
  36.   
  37.     /** 
  38.      * General purpose static method which reads bytes from a Channel, decodes 
  39.      * them according 
  40.      *  
  41.      * @param source 
  42.      *            A ReadableByteChannel object which will be read to EOF as a 
  43.      *            source of encoded bytes. 
  44.      * @param writer 
  45.      *            A Writer object to which decoded chars will be written. 
  46.      * @param charset 
  47.      *            A Charset object, whose CharsetDecoder will be used to do the 
  48.      *            character set decoding. Java NIO 206 
  49.      */  
  50.     public static void decodeChannel(ReadableByteChannel source, Writer writer,  
  51.             Charset charset) throws UnsupportedCharsetException, IOException {  
  52.         // Get a decoder instance from the Charset  
  53.         CharsetDecoder decoder = charset.newDecoder();  
  54.         // Tell decoder to replace bad chars with default mark  
  55.         decoder.onMalformedInput(CodingErrorAction.REPLACE);  
  56.         decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);  
  57.         // Allocate radically different input and output buffer sizes  
  58.         // for testing purposes  
  59.         ByteBuffer bb = ByteBuffer.allocateDirect(16 * 1024);  
  60.         CharBuffer cb = CharBuffer.allocate(57);  
  61.         // Buffer starts empty; indicate input is needed  
  62.         CoderResult result = CoderResult.UNDERFLOW;  
  63.         boolean eof = false;  
  64.         while (!eof) {  
  65.             // Input buffer underflow; decoder wants more input  
  66.             if (result == CoderResult.UNDERFLOW) {  
  67.                 // decoder consumed all input, prepare to refill  
  68.                 bb.clear();  
  69.                 // Fill the input buffer; watch for EOF  
  70.                 eof = (source.read(bb) == -1);  
  71.                 // Prepare the buffer for reading by decoder  
  72.                 bb.flip();  
  73.             }  
  74.             // Decode input bytes to output chars; pass EOF flag  
  75.             result = decoder.decode(bb, cb, eof);  
  76.             // If output buffer is full, drain output  
  77.             if (result == CoderResult.OVERFLOW) {  
  78.                 drainCharBuf(cb, writer);  
  79.             }  
  80.         }  
  81.         // Flush any remaining state from the decoder, being careful  
  82.         // to detect output buffer overflow(s)  
  83.         while (decoder.flush(cb) == CoderResult.OVERFLOW) {  
  84.             drainCharBuf(cb, writer);  
  85.         }  
  86.         // Drain any chars remaining in the output buffer  
  87.         drainCharBuf(cb, writer);  
  88.         // Close the channel; push out any buffered data to stdout  
  89.         source.close();  
  90.         writer.flush();  
  91.     }  
  92.   
  93.     /** 
  94.      * Helper method to drain the char buffer and write its content to the given 
  95.      * Writer object. Upon return, the buffer is empty and ready to be refilled. 
  96.      *  
  97.      * @param cb 
  98.      *            A CharBuffer containing chars to be written. 
  99.      * @param writer 
  100.      *            A Writer object to consume the chars in cb. 
  101.      */  
  102.     static void drainCharBuf(CharBuffer cb, Writer writer) throws IOException {  
  103.         cb.flip(); // Prepare buffer for draining  
  104.         // This writes the chars contained in the CharBuffer but  
  105.         // doesn't actually modify the state of the buffer.  
  106.         // If the char buffer was being drained by calls to get( ),  
  107.         // a loop might be needed here.  
  108.         if (cb.hasRemaining()) {  
  109.             writer.write(cb.toString());  
  110.         }  
  111.         cb.clear(); // Prepare buffer to be filled again  
  112.     }  
  113. }  



字符集服务器供应者接口:可插拔的 SPI 结构是在许多不同的内容中贯穿于 Java 环境使用的。在 1.4JDK中有八个包,一个叫spi 而剩下的有其它的名称。可插拔是一个功能强大的设计技术,是在 Java 的可移植性和适应性上建立的基石之一。 

在浏览 API 之前,需要解释一下 Charset SPI 如何工作。java.nio.charset.spi 包仅包含一个抽取类,CharsetProvider 。这个类的具体实现供给与它们提供过的 Charset对象相关的信息。为了定义自定义字符集,您首先必须从 java.nio.charset package中创建 Charset, CharsetEncoder,以及CharsetDecoder 的具体实现。然后您创建CharsetProvider 的自定义子类,它将把那些类提供给JVM。

创建自定义字符集:

您至少要做的是创建 java.nio.charset.Charset 的子类、提供三个抽取方法的具体实现以及一个构造函数。Charset类没有默认的,无参数的构造函数。这表示您的自定义字符集类必须有一个构造函数,即使它不接受参数。这是因为您必须在实例化时调用 Charset的构造函数(通过在您的构造函数的开端调用 super() ),从而通过您的字符集规范名称和别名供给它。这样做可以让 Charset类中的方法帮您处理和名称相关的事情,所以是件好事。

同样地,您需要提供 CharsetEncoder和CharsetDecoder 的具体实现。回想一下,字符集是编码的字符和编码/解码方案的集合。如我们之前所看到的,编码和解码在 API 水平上几乎是对称的。这里给出了关于实现编码器所需要的东西的简短讨论:一样适用于建立解码器。

与Charset类似的, CharsetEncoder 没有默认的构造函数,所以您需要在具体类构造函数中调用super() ,提供需要的参数。

为了供给您自己的 CharsetEncoder 实现,您至少要提供具体encodeLoop () 方法。对于简单的编码运算法则,其他方法的默认实现应该可以正常进行。注意encodeLoop() 采用和 encode() 的参数类似的参数,不包括布尔标志。encode () 方法代表到encodeLoop() 的实际编码,它仅需要关注来自 CharBuffer 参数消耗的字符,并且输出编码的字节到提供的 ByteBuffer上。


现在,我们已经看到了如何实现自定义字符集,包括相关的编码器和解码器,让我们看一下如何把它们连接到 JVM中,这样可以利用它们运行代码。


供给您的自定义字符集:

 为了给 JVM运行时环境提供您自己的 Charset实现,您必须在 java.nio.charsets. - spi 中创建 CharsetProvider 类的具体子类,每个都带有一个无参数构造函数。无参数构造函数很重要,因为您的 CharsetProvider 类将要通过读取配置文件的全部合格名称进行定位。之后这个类名称字符串将被导入到 Class.newInstance() 来实例化您的提供方,它仅通过无参数构造函数起作用。 
 
JVM读取的配置文件定位字符集提供方,被命名为 java.nio.charset.spi.CharsetProvider 。它在JVM类路径中位于源目录(META-INF/services)中。每一个 JavaArchive(Java 档案文件)(JAR )都有一个 META-INF 目录,它可以包含在那个 JAR 中的类和资源的信息。一个名为META-INF 的目录也可以在 JVM类路径中放置在常规目录的顶端。 

CharsetProvider 的API 几乎是没有作用的。提供自定义字符集的实际工作是发生在创建自定义 Charset,CharsetEncoder,以及 CharsetDecoder 类中。CharsetProvider 仅是连接您的字符集和运行时环境的促进者。


示例 6-3 中演示了自定义 Charset和CharsetProvider 的实现,包含说明字符集使用的取样代码,编码和解码,以及 Charset SPI。示例 6-3 实现了一个自定义Charset。 
 示例6 -3. 自定义Rot13 字符集 

[java]  view plain copy
  1. package com.ronsoft.books.nio.charset;  
  2.   
  3. import java.nio.CharBuffer;  
  4. import java.nio.ByteBuffer;  
  5. import java.nio.charset.Charset;  
  6. import java.nio.charset.CharsetEncoder;  
  7. import java.nio.charset.CharsetDecoder;  
  8. import java.nio.charset.CoderResult;  
  9. import java.util.Map;  
  10. import java.util.Iterator;  
  11. import java.io.Writer;  
  12. import java.io.PrintStream;  
  13. import java.io.PrintWriter;  
  14. import java.io.OutputStreamWriter;  
  15. import java.io.BufferedReader;  
  16. import java.io.InputStreamReader;  
  17. import java.io.FileReader;  
  18.   
  19. /** 
  20.  * A Charset implementation which performs Rot13 encoding. Rot -13 encoding is a 
  21.  * simple text obfuscation algorithm which shifts alphabetical characters by 13 
  22.  * so that 'a' becomes 'n', 'o' becomes 'b', etc. This algorithm was popularized 
  23.  * by the Usenet discussion forums many years ago to mask naughty words, hide 
  24.  * answers to questions, and so on. The Rot13 algorithm is symmetrical, applying 
  25.  * it to text that has been scrambled by Rot13 will give you the original 
  26.  * unscrambled text. 
  27.  *  
  28.  * Applying this Charset encoding to an output stream will cause everything you 
  29.  * write to that stream to be Rot13 scrambled as it's written out. And appying 
  30.  * it to an input stream causes data read to be Rot13 descrambled as it's read. 
  31.  *  
  32.  * @author Ron Hitchens (ron@ronsoft.com) 
  33.  */  
  34. public class Rot13Charset extends Charset {  
  35.     // the name of the base charset encoding we delegate to  
  36.     private static final String BASE_CHARSET_NAME = "UTF-8";  
  37.     // Handle to the real charset we'll use for transcoding between  
  38.     // characters and bytes. Doing this allows us to apply the Rot13  
  39.     // algorithm to multibyte charset encodings. But only the  
  40.     // ASCII alpha chars will be rotated, regardless of the base encoding.  
  41.     Charset baseCharset;  
  42.   
  43.     /** 
  44.      * Constructor for the Rot13 charset. Call the superclass constructor to 
  45.      * pass along the name(s) we'll be known by. Then save a reference to the 
  46.      * delegate Charset. 
  47.      */  
  48.     protected Rot13Charset(String canonical, String[] aliases) {  
  49.         super(canonical, aliases);  
  50.         // Save the base charset we're delegating to  
  51.         baseCharset = Charset.forName(BASE_CHARSET_NAME);  
  52.     }  
  53.   
  54.     // ----------------------------------------------------------  
  55.     /** 
  56.      * Called by users of this Charset to obtain an encoder. This implementation 
  57.      * instantiates an instance of a private class (defined below) and passes it 
  58.      * an encoder from the base Charset. 
  59.      */  
  60.     public CharsetEncoder newEncoder() {  
  61.         return new Rot13Encoder(this, baseCharset.newEncoder());  
  62.     }  
  63.   
  64.     /** 
  65.      * Called by users of this Charset to obtain a decoder. This implementation 
  66.      * instantiates an instance of a private class (defined below) and passes it 
  67.      * a decoder from the base Charset. 
  68.      */  
  69.     public CharsetDecoder newDecoder() {  
  70.         return new Rot13Decoder(this, baseCharset.newDecoder());  
  71.     }  
  72.   
  73.     /** 
  74.      * This method must be implemented by concrete Charsets. We always say no, 
  75.      * which is safe. 
  76.      */  
  77.     public boolean contains(Charset cs) {  
  78.         return (false);  
  79.     }  
  80.   
  81.     /** 
  82.      * Common routine to rotate all the ASCII alpha chars in the given 
  83.      * CharBuffer by 13. Note that this code explicitly compares for upper and 
  84.      * lower case ASCII chars rather than using the methods 
  85.      * Character.isLowerCase and Character.isUpperCase. This is because the 
  86.      * rotate-by-13 scheme only works properly for the alphabetic characters of 
  87.      * the ASCII charset and those methods can return true for non-ASCII Unicode 
  88.      * chars. 
  89.      */  
  90.     private void rot13(CharBuffer cb) {  
  91.         for (int pos = cb.position(); pos < cb.limit(); pos++) {  
  92.             char c = cb.get(pos);  
  93.             char a = '\u0000';  
  94.             // Is it lowercase alpha?  
  95.             if ((c >= 'a') && (c <= 'z')) {  
  96.                 a = 'a';  
  97.             }  
  98.             // Is it uppercase alpha?  
  99.             if ((c >= 'A') && (c <= 'Z')) {  
  100.                 a = 'A';  
  101.             }  
  102.             // If either, roll it by 13  
  103.             if (a != '\u0000') {  
  104.                 c = (char) ((((c - a) + 13) % 26) + a);  
  105.                 cb.put(pos, c);  
  106.             }  
  107.         }  
  108.     }  
  109.   
  110.     // --------------------------------------------------------  
  111.     /** 
  112.      * The encoder implementation for the Rot13 Chars et. This class, and the 
  113.      * matching decoder class below, should also override the "impl" methods, 
  114.      * such as implOnMalformedInput( ) and make passthrough calls to the 
  115.      * baseEncoder object. That is left as an exercise for the hacker. 
  116.      */  
  117.     private class Rot13Encoder extends CharsetEncoder {  
  118.         private CharsetEncoder baseEncoder;  
  119.   
  120.         /** 
  121.          * Constructor, call the superclass constructor with the Charset object 
  122.          * and the encodings sizes from the delegate encoder. 
  123.          */  
  124.         Rot13Encoder(Charset cs, CharsetEncoder baseEncoder) {  
  125.             super(cs, baseEncoder.averageBytesPerChar(), baseEncoder  
  126.                     .maxBytesPerChar());  
  127.             this.baseEncoder = baseEncoder;  
  128.         }  
  129.   
  130.         /** 
  131.          * Implementation of the encoding loop. First, we apply the Rot13 
  132.          * scrambling algorithm to the CharBuffer, then reset the encoder for 
  133.          * the base Charset and call it's encode( ) method to do the actual 
  134.          * encoding. This may not work properly for non -Latin charsets. The 
  135.          * CharBuffer passed in may be read -only or re-used by the caller for 
  136.          * other purposes so we duplicate it and apply the Rot13 encoding to the 
  137.          * copy. We DO want to advance the position of the input buffer to 
  138.          * reflect the chars consumed. 
  139.          */  
  140.         protected CoderResult encodeLoop(CharBuffer cb, ByteBuffer bb) {  
  141.             CharBuffer tmpcb = CharBuffer.allocate(cb.remaining());  
  142.             while (cb.hasRemaining()) {  
  143.                 tmpcb.put(cb.get());  
  144.             }  
  145.             tmpcb.rewind();  
  146.             rot13(tmpcb);  
  147.             baseEncoder.reset();  
  148.             CoderResult cr = baseEncoder.encode(tmpcb, bb, true);  
  149.             // If error or output overflow, we need to adjust  
  150.             // the position of the input buffer to match what  
  151.             // was really consumed from the temp buffer. If  
  152.             // underflow (all input consumed), this is a no-op.  
  153.             cb.position(cb.position() - tmpcb.remaining());  
  154.             return (cr);  
  155.         }  
  156.     }  
  157.   
  158.     // --------------------------------------------------------  
  159.     /** 
  160.      * The decoder implementation for the Rot13 Charset. 
  161.      */  
  162.     private class Rot13Decoder extends CharsetDecoder {  
  163.         private CharsetDecoder baseDecoder;  
  164.   
  165.         /** 
  166.          * Constructor, call the superclass constructor with the Charset object 
  167.          * and pass alon the chars/byte values from the delegate decoder. 
  168.          */  
  169.         Rot13Decoder(Charset cs, CharsetDecoder baseDecoder) {  
  170.             super(cs, baseDecoder.averageCharsPerByte(), baseDecoder  
  171.                     .maxCharsPerByte());  
  172.             this.baseDecoder = baseDecoder;  
  173.         }  
  174.   
  175.         /** 
  176.          * Implementation of the decoding loop. First, we reset the decoder for 
  177.          * the base charset, then call it to decode the bytes into characters, 
  178.          * saving the result code. The CharBuffer is then de-scrambled with the 
  179.          * Rot13 algorithm and the result code is returned. This may not work 
  180.          * properly for non -Latin charsets. 
  181.          */  
  182.         protected CoderResult decodeLoop(ByteBuffer bb, CharBuffer cb) {  
  183.             baseDecoder.reset();  
  184.             CoderResult result = baseDecoder.decode(bb, cb, true);  
  185.             rot13(cb);  
  186.             return (result);  
  187.         }  
  188.     }  
  189.   
  190.     // --------------------------------------------------------  
  191.     /** 
  192.      * Unit test for the Rot13 Charset. This main( ) will open and read an input 
  193.      * file if named on the command line, or stdin if no args are provided, and 
  194.      * write the contents to stdout via the X -ROT13 charset encoding. The 
  195.      * "encryption" implemented by the Rot13 algorithm is symmetrical. Feeding 
  196.      * in a plain-text file, such as Java source code for example, will output a 
  197.      * scrambled version. Feeding the scrambled version back in will yield the 
  198.      * original plain-text document. 
  199.      */  
  200.     public static void main(String[] argv) throws Exception {  
  201.         BufferedReader in;  
  202.         if (argv.length > 0) {  
  203.             // Open the named file  
  204.             in = new BufferedReader(new FileReader(argv[0]));  
  205.         } else {  
  206.             // Wrap a BufferedReader around stdin  
  207.             in = new BufferedReader(new InputStreamReader(System.in));  
  208.         }  
  209.         // Create a PrintStream that uses the Rot13 encoding  
  210.         PrintStream out = new PrintStream(System.out, false"X -ROT13");  
  211.         String s = null;  
  212.         // Read all input and write it to the output.  
  213.         // As the data passes through the PrintStream,  
  214.         // it will be Rot13-encoded.  
  215.         while ((s = in.readLine()) != null) {  
  216.             out.println(s);  
  217.         }  
  218.         out.flush();  
  219.     }  
  220. }  

为了使用这个 Charset和它的编码器与解码器,它必须对 Java 运行时环境有效。用CharsetProvider 类完成(示例 6-4)。 
 
示例6 -4. 自定义字符集提供方

[java]  view plain copy
  1. package com.ronsoft.books.nio.charset;  
  2.   
  3. import java.nio.charset.Charset;  
  4. import java.nio.charset.spi.CharsetProvider;  
  5. import java.util.HashSet;  
  6. import java.util.Iterator;  
  7.   
  8. /** 
  9.  * A CharsetProvider class which makes available the charsets provided by 
  10.  * Ronsoft. Currently there is only one, namely the X -ROT13 charset. This is 
  11.  * not a registered IANA charset, so it's name begins with "X-" to avoid name 
  12.  * *es with offical charsets. 
  13.  *  
  14.  * To activate this CharsetProvider, it's necessary to add a file to the 
  15.  * classpath of the JVM runtime at the following location: 
  16.  * META-INF/services/java.nio.charsets.spi.CharsetP rovider 
  17.  *  
  18.  * That file must contain a line with the fully qualified name of this class on 
  19.  * a line by itself: com.ronsoft.books.nio.charset.RonsoftCharsetProvider Java 
  20.  * NIO 216 
  21.  *  
  22.  * See the javadoc page for java.nio.charsets.spi.CharsetProvider for full 
  23.  * details. 
  24.  *  
  25.  * @author Ron Hitchens (ron@ronsoft.com) 
  26.  */  
  27. public class RonsoftCharsetProvider extends CharsetProvider {  
  28.     // the name of the charset we provide  
  29.     private static final String CHARSET_NAME = "X-ROT13";  
  30.     // a handle to the Charset object  
  31.     private Charset rot13 = null;  
  32.   
  33.     /** 
  34.      * Constructor, instantiate a Charset object and save the reference. 
  35.      */  
  36.     public RonsoftCharsetProvider() {  
  37.         this.rot13 = new Rot13Charset(CHARSET_NAME, new String[0]);  
  38.     }  
  39.   
  40.     /** 
  41.      * Called by Charset static methods to find a particular named Charset. If 
  42.      * it's the name of this charset (we don't have any aliases) then return the 
  43.      * Rot13 Charset, else return null. 
  44.      */  
  45.     public Charset charsetForName(String charsetName) {  
  46.         if (charsetName.equalsIgnoreCase(CHARSET_NAME)) {  
  47.             return (rot13);  
  48.         }  
  49.         return (null);  
  50.     }  
  51.   
  52.     /** 
  53.      * Return an Iterator over the set of Charset objects we provide. 
  54.      *  
  55.      * @return An Iterator object containing references to all the Charset 
  56.      *         objects provided by this class. 
  57.      */  
  58.     public Iterator<Charset> charsets() {  
  59.         HashSet<Charset> set = new HashSet<Charset>(1);  
  60.         set.add(rot13);  
  61.         return (set.iterator());  
  62.     }  
  63. }  
对于通过 JVM运行时环境看到的这个字符集提供方,名为META_INF/services/java.nio.charset.spi.CharsetProvider的文件必须存在于 JARs 之一内或类路径的目录中。那个文件的内容必须是: 
 com.ronsoft.books.nio.charset.RonsoftCharsetProvider 

[java]  view plain copy
  1. 在示例 6-1 中的字符集清单中添加 X -ROT13,产生这个额外的输出:   
  2.    
  3. Charset: X-ROT13   
  4.   Input:   żMańana?    
  5. Encoded:      
  6.    0: c2 (Ż)   
  7.    1: bf (ż)   
  8.    2: 5a (Z)   
  9.    3: 6e (n)   
  10.    4: c3 (Ă)   
  11.    5: b1 (±)   
  12.    6: 6e (n)   
  13.    761 (a)   
  14.    8: 6e (n)   
  15.    9: 3f (?)   

总结:许多Java 编程人员永远不会需要处理字符集编码转换问题,而大多数永远不会创建自定义字符集。但是对于那些需要的人,在 java.nio.charset 和java.charset.spi 中的一系列类为字符处理提供了强大的以及弹性的机制。

Charset(字符集类) 
         封装编码的字符集编码方案,用来表示与作为字节序列的字符集不同的字符序列。 
 
CharsetEncoder(字符集编码类) 
         编码引擎,把字符序列转化成字节序列。之后字节序列可以被解码从而重新构造源字符序列。 
 
CharsetDecoder(字符集解码器类) 
解码引擎,把编码的字节序列转化为字符序列。 
 
CharsetProvider  SPI(字符集供应商 SPI) 
         通过服务器供应商机制定位并使 Charset实现可用,从而在运行时环境中使用。