转换数据(编码问题)
在nio(一)中的GetChannel.java
这个类中,为了输出文件中的信息,我们必须每次只读取一个字节的数据,然后将每个byte类型强制转换成char
类型。但是在java.nio.CharBuffer
这个类中有一个toString()
方法,它的定义是:“返回一个包含缓冲器中所有字符的字符串”。ByteBuffer
可以看作是具有asCharBuffer()
方法的CharBuffer
。使用方法如下,但存在编码问题:
public class BufferToText {
private static final int BSIZE = 1024;
public static void main(String[] args) throws IOException {
FileChannel fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text".getBytes()));
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
// 不起效
System.out.println("第一次:" + buff.asCharBuffer());
/**
* 用系统默认编码进行解码
*/
buff.rewind();
String encoding = System.getProperty("file.encoding");
System.out.println("第二次:" + "Decoded using " + encoding + ": " + Charset.forName(encoding).decode(buff));
/**
* 在写入时 对将要写入的内容进行编码
*/
fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap("Some Text ".getBytes("UTF-16BE")));
fc.close();
// 重新读取
fc = new FileInputStream("data.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println("第三次:" + buff.asCharBuffer());
/**
* 使用buff.asCharBuffer().put 写入
*/
// 使用CharBuffer 写入
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24);
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println("第四次:" + buff.asCharBuffer());
}
}
缓冲器容纳的是普通的字节,为了把它们转换为字符,我们要么在输入它们的时候对其进行编码,要么在将其从缓冲器中输出时对它们进行解码。可以使用java.nio.charset.Charset
类实现这些功能,该类提供了把数据编码成多种不同类型的字符集的工具:
public class AvailableCharSets {
public static void main(String[] args) {
SortedMap<String, Charset> charSets = Charset.availableCharsets();
Iterator<String> it = charSets.keySet().iterator();
while(it.hasNext()){
String csName = it.next();
System.out.print(csName);
Iterator aliases = charSets.get(csName).aliases().iterator();
if(aliases.hasNext()){
System.out.print(": ");
}
while(aliases.hasNext()){
System.out.print(aliases.next());
if (aliases.hasNext()){
System.out.print(", ");
}
}
System.out.println();
}
}
}
编码问题的处理方法
- 使用默认字符集对数据进行
decode()
:可以使用System.getProperty("file.encoding")
发现默认字符集,它会产生代表字符集名称的字符串。把该字符串传送给Charset.forName()
用以产生CharSet
对象,可以用它对字符串进行解码 - 另一种选择是在读文件时,使用能够产生可打印的输出的字符集进行
encode()
- 第三种方法是通过
CharBuffer
向ByteBuffer
写入。
获取基本数据类型
public class GetData {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
int i = 0;
while(i++ < bb.limit()){
if(bb.get() != 0){
System.out.println("nonzero");
}
}
System.out.println("i= " + i);
bb.rewind();
// 存储和读取一个字符数组
bb.asCharBuffer().put("Howdy!");
char c;
while ((c = bb.getChar()) != 0){
System.out.print(c + " ");
}
bb.rewind();
// 存储和读取short
bb.asShortBuffer().put((short)471142);
System.out.println(bb.getShort());
bb.rewind();
// 存储和读取int
bb.asIntBuffer().put(99471142);
System.out.println(bb.getInt());
bb.rewind();
// 存储和读取long
bb.asLongBuffer().put(99471142);
System.out.println(bb.getLong());
bb.rewind();
// 存储和读取float
bb.asFloatBuffer().put(99471142);
System.out.println(bb.getFloat());
bb.rewind();
// 存储和读取double
bb.asDoubleBuffer().put(99471142);
System.out.println(bb.getDouble());
bb.rewind();
}
}
获取基本数据类型的方法
如上代码所示,在分配一个ByteBuffer
之后,可以通过检测它的值来查看缓冲器的分配方式是否将其自动置零。
向ByteBuffer
插入基本数据类型数据的最简单的方法是:利用asCharBuffer()、asShortBuffer()等获取该缓冲器上的视图,然后使用该视图的put()
方法。此方法适用于除ShortBuffer
的put
方法外的所有基本数据类型。使用ShortBuffer
的put
方法时要对数据进行类型转换(类型转换会截取或改变结果)。
视图缓冲器
视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层ByteBuffer
,ByteBuffer
依然是实际存储数据的地方。对视图的任何修改都会映射成为对ByteBuffer
中数据的修改。
视图还允许我们从ByteBuffer
单个地或者批量地读取基本类型值。如下:
public class IntBuffDemo {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer();
// 保存int数组
ib.put(new int[]{10, 20, 1, 4, 41});
System.out.println(ib.get(2));
ib.put(3, 1231);
ib.flip();
while (ib.hasRemaining()){
int i = ib.get();
System.out.println(i);
}
}
}
先用重载后的put()
方法存储一个整数数组,接着get()
和put()
方法调用直接访问底层ByteBuffer
中的某个整数位置。
一旦底层的ByteBuffer
通过视图缓冲器填满了整数或其他基本类型时,就可以直接被写到通道中了。
使用视图缓冲器可以把任何数据都转化成某一特定的基本类型。但是当从不同类型的缓冲器读取时,数据显示的方式也不同。
字节存放次序
不同的及其可能会使用不同的字节排序方法来存储数据。“big endian”(高位优先)将最重要的字节存放在地址最低的存储器单元。而“little endian”(低位优先)则是将最重要的字节放在地址最高的存储器单元。
下面这个例子展示了怎样通过字节存放模式设置来改变字符中的字节次序:
public class Endians {
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
bb.asCharBuffer().put("abcdef");
System.out.println(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put("abcdef");
System.out.println(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put("abcdef");
System.out.println(Arrays.toString(bb.array()));
}
}
可以调用array()
方法显示视图底层的自己。但我们只能对由数组支持的缓冲器调用此方法,否则,将会抛出UnsupportedOperationException
通过CharBuffer
视图可以将charArray
插入到ByteBuffer
中。默认的字节次序和高位优先次序相同;低位优先次序则与之相反,后者交换了字节次序。
用缓冲器操纵数据
- 缓冲器的限制:
ByteBuffer
是唯一将数据移进移出通道的唯一方式,并且我们只能创建一个独立的基本数据类型缓冲器,或者使用“as”方法从ByteBuffer
中获得。也就是说,我们不能把基本数据类型的缓冲器转换成ByteBuffer
。然而,我们可以使用经由试图缓冲器将基本数据类型数据移进移出ByteBuffer
。所以这其实也不是什么真正的限制。 - 数据的读写过程:如果我们想把一个字节数组写到文件中去,那么就应该使用
ByteBuffer.wrap()
方法把字节数组包装起来,然后用getchannel
方法在FileOutputStream
上打开一个通道,接着将来自于ByteBuffer
的数据写到FileChannel
中 - 下面代码是一个简单的交换相邻字符代码:
public class UsingBuffers {
private static void symmetricScramble(CharBuffer charBuffer){
while(charBuffer.hasRemaining()){
charBuffer.mark(); // 将此处标记为position
char c1 = charBuffer.get();
char c2 = charBuffer.get();
charBuffer.reset(); // 回到position处
charBuffer.put(c2).put(c1);
}
}
public static void main(String[] args) {
char[] chars = "UsingBuffers".toCharArray();
ByteBuffer byteBuffer = ByteBuffer.allocate(chars.length * 2);
CharBuffer charBuffer = byteBuffer.asCharBuffer();
charBuffer.put(chars);
System.out.println(charBuffer.rewind()); //rewind是将position重置为0
symmetricScramble(charBuffer);
System.out.println(charBuffer.rewind());
symmetricScramble(charBuffer);
System.out.println(charBuffer.rewind());
}
}