创建Java的字符集
Java中的字符集
Java在内部使用UTF-16处理字符,并具有将UTF-16和其他字符编码互相转换的功能。本文介绍了创建”Charset”的方法,即创建自定义字符编码转换器的方法。
创建字符集的概述
基本上,需要准备以下四个类和一个配置文件(提供者配置文件),并将其添加到类路径中。
-
- java.nio.charset.Charset を拡張したクラス
-
- java.nio.charset.CharsetDecoder を拡張したクラス
-
- java.nio.charset.CharsetEncoder を拡張したクラス
-
- java.nio.charset.spi.CharsetProvider を拡張したクラス
- META-INF/services/java.nio.charset.spi.CharsetProvider
等级0:首先是磨练肩膀
首先,让我们用最基本的准备工作来创建一个新名称的字符集。
-
- XMS932Xという名前のCharset
- 変換はMS932と全く同じ
在这里,由于实际的转换是通过现有的MS932转换器完成的,所以不需要创建Decoder/Encoder。我们只需要一个CharsetProvider和一个提供程序配置文件(即上面列表中的最后两个)。
首先,创建一个继承自CharsetProvider的类,如下所示。仔细查看代码,您会发现我们创建了一个MS932 Charset对象。
package mycode ;
import java.nio.charset.* ;
import java.nio.charset.spi.CharsetProvider ;
import java.util.* ;
public class XMS932XProvider extends CharsetProvider {
private ArrayList<Charset> list = null ;
private Iterator<Charset> getIter() {
if (list == null) {
list = new ArrayList(1) ;
list.add(Charset.forName("MS932")) ;
}
return list.iterator() ;
}
public Iterator<Charset> charsets() {
return getIter() ;
}
public Charset charsetForName(String charsetName) {
if ("XMS932X".equals(charsetName)
|| "MS932".equals(charsetName)) return (Charset)list.get(0) ;
return null ;
}
}
另外,您需要创建一个供应商配置文件。请在以下文件名中包含您创建的CharsetProvider的名称。
mycode.XMS932XProvider
编译上述Java程序后,将其打包成jar文件。
javac mycode/XMS932XProvider.java
jar cvf xms932x.jar META-INF/services/java.nio.charset.spi.CharsetProvider mycode/XMS932XProvider.class
如果将这个生成的 xms932x.jar 添加到类路径中,就可以安全地使用名为 XMS932X 的字符集。当然了,它会执行完全相同的 MS932 字符编码转换。
一级水平:先试着实现四个类
然后,我们创建一个名为XMS932XX的字符集。和上面的Level 0一样,它仍然执行MS932转换。
然而,我们在这里准备了实际上用于转换字符串数据的主体,即CharsetDecode/CharsetEncoder的实现,在其中调用MS932转换。
首先,准备一个扩展了CharsetEncoder的类,如下所示。只需实现encoderLoop方法。通过查看encodeLoop的参数,您可以了解到它是从CharBuffer读取字符并将其作为字节序列输出到ByteBuffer的方法。本例中,我们调用了MS932的编码器并直接使用了它。
package mycode ;
import java.nio.charset.* ;
import java.nio.* ;
public class XMS932XXEncoder extends CharsetEncoder {
protected XMS932XXEncoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) {
super(cs, averageCharsPerByte,maxCharsPerByte) ;
}
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
Charset charset = Charset.forName("MS932");
CharsetEncoder ce = charset.newEncoder() ;
ce.reset() ;
return ce.encode(in, out, false) ;
}
}
同样地,我们准备了一个扩展CharsetDecoder的类。当您查看decodeLoop时,可以看到与之前相反的是,它是一个将字节序列读取并输出为字符的方法。
package mycode ;
import java.nio.charset.* ;
import java.nio.* ;
public class XMS932XXDecoder extends CharsetDecoder {
protected XMS932XXDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) {
super(cs, averageCharsPerByte,maxCharsPerByte) ;
}
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out){
Charset charset = Charset.forName("MS932") ;
CharsetDecoder cd = charset.newDecoder() ;
cd.reset() ;
return cd.decode(in, out, false) ;
}
}
接下来,我们将创建一个扩展Charset的类。在其中,我们将使用newDecoder/newEncoder方法来创建并返回相应的Decoder/Encoder实例。构造函数中的两个参数 (float)2 分别表示字节序列(本机代码)转换为平均字节数和最大字节数。由于MS932是SJIS编码,所以一个字符最多占用2个字节,因此我们指定2。平均值可能可以更小。
contains方法将返回true,如果指定的Charset的转换包含在其中。当然,它也需要包含它自己。在这个示例中,也许可以包含MS932。
package mycode ;
import java.nio.charset.* ;
import java.nio.* ;
public class XMS932XX extends Charset {
public XMS932XX(String s, String[] ss) {
super(s, ss) ;
}
public boolean contains(Charset cs) {
if (cs instanceof XMS932XX) return true ;
return false ;
}
public CharsetDecoder newDecoder() {
return new XMS932XXDecoder(this, (float)2, (float)2) ;
}
public CharsetEncoder newEncoder() {
return new XMS932XXEncoder(this, (float)2, (float)2) ;
}
}
以下是一个扩展了CharsetProvider的类。
在级别零时,它返回一个 “MS932” 的实例作为Charset,但在这里我们将返回一个XMS932XX的实例。
package mycode ;
import java.nio.charset.* ;
import java.nio.charset.spi.CharsetProvider ;
import java.util.* ;
public class XMS932XXProvider extends CharsetProvider {
private ArrayList<Charset> list = null ;
private Iterator ite = getIter() ;
private Iterator<Charset> getIter() {
if (list == null) {
list = new ArrayList(1) ;
list.add(new XMS932XX("XMS932XX", new String[]{"XMS932XX"})) ;
}
return list.iterator() ;
}
public Iterator<Charset> charsets() {
return getIter() ;
}
public Charset charsetForName(String charsetName) {
if ("XMS932XX".equals(charsetName)) return (Charset)list.get(0) ;
return null ;
}
}
最后,我们要准备供应商配置文件。
mycode.XMS932XXProvider
然后,将每个Java文件编译后打包成Jar文件。
javac mycode/XMS932XX*.java
jar cvf xms932xx.jar META-INF/services/java.nio.charset.spi.CharsetProvider mycode/XMS932XX*.class
如果将这个名为xms932xx.jar的文件包含在类路径中,您就可以使用名为XMS932XX的字符集了。实际上,它与MS932的转换完全相同。但是,通过准备解码器/编码器,您可能对实际实现的方法有了一定的了解。
等级2:实施独特的(小的)转换!
理解了机制后,让我们来创建一个能够实际进行独特转换的Charset。
在这里,我们考虑将半角片假名转换为全角片假名,来实现从本地代码(MS932)到Unicode的转换。
因为从头准备所有文件很麻烦,所以我们来修改在Level 1创建的XMS932XX。要改造哪个部分呢?当然是Decoder。
package mycode ;
import java.nio.charset.* ;
import java.nio.charset.spi.CharsetProvider ;
import java.nio.* ;
import java.io.* ;
public class XMS932XXDecoder extends CharsetDecoder {
static final String hankana = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン゚゙" ;
static final char[] zenkana = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン゛゜".toCharArray() ;
protected XMS932XXDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) {
super(cs, averageCharsPerByte,maxCharsPerByte) ;
}
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
Charset charset = Charset.forName("MS932") ;
CharsetDecoder cd = charset.newDecoder() ;
CharBuffer tmp = CharBuffer.allocate(out.remaining()) ;//一旦ここに変換
//このループでできるだけinをtmpにMS932変換する
while (in.hasRemaining()) {
if (in.remaining() == 1 &&
((int)(in.get(in.position()))&0xff)>0x80) break ;//SJIS1バイト目だけ残った場合
CoderResult result = cd.decode(in, tmp, false);
if (result.isError()) {
return result;
} else if (result.isOverflow()) {
break;
}
}
tmp.flip();
int pos = -1 ;
//半角カナを見つけたら全角カナに置き換えて出力
while (tmp.hasRemaining()) {
char c = tmp.get();
if ((pos = hankana.indexOf(c)) >= 0) {
c = zenkana[pos] ;
}
out.put(c) ;
}
//outがいっぱいのときはOVERFLOW、inの残りが無いときはUNDERFLOW
if (out.remaining()==0) return CoderResult.OVERFLOW ;
else return CoderResult.UNDERFLOW;
}
}
因为有点复杂,所以我将解释一下decodeLoop。
首先,在第一个while块中,我们使用MS932转换器将给定的字节序列in转换为CharBuffer tmp。
需要注意的是while后面的if语句,如果没有它,可能会陷入无限循环。该while块的退出条件是
- 当从in中读取的数据用完时,或者当tmp被填满时,或者出现错误时。
如果in的数据以SJIS的两字节字符的第一个字节结尾,那么在MS932解码时无法完全解码in的数据,导致in还剩下一个字符,而tmp也不满,并且没有错误发生,这个情况不满足结束条件。因此,在该if语句中,如果剩下一个字节,并且确定是两字节字符的第一个字节(大于0x80),则从while循环中退出。
第二个 while 循环逐个检查经过 MS932 转换的字符串,如果是半角片假名,则输出对应的全角片假名到 out,否则将该字符直接输出到 out。
通过编译这个类,并将其打包为xms932xx.jar,就可以使用XMS932XX Charset来将半角假名转换为全角假名。
可以准备一个包含半角片假名的文本文件,并通过XMS932XX进行转换,确认它会被转换为全角片假名。
アイウエオアイウエオ
import java.io.* ;
public class Test{
public static void main(String[] args) throws Exception {
String enc = ( args.length > 0 ? args[0] : "XMS932XX") ;
String line = null ;
BufferedReader r = new BufferedReader(new InputStreamReader(System.in, enc)) ;
while((line = r.readLine()) != null) {
System.out.println("line = " + line) ;
}
}
}
执行结果
$ java -cp xms932xx.jar:. Test XMS932XX < input-sjis.txt
line = アイウエオアイウエオ
结尾
以前,我记得在搜索网络时会偶尔找到一些有关创建Java的Charset的信息,但最近很少见到了,即使搜索也找不到,所以我就写了下来。希望对您有所帮助。