Java中使用流来处理程序的输入和输出操作,流是一个抽象的概念,封装了程序数据于输入输出设备交换的底层细节。JavaIO中又将流分为字节流和字符流,字节流主要用于处理诸如图像,音频视频等二进制格式数据,而字符流主要用于处理文本字符等类型的输入输出。
1.字节输入流InputStream
输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。
常用字节输入流的主要类型:
(1).ByteArrayInputStream字节数组输入流:
主要功能:允许内存缓存作为输入流。
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。
注意:关闭ByteArrayInputStream无效,该类中的方法在关闭此流之后仍可被调用,而不会产生任何的IOException。
(2).FileInputStream文件输入流:
主要功能:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。
(3).PipedInputStream管道输入流:
主要功能:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端。
管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,这些数据有某个线程从PipedInputStream对象中读取,并有其他线程将其写入到相应的PipedOutputStream对象中。
注意:不建议PipedInputStream和PipedOutputStream对象使用单线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可以在缓冲区限定范围内将读操作和写操作分离开,如果先连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
(4).SequenceInputStream顺序输入流:
重要功能:将两个或多个输入流对象转换为一个单个输入流对象。
SequenceInputStream表示其他输入流的逻辑串联关系,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
(5).FilterInputStream过滤输入流:
主要功能:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。
常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。
2.字节输出流OutputStream:
和字节输入流相对应,字节输出流负责字节类型数据想目标文件或设备的输出。常见的字节输出流如下:
(1).ByteArrayOutputStream字节数组输出流:
主要功能:在内存中创建一个缓冲区,将接收到的数据放入该内存缓冲区中。
ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。
注意:和ByteArrayInputStream类似,关闭ByteArrayOutputStream也是无效的,此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。
(2).FileOutputStream文件输出流:
主要功能:将数据写入到指定文件中。
文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。
(3).PipedOutputStream管道输出流:
主要功能:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。
(4).FilterOutputStream过滤输出流:
主要功能:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。
常用的FIlterOutputStream是DataOutputStream数据输出流,它允许程序以适当的方式将java基本数据类型写入输出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。
3.字符流:
Java中得字节流只能针对字节类型数据,即支持处理8位的数据类型,由于java中的是Unicode码,即两个字节代表一个字符,于是在JDK1.1之后提供了字符流Reader和Writer。
字符流相关常用类如下:
(1).Reader:
用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。
(2).InputStreamReader:
是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。
它使用的字符集可以有名称指定或显式给定,也可以使用平台默认的字符集。
(3).Writer:
用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。
(4).OutputStreamWriter:
是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。
4.综合使用java IO各种流:
Java IO中的各种流,很少单独使用,经常结合起来综合使用,既可以满足特定需求,又搞效。例子如下:
(1).使用缓冲流读取文件:
import java.io.*;
public class BufferedInputFile{
public static String read(String filename) throws IOException{
//缓冲字符输入流
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
//每次读取文件中的一行
While((s = in.readLine()) != null){
sb.append(s + “\n”);
}
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException{
System.out.println(read(“BufferedInputFile.java”));
}
}
(2).读取内存中的字符串:
import java.io.*;
public class MemoryInput{
public static void main(String[] args) throws IOException{
//将字符串包装为字符输入流
StringReader in = new StringReader(
BufferedInputFile.read(“BufferedInputFile.java”));
int c;
//读取字符输入流中的字符
while((c == in.read()) != -1){
System.out.println((char)c);
}
}
}
(3).数据输入/输出流:
import java.io.*;
public class DataInputOutput{
public static void main(String[] args) thows IOException{
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(“Data.txt”)));
out.writeDouble(3.14159);
out.writeUTF(“That was pi”);
out.writeDouble(1.41413);
out.writeUTF(“Square root of 2”);
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(
new FileOutputStream(“Data.txt”)));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
输出结果:
3.14159
That was pi
1.41413
Square root of 2
(4).文本文件输出流:
import java.io.*;
public class TextFileOutput{
//输出文件名
static String file = “BasicFileOutput.out”;
public static void main(String[] args) throws IOException{
//将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
BufferedReader in = new BufferedReader(new StringReader
(BufferedInputFile.read(“TextFileOutput.java”)));
//字符输出流
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
int lineCount = 1;
String s;
While((s = in.readLine()) != null){
out.println(lineCount++ + “: ” + s);
}
out.close();
}
}
(5).二进制文件读写:
import java.io.*;
public class BinaryFileOutput{
//输出文件名
static String file = “BinaryFileOutput.out”;
public static void main(String[] args) throws IOException{
//将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
BufferedInputStream in = new BufferedInputStream(
new FileInputStream(“TestFile.png”)));
//字符输出流
BufferedOutputStream out = new BufferedOutputStream (
new FileOutputStream(file));
byte[] buf = new byte[1024];
int n;
While((n = in.read(buf)) > 0){
out.write(buf, 0, n);
}
out.close();
}
}
为了提高Java I/O的速度和效率,从JDK1.4开始引入了java.nio.*包,即java new I/O(NIO)。
事实上,为了利用java nio的速度和效率优势,原来的java I/O包中相关的类已经使用java nio重新实现,因此在编程中即使没有显式地使用java nio的代码,使用传统java I/O还是利用了nio的速度和效率优势。Java nio的速度提高主要在:文件I/O和网络I/O两个方面。
Java nio的速度提升主要因为使用了类似于操作系统本身I/O的数据结构:I/O通道(Channel)和缓冲区。
1.Channel通道:
通道表示实体,如硬件设备、文件、网络套接字或可以执行的一个或多个不同的I/O操作(如读取或写入)的程序组件的开发的链接,用于I/O操作的链接。
通道可处于打开或关闭状态。创建通道时它处于打开状态,一旦将其关闭,则保持关闭状态。一旦关闭了某个通道,试图对其调用I/O操作都会抛出ClosedChannelException异常,通过调用通道的isOpen()方法可以探测通道是否处于打开状态。一般情况下通道对于多线程的访问是安全的。
2.ByteBuffer字节缓冲区:
字节缓冲区是nio中唯一直接和通道channel打交道的缓冲区。字节缓冲区可以存放原始字节,也可以存放java的8中基本类型数据,但是不能存放引用类型数据,String也是不能存放的。正是由于这种底层的存放类型似的字节缓冲区可以更加高效和绝大部分操作系统的I/O进行映射。
字节缓冲区通过allocation()方法创建,此方法为该缓冲区的内容分配空间,或者通过wrapping方法将现有的字节数组包装到缓冲区中来创建。
字节缓冲区的常用操作:
(1).读写单个字节的绝对和相对get和put方法:
a. 绝对方法:
get(int index):读取指定索引处的字节。
put(int index, byte b):将字节写入指定索引处。
b.相对方法:
get():读取此缓冲区当前位置的字节,然后该位置递增。
put(byte b):将字节写入此缓冲区的当前位置,然后该位置递增。
(2).相对批量get方法:
ByteBuffer get(byte[] dst):将此缓冲区的字节传输到给定的目标数组中。
(3).相对批量put方法:
ByteBuffer put(byte[] src):将给定的源byte数组的所有内容传输到此缓冲区中。
(4).读写其他基本类型值:
getChar(), putChar(char value), getChare(int index), putChar(int index, char value).
getInt(), putInt(int value), getInt(int index), putInt(int index, int value)等等读写基本类型值得相对和绝对方法。
注意:基本类型值得相对和绝对读写方法,根据java基本类型数据底层字节数进行缓冲区移动。
(5).创建视图缓冲区:
为了访问同类二进制数据,允许将字节缓冲区视为包含它们基本类型值的缓冲区,视图缓冲区只是其内容受该字节缓冲区支持的另一种缓冲区。字节缓冲区和视图缓冲区内容更改是相互可见的。这两种缓冲区的位置、限制和标记值都是独立的。创建方法如下:
asCharBuffer(), asDoubleBuffer(), asFloatBuffer(), asIntBuffer(), asLongBuffer(), asReadOnlyBuffer(), asShortBuffer()。
与具体类型的get和put方法系列相比,视图缓冲区优势如下:
a视图缓冲区不是根据字节进行索引,而是根据其特定类型的值得大小进行索引。
b.视图缓冲区提供了相对批量get和put方法,这些方法可以在缓冲区和数组或相同类型的其他缓冲区直接传输连续序列的值。
c.视图缓冲区可能更搞效,因为当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
(6).缓冲区其他操作:
a.ByteBuffer compact():压缩缓冲区,从缓冲区写入数据之后调用,以防写入不完整。
b.ByteBuffer duplicate():创建共享此缓冲区内容的新的字节缓冲区,新缓冲区中的内容为此缓冲区的内容,此缓冲区和新缓冲区内容更改是相互可见的。
c.ByteBuffer slice():创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。
3.直接与非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。
如果字节缓冲区是直接的,则JVM会尽最大努力直接在此缓冲区上执行本机的I/O操作,即在每次调用底层操作系统的一个本机I/O之前(或之后),JVM都会尽量避免将缓冲区的内容复制到中间缓冲区中(或尽量避免从中间缓冲区复制内容)。直接字节缓冲区可以通过调用allocateDirect()方法来创建,此方法返回的缓冲区进行分配和取消分配所需的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此它对应用程序的内存需求量造成的影响可能并不明显,所以建议将直接缓冲区主要分配给那些容易受底层操作系统的本机I/O操作影响的大型的、持久的缓冲区
可以通过调用isDirect()来判断字节缓冲区是直接的还是非直接的。
4.文件通道:
Java I/O的FileInputStream,FileOutputStream和RandomAccessFile可以产生文件通道,例子如下:
[java] view plaincopy
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class FileChannel{
//分配字节缓冲区时指定其大小
private static final int BSIZE = 1024;
public static void main(String[] args) throw Exception{
//获取FileOutputStram的文件通道
FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
//向字节缓冲区中写入字节数组
fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
fc.close();
//以读写方式获取随机访问文件的文件通道
fc = new RandomAccessFile(“data.txt”, “rw”).getChannel();
//定位到字节缓冲区当前内容之后
fc.position(fc.size());
//向字节缓冲区中追加内容
fc.write(ByteBuffer.wrap(“Some more”.getBytes()));
fc.close();
//获取FileInputStream的文件通道
fc = new FileInputStream(“data.txt”).getChannel();
//分配字节缓冲区
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
//将字节数组从文件通道读入到字节缓冲区中
fc.read(buff);
//放置缓冲区,为缓冲区写出或相对获取做准备
buff.flip();
//判断缓冲区是是否还有元素
while(buff.hasRemaining()){
//获取字节缓冲区字节的相对方法
System.out.println((char)buff.get());
}
}
}
输出结果:
Some text Some more
传统java I/O中FileInputStream, FileOutputStream和RandomAccessFile三个类可以产生文件通道。
注意:Java new I/O的目标是提高I/O速度,快速移动大批量的数据,因此,字节缓冲区的大小非常重要,例子中的1K大小不一定是合适的,应用程序需要在生产环境中测试确定合适的缓冲区大小。
5.文件复制:
文件通道和字节缓冲区不但可以实现数据从文件读入和读出,还可以实现文件的复制,例子如下:
[java] view plaincopy
import java.nio.*;
import java.nio.channels/*;
import java.io.*
public class FileCopy{
//字节缓冲区大小
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
//获取文件输入通道
FileChannel in = new FileInputStream(“FileCopy.java”).getChannel();
//获取文件输出通道
FileChannel out = new FileOutputStream(“FileOut.txt”).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
While(in.read(buffer) != -1){
//重置缓冲区,准备写出
buffer.flip();
//将字节缓冲区内容写出到文件输出通道
out.write(buffer);
//清除字节缓冲区
buffer.clear();
}
}
}
6.文件通道传输:
文件通道的transferFrom(ReadableByteChannel src, long position, long count)方法将字节从给定的可读字节通道传输到此通道的文件中,transferTo(long position, long count, WritableByteChannel target)方法将此通道的文件传输到给定的可写字节通道。例子如下:
[java] view plaincopy
import java.nio.channels.*;
import java.io.*
public class ChannelTransfer{
public static void main(String[] args) throws Exception{
FileChannel in = new FileInputStream(“ChannelTransfer.java”).getChannel();
FileChannel out = new FileOutputStream(“out.txt”).getChannel();
//将输入文件通道传输到输出文件通道
in.transferTo(0, in.size(); out);
//从输入文件通道传输到输出文件通道
out.transferFrom(in, 0, in.size());
}
}
1.数据转换:
使用字节缓冲区时,向字节缓冲区写入或者从字节缓冲区读取内容时,如果内容是字符类型,写入时指定字符编码集对内容进行编码,读取时指定字节编码集对内容解码。
每次从字节缓冲区中读取一个字节,程序中还需要对读取的字节进行类型转换才可以使用,如果使用视图缓冲区就可以直接获取所需的数据类型,例子如下:
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.io.*;
public class BufferToText{
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
fc.close();
fc = new FileInputStream(“data.txt”).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
fc.read(buffer);
buffer.flip();
//创建字节缓冲区视图,作为char缓冲区,由于编码问题输出乱码
System.out.println(buffer.asCharBuffer());
//重绕字节缓冲区,即回到字节缓冲区开始处
buffer.rewind();
//获取系统默认字节编码
String encoding = System.getProperties(“file.encoding”);
System.out.println(“Decode using ” + encoding + “: ”
+ Charset.forName(encoding).decode(buffer));
fc = new FileOutputStream(“data2.txt”).getChannel();
//创建字节缓冲区时指定字符集
fc.write(ByteBuffer.wrap(“Some text”.getBytes(“UTF-16BE”)));
fc.close();
fc= new FileInputStream(“data2.txt”).getChannel();
buffer.clear();
fc.read(buffer);
buffer.flip();
//创建字节缓冲区的视图,将其作为char缓冲
System.out.println(buffer.asCharBuffer());
}
}
输出结果:
????
Decoded using Cp1252: Some text
Some text
从上面例子可以看出,使用java.nio.charset.Charset类对字符进行编码/解码之后,写入/读取字节缓冲区时,字节缓冲区中的字符内容才可以正常显示。
2.创建字节缓冲区视图:
字节缓冲区中只能存放字节内容,java 8种基本类型数据向字节缓冲区中写入,或者从字节缓冲区中读取,就需要使用字节缓冲区视图和读取8中java基本类型数据的方法,例子如下:
import java.nio.*;
public class ViewBuffer{
private static final int BSIZE = 1024;
public static void main(String[] args){
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
int i = 0;
//检查字节缓冲区的内容是否为0
while(i++ < bb.limit()){
if(bb.get() != 0){
System.out.println(“nonzero”);
}
System.out.println(“i = ” + i);
//回到字节缓冲区开始处
bb.rewind();
}
//创建字节缓冲区视图,作为char缓冲
bb.asCharBuffer().put(“Hello!”);
char c;
while((c = bb.getChar)) != 0){
System.out.println(c + “ ”);
}
bb.rewind();
//创建字节缓冲区视图,作为short缓冲
bb.asShortBuffer().put((Short)12390);
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());
}
}
输出结果:
i = 1025
H e l l o !
12390
99471142
99471142
9.9471142E7
9.9471142E7
对于刚分配的字节缓冲区来说,其所有内容都是0,所有第一次i的输出值为1025.
向字节缓冲区中写入,或者从字节缓冲区中读取8中基本类型java数据最简便的方法是使用字节缓冲区的视图缓冲区。
3.java nio相关类图:
下图是从《java编程思想第4版》中截的java nio相关类图:
从类图中可以看出,java nio中,只有字节缓冲区ByteBuffer可以向文件通道写数据或者从文件通道中读取数据。
4.缓冲区详解:
缓冲区由:内容数据和4个索引组成。
缓冲区索引:
a. 缓冲区标记(mark):使缓冲区能够记住一个位置并在之后将其返回。并非总需要定义标记,但在定义标记时,不能将其定义为负数,且不能大于其位置。
b. 缓冲区位置(position):是缓冲区下一个要读取或写入元素的索引,位置不能为负,且不能大于其限制。
c. 缓冲区限制(limit):是第一个不应该读取或写入的元素的索引,限制不能为负,且不能大于其容量。
d. 缓冲区容量(capacity):是缓冲区所包含的元素数量,不能为负,且不能更改。
缓冲区索引遵循以下不变公式:
0 <= 标记 <= 位置 <= 限制 <= 容量
通过这些索引,缓冲区可以高效的访问和操作缓冲区中的内容数据,新创建的缓冲区总有一个0位置和一个未定义的标记,初始限制可以为0,也可以为其他值,取决于缓冲区类型和构建方式。
缓冲区的以下方法可以查询,设置和充值缓冲区的索引:
(1).capacity()方法:
返回此缓冲区的容量。
(2).clear()方法:
清除此缓冲区,将缓冲区位置设置为0,将缓冲区限制设置为容量,并丢弃标记。
(3).flip()方法:
反转此缓冲区,首先将限制设置为当前位置,然后将位置设置为0,如果已定义了标记,则丢弃该标记。
(4).limit()方法:
返回此缓冲区的限制。
(5).limit(int lim)方法:
设置此缓冲区的限制,如果位置大于新的限制,则将位置设置为新的限制,如果标记已定义且大于新限制,则丢弃该标记。
(6).mark()方法:
在此缓冲区的位置设置标记。
(7).position()方法:
返回此缓冲区的位置。
(8).position(int pos)方法:
设置此缓冲区的位置,如果标记已定义且大于新的位置,则丢弃该标记。
(9).remaining()方法:
返回当前位置与限制之间的元素个数,即limit-position。
(10).hasRemaining()方法:
判断在当前位置和限制之间是否有元素。
从File类开始,到文件的读写控制来叙述。
(1)File 文件或文件集名,抽象路径名,并且可以据此来查看文件的属性,同时,对象一旦建立则不能改变。
(2)最初的I/O类:
(3)java1.1中提供了兼容Unicode与面向字符的I/O功能类
看些使用的例子:
带缓冲的文件输入:
BufferedReader in = new BufferedReader( new FileReader(stringFileName) );
StringReader in = new StringReader( BufferedInputFile.read("") );
基本的文件输出:
PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter()));
PrintWriter out = new PrintWriter(stringFileName);
标准I/O,System.in out err以及重定向SetIn Out Err这里就不细说了。
(4)新I/O nio
首先是,实际上旧的I/O包已经使用nio重新实现过,因此不显式地使用nio也是用到了。
FileChannel ( FileInputStream FileOutputStream RandomAccessFile )
FileChannel fc = new FileOutputStream("").getChannel();
fc.write();
fc = new RandomAccessFile("").getChannel();
其次是视图缓冲器一说:通过在同一个ByteBuffer上建立不同的视图缓冲器,将同一个字节序列翻译成short,int,float,long和double类型,相当于说是在同一个存储区域以不同的类型形式展现。
最好是内存映射文件,我原来有篇文章简单说了下c++下的使用,意思很明显就是在磁盘上建立一块区域用来存放大文件(可能一下子无法装载进来,选择部分),用起来感觉都已经在内存中了,而不用进行文件的I/O操作。
MappedByteBuffer out = new RandomAccessFile("").getChannel().map( FileChannel.MapMode.READ_WRITE,0,length());
(5)文件加锁
其实会和后面并发中的加锁类似(是一个共享竞争资源),可以使用trylock 或 lock对文件或者映射文件的部分加锁;后者是直接获取锁,如果无法获得而阻塞;前者是尝试获取锁,无法获得直接返回;还有可以设定些等待时间之类的。
对象的序列化将那些实现了Serializable(Externalizable)接口的对象转换成一个字节序列,并能在以后将这字节序列完全恢复为原来的对象;说得白话些就是将对象相关的信息保存起来(可以写到文件里,或者远程发送出去),在需要的时候把这些保存的信息再恢复出来形成对象;这里就会有下面一些问题:
1.保存了完整的对象信息了吗?
对象的序列化的字节序列中,保存了对象的数据成员(属性),同时也包括了其成员所引用的信息;在恢复过程中不需要调用任何构造器;那么经过远程传输为什么却不可以直接恢复,原因是需要用到起class对象信息,看一下简单的用法:
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(""));
out.writeObject(duixiang);
ObjectInputStream in = new ObjectInputStream( new FileInputStream(""));
(类型)in.readObject();
这是一个简单的将对象序列化到文件中,然后读取文件再恢复的过程;不难理解如果是不同程序之间对象的恢复,至少得有这个对象的类型信息啊。
2.有些成员不想保存当前信息
对于序列化的控制,可以使用Externalizable接口,通过writeExternal() readExternal()方法来做。这个在序列化时通过将当前信息在writeExterna方法中保存想要保存的信息,恢复时,调用默认的构造器之后,通过readExternal方法将保存的信息恢复出来。这里就看到与实现Serializable接口的区别,一个不需要调用任何的构造器,一个调用默认的构造器。
那么如果想用到Serializable的自动化,又不想保存部分成员信息呢,可以使用transient关键字:
private transient string password;
3.对于引用的分歧
如果序列化多个对象,而都引用了一个对象,那么这个对象在恢复的时候是有不同的副本还是只有一个存储地址?通过下面的例子可以看到:
package com.test.myjava;
//主要演示了Serializable接口以及transient关键字
import java.io.*;
class Information implements Serializable{}
public class SerializableTest implements Serializable{
private int id;
private transient String password;
private String name;
private Information otherInformation;
public SerializableTest(int i,String p,String n,Information f){
System.out.println("Constructor:"+n);
id = i;
password = p;
name = n;
otherInformation = f;
}
public String toString(){
return "id:"+id +" name:"+name +" pasword:"+password //密码没有保存,为null
+" friendName:"+otherInformation;//otherInformation.hashCode()
}
public static void main(String[] args ) throws IOException,ClassNotFoundException{
Information allFriendName = new Information();
SerializableTest user1 = new SerializableTest(1,"1","user1",allFriendName);
SerializableTest user2 = new SerializableTest(2,"2","user2",allFriendName);
SerializableTest user3 = new SerializableTest(3,"3","user3",allFriendName);
System.out.println("Start\n");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
//序列化
ByteArrayOutputStream buf1 = new ByteArrayOutputStream();
ObjectOutputStream out1 = new ObjectOutputStream(buf1);
out1.writeObject(user1);
out1.writeObject(user2);
ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(buf2);
out2.writeObject(user3);
//恢复
ObjectInputStream in1 = new ObjectInputStream(
new ByteArrayInputStream(buf1.toByteArray()));
ObjectInputStream in2 = new ObjectInputStream(
new ByteArrayInputStream(buf2.toByteArray()));
user1 = (SerializableTest)in1.readObject();
user2 = (SerializableTest)in1.readObject();
user3 = (SerializableTest)in2.readObject();
System.out.println("Then\n");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
}
}
输出结果:
Constructor:user1
Constructor:user2
Constructor:user3
Start
id:1 name:user1 pasword:1 friendName:com.test.myjava.Information@c17164
id:2 name:user2 pasword:2 friendName:com.test.myjava.Information@c17164
id:3 name:user3 pasword:3 friendName:com.test.myjava.Information@c17164
Then
id:1 name:user1 pasword:null friendName:com.test.myjava.Information@19b49e6
id:2 name:user2 pasword:null friendName:com.test.myjava.Information@19b49e6
id:3 name:user3 pasword:null friendName:com.test.myjava.Information@10d448
对于写到一个流中的引用,确实都指向一个地址,而对于不同的流则有不同的副本。
速度的提高来自所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。我们可以把它想像成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer --------- 也就是说,可以存储未加工字节的缓冲器。当我们查询JDK文档中的java.nio.ByteBuffer时,会发现它是相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。
旧I/O类库中有三个类被修改了,用于产生FileChannel。这三个类是FileInputStream,FileOutputStream以及用于既读又写的RandomAccessFile。注意这些是字节操作流,与低层的nio性质一致。Reader和Writer这种字符模式不能用于产生通道;但是java.nio.channels.Channels类提供了实用方法,用于在通道中产生Reader和Writer。
下面的简单实例演示了上面三种类型的流,用以在通道产生可写的,可读的及可读可写的通道:
package ioStudy;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class GetChannel {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
//通过FileInputStream产生通道
FileChannel fc = new FileOutputStream("data.txt").getChannel();
//写入字符串Some text
fc.write(ByteBuffer.wrap("Some text".getBytes()));
fc.close();
//通过RandomAccessFile产生通道
fc = new RandomAccessFile("data.txt", "rw").getChannel();
//定位
fc.position(fc.size());
//写入字符串Some text
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
//通过FileOutputStream产生通道
fc = new FileInputStream("data.txt").getChannel();
//ByteBuffer.allocate申请分配ByteBuffer
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
//读取内容
fc.read(buff);
buff.flip();
while(buff.hasRemaining())
System.out.print((char)buff.get());
}
}
对于上面所展示的任何流类,getChannel()将产生一个FileChannel。通道是一种相当基础的东西:可以向它传递用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。不过,也可以使用warp()方法将已存在的字节数组“包装”到ByteBuffer中。
data.txt文件用RandomAccessFile再次打开。注意我们可以在文件内随处移动FileChannel;在这里,我们把它移到最后,以便附加其他的写操作。
对于只读访问,我们必须显式地使用静态的allocate()方法来分配ByteBuffer。nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要 -------- 实际上,这里使用的1K可能比我们通常使用的小一点(必须通过实际运行应用程序来找到最佳尺寸)。
可以使用allocateDirect()产生一个与操作系统有更高耦合的“直接”缓冲器。这可能达到更高的速度。但这种分配的开支会更大,并且具体实现也随着操作系统的不同而不同,因此必须再次实际运行应用程序来直接缓冲测试是否获得速度上的优势。
一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备(是的,这似乎有一点拙劣,但是请记住,它是很拙劣的,但却适用于获取最大速度)。如果我们打算使用缓冲器执行进一步的read()操作,我们也必须得调用clear()来为每个read()做好准备。
读写操作
package ioStudy;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws IOException {
FileChannel in = new FileInputStream("src/ioStudy/ChannelCopy.java").getChannel();
FileChannel out = new FileOutputStream("ChannelCopy.data").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
in.close();
out.close();
}
}
可以看到,打开一个FileChannel以用于读,而打开另一个用于写。ByteBuffer被分配了空间,当FileChannel.read()返回-1时,表示我们已经到达了输入的未尾。每次read()操作后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲器中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接受数据的准备。
然而,上面的程序并不是处理此类操作的理想方式。特殊方法transferTo()和transferFrom()则允许我们将一个通道和另一个通道直接相连:
package ioStudy;
import java.io.*;
import java.nio.channels.FileChannel;
public class TransferTo {
public static void main(String[] args) throws IOException {
FileChannel in = new FileInputStream("src/ioStudy/TransferTo.java").getChannel();
FileChannel out = new FileOutputStream("TransferTo.data").getChannel();
in.transferTo(0, in.size(), out);
in.close();
out.close();
}
}
转换数据
通过GetChannel 可以发现,为了输出文件中信息,必须每次只读取一个字节的数据,然后将每个byte类型强制转换成char类型。这种方法似乎有点原始 ------ 但我们可以通过getChannel()方法将ByteBuffer转化为CharBuffer,而CharBuffer的toString()方法是这样定义的“返回一个包含缓冲器中所有字符的字符串”,但是注意缓冲器容纳的是普通的字节,为了把他们转换成字符,我们要么在输入它们的时候对其进行编码。
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
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("data2.txt").getChannel();
//读取内容
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//通过CharBuffer向ByteBuffer写入内容
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24);
//通过CharBuffer向ByteBuffer写入内容
buff.asCharBuffer().put("Some text");
//将ByteBuffer内容输出
fc.write(buff);
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
//读取内容
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
输出结果:
我们对缓冲器调用rewind()方法(调用该方法是为了返回到数据开始部分),接着使用平台的默认字符集对数据进行decode(),那么作为结果的CharBuffer可以很好地输出打印到控制台。可以使用System.getProperty(“file.encoding")得到默认字符集,它会产生代表字符集名称的字符串。把该字符串传送给Charset.forName()用以产生Charset对象,可以用它对字符串进行解码。
另一选择是在读文件时,使用能够产生可打印的输出的字符集进行encode(),正如上例中的UTF-16BE,可以把文本写到文件中,当读取时,我们只需要把它转换成CharBuffer,就会产生所期望的文本。
最后,通过CharBuffer向ByteBuffer写入。注意,我们为ByteBuffer分配了24个字节。一个字符需要2个字节,那么一个ByteBuffer足可以容纳12个字符,但是”Some
text“只有9个字符,剩余的内空为零的字节仍出现在由CharBuffer的toString()表示中,我们可以在输出看到。
最后,通过CharBuffer向ByteBuffer写入。注意,我们为ByteBuffer分配了24个字节。一个字符需要2个字节,那么一个ByteBuffer足可以容纳12个字符,但是”Some text“只有9个字符,剩余的内空为零的字节仍出现在由CharBuffer的toString()表示中,我们可以在输出看到。
字符集
可以使用java.nio.charset.Charset类实现编码功能,该类提供了把数据编码成多种不同类型的字符集的工具,下面这个例子中输出了所有的字符集:
import java.nio.charset.Charset;
import java.util.*;
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<String> 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();
}
}
}
使用java.nio.CharBuffer可以将Unicode字符串转换成字节序列,如下:
Charset cset = Charset.forName("ISO-8859-1");
byte[] bytes = ...;
ByteBuffer bbuf = ByteBuffer.wrap(bytes, 0, bytes.length);
CharBuffer cbuf = cset.decode(bbuf);
System.out.println(cbuf.toString()); //解码
相反,使用ByteBuffer数组的静态方法wrap可以将一个字节数组转换成一个字节缓冲区ByteBuffer。Charset的decode方法可以将ByteBuffer转换为CharBuffer,调用它的toString方法可以获得字符串:
byte[] bytes = ...;
ByteBuffer bbuf = ByteBuffer.wrap(bytes, 0, bytes.length);
CharBuffer cbuf = cset.decode(bbuf);
System.out.println(cbuf.toString()); //解码
ByteBuffer存取数据
尽管ByteBuffer只能保存字节类型的数据,但是它具有可以从所容纳的字节中产生各种不同类型基本类型值的方法。下面这个例子展示了怎样使用这些方法来插入和抽取各种数值:
import java.nio.ByteBuffer;
public class GetData {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
//allocate方法分配空间后,空间值自动为0
int i = 0;
while(i++ < bb.limit())
if(bb.get() != 0)
System.out.println("nonzero");
System.out.println("i = " + i);
bb.rewind();
//储存和读取char
bb.asCharBuffer().put("Howdy!");
char c;
while((c = bb.getChar()) != 0)
System.out.print(c + " ");
System.out.println();
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之后,可以通过检测它的值到查看缓冲器的分配方式是否将内容自动置零 -------- 它确实是这样做的。这里一共检测了1024个值(由缓冲器的limit()决定),并且所有的值都是零。
向ByteBuffer插入基本类型数据的最简单方法是:利用asCharBuffer(),asShortBuffer()等获得该缓冲器上的视图,然后使用视图的put()方法。此方法适用于所有基本数据类型。仅有一个小小的例外,即,使用ShortBuffer的put()方法,需要进行类型转换(注意类型转换会截取或改变结果)。而其他所有的视图缓冲器在使用put()方法时,不需要进行类型转换。
视图缓冲器
视图缓冲器可以让我们通过某个特定的基本数据类型的视窗(asCharBuffer()等)操作其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此,对视图任何修改都会映射成为对ByteBuffer中数据的修改。这使我们可以很方便地向ByteBuffer插入数据。视图还允许我们从ByteBuffer一次一个地(与ByteBuffer所支持的方式相同)或者成批地(放入数组中)读取基本类型值。下面例子通过IntBuffer操作ByteBuffer中的int型数据:
import java.nio.*;
public class IntBufferDemo {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer(); //转化为<span style="color: rgb(54, 46, 43); font-family: Arial; font-size: 14px; line-height: 26px;">视图缓冲器</span>
//储存int数组
ib.put(new int[]{11, 42, 47, 99, 143, 811, 1016});
System.out.println("get(3) is " + ib.get(3));
//setting a new limit before rewinding the buffer.
ib.flip();
while(ib.hasRemaining()) {
int i = ib.get();
System.out.print(i + " ");
}
}
}
先用重载后的put()方法存储一个整数数组。接着get()和put()方法调用直接访问底层ByteBuffer中某个整数位置。
一旦底层的ByteBuffer通过视图缓冲器填满了整数或其他基本类型时,就可以直接写到通道中。正如从通道中读取那样容易,使用视图缓冲器可以把任何数据都转化成某一特定的基本类型。在下面的例子中,通过在同一个ByteBuffer上建立不同的视图缓冲器,将同一字节序列翻译成short, int , float, long 和double 类型的数据:
import java.nio.*;
public class ViewBuffers {
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 'a'});
//下面将ByteBuffer转化为各种视图缓冲器,并读取结果
bb.rewind();
System.out.print("Byte Buffer ");
while(bb.hasRemaining())
System.out.print(bb.position() + " -> " + bb.get() + " | ");
System.out.println();
CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer();
System.out.print("Char Buffer ");
while(cb.hasRemaining())
System.out.print(cb.position() + " -> " + cb.get() + " | ");
System.out.println();
FloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffer();
System.out.print("Float Buffer");
while(fb.hasRemaining())
System.out.print(fb.position() + " -> " + fb.get() + " | ");
System.out.println();
IntBuffer ib = ((ByteBuffer)bb.rewind()).asIntBuffer();
System.out.print("Int Buffer");
while(ib.hasRemaining())
System.out.print(ib.position() + " -> " + ib.get() + " | ");
System.out.println();
LongBuffer lb = ((ByteBuffer)bb.rewind()).asLongBuffer();
System.out.print("Long Buffer");
while(lb.hasRemaining())
System.out.print(lb.position() + " -> " + lb.get() + " | ");
System.out.println();
ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffer();
System.out.print("Short Buffer");
while(sb.hasRemaining())
System.out.print(sb.position() + " -> " + sb.get() + " | ");
System.out.println();
DoubleBuffer db = ((ByteBuffer)bb.rewind()).asDoubleBuffer();
System.out.print("Double Buffer");
while(db.hasRemaining())
System.out.print(db.position() + " -> " + db.get() + " | ");
}
}
输出结果:
ByteBuffer通过一个被“包装”过的8字节数组产生,然后通过各种不同的基本类型的视图缓冲器显示出来。从下图可看出,当从不同类型的缓冲器读取时,数据显示的方式也不同。与上面程序的输出相对应。
内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组来访问。这种方法极大地简化了用于修改文件的代码:
package ioStudy;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class LargeMappedFiles {
static int length = 0x8FFFFFF; // 128MB
public static void main(String[] args) throws FileNotFoundException, IOException {
MappedByteBuffer out =
new RandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);//构造内存映射文件
//存入数据
for(int i = 0;i < length; i++){
out.put((byte)'x');
}
System.out.println("Finished writing ");
//读取数据
for(int i = length/2; i < length/2 + 6; i++) {
System.out.println((char)out.get(i));
}
}
}
为了既能定又能读,我们通过RandomAccessFile获得该文件的通道,然后调用map()产生MappedByteBuffer,这是一种特殊类型的直接缓冲器。注意我们必须指定映射文件的初始位置和映射区域的长度,这意味着我们可以映射某个大文件的较小的部分。
可以指定文件区域与映射模式,有三种模式:
FileChannel.MapMode.READ_ONLY:所产生的缓冲区是只读的,任何对该缓冲区写入的操作都会导致ReadOnlyBufferException异常。
FileChannel.MapMode.READ_WRITE:所产生的缓冲区是可写的,任何修改都会在某个时刻写到文件中。注意:其他映射同一个文件的程序可能不能立即看到这些修改,多个程序同时时行文件映射的确切行为依赖于操作系统。
FileChannel.MapMode.PRIVATE:所产生的缓冲区是可写的,但任何修改对这个缓冲区来说都是私有的,不会传播到文件中。
MappedByteBuffer由ByteBuffer继承而来,因此它具有ByteBuffer的所有方法。这例子仅仅展示了非常简单的put()和get(),但我们同样可以使用像asCharBuffer()等方法。
获得缓冲区后,可以使用ByteBuffer类和Buffer超类的方法读写数据。
可以顺序遍历缓冲区所有字节:
while(buffer.hasRemaining()){
byte b = buffer.get();
...
}
也可以随机访问:
for(int i = 0; i < buffer.limit(); i++){
byte b = buffer.get(i);
...
}
也可以用下面方法来读写数组:
get(byte[] bytes);
get(byte[],int offset,int length);
还可用下面方法读入在文件中存储为二进制值的基本类型值:getInt/getLong/getShort/getFloat/getDouble
上面例子创建的文件为128MB,这可能比操作系统所允许一次载入内存的空间大。但我们似乎可以一次访问整个文件,因为只有一部分放入内存,文件的其他部分被交换了出去。用这种方式,很大的文件(可达2G)也可以很容易地修改。注意底层操作系统的文件映射工具是用来最大化提高性能的。
性能
尽管“旧”的I/O流用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度。下面程序进行简单的性能比较:
package ioStudy;
import java.io.*;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
public class MappedIO {
private static int numOfInts = 4000000;
private static int numOfUbuffInts = 2000000;
private abstract static class Tester {
private String name;
public Tester(String name) {
this.name = name;
}
public void runTest(){
System.out.println(name + ": ");
try {
long start = System.nanoTime();
test();
double duration = System.nanoTime() - start;
System.out.format("%.2f\n", duration/1.0e9);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
public abstract void test() throws IOException;
}
private static Tester[] tests = {
new Tester("Stream Write") {
public void test() throws IOException {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(new File("temp.tmp"))));
for(int i = 0; i < numOfInts; i++)
dos.writeInt(i);
dos.close();
}
},
new Tester("Mapped Write") {
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size())
.asIntBuffer();
for(int i = 0; i < numOfInts; i++)
ib.put(i);
fc.close();
}
},
new Tester("Stream read") {
public void test() throws IOException {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("temp.tmp")));
for(int i = 0;i < numOfInts; i++)
dis.readInt();
dis.close();
}
},
new Tester("Mapped read") {
public void test() throws IOException {
FileChannel fc = new FileInputStream(new File("temp.tmp")).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer();
while(ib.hasRemaining())
ib.get();
fc.close();
}
},
new Tester("Stream Read/Write") {
public void test() throws IOException {
RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"),"rw");
raf.writeInt(1);
for(int i = 0; i < numOfUbuffInts; i++) {
raf.seek(raf.length() - 4);
raf.writeInt(raf.readInt());
}
raf.close();
}
},
new Tester("Mapped Read/Write") {
public void test() throws IOException {
FileChannel fc = new RandomAccessFile(new File("temp.tmp"),"rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0 ,fc.size()).asIntBuffer();
ib.put(0);
for(int i = 1; i < numOfUbuffInts; i++)
ib.put(ib.get(i - 1));
fc.close();
}
}
};
public static void main(String[] args) {
for(Tester test : tests)
test.runTest();
}
}
输出结果:
尽管 "映射写"要用到FileOutputStream,但映射文件中所有输出必须使用RandomAccessFile,如上例所示。
注意test()方法包括初始化各种I/O对象的时间,因此,即使建立映射文件的花费很大,但是整体受益比起I/O流来说还是很显著的。
new I/O:目的是为了提高速度,在新版本的JDK中,旧的I/O也使用nio重新实现,有性能提升。速度的提高来自于所使用的结构更接近于操作系统的执行I/O方式——通道和缓冲器。
流使用getChannel()方法会产生一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问(后面有提到)。
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class GetChannel {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
// Write a file:
FileChannel fc =
new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
// Add to the end of the file:
fc = new RandomAccessFile("data.txt", "rw").getChannel();
fc.position(fc.size()); // Move to the end
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
// Read the file:
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while(buff.hasRemaining())
System.out.print((char)buff.get());
}
}
将字节存放于ByteBuffer有2种方法:一是用put方法直接填充一个或多个字节,或基本数据类型;另一种是用warp方法将已存在的字节数组包装到ByteBuffer中。对于只读访问,我们必须显示的使用静态的allocate方法分配ByteBuffer。nio的目标就是快速移动大量数据,因此ByteBuffer的大小就很重要——需要在实际运行的程序中测试来找到最佳值。要达到更高的速度也有可能,方法就是使用allocateDirect()而不是allocate(),以产生一个与操作系统有更高耦合性的“直接”缓冲器,但效果需要实际测试一下。
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
if(args.length != 2) {
System.out.println("arguments: sourcefile destfile");
System.exit(1);
}
FileChannel in = new FileInputStream(args[0]).getChannel();
FileChannel out = new FileOutputStream(args[1]).getChannel();
//有一种特殊的办法,将2个通道直接相连
// in = transferTo(0, in.size(), out); 或者 out = transferFrom(in, 0, in.size());
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1) {
buffer.flip(); // Prepare for writing
out.write(buffer);
buffer.clear(); // Prepare for reading
}
}
}
BufferWriter转换成char型来操作,很不方便,我们利用CharBuffer的toString()方法来转换成String型就方便多了。
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.io.*;
public class BufferToText {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
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();
// Doesn't work:
System.out.println(buff.asCharBuffer());
// Decode using this system's default Charset:
buff.rewind();
String encoding = System.getProperty("file.encoding");
System.out.println("Decoded using " + encoding + ": "
+ Charset.forName(encoding).decode(buff));
// Or, we could encode with something that will print:
fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap(
"Some text".getBytes("UTF-16BE")));
fc.close();
// Now try reading again:
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
// Use a CharBuffer to write through:
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24); // More than needed
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
// Read and display:
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
-------------------------------------------------------------------------------------------------------------------------------------------
内存映射文件:它允许我们创建和修改那些因为太大而不能放入内存的文件。
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import static net.mindview.util.Print.*;
public class LargeMappedFiles {
static int length = 0x8FFFFFF; // 128 MB
public static void main(String[] args) throws Exception {
MappedByteBuffer out =
new RandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);
for(int i = 0; i < length; i++)
out.put((byte)'x');
print("Finished writing");
for(int i = length/2; i < length/2 + 6; i++)
printnb((char)out.get(i));
}
}
文件加锁:通过对FileChannel调用tryLock()和lock(),就可以获得整个文件的FileLock。启动tryLock()是非阻塞式的,它设法获取锁,但如果不能获得,将直接放方法调用返回。lock()是阻塞式的,它要阻塞线程直到获得锁,或者调用lock()的线程中断,或者调用lock()的通道关闭。
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;
public class FileLocking {
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();
if(fl != null) {
System.out.println("Locked File");
TimeUnit.MILLISECONDS.sleep(100);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
}
对映射文件的部分加锁:
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class LockingMappedFiles {
static final int LENGTH = 0x8FFFFFF; // 128 MB
static FileChannel fc;
public static void main(String[] args) throws Exception {
fc =
new RandomAccessFile("test.dat", "rw").getChannel();
MappedByteBuffer out =
fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
for(int i = 0; i < LENGTH; i++)
out.put((byte)'x');
new LockAndModify(out, 0, 0 + LENGTH/3);
new LockAndModify(out, LENGTH/2, LENGTH/2 + LENGTH/4);
}
private static class LockAndModify extends Thread {
private ByteBuffer buff;
private int start, end;
LockAndModify(ByteBuffer mbb, int start, int end) {
this.start = start;
this.end = end;
mbb.limit(end);
mbb.position(start);
buff = mbb.slice();
start();
}
public void run() {
try {
// Exclusive lock with no overlap:
FileLock fl = fc.lock(start, end, false);
System.out.println("Locked: "+ start +" to "+ end);
// Perform modification:
while(buff.position() < buff.limit() - 1)
buff.put((byte)(buff.get() + 1));
fl.release();
System.out.println("Released: "+start+" to "+ end);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
}
1,对程序语言设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的事。
2,File类:
它是能代表一个特定的文件的名称,又能代表一个目录下的一组文件的名称。如果它代表一个文件集,我们就可以对此集合调用list()方法,这个方法会返回一个字符的数组。
3,文件目录器
public class DirList {
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new DirFilter(args[0]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
}
class DirFilter implements FilenameFilter {
private Pattern pattern;
public DirFilter(String regex) {
pattern = Pattern.compile(regex);
}
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
} /* Output:
列出当前目录下所有文件。
下面改用匿名内部类:
public class DirList2 {
public static FilenameFilter filter(final String regex) {
// Creation of anonymous inner class:
return new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
}; // End of anonymous inner class
}
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(filter(args[0]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
} /* Output:
进一步改进:
public class DirList3 {
public static void main(final String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new FilenameFilter() {
private Pattern pattern = Pattern.compile(args[0]);
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
} /
代码更少,将解决特定问题的代码隔离、聚拢于一点。但是代码不易于阅读。应该谨慎使用。
4,目录实用工具
程序设计中一项常见的任务就是在文件集上执行操作,这些文件要么来自本地目录,要不遍布整个目录树中。下面这个工具
能够为你产生这个文件集,很有用。
package net.mindview.util;
import java.util.regex.*;
import java.io.*;
import java.util.*;
public final class Directory {
public static File[]
local(File dir, final String regex) {
return dir.listFiles(new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(
new File(name).getName()).matches();
}
});
}
public static File[]
local(String path, final String regex) { // Overloaded
return local(new File(path), regex);
}
// A two-tuple for returning a pair of objects:
public static class TreeInfo implements Iterable<File> {
public List<File> files = new ArrayList<File>();
public List<File> dirs = new ArrayList<File>();
// The default iterable element is the file list:
public Iterator<File> iterator() {
return files.iterator();
}
void addAll(TreeInfo other) {
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public String toString() {
return "dirs: " + PPrint.pformat(dirs) +
"\n\nfiles: " + PPrint.pformat(files);
}
}
public static TreeInfo
walk(String start, String regex) { // Begin recursion
return recurseDirs(new File(start), regex);
}
public static TreeInfo
walk(File start, String regex) { // Overloaded
return recurseDirs(start, regex);
}
public static TreeInfo walk(File start) { // Everything
return recurseDirs(start, ".*");
}
public static TreeInfo walk(String start) {
return recurseDirs(new File(start), ".*");
}
static TreeInfo recurseDirs(File startDir, String regex){
TreeInfo result = new TreeInfo();
for(File item : startDir.listFiles()) {
if(item.isDirectory()) {
result.dirs.add(item);
result.addAll(recurseDirs(item, regex));
} else // Regular file
if(item.getName().matches(regex))
result.files.add(item);
}
return result;
}
// Simple validation test:
public static void main(String[] args) {
if(args.length == 0)
System.out.println(walk("."));
else
for(String arg : args)
System.out.println(walk(arg));
}
} ///:~
“灵巧打印机”
package net.mindview.util;
import java.util.*;
public class PPrint {
public static String pformat(Collection<?> c) {
if(c.size() == 0) return "[]";
StringBuilder result = new StringBuilder("[");
for(Object elem : c) {
if(c.size() != 1)
result.append("\n ");
result.append(elem);
}
if(c.size() != 1)
result.append("\n");
result.append("]");
return result.toString();
}
public static void pprint(Collection<?> c) {
System.out.println(pformat(c));
}
public static void pprint(Object[] c) {
System.out.println(pformat(Arrays.asList(c)));
}
} ///:~
使用的例子:
public class DirectoryDemo {
public static void main(String[] args) {
// All directories:
PPrint.pprint(Directory.walk(".").dirs);
// All files beginning with 'T'
for(File file : Directory.local(".", "T.*"))
print(file);
print("----------------------");
// All Java files beginning with 'T':
for(File file : Directory.walk(".", "T.*\\.java"))
print(file);
print("======================");
// Class files containing "Z" or "z":
for(File file : Directory.walk(".",".*[Zz].*\\.class"))
print(file);
}
} /* Output: (Sample)
下面的工具,可以在目录中穿行,根据Strategy对象来处理这些目录中的文件。
package net.mindview.util;
import java.io.*;
public class ProcessFiles {
public interface Strategy {
void process(File file);
}
private Strategy strategy;
private String ext;
public ProcessFiles(Strategy strategy, String ext) {
this.strategy = strategy;
this.ext = ext;
}
public void start(String[] args) {
try {
if(args.length == 0)
processDirectoryTree(new File("."));
else
for(String arg : args) {
File fileArg = new File(arg);
if(fileArg.isDirectory())
processDirectoryTree(fileArg);
else {
// Allow user to leave off extension:
if(!arg.endsWith("." + ext))
arg += "." + ext;
strategy.process(
new File(arg).getCanonicalFile());
}
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
public void
processDirectoryTree(File root) throws IOException {
for(File file : Directory.walk(
root.getAbsolutePath(), ".*\\." + ext))
strategy.process(file.getCanonicalFile());
}
// Demonstration of how to use it:
public static void main(String[] args) {
new ProcessFiles(new ProcessFiles.Strategy() {
public void process(File file) {
System.out.println(file);
}
}, "xml").start(args);
}
} /* (Execute to see output) *///:~
5,InputStream类型
包括:ByteArrayInputStream:允许将内存缓冲区当做InputStream
StringBufferInputStream:将String转换成InputStream
FileInputStream:从文件中读取信息
PipedInputStream:产生用于写入相关PipedOutSream的数据。实现“管道化”概念
SequenceInputStream:将两个或多个InputSream转换为单个InputStream
FilterInputStream:抽象类,作为“装饰类”的接口
6,OutputStream类型
包括:
ByteArrayOutputStream:在内存中创建缓冲区,所有送往"流"的数据都要放置在此缓冲区。
FileOutputStream:将信息写到文件。
PipedOutputStream:任何写入其中的信息都会自动作为相关PipedInputSream的输出。实现“管道化”概念。
FilterOutputStream":抽象类,作为“装饰类”的接口。
7:文件读写的实用工具
一个常见的程序化任务就是读取文件到内存,修改,然后再写出。java I/O类库的问题之一就是:它需要编写相当多的代
码去执行这些常用操作。下面的工具能简化对文件的读写操作:
package net.mindview.util;
import java.io.*;
import java.util.*;
public class TextFile extends ArrayList<String> {
// Read a file as a single string:
public static String read(String fileName) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader in= new BufferedReader(new FileReader(
new File(fileName).getAbsoluteFile()));
try {
String s;
while((s = in.readLine()) != null) {
sb.append(s);
sb.append("\n");
}
} finally {
in.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
// Write a single file in one method call:
public static void write(String fileName, String text) {
try {
PrintWriter out = new PrintWriter(
new File(fileName).getAbsoluteFile());
try {
out.print(text);
} finally {
out.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
// Read a file, split by any regular expression:
public TextFile(String fileName, String splitter) {
super(Arrays.asList(read(fileName).split(splitter)));
// Regular expression split() often leaves an empty
// String at the first position:
if(get(0).equals("")) remove(0);
}
// Normally read by lines:
public TextFile(String fileName) {
this(fileName, "\n");
}
public void write(String fileName) {
try {
PrintWriter out = new PrintWriter(
new File(fileName).getAbsoluteFile());
try {
for(String item : this)
out.println(item);
} finally {
out.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
// Simple test:
public static void main(String[] args) {
String file = read("src\\net\\mindview\\util\\TextFile.java");
write("test.txt", file);
TextFile text = new TextFile("test.txt");
text.write("test2.txt");
// Break into unique sorted list of words:
TreeSet<String> words = new TreeSet<String>(
new TextFile("src\\net\\mindview\\util\\TextFile.java", "\\W+"));
// Display the capitalized words:
System.out.println(words.headSet("a"));
}
} /* Output:
8,进程控制
你经常会需要在java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出。java类库提供了执行这些操作的
类。
public class OSExecuteException extends RuntimeException {
public OSExecuteException(String why) { super(why); }
} ///:~
package net.mindview.util;
import java.io.*;
public class OSExecute {
public static void command(String command) {
boolean err = false;
try {
Process process =
new ProcessBuilder(command.split(" ")).start();
BufferedReader results = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String s;
while((s = results.readLine())!= null)
System.out.println(s);
BufferedReader errors = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
// Report errors and return nonzero value
// to calling process if there are problems:
while((s = errors.readLine())!= null) {
System.err.println(s);
err = true;
}
} catch(Exception e) {
// Compensate for Windows 2000, which throws an
// exception for the default command line:
if(!command.startsWith("CMD /C"))
command("CMD /C " + command);
else
throw new RuntimeException(e);
}
if(err)
throw new OSExecuteException("Errors executing " +
command);
}
} ///:~
public class OSExecuteDemo {
public static void main(String[] args) {
OSExecute.command("javap bin/OSExecuteDemo");
}
} /* Output:
9,文件加锁
jdk1.4 引入了文件加锁机制,它允许我们同步某个作为共享资源的文件。
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;
public class FileLocking {
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().lock();
if(fl != null) {
System.out.println("Locked File");
System.out.println("isShared?"+fl.isShared());;
TimeUnit.MILLISECONDS.sleep(100);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
} /* Output:
通过对FileChannel调用tryLock()或lock()。前者是非阻塞的,后者是阻塞的。
10,压缩
java I/O类库中的类支持读写压缩格式的数据流。主要有如下类:
CheckedInputStream: GetCheckSum()为任何InputSream产生校验和(不仅是解压缩)
CheckedOutputSream:GetCheckSum()为任何OutputStream产生校验和(不仅是压缩)
DeflaterOutputStream:压缩类的基类
ZipOutputStream:一个DeflaterOutputStream,用于将数据压缩成Zip文件格式。
GZIPOutputStream:一个DeflaterOutputStream,用于将数据压缩成GZIP文件格式。
InflaterInputStream:解压缩类的基类
ZipInputStream:一个InflaterInputStream,用于解压缩Zip格式的文件数据
GZIPInoutStream:一个InflaterInputStream,用于解压缩GZIP格式的文件数据
11,用GZIP进行简单压缩
import java.util.zip.*;
import java.io.*;
public class GZIPcompress {
public static void main(String[] args)
throws IOException {
if(args.length == 0) {
System.out.println(
"Usage: \nGZIPcompress file\n" +
"\tUses GZIP compression to compress " +
"the file to test.gz");
System.exit(1);
}
BufferedReader in = new BufferedReader(
new FileReader(args[0]));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream("test.gz")));
System.out.println("Writing file");
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.close();
System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(
new InputStreamReader(new GZIPInputStream(
new FileInputStream("test.gz"))));
String s;
while((s = in2.readLine()) != null)
System.out.println(s);
}
} /* (Execute to see output) *///:~
12,用ZIP进行多个文件的保存:
import java.util.zip.*;
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class ZipCompress {
public static void main(String[] args)
throws IOException {
FileOutputStream f = new FileOutputStream("test.zip");
CheckedOutputStream csum =
new CheckedOutputStream(f, new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out =
new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
// No corresponding getComment(), though.
for(String arg : args) {
print("Writing file " + arg);
BufferedReader in =
new BufferedReader(new FileReader(arg));
zos.putNextEntry(new ZipEntry(arg));
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.flush();
}
out.close();
// Checksum valid only after the file has been closed!
print("Checksum: " + csum.getChecksum().getValue());
// Now extract the files:
print("Reading file");
FileInputStream fi = new FileInputStream("test.zip");
CheckedInputStream csumi =
new CheckedInputStream(fi, new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null) {
print("Reading file " + ze);
int x;
while((x = bis.read()) != -1)
System.out.write(x);
}
if(args.length == 1)
print("Checksum: " + csumi.getChecksum().getValue());
bis.close();
// Alternative way to open and read Zip files:
ZipFile zf = new ZipFile("test.zip");
Enumeration e = zf.entries();
while(e.hasMoreElements()) {
ZipEntry ze2 = (ZipEntry)e.nextElement();
print("File: " + ze2);
// ... and extract the data as before
}
/* if(args.length == 1) */
}
} /* (Execute to see output) *///:~
13:java档案文件
Zip格式也被应用于JAR格式的文件。jar cf myJarfile.jar *.class
14:对象序列化
java的对象序列化是将那些实现了Serializable接口的对象转
换成一个字节序列,并能够在以后将这个字节完整恢复为原来的对象。
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
class Data implements Serializable {
private int n;
public Data(int n) { this.n = n; }
public String toString() { return Integer.toString(n); }
}
public class Worm implements Serializable {
private static Random rand = new Random(47);
private Data[] d = {
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10))
};
private Worm next;
private char c;
// Value of i == number of segments
public Worm(int i, char x) {
print("Worm constructor: " + i);
c = x;
if(--i > 0)
next = new Worm(i, (char)(x + 1));
}
public Worm() {
print("Default constructor");
}
public String toString() {
StringBuilder result = new StringBuilder(":");
result.append(c);
result.append("(");
for(Data dat : d)
result.append(dat);
result.append(")");
if(next != null)
result.append(next);
return result.toString();
}
public static void main(String[] args)
throws ClassNotFoundException, IOException {
Worm w = new Worm(6, 'a');
print("w = " + w);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("worm.out"));
out.writeObject("Worm storage\n");
out.writeObject(w);
out.close(); // Also flushes output
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("worm.out"));
String s = (String)in.readObject();
Worm w2 = (Worm)in.readObject();
print(s + "w2 = " + w2);
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(bout);
out2.writeObject("Worm storage\n");
out2.writeObject(w);
out2.flush();
ObjectInputStream in2 = new ObjectInputStream(
new ByteArrayInputStream(bout.toByteArray()));
s = (String)in2.readObject();
Worm w3 = (Worm)in2.readObject();
print(s + "w3 = " + w3);
}
} /* Output:
15,序列化的控制
在特殊的情况下,这个实现了Externalizable接口。这个代替实现了Serializable接口,来对序列化过程进行控制。
这个Externalizable接口继承了Serializable接口。同时增加了两个方法。writeExternale()和readExternal()
这两个方法在序列化合反序列化还原的过程中被自动调用,以便执行一些特殊的操作。
import java.io.*;
import static net.mindview.util.Print.*;
class Blip1 implements Externalizable {
public Blip1() {
print("Blip1 Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip1.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip1.readExternal");
}
}
class Blip2 implements Externalizable {
public Blip2() {
print("Blip2 Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip2.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip2.readExternal");
}
}
public class Blips {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
print("Constructing objects:");
Blip1 b1 = new Blip1();
Blip2 b2 = new Blip2();
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blips.out"));
print("Saving objects:");
o.writeObject(b1);
o.writeObject(b2);
o.close();
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blips.out"));
print("Recovering b1:");
b1 = (Blip1)in.readObject();
// OOPS! Throws an exception:
print("Recovering b2:");
b2 = (Blip2)in.readObject();
}
} /* Output:
正式的调用序列化:
public class Blip3 implements Externalizable {
private int i;
private String s; // No initialization
public Blip3() {
print("Blip3 Constructor");
// s, i not initialized
}
public Blip3(String x, int a) {
print("Blip3(String x, int a)");
s = x;
i = a;
// s & i initialized only in non-default constructor.
}
public String toString() { return s + i; }
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip3.writeExternal");
// You must do this:
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip3.readExternal");
// You must do this:
s = (String)in.readObject();
i = in.readInt();
}
public static void main(String[] args)
throws IOException, ClassNotFoundException {
print("Constructing objects:");
Blip3 b3 = new Blip3("A String ", 47);
print(b3);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blip3.out"));
print("Saving object:");
o.writeObject(b3);
o.close();
// Now get it back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blip3.out"));
print("Recovering b3:");
b3 = (Blip3)in.readObject();
print(b3);
}
}
16,transient(瞬时)关键字
如果我们正在操作一个Serializable对象,那么所有的序列化都会自动进行,为了能够予以控制,可以用transient关键字
逐个字段的关闭序列化,它的意思是:“不用麻烦你保存或者恢复数据--我自己会处理的”。
import java.util.concurrent.*;
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password;
public Logon(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
return "logon info: \n username: " + username +
"\n date: " + date + "\n password: " + password;
}
public static void main(String[] args) throws Exception {
Logon a = new Logon("Hulk", "myLittlePony");
print("logon a = " + a);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Logon.out"));
o.writeObject(a);
o.close();
TimeUnit.SECONDS.sleep(1); // Delay
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Logon.out"));
print("Recovering object at " + new Date());
a = (Logon)in.readObject();
print("logon a = " + a);
}
} /* Output: (Sample)
如果想序列化static的值,必须自己动手去实现:
import java.io.*;
import java.util.*;
abstract class Shape12 implements Serializable {
public static final int RED = 1, BLUE = 2, GREEN = 3;
private int xPos, yPos, dimension;
private static Random rand = new Random(47);
private static int counter = 0;
public abstract void setColor(int newColor);
public abstract int getColor();
public Shape12(int xVal, int yVal, int dim) {
xPos = xVal;
yPos = yVal;
dimension = dim;
}
public String toString() {
return getClass() +
"color[" + getColor() + "] xPos[" + xPos +
"] yPos[" + yPos + "] dim[" + dimension + "]\n";
}
public static Shape12 randomFactory() {
int xVal = rand.nextInt(100);
int yVal = rand.nextInt(100);
int dim = rand.nextInt(100);
switch(counter++ % 3) {
default:
case 0: return new Circle(xVal, yVal, dim);
case 1: return new Square(xVal, yVal, dim);
case 2: return new Line(xVal, yVal, dim);
}
}
}
class Circle extends Shape12 {
private static int color = RED;
public Circle(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
class Square extends Shape12 {
private static int color;
public Square(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
color = RED;
}
public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
class Line extends Shape12 {
private static int color = RED;
public static void
serializeStaticState(ObjectOutputStream os)
throws IOException { os.writeInt(color); }
public static void
deserializeStaticState(ObjectInputStream os)
throws IOException { color = os.readInt(); }
public Line(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
public class StoreCADState {
public static void main(String[] args) throws Exception {
List<Class<? extends Shape12>> shapeTypes =
new ArrayList<Class<? extends Shape12>>();
// Add references to the class objects:
shapeTypes.add(Circle.class);
shapeTypes.add(Square.class);
shapeTypes.add(Line.class);
List<Shape12> shapes = new ArrayList<Shape12>();
// Make some shapes:
for(int i = 0; i < 10; i++)
shapes.add(Shape12.randomFactory());
// Set all the static colors to GREEN:
for(int i = 0; i < 10; i++)
((Shape12)shapes.get(i)).setColor(Shape12.GREEN);
// Save the state vector:
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("CADState.out"));
out.writeObject(shapeTypes);
Line.serializeStaticState(out);
out.writeObject(shapes);
// Display the shapes:
System.out.println(shapes);
}
} /* Output:
恢复cad系统:
public class RecoverCADState {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("CADState.out"));
// Read in the same order they were written:
List<Class<? extends Shape>> shapeTypes =
(List<Class<? extends Shape>>)in.readObject();
Line.deserializeStaticState(in);
List<Shape> shapes = (List<Shape>)in.readObject();
System.out.println(shapes);
}
} /
17,Preferences
public class PreferencesDemo {
public static void main(String[] args) throws Exception {
Preferences prefs = Preferences
.userNodeForPackage(PreferencesDemo.class);
prefs.put("Location", "Oz");
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?", true);
int usageCount = prefs.getInt("UsageCount", 0);
usageCount++;
prefs.putInt("UsageCount", usageCount);
for(String key : prefs.keys())
print(key + ": "+ prefs.get(key, null));
// You must always provide a default value:
print("How many companions does Dorothy have? " +
prefs.getInt("Companions", 0));
}
} /
windows系统数据保存到注册表了。
1、Java中“流“类库让人迷惑的主要原因:创建单一的结果流,却需要创建多个对象。
2、使用层叠的数个对象为单个对象动态地、透明地添加职责的方式,称作“修饰器“模式。修饰器必须与其所修饰的对象具有相同的接口,这使得修饰器的基本应用具有透明性——我们可以想修饰过或没有修饰过的对象发送相同的消息。
3、为什么使用修饰器?
在直接使用扩展子类的方法时,如果导致产生了大量的、用以满足所需的各种可能的组合的子类,这是通常就会使用修饰器——处理太多的子类已经不太实际。
4、修饰器的缺点:增加代码的复杂性。
5、JavaI/O类库操作不便的原因在于:我们必须创建许多类——“核心”I/O类型加上所有的修饰器,才能得到我们所希望的单个I/O对象。
6、FilterInputStream和FilterOutputStream是用来提供修饰器类接口以控制特定输入流和输出流的俩个类。它们分别继承自I/O类库中的基类InputStream和OutputStream。
7、DataInputStream是FilterInputStream的直接子类,它允许我们读取不用的基本类型数据以及String对象。
8、InputStream和OutputStream是面向字节的,而Reader和Writer是面向字符与兼容Unicode。
9、在InputStream和OutputStream的继承层次结构仅支持8位的字节流,并且不能很好地处理16位的Unicode。由于Unicode用用于字符国际化,所以添加Reader和Write继承结构就是为了在所有的I/O操作中都支持Unicode。另外添加了Reader和Write的I/O流类库使得它的操作比旧类库更快。
10、但是,Reader和Write并不是用来取代nputStream和OutputStream的,因为InputStream和OutputStream在面向字节形式的I/O中仍然提供了极有价值的功能。
11、有时,我们需要把来自于“字节”层次结构的类和“字符”层次结构的类结合起来使用。这时,需要使用“适配器(adapter)”类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。
12、当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码。UTF-8是Unicode的变体,后者把所有字符都存储成两个字节的形式。而我们使用的只是ASCII或者几乎是ASCII字符(只占7位),这么做就显得极其浪费空间和宽带,所以UTF-8将ASCII字符编码成单一字节的形式,而非ASCII字符则编码成两到三个字节的形式。另外,字符串的长度存储在两个字节中。
13、System.in是一个没有被加工过的InputStream,所以在读取System.in之前必须对其进行加工。
14、标准I/O重定向:如果我们突然开始在显示器上创建大量输出,而这些输出滚动得太快以至于无法阅读是,重定向输出就显得极为有用。重定向有一下方法:
SetIn(InputStream)
SetOut(InputStream)
SetErr(InputStream)
注意:重定向操作的是字节流(InputStream、OutputStream),而不是字符流。
15、JDK1.4的java.nio.*包引入了新的JavaI/O类库,其目的在于提高速度。速度的提高来自于所使用的结构更接近操作系统的I/O的方式:通道和缓冲器。
我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互;只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer——也就是说,可以存储未加工字节的缓冲器。ByteBuffer是一个相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用以原始的字节形式或基本数据类型输出和读取数据。
16、FileInputStream、FileOutputStreaam以及用于既读又写的RandomAccessFile被使用nio重新实现过,都有一个getChannel()方法返回一个FileChannel(通道)。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。Reader和Writer这种字符模式类不能产生通道;但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Wirter。
以下
17、转换数据:缓冲器容纳的是普通的字节,为了把它们转化成字符,我们要么在输入它们的时候对其进行编码,要么在将其从缓冲器输出时对它们进行解码。请参看下面例子:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class GetChannel{
public static void main(String[] args) throws Exception{
//写入data.txt
FileChannel fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text".getBytes()));
fc.close();
//读取data.txt
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(1024);
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//使用系统默认的字符集进行解码(输出时对其进行解码)
buff.rewind();/返回数据开始的部分
String encoding = System.getProperty("file.encoding");
System.out.println("Decodedusing " + encoding + ":"
+ Charset.forName(encoding).decode(buff));
//再次写入(输入时对其进行编码)
fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE")));
fc.close();
//再次读取
fc = new FileInputStream("data.txt").getChannel();
buff = ByteBuffer.allocate(1024);
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//再次写入(输入时对其进行编码)
fc = new FileOutputStream("data.txt").getChannel();
buff = ByteBuffer.allocate(24);
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
//再次读取
fc = new FileInputStream("data.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
运行结果:
18、获取基本类型:尽管ByteBuffer只能保存字节类型的数据,但是它具有从其所容纳的字节中产生出各种不同的类型值的方法。
import java.nio.ByteBuffer;
public class GetData{
private final static 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();
//char
bb.asCharBuffer().put("Howdy");
char c;
while((c = bb.getChar()) != 0){
System.out.print(c + " ");
}
System.out.println();
bb.rewind();
//short。是一个特例,必须加上(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();
}
}
运行结果:
19、视图缓冲器:可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
//asIntBuffer() 创建此字节缓冲区的视图,作为 int 缓冲区。
IntBuffer ib = bb.asIntBuffer();
20、不同机器可能会有不同的字节排序方法来存储数据。默认的ByteBuffer是以高位优先的顺序存储数据的。考虑下面两个字节:
如果我们以short(ByteBuffer.asShortBuffer)高位优先读出的是97(0000000001100001),若更改ByteBuffer更改为地位优先,则读出的是24832(0110000100000000)。再看下面例子:
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
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");
//array()只能对有数组支持的缓冲器调用
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()));
}
}
运行结果:
21、如果想把一个字节数组写到文件中去,那么应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel中去。
22、存储器映射文件:允许我们创建和修改那些因为太大而不能放入内存的文件。有了存储器映射文件,我们就可以假定整个文件都在内存中,而且可以完全把它当作非常大的数组来访问。
尽管“旧”的I/O在用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度。
23、文件加锁机制:允许我们同步访问某个作为共享资源的文件。文件锁对其它操作系统进程是可见的,因为Java的文件加锁之间映射到本地操作系统的加锁工具。另外,利用对映射文件的部分加锁,可以对巨大的文件进行部分加锁,以便其他进程可以对修改文件中未被加锁的部分。
24、压缩:Java压缩类库是按字节方式的,它们继承自InputStream和OutputStream。
1).GZIP:如果只想对单个数据流(而不是一系列互异数据)进行压缩,那么它是比较适合的选择。下面是对单个文件进行压缩的例子:
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GZIPcompress{
public static void main(String[] args) throws IOException{
//InputStreamReader中设置字符编码为GBK
BufferedReader in = new BufferedReader(new InputStreamReader(new
FileInputStream("test.txt"), "GBK"));
BufferedOutputStream out = new BufferedOutputStream((
new GZIPOutputStream(new FileOutputStream("test.txt.gz"))));
System.out.println("Writingfile");
int c;
while((c = in.read()) != -1){
//使用GBK字符编码将此 String编码到 out中
out.write(String.valueOf((char) c).getBytes("GBK"));
}
in.close();
out.close();
System.out.println("ReadingFile");
BufferedReader in2 = new BufferedReader((new InputStreamReader
(new GZIPInputStream(new FileInputStream("test.txt.gz")))));
String s;
while((s = in2.readLine()) != null){
System.out.println(s);
}
}
}
2).用Zip进行多文件压缩:对于每一个要加入压缩档案的文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。
25、对象的序列化:将那些实现Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。利用它可以实现“轻量持久性”。
1).“持久化”:意味一个对象的生存周期并不取决于程序是否正在执行。通过将一个序列化对象写入磁盘,然后再重新调用程序时恢复该对象,就能够实现持久性的效果。
2).“轻量级”:因为它不能用某种“persistent”(持久)关键字来简单定一个对象,并让系统自动维护其他细节问题。相反,对象必须在程序中显示地序列化和反序列化还原。
26、对象序列化的概念加入到语言中是为了支持两种主要特性:
1).Java的“远程方法调用”(Remote Method Invocation,RMI)。
2).Java Beans。
27、
1).序列化:创建OutputStream对象,封装到ObjectOutputStream,再调用writeObject()
2).反序列化:创建InputStream对象,封装到ObjectInputStream,再调用readObject()
3).Serializable的对象在还原的过程中,没有调用任何构造器。
28、对象序列化是面向字节的,因此采用InputStream和OutputStream层次结构。
29、序列化的控制:
1).实现Externalizable接口,接口继承自Serializable,添加了writeExternal()和readExternal()两个方法,它们会在序列化和反序列化还原的过程中被自动调用。
2).反序列过程中,对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不是调用构造器。而Externalizeble对象,普通的缺省构造函数会被调用。
3).transient(瞬时)关键字:只能和Serializable对象一起使用。
private transient String password;
password将不会被保存到磁盘中。
30、序列化/反序列化static成员:显示调用serializeStaticState()和deserializeStaticState()
31、Preferences:用于存储和读取用户的偏好(preferences)以及程序配置项的设置。只能用于小的、受限的数据集合——我们只能保存基本数据类型和字符串,并且每个字符串的存储类型长度不能超过8K(不是很小,单我们也不想用它来创建任何重要的东西)。它是一个键-值集合(类似映射),存储在一个节点层次结构中。
import java.util.Arrays;
import java.util.Iterator;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
public class PreferencesDemo{
public static void main(String[] args) throws BackingStoreException{
Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
prefs.put("日期", "2012年02月05日");
prefs.put("天气", "晴");
prefs.putInt("放假多少天了?", 24);
prefs.putBoolean("现在在家?", true);
int usageCount = prefs.getInt("寒假还剩多少天?", 16);
usageCount--;
prefs.putInt("寒假还剩多少天?", usageCount);
Iterator<String> it = Arrays.asList(prefs.keys()).iterator();
while(it.hasNext()){
String key = it.next().toString();
//null作为缺省
System.out.println(key + ":" + prefs.get(key, null));
}
System.out.println("放假多少天了?" + prefs.getInt("放假多少天了?", 0));
}
}
注意:每次运行,usageCount的值没都减少1。即每次打印出“寒假还剩多少天?”后面的数字都减少1。然而,在程序第一次运行之后,并没有任何本地文件出现。Preferences API利用合适的系统资源完成了这个任务,并且这些资源会随操作系统的不同而不同。例如在windows里,就使用注册表。
32、正则表达式:
1). 在Java ,“\\”意味“我正在插入一个正则表达式反斜杠”,那么随后的字符具有特殊意义。若想插入一个字面意义上的反斜杠,得这样子表示“\\\\”。
2).量词:
·贪婪的:竟可能的模式发现尽可能的匹配。
·勉强的:用问号来指定,匹配满足模式所需的最少字符数。
·占有的:只有在Java语言中才可用,并且它也更高效,常常用于防止正则表达式失控,因此可以是正则表达式执行起来更有效。
3).注意abc+与(abc)+的不同。
4).在java中,正则表达式是通过java.util.regex包里面的Pattern和Matcher类来实现的。
·Pattern对象表示一个正则表达式的编译版本。静态的complie()方法将一个正则表达式字符串编译成Pattern对象。
·Matcher对象有matcher()方法和输入字符串编译过的Pattern对象中产生Matcher对象。
5).split()分裂操作将输入字符串断开成字符串对象数组。
18.1File类
File既可以代表一个特定文件,又可以代表一个目录下的一组文件。
通过java.util.Arrays.sort()和String.CASE_INSENSITIVE.ORDERComparator,对文件名列表排序很容易实现。
目录列表器,两种方式获取File对象:1.调用不带参数的list()方法获取此File对象的所有列表;2.通过一个目录过滤器获取一个受限列表。
18.2输入和输出
Java中流的类库让人迷惑的真正原因是:创建单一的结果流,需要创建多个对象。
与输入有关的类都从InputStream继承,与输出相关的类都从OutputStream继承。
InputStream可能的数据源有:1.字节数组2.String队形3.文件4.管道5.一个从其他种类的流组成的序列6.其他数据源如Internet链接等7.FilterInputStream。每一种数据源都有相应的InputStream子类。
X
输出的主要目标是:1.字节数组2.文件3.管道
18.3添加属性和有用的接口
18.4Reader和Writer
InputStream和OutputStream在面向字节的I/O中发挥重要作用,Reader和Writer则提供unicode和面向字符的I/O。InputStreamReader可以把inputStream转化为Reader而outputStreamWriter可以把outputStream转化为Writer。
18.6I/O流的典型使用方式
18.6.1缓冲输入文件
Java中使用流来处理程序的输入和输出操作,流是一个抽象的概念,封装了程序数据于输入输出设备交换的底层细节。JavaIO中又将流分为字节流和字符流,字节流主要用于处理诸如图像,音频视频等二进制格式数据,而字符流主要用于处理文本字符等类型的输入输出。
1.字节输入流InputStream
输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。
常用字节输入流的主要类型:
(1).ByteArrayInputStream字节数组输入流:
主要功能:允许内存缓存作为输入流。
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。
注意:关闭ByteArrayInputStream无效,该类中的方法在关闭此流之后仍可被调用,而不会产生任何的IOException。
(2).FileInputStream文件输入流:
主要功能:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。
(3).PipedInputStream管道输入流:
主要功能:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端。
管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,这些数据有某个线程从PipedInputStream对象中读取,并有其他线程将其写入到相应的PipedOutputStream对象中。
注意:不建议PipedInputStream和PipedOutputStream对象使用单线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可以在缓冲区限定范围内将读操作和写操作分离开,如果先连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
(4).SequenceInputStream顺序输入流:
重要功能:将两个或多个输入流对象转换为一个单个输入流对象。
SequenceInputStream表示其他输入流的逻辑串联关系,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
(5).FilterInputStream过滤输入流:
主要功能:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。
常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。
2.字节输出流OutputStream:
和字节输入流相对应,字节输出流负责字节类型数据想目标文件或设备的输出。常见的字节输出流如下:
(1).ByteArrayOutputStream字节数组输出流:
主要功能:在内存中创建一个缓冲区,将接收到的数据放入该内存缓冲区中。
ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。
注意:和ByteArrayInputStream类似,关闭ByteArrayOutputStream也是无效的,此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。
(2).FileOutputStream文件输出流:
主要功能:将数据写入到指定文件中。
文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。
(3).PipedOutputStream管道输出流:
主要功能:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。
(4).FilterOutputStream过滤输出流:
主要功能:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。
常用的FIlterOutputStream是DataOutputStream数据输出流,它允许程序以适当的方式将java基本数据类型写入输出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。
3.字符流:
Java中得字节流只能针对字节类型数据,即支持处理8位的数据类型,由于java中的是Unicode码,即两个字节代表一个字符,于是在JDK1.1之后提供了字符流Reader和Writer。
字符流相关常用类如下:
(1).Reader:
用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。
(2).InputStreamReader:
是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。
它使用的字符集可以有名称指定或显式给定,也可以使用平台默认的字符集。
(3).Writer:
用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。
(4).OutputStreamWriter:
是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。
4.综合使用java IO各种流:
Java IO中的各种流,很少单独使用,经常结合起来综合使用,既可以满足特定需求,又搞效。例子如下:
(1).使用缓冲流读取文件:
1 import java.io.*;
2
3 public class BufferedInputFile{
4 public static String read(String filename) throws IOException{
5 //缓冲字符输入流
6 BufferedReader in = new BufferedReader(new FileReader(filename));
7 String s;
8 StringBuilder sb = new StringBuilder();
9 //每次读取文件中的一行
10 While((s = in.readLine()) != null){
11 sb.append(s + “\n”);
12 }
13 in.close();
14 return sb.toString();
15 }
16 public static void main(String[] args) throws IOException{
17 System.out.println(read(“BufferedInputFile.java”));
18 }
19 }
(2).读取内存中的字符串:
1 import java.io.*;
2
3 public class MemoryInput{
4 public static void main(String[] args) throws IOException{
5 //将字符串包装为字符输入流
6 StringReader in = new StringReader(
7 BufferedInputFile.read(“BufferedInputFile.java”));
8 int c;
9 //读取字符输入流中的字符
10 while((c == in.read()) != -1){
11 System.out.println((char)c);
12 }
13 }
14 }
(3).数据输入/输出流:
1 import java.io.*;
2
3 public class DataInputOutput{
4 public static void main(String[] args) thows IOException{
5 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
6 new FileOutputStream(“Data.txt”)));
7 out.writeDouble(3.14159);
8 out.writeUTF(“That was pi”);
9 out.writeDouble(1.41413);
10 out.writeUTF(“Square root of 2”);
11 out.close();
12 DataInputStream in = new DataInputStream(new BufferedInputStream(
13 new FileOutputStream(“Data.txt”)));
14 System.out.println(in.readDouble());
15 System.out.println(in.readUTF());
16 System.out.println(in.readDouble());
17 System.out.println(in.readUTF());
18 }
19 }
输出结果:
3.14159
That was pi
1.41413
Square root of 2
(4).文本文件输出流:
1 import java.io.*;
2
3 public class TextFileOutput{
4 //输出文件名
5 static String file = “BasicFileOutput.out”;
6 public static void main(String[] args) throws IOException{
7 //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
8 BufferedReader in = new BufferedReader(new StringReader
9 (BufferedInputFile.read(“TextFileOutput.java”)));
10 //字符输出流
11 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
12 int lineCount = 1;
13 String s;
14 While((s = in.readLine()) != null){
15 out.println(lineCount++ + “: ” + s);
16 }
17 out.close();
18 }
19 }
(5).二进制文件读写:
1 import java.io.*;
2
3 public class BinaryFileOutput{
4 //输出文件名
5 static String file = “BinaryFileOutput.out”;
6 public static void main(String[] args) throws IOException{
7 //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
8 BufferedInputStream in = new BufferedInputStream(
9 new FileInputStream(“TestFile.png”)));
10 //字符输出流
11 BufferedOutputStream out = new BufferedOutputStream (
12 new FileOutputStream(file));
13 byte[] buf = new byte[1024];
14 int n;
15 While((n = in.read(buf)) > 0){
16 out.write(buf, 0, n);
17 }
18 out.close();
19 }
20 }
,
1.字节输入流InputStream
输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。
常用字节输入流的主要类型:
(1).ByteArrayInputStream字节数组输入流:
主要功能:允许内存缓存作为输入流。
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。
注意:关闭ByteArrayInputStream无效,该类中的方法在关闭此流之后仍可被调用,而不会产生任何的IOException。
(2).FileInputStream文件输入流:
主要功能:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。
(3).PipedInputStream管道输入流:
主要功能:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端。
管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,这些数据有某个线程从PipedInputStream对象中读取,并有其他线程将其写入到相应的PipedOutputStream对象中。
注意:不建议PipedInputStream和PipedOutputStream对象使用单线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可以在缓冲区限定范围内将读操作和写操作分离开,如果先连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
(4).SequenceInputStream顺序输入流:
重要功能:将两个或多个输入流对象转换为一个单个输入流对象。
SequenceInputStream表示其他输入流的逻辑串联关系,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
(5).FilterInputStream过滤输入流:
主要功能:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。
常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。
2.字节输出流OutputStream:
和字节输入流相对应,字节输出流负责字节类型数据想目标文件或设备的输出。常见的字节输出流如下:
(1).ByteArrayOutputStream字节数组输出流:
主要功能:在内存中创建一个缓冲区,将接收到的数据放入该内存缓冲区中。
ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。
注意:和ByteArrayInputStream类似,关闭ByteArrayOutputStream也是无效的,此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。
(2).FileOutputStream文件输出流:
主要功能:将数据写入到指定文件中。
文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。
(3).PipedOutputStream管道输出流:
主要功能:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。
(4).FilterOutputStream过滤输出流:
主要功能:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。
常用的FIlterOutputStream是DataOutputStream数据输出流,它允许程序以适当的方式将java基本数据类型写入输出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。
3.字符流:
Java中得字节流只能针对字节类型数据,即支持处理8位的数据类型,由于java中的是Unicode码,即两个字节代表一个字符,于是在JDK1.1之后提供了字符流Reader和Writer。
字符流相关常用类如下:
(1).Reader:
用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。
(2).InputStreamReader:
是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。
它使用的字符集可以有名称指定或显式给定,也可以使用平台默认的字符集。
(3).Writer:
用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。
(4).OutputStreamWriter:
是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。
4.综合使用java IO各种流:
Java IO中的各种流,很少单独使用,经常结合起来综合使用,既可以满足特定需求,又搞效。例子如下:
(1).使用缓冲流读取文件:
import java.io.*;
public class BufferedInputFile{
public static String read(String filename) throws IOException{
//缓冲字符输入流
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
//每次读取文件中的一行
While((s = in.readLine()) != null){
sb.append(s + “\n”);
}
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException{
System.out.println(read(“BufferedInputFile.java”));
}
}
(2).读取内存中的字符串:
import java.io.*;
public class MemoryInput{
public static void main(String[] args) throws IOException{
//将字符串包装为字符输入流
StringReader in = new StringReader(
BufferedInputFile.read(“BufferedInputFile.java”));
int c;
//读取字符输入流中的字符
while((c == in.read()) != -1){
System.out.println((char)c);
}
}
}
(3).数据输入/输出流:
import java.io.*;
public class DataInputOutput{
public static void main(String[] args) thows IOException{
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(“Data.txt”)));
out.writeDouble(3.14159);
out.writeUTF(“That was pi”);
out.writeDouble(1.41413);
out.writeUTF(“Square root of 2”);
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(
new FileOutputStream(“Data.txt”)));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
输出结果:
3.14159
That was pi
1.41413
Square root of 2
(4).文本文件输出流:
import java.io.*;
public class TextFileOutput{
//输出文件名
static String file = “BasicFileOutput.out”;
public static void main(String[] args) throws IOException{
//将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
BufferedReader in = new BufferedReader(new StringReader
(BufferedInputFile.read(“TextFileOutput.java”)));
//字符输出流
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
int lineCount = 1;
String s;
While((s = in.readLine()) != null){
out.println(lineCount++ + “: ” + s);
}
out.close();
}
}
(5).二进制文件读写:
import java.io.*;
public class BinaryFileOutput{
//输出文件名
static String file = “BinaryFileOutput.out”;
public static void main(String[] args) throws IOException{
//将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
BufferedInputStream in = new BufferedInputStream(
new FileInputStream(“TestFile.png”)));
//字符输出流
BufferedOutputStream out = new BufferedOutputStream (
new FileOutputStream(file));
byte[] buf = new byte[1024];
int n;
While((n = in.read(buf)) > 0){
out.write(buf, 0, n);
}
out.close();
}
}
为了提高Java I/O的速度和效率,从JDK1.4开始引入了java.nio.*包,即java new I/O(NIO)。
事实上,为了利用java nio的速度和效率优势,原来的java I/O包中相关的类已经使用java nio重新实现,因此在编程中即使没有显式地使用java nio的代码,使用传统java I/O还是利用了nio的速度和效率优势。Java nio的速度提高主要在:文件I/O和网络I/O两个方面。
Java nio的速度提升主要因为使用了类似于操作系统本身I/O的数据结构:I/O通道(Channel)和缓冲区。
1.Channel通道:
通道表示实体,如硬件设备、文件、网络套接字或可以执行的一个或多个不同的I/O操作(如读取或写入)的程序组件的开发的链接,用于I/O操作的链接。
通道可处于打开或关闭状态。创建通道时它处于打开状态,一旦将其关闭,则保持关闭状态。一旦关闭了某个通道,试图对其调用I/O操作都会抛出ClosedChannelException异常,通过调用通道的isOpen()方法可以探测通道是否处于打开状态。一般情况下通道对于多线程的访问是安全的。
2.ByteBuffer字节缓冲区:
字节缓冲区是nio中唯一直接和通道channel打交道的缓冲区。字节缓冲区可以存放原始字节,也可以存放java的8中基本类型数据,但是不能存放引用类型数据,String也是不能存放的。正是由于这种底层的存放类型似的字节缓冲区可以更加高效和绝大部分操作系统的I/O进行映射。
字节缓冲区通过allocation()方法创建,此方法为该缓冲区的内容分配空间,或者通过wrapping方法将现有的字节数组包装到缓冲区中来创建。
字节缓冲区的常用操作:
(1).读写单个字节的绝对和相对get和put方法:
a. 绝对方法:
get(int index):读取指定索引处的字节。
put(int index, byte b):将字节写入指定索引处。
b.相对方法:
get():读取此缓冲区当前位置的字节,然后该位置递增。
put(byte b):将字节写入此缓冲区的当前位置,然后该位置递增。
(2).相对批量get方法:
ByteBuffer get(byte[] dst):将此缓冲区的字节传输到给定的目标数组中。
(3).相对批量put方法:
ByteBuffer put(byte[] src):将给定的源byte数组的所有内容传输到此缓冲区中。
(4).读写其他基本类型值:
getChar(), putChar(char value), getChare(int index), putChar(int index, char value).
getInt(), putInt(int value), getInt(int index), putInt(int index, int value)等等读写基本类型值得相对和绝对方法。
注意:基本类型值得相对和绝对读写方法,根据java基本类型数据底层字节数进行缓冲区移动。
(5).创建视图缓冲区:
为了访问同类二进制数据,允许将字节缓冲区视为包含它们基本类型值的缓冲区,视图缓冲区只是其内容受该字节缓冲区支持的另一种缓冲区。字节缓冲区和视图缓冲区内容更改是相互可见的。这两种缓冲区的位置、限制和标记值都是独立的。创建方法如下:
asCharBuffer(), asDoubleBuffer(), asFloatBuffer(), asIntBuffer(), asLongBuffer(), asReadOnlyBuffer(), asShortBuffer()。
与具体类型的get和put方法系列相比,视图缓冲区优势如下:
a视图缓冲区不是根据字节进行索引,而是根据其特定类型的值得大小进行索引。
b.视图缓冲区提供了相对批量get和put方法,这些方法可以在缓冲区和数组或相同类型的其他缓冲区直接传输连续序列的值。
c.视图缓冲区可能更搞效,因为当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
(6).缓冲区其他操作:
a.ByteBuffer compact():压缩缓冲区,从缓冲区写入数据之后调用,以防写入不完整。
b.ByteBuffer duplicate():创建共享此缓冲区内容的新的字节缓冲区,新缓冲区中的内容为此缓冲区的内容,此缓冲区和新缓冲区内容更改是相互可见的。
c.ByteBuffer slice():创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。
3.直接与非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。
如果字节缓冲区是直接的,则JVM会尽最大努力直接在此缓冲区上执行本机的I/O操作,即在每次调用底层操作系统的一个本机I/O之前(或之后),JVM都会尽量避免将缓冲区的内容复制到中间缓冲区中(或尽量避免从中间缓冲区复制内容)。直接字节缓冲区可以通过调用allocateDirect()方法来创建,此方法返回的缓冲区进行分配和取消分配所需的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此它对应用程序的内存需求量造成的影响可能并不明显,所以建议将直接缓冲区主要分配给那些容易受底层操作系统的本机I/O操作影响的大型的、持久的缓冲区
可以通过调用isDirect()来判断字节缓冲区是直接的还是非直接的。
4.文件通道:
Java I/O的FileInputStream,FileOutputStream和RandomAccessFile可以产生文件通道,例子如下:
[java] view plaincopy
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class FileChannel{
//分配字节缓冲区时指定其大小
private static final int BSIZE = 1024;
public static void main(String[] args) throw Exception{
//获取FileOutputStram的文件通道
FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
//向字节缓冲区中写入字节数组
fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
fc.close();
//以读写方式获取随机访问文件的文件通道
fc = new RandomAccessFile(“data.txt”, “rw”).getChannel();
//定位到字节缓冲区当前内容之后
fc.position(fc.size());
//向字节缓冲区中追加内容
fc.write(ByteBuffer.wrap(“Some more”.getBytes()));
fc.close();
//获取FileInputStream的文件通道
fc = new FileInputStream(“data.txt”).getChannel();
//分配字节缓冲区
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
//将字节数组从文件通道读入到字节缓冲区中
fc.read(buff);
//放置缓冲区,为缓冲区写出或相对获取做准备
buff.flip();
//判断缓冲区是是否还有元素
while(buff.hasRemaining()){
//获取字节缓冲区字节的相对方法
System.out.println((char)buff.get());
}
}
}
输出结果:
Some text Some more
传统java I/O中FileInputStream, FileOutputStream和RandomAccessFile三个类可以产生文件通道。
注意:Java new I/O的目标是提高I/O速度,快速移动大批量的数据,因此,字节缓冲区的大小非常重要,例子中的1K大小不一定是合适的,应用程序需要在生产环境中测试确定合适的缓冲区大小。
5.文件复制:
文件通道和字节缓冲区不但可以实现数据从文件读入和读出,还可以实现文件的复制,例子如下:
[java] view plaincopy
import java.nio.*;
import java.nio.channels/*;
import java.io.*
public class FileCopy{
//字节缓冲区大小
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
//获取文件输入通道
FileChannel in = new FileInputStream(“FileCopy.java”).getChannel();
//获取文件输出通道
FileChannel out = new FileOutputStream(“FileOut.txt”).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
While(in.read(buffer) != -1){
//重置缓冲区,准备写出
buffer.flip();
//将字节缓冲区内容写出到文件输出通道
out.write(buffer);
//清除字节缓冲区
buffer.clear();
}
}
}
6.文件通道传输:
文件通道的transferFrom(ReadableByteChannel src, long position, long count)方法将字节从给定的可读字节通道传输到此通道的文件中,transferTo(long position, long count, WritableByteChannel target)方法将此通道的文件传输到给定的可写字节通道。例子如下:
[java] view plaincopy
import java.nio.channels.*;
import java.io.*
public class ChannelTransfer{
public static void main(String[] args) throws Exception{
FileChannel in = new FileInputStream(“ChannelTransfer.java”).getChannel();
FileChannel out = new FileOutputStream(“out.txt”).getChannel();
//将输入文件通道传输到输出文件通道
in.transferTo(0, in.size(); out);
//从输入文件通道传输到输出文件通道
out.transferFrom(in, 0, in.size());
}
}
1.数据转换:
使用字节缓冲区时,向字节缓冲区写入或者从字节缓冲区读取内容时,如果内容是字符类型,写入时指定字符编码集对内容进行编码,读取时指定字节编码集对内容解码。
每次从字节缓冲区中读取一个字节,程序中还需要对读取的字节进行类型转换才可以使用,如果使用视图缓冲区就可以直接获取所需的数据类型,例子如下:
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.io.*;
public class BufferToText{
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
fc.close();
fc = new FileInputStream(“data.txt”).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
fc.read(buffer);
buffer.flip();
//创建字节缓冲区视图,作为char缓冲区,由于编码问题输出乱码
System.out.println(buffer.asCharBuffer());
//重绕字节缓冲区,即回到字节缓冲区开始处
buffer.rewind();
//获取系统默认字节编码
String encoding = System.getProperties(“file.encoding”);
System.out.println(“Decode using ” + encoding + “: ”
+ Charset.forName(encoding).decode(buffer));
fc = new FileOutputStream(“data2.txt”).getChannel();
//创建字节缓冲区时指定字符集
fc.write(ByteBuffer.wrap(“Some text”.getBytes(“UTF-16BE”)));
fc.close();
fc= new FileInputStream(“data2.txt”).getChannel();
buffer.clear();
fc.read(buffer);
buffer.flip();
//创建字节缓冲区的视图,将其作为char缓冲
System.out.println(buffer.asCharBuffer());
}
}
输出结果:
????
Decoded using Cp1252: Some text
Some text
从上面例子可以看出,使用java.nio.charset.Charset类对字符进行编码/解码之后,写入/读取字节缓冲区时,字节缓冲区中的字符内容才可以正常显示。
2.创建字节缓冲区视图:
字节缓冲区中只能存放字节内容,java 8种基本类型数据向字节缓冲区中写入,或者从字节缓冲区中读取,就需要使用字节缓冲区视图和读取8中java基本类型数据的方法,例子如下:
import java.nio.*;
public class ViewBuffer{
private static final int BSIZE = 1024;
public static void main(String[] args){
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
int i = 0;
//检查字节缓冲区的内容是否为0
while(i++ < bb.limit()){
if(bb.get() != 0){
System.out.println(“nonzero”);
}
System.out.println(“i = ” + i);
//回到字节缓冲区开始处
bb.rewind();
}
//创建字节缓冲区视图,作为char缓冲
bb.asCharBuffer().put(“Hello!”);
char c;
while((c = bb.getChar)) != 0){
System.out.println(c + “ ”);
}
bb.rewind();
//创建字节缓冲区视图,作为short缓冲
bb.asShortBuffer().put((Short)12390);
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());
}
}
输出结果:
i = 1025
H e l l o !
12390
99471142
99471142
9.9471142E7
9.9471142E7
对于刚分配的字节缓冲区来说,其所有内容都是0,所有第一次i的输出值为1025.
向字节缓冲区中写入,或者从字节缓冲区中读取8中基本类型java数据最简便的方法是使用字节缓冲区的视图缓冲区。
3.java nio相关类图:
下图是从《java编程思想第4版》中截的java nio相关类图:
从类图中可以看出,java nio中,只有字节缓冲区ByteBuffer可以向文件通道写数据或者从文件通道中读取数据。
4.缓冲区详解:
缓冲区由:内容数据和4个索引组成。
缓冲区索引:
a. 缓冲区标记(mark):使缓冲区能够记住一个位置并在之后将其返回。并非总需要定义标记,但在定义标记时,不能将其定义为负数,且不能大于其位置。
b. 缓冲区位置(position):是缓冲区下一个要读取或写入元素的索引,位置不能为负,且不能大于其限制。
c. 缓冲区限制(limit):是第一个不应该读取或写入的元素的索引,限制不能为负,且不能大于其容量。
d. 缓冲区容量(capacity):是缓冲区所包含的元素数量,不能为负,且不能更改。
缓冲区索引遵循以下不变公式:
0 <= 标记 <= 位置 <= 限制 <= 容量
通过这些索引,缓冲区可以高效的访问和操作缓冲区中的内容数据,新创建的缓冲区总有一个0位置和一个未定义的标记,初始限制可以为0,也可以为其他值,取决于缓冲区类型和构建方式。
缓冲区的以下方法可以查询,设置和充值缓冲区的索引:
(1).capacity()方法:
返回此缓冲区的容量。
(2).clear()方法:
清除此缓冲区,将缓冲区位置设置为0,将缓冲区限制设置为容量,并丢弃标记。
(3).flip()方法:
反转此缓冲区,首先将限制设置为当前位置,然后将位置设置为0,如果已定义了标记,则丢弃该标记。
(4).limit()方法:
返回此缓冲区的限制。
(5).limit(int lim)方法:
设置此缓冲区的限制,如果位置大于新的限制,则将位置设置为新的限制,如果标记已定义且大于新限制,则丢弃该标记。
(6).mark()方法:
在此缓冲区的位置设置标记。
(7).position()方法:
返回此缓冲区的位置。
(8).position(int pos)方法:
设置此缓冲区的位置,如果标记已定义且大于新的位置,则丢弃该标记。
(9).remaining()方法:
返回当前位置与限制之间的元素个数,即limit-position。
(10).hasRemaining()方法:
判断在当前位置和限制之间是否有元素。
从File类开始,到文件的读写控制来叙述。
(1)File 文件或文件集名,抽象路径名,并且可以据此来查看文件的属性,同时,对象一旦建立则不能改变。
(2)最初的I/O类:
(3)java1.1中提供了兼容Unicode与面向字符的I/O功能类
看些使用的例子:
带缓冲的文件输入:
BufferedReader in = new BufferedReader( new FileReader(stringFileName) );
StringReader in = new StringReader( BufferedInputFile.read("") );
基本的文件输出:
PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter()));
PrintWriter out = new PrintWriter(stringFileName);
标准I/O,System.in out err以及重定向SetIn Out Err这里就不细说了。
(4)新I/O nio
首先是,实际上旧的I/O包已经使用nio重新实现过,因此不显式地使用nio也是用到了。
FileChannel ( FileInputStream FileOutputStream RandomAccessFile )
FileChannel fc = new FileOutputStream("").getChannel();
fc.write();
fc = new RandomAccessFile("").getChannel();
其次是视图缓冲器一说:通过在同一个ByteBuffer上建立不同的视图缓冲器,将同一个字节序列翻译成short,int,float,long和double类型,相当于说是在同一个存储区域以不同的类型形式展现。
最好是内存映射文件,我原来有篇文章简单说了下c++下的使用,意思很明显就是在磁盘上建立一块区域用来存放大文件(可能一下子无法装载进来,选择部分),用起来感觉都已经在内存中了,而不用进行文件的I/O操作。
MappedByteBuffer out = new RandomAccessFile("").getChannel().map( FileChannel.MapMode.READ_WRITE,0,length());
(5)文件加锁
其实会和后面并发中的加锁类似(是一个共享竞争资源),可以使用trylock 或 lock对文件或者映射文件的部分加锁;后者是直接获取锁,如果无法获得而阻塞;前者是尝试获取锁,无法获得直接返回;还有可以设定些等待时间之类的。
对象的序列化将那些实现了Serializable(Externalizable)接口的对象转换成一个字节序列,并能在以后将这字节序列完全恢复为原来的对象;说得白话些就是将对象相关的信息保存起来(可以写到文件里,或者远程发送出去),在需要的时候把这些保存的信息再恢复出来形成对象;这里就会有下面一些问题:
1.保存了完整的对象信息了吗?
对象的序列化的字节序列中,保存了对象的数据成员(属性),同时也包括了其成员所引用的信息;在恢复过程中不需要调用任何构造器;那么经过远程传输为什么却不可以直接恢复,原因是需要用到起class对象信息,看一下简单的用法:
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(""));
out.writeObject(duixiang);
ObjectInputStream in = new ObjectInputStream( new FileInputStream(""));
(类型)in.readObject();
这是一个简单的将对象序列化到文件中,然后读取文件再恢复的过程;不难理解如果是不同程序之间对象的恢复,至少得有这个对象的类型信息啊。
2.有些成员不想保存当前信息
对于序列化的控制,可以使用Externalizable接口,通过writeExternal() readExternal()方法来做。这个在序列化时通过将当前信息在writeExterna方法中保存想要保存的信息,恢复时,调用默认的构造器之后,通过readExternal方法将保存的信息恢复出来。这里就看到与实现Serializable接口的区别,一个不需要调用任何的构造器,一个调用默认的构造器。
那么如果想用到Serializable的自动化,又不想保存部分成员信息呢,可以使用transient关键字:
private transient string password;
3.对于引用的分歧
如果序列化多个对象,而都引用了一个对象,那么这个对象在恢复的时候是有不同的副本还是只有一个存储地址?通过下面的例子可以看到:
package com.test.myjava;
//主要演示了Serializable接口以及transient关键字
import java.io.*;
class Information implements Serializable{}
public class SerializableTest implements Serializable{
private int id;
private transient String password;
private String name;
private Information otherInformation;
public SerializableTest(int i,String p,String n,Information f){
System.out.println("Constructor:"+n);
id = i;
password = p;
name = n;
otherInformation = f;
}
public String toString(){
return "id:"+id +" name:"+name +" pasword:"+password //密码没有保存,为null
+" friendName:"+otherInformation;//otherInformation.hashCode()
}
public static void main(String[] args ) throws IOException,ClassNotFoundException{
Information allFriendName = new Information();
SerializableTest user1 = new SerializableTest(1,"1","user1",allFriendName);
SerializableTest user2 = new SerializableTest(2,"2","user2",allFriendName);
SerializableTest user3 = new SerializableTest(3,"3","user3",allFriendName);
System.out.println("Start\n");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
//序列化
ByteArrayOutputStream buf1 = new ByteArrayOutputStream();
ObjectOutputStream out1 = new ObjectOutputStream(buf1);
out1.writeObject(user1);
out1.writeObject(user2);
ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(buf2);
out2.writeObject(user3);
//恢复
ObjectInputStream in1 = new ObjectInputStream(
new ByteArrayInputStream(buf1.toByteArray()));
ObjectInputStream in2 = new ObjectInputStream(
new ByteArrayInputStream(buf2.toByteArray()));
user1 = (SerializableTest)in1.readObject();
user2 = (SerializableTest)in1.readObject();
user3 = (SerializableTest)in2.readObject();
System.out.println("Then\n");
System.out.println(user1.toString());
System.out.println(user2.toString());
System.out.println(user3.toString());
}
}
输出结果:
Constructor:user1
Constructor:user2
Constructor:user3
Start
id:1 name:user1 pasword:1 friendName:com.test.myjava.Information@c17164
id:2 name:user2 pasword:2 friendName:com.test.myjava.Information@c17164
id:3 name:user3 pasword:3 friendName:com.test.myjava.Information@c17164
Then
id:1 name:user1 pasword:null friendName:com.test.myjava.Information@19b49e6
id:2 name:user2 pasword:null friendName:com.test.myjava.Information@19b49e6
id:3 name:user3 pasword:null friendName:com.test.myjava.Information@10d448
对于写到一个流中的引用,确实都指向一个地址,而对于不同的流则有不同的副本。
速度的提高来自所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。我们可以把它想像成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer --------- 也就是说,可以存储未加工字节的缓冲器。当我们查询JDK文档中的java.nio.ByteBuffer时,会发现它是相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。
旧I/O类库中有三个类被修改了,用于产生FileChannel。这三个类是FileInputStream,FileOutputStream以及用于既读又写的RandomAccessFile。注意这些是字节操作流,与低层的nio性质一致。Reader和Writer这种字符模式不能用于产生通道;但是java.nio.channels.Channels类提供了实用方法,用于在通道中产生Reader和Writer。
下面的简单实例演示了上面三种类型的流,用以在通道产生可写的,可读的及可读可写的通道:
package ioStudy;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class GetChannel {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
//通过FileInputStream产生通道
FileChannel fc = new FileOutputStream("data.txt").getChannel();
//写入字符串Some text
fc.write(ByteBuffer.wrap("Some text".getBytes()));
fc.close();
//通过RandomAccessFile产生通道
fc = new RandomAccessFile("data.txt", "rw").getChannel();
//定位
fc.position(fc.size());
//写入字符串Some text
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
//通过FileOutputStream产生通道
fc = new FileInputStream("data.txt").getChannel();
//ByteBuffer.allocate申请分配ByteBuffer
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
//读取内容
fc.read(buff);
buff.flip();
while(buff.hasRemaining())
System.out.print((char)buff.get());
}
}
对于上面所展示的任何流类,getChannel()将产生一个FileChannel。通道是一种相当基础的东西:可以向它传递用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。不过,也可以使用warp()方法将已存在的字节数组“包装”到ByteBuffer中。
data.txt文件用RandomAccessFile再次打开。注意我们可以在文件内随处移动FileChannel;在这里,我们把它移到最后,以便附加其他的写操作。
对于只读访问,我们必须显式地使用静态的allocate()方法来分配ByteBuffer。nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要 -------- 实际上,这里使用的1K可能比我们通常使用的小一点(必须通过实际运行应用程序来找到最佳尺寸)。
可以使用allocateDirect()产生一个与操作系统有更高耦合的“直接”缓冲器。这可能达到更高的速度。但这种分配的开支会更大,并且具体实现也随着操作系统的不同而不同,因此必须再次实际运行应用程序来直接缓冲测试是否获得速度上的优势。
一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备(是的,这似乎有一点拙劣,但是请记住,它是很拙劣的,但却适用于获取最大速度)。如果我们打算使用缓冲器执行进一步的read()操作,我们也必须得调用clear()来为每个read()做好准备。
读写操作
package ioStudy;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws IOException {
FileChannel in = new FileInputStream("src/ioStudy/ChannelCopy.java").getChannel();
FileChannel out = new FileOutputStream("ChannelCopy.data").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
in.close();
out.close();
}
}
可以看到,打开一个FileChannel以用于读,而打开另一个用于写。ByteBuffer被分配了空间,当FileChannel.read()返回-1时,表示我们已经到达了输入的未尾。每次read()操作后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲器中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接受数据的准备。
然而,上面的程序并不是处理此类操作的理想方式。特殊方法transferTo()和transferFrom()则允许我们将一个通道和另一个通道直接相连:
package ioStudy;
import java.io.*;
import java.nio.channels.FileChannel;
public class TransferTo {
public static void main(String[] args) throws IOException {
FileChannel in = new FileInputStream("src/ioStudy/TransferTo.java").getChannel();
FileChannel out = new FileOutputStream("TransferTo.data").getChannel();
in.transferTo(0, in.size(), out);
in.close();
out.close();
}
}
转换数据
通过GetChannel 可以发现,为了输出文件中信息,必须每次只读取一个字节的数据,然后将每个byte类型强制转换成char类型。这种方法似乎有点原始 ------ 但我们可以通过getChannel()方法将ByteBuffer转化为CharBuffer,而CharBuffer的toString()方法是这样定义的“返回一个包含缓冲器中所有字符的字符串”,但是注意缓冲器容纳的是普通的字节,为了把他们转换成字符,我们要么在输入它们的时候对其进行编码。
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
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("data2.txt").getChannel();
//读取内容
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//通过CharBuffer向ByteBuffer写入内容
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24);
//通过CharBuffer向ByteBuffer写入内容
buff.asCharBuffer().put("Some text");
//将ByteBuffer内容输出
fc.write(buff);
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
//读取内容
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
输出结果:
我们对缓冲器调用rewind()方法(调用该方法是为了返回到数据开始部分),接着使用平台的默认字符集对数据进行decode(),那么作为结果的CharBuffer可以很好地输出打印到控制台。可以使用System.getProperty(“file.encoding")得到默认字符集,它会产生代表字符集名称的字符串。把该字符串传送给Charset.forName()用以产生Charset对象,可以用它对字符串进行解码。
另一选择是在读文件时,使用能够产生可打印的输出的字符集进行encode(),正如上例中的UTF-16BE,可以把文本写到文件中,当读取时,我们只需要把它转换成CharBuffer,就会产生所期望的文本。
最后,通过CharBuffer向ByteBuffer写入。注意,我们为ByteBuffer分配了24个字节。一个字符需要2个字节,那么一个ByteBuffer足可以容纳12个字符,但是”Some
text“只有9个字符,剩余的内空为零的字节仍出现在由CharBuffer的toString()表示中,我们可以在输出看到。
最后,通过CharBuffer向ByteBuffer写入。注意,我们为ByteBuffer分配了24个字节。一个字符需要2个字节,那么一个ByteBuffer足可以容纳12个字符,但是”Some text“只有9个字符,剩余的内空为零的字节仍出现在由CharBuffer的toString()表示中,我们可以在输出看到。
字符集
可以使用java.nio.charset.Charset类实现编码功能,该类提供了把数据编码成多种不同类型的字符集的工具,下面这个例子中输出了所有的字符集:
import java.nio.charset.Charset;
import java.util.*;
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<String> 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();
}
}
}
使用java.nio.CharBuffer可以将Unicode字符串转换成字节序列,如下:
Charset cset = Charset.forName("ISO-8859-1");
byte[] bytes = ...;
ByteBuffer bbuf = ByteBuffer.wrap(bytes, 0, bytes.length);
CharBuffer cbuf = cset.decode(bbuf);
System.out.println(cbuf.toString()); //解码
相反,使用ByteBuffer数组的静态方法wrap可以将一个字节数组转换成一个字节缓冲区ByteBuffer。Charset的decode方法可以将ByteBuffer转换为CharBuffer,调用它的toString方法可以获得字符串:
byte[] bytes = ...;
ByteBuffer bbuf = ByteBuffer.wrap(bytes, 0, bytes.length);
CharBuffer cbuf = cset.decode(bbuf);
System.out.println(cbuf.toString()); //解码
ByteBuffer存取数据
尽管ByteBuffer只能保存字节类型的数据,但是它具有可以从所容纳的字节中产生各种不同类型基本类型值的方法。下面这个例子展示了怎样使用这些方法来插入和抽取各种数值:
import java.nio.ByteBuffer;
public class GetData {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
//allocate方法分配空间后,空间值自动为0
int i = 0;
while(i++ < bb.limit())
if(bb.get() != 0)
System.out.println("nonzero");
System.out.println("i = " + i);
bb.rewind();
//储存和读取char
bb.asCharBuffer().put("Howdy!");
char c;
while((c = bb.getChar()) != 0)
System.out.print(c + " ");
System.out.println();
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之后,可以通过检测它的值到查看缓冲器的分配方式是否将内容自动置零 -------- 它确实是这样做的。这里一共检测了1024个值(由缓冲器的limit()决定),并且所有的值都是零。
向ByteBuffer插入基本类型数据的最简单方法是:利用asCharBuffer(),asShortBuffer()等获得该缓冲器上的视图,然后使用视图的put()方法。此方法适用于所有基本数据类型。仅有一个小小的例外,即,使用ShortBuffer的put()方法,需要进行类型转换(注意类型转换会截取或改变结果)。而其他所有的视图缓冲器在使用put()方法时,不需要进行类型转换。
视图缓冲器
视图缓冲器可以让我们通过某个特定的基本数据类型的视窗(asCharBuffer()等)操作其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此,对视图任何修改都会映射成为对ByteBuffer中数据的修改。这使我们可以很方便地向ByteBuffer插入数据。视图还允许我们从ByteBuffer一次一个地(与ByteBuffer所支持的方式相同)或者成批地(放入数组中)读取基本类型值。下面例子通过IntBuffer操作ByteBuffer中的int型数据:
import java.nio.*;
public class IntBufferDemo {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer(); //转化为<span style="color: rgb(54, 46, 43); font-family: Arial; font-size: 14px; line-height: 26px;">视图缓冲器</span>
//储存int数组
ib.put(new int[]{11, 42, 47, 99, 143, 811, 1016});
System.out.println("get(3) is " + ib.get(3));
//setting a new limit before rewinding the buffer.
ib.flip();
while(ib.hasRemaining()) {
int i = ib.get();
System.out.print(i + " ");
}
}
}
先用重载后的put()方法存储一个整数数组。接着get()和put()方法调用直接访问底层ByteBuffer中某个整数位置。
一旦底层的ByteBuffer通过视图缓冲器填满了整数或其他基本类型时,就可以直接写到通道中。正如从通道中读取那样容易,使用视图缓冲器可以把任何数据都转化成某一特定的基本类型。在下面的例子中,通过在同一个ByteBuffer上建立不同的视图缓冲器,将同一字节序列翻译成short, int , float, long 和double 类型的数据:
import java.nio.*;
public class ViewBuffers {
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 'a'});
//下面将ByteBuffer转化为各种视图缓冲器,并读取结果
bb.rewind();
System.out.print("Byte Buffer ");
while(bb.hasRemaining())
System.out.print(bb.position() + " -> " + bb.get() + " | ");
System.out.println();
CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer();
System.out.print("Char Buffer ");
while(cb.hasRemaining())
System.out.print(cb.position() + " -> " + cb.get() + " | ");
System.out.println();
FloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffer();
System.out.print("Float Buffer");
while(fb.hasRemaining())
System.out.print(fb.position() + " -> " + fb.get() + " | ");
System.out.println();
IntBuffer ib = ((ByteBuffer)bb.rewind()).asIntBuffer();
System.out.print("Int Buffer");
while(ib.hasRemaining())
System.out.print(ib.position() + " -> " + ib.get() + " | ");
System.out.println();
LongBuffer lb = ((ByteBuffer)bb.rewind()).asLongBuffer();
System.out.print("Long Buffer");
while(lb.hasRemaining())
System.out.print(lb.position() + " -> " + lb.get() + " | ");
System.out.println();
ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffer();
System.out.print("Short Buffer");
while(sb.hasRemaining())
System.out.print(sb.position() + " -> " + sb.get() + " | ");
System.out.println();
DoubleBuffer db = ((ByteBuffer)bb.rewind()).asDoubleBuffer();
System.out.print("Double Buffer");
while(db.hasRemaining())
System.out.print(db.position() + " -> " + db.get() + " | ");
}
}
输出结果:
ByteBuffer通过一个被“包装”过的8字节数组产生,然后通过各种不同的基本类型的视图缓冲器显示出来。从下图可看出,当从不同类型的缓冲器读取时,数据显示的方式也不同。与上面程序的输出相对应。
内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组来访问。这种方法极大地简化了用于修改文件的代码:
package ioStudy;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class LargeMappedFiles {
static int length = 0x8FFFFFF; // 128MB
public static void main(String[] args) throws FileNotFoundException, IOException {
MappedByteBuffer out =
new RandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);//构造内存映射文件
//存入数据
for(int i = 0;i < length; i++){
out.put((byte)'x');
}
System.out.println("Finished writing ");
//读取数据
for(int i = length/2; i < length/2 + 6; i++) {
System.out.println((char)out.get(i));
}
}
}
为了既能定又能读,我们通过RandomAccessFile获得该文件的通道,然后调用map()产生MappedByteBuffer,这是一种特殊类型的直接缓冲器。注意我们必须指定映射文件的初始位置和映射区域的长度,这意味着我们可以映射某个大文件的较小的部分。
可以指定文件区域与映射模式,有三种模式:
FileChannel.MapMode.READ_ONLY:所产生的缓冲区是只读的,任何对该缓冲区写入的操作都会导致ReadOnlyBufferException异常。
FileChannel.MapMode.READ_WRITE:所产生的缓冲区是可写的,任何修改都会在某个时刻写到文件中。注意:其他映射同一个文件的程序可能不能立即看到这些修改,多个程序同时时行文件映射的确切行为依赖于操作系统。
FileChannel.MapMode.PRIVATE:所产生的缓冲区是可写的,但任何修改对这个缓冲区来说都是私有的,不会传播到文件中。
MappedByteBuffer由ByteBuffer继承而来,因此它具有ByteBuffer的所有方法。这例子仅仅展示了非常简单的put()和get(),但我们同样可以使用像asCharBuffer()等方法。
获得缓冲区后,可以使用ByteBuffer类和Buffer超类的方法读写数据。
可以顺序遍历缓冲区所有字节:
while(buffer.hasRemaining()){
byte b = buffer.get();
...
}
也可以随机访问:
for(int i = 0; i < buffer.limit(); i++){
byte b = buffer.get(i);
...
}
也可以用下面方法来读写数组:
get(byte[] bytes);
get(byte[],int offset,int length);
还可用下面方法读入在文件中存储为二进制值的基本类型值:getInt/getLong/getShort/getFloat/getDouble
上面例子创建的文件为128MB,这可能比操作系统所允许一次载入内存的空间大。但我们似乎可以一次访问整个文件,因为只有一部分放入内存,文件的其他部分被交换了出去。用这种方式,很大的文件(可达2G)也可以很容易地修改。注意底层操作系统的文件映射工具是用来最大化提高性能的。
性能
尽管“旧”的I/O流用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度。下面程序进行简单的性能比较:
package ioStudy;
import java.io.*;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
public class MappedIO {
private static int numOfInts = 4000000;
private static int numOfUbuffInts = 2000000;
private abstract static class Tester {
private String name;
public Tester(String name) {
this.name = name;
}
public void runTest(){
System.out.println(name + ": ");
try {
long start = System.nanoTime();
test();
double duration = System.nanoTime() - start;
System.out.format("%.2f\n", duration/1.0e9);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
public abstract void test() throws IOException;
}
private static Tester[] tests = {
new Tester("Stream Write") {
public void test() throws IOException {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(new File("temp.tmp"))));
for(int i = 0; i < numOfInts; i++)
dos.writeInt(i);
dos.close();
}
},
new Tester("Mapped Write") {
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size())
.asIntBuffer();
for(int i = 0; i < numOfInts; i++)
ib.put(i);
fc.close();
}
},
new Tester("Stream read") {
public void test() throws IOException {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("temp.tmp")));
for(int i = 0;i < numOfInts; i++)
dis.readInt();
dis.close();
}
},
new Tester("Mapped read") {
public void test() throws IOException {
FileChannel fc = new FileInputStream(new File("temp.tmp")).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer();
while(ib.hasRemaining())
ib.get();
fc.close();
}
},
new Tester("Stream Read/Write") {
public void test() throws IOException {
RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"),"rw");
raf.writeInt(1);
for(int i = 0; i < numOfUbuffInts; i++) {
raf.seek(raf.length() - 4);
raf.writeInt(raf.readInt());
}
raf.close();
}
},
new Tester("Mapped Read/Write") {
public void test() throws IOException {
FileChannel fc = new RandomAccessFile(new File("temp.tmp"),"rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0 ,fc.size()).asIntBuffer();
ib.put(0);
for(int i = 1; i < numOfUbuffInts; i++)
ib.put(ib.get(i - 1));
fc.close();
}
}
};
public static void main(String[] args) {
for(Tester test : tests)
test.runTest();
}
}
输出结果:
尽管 "映射写"要用到FileOutputStream,但映射文件中所有输出必须使用RandomAccessFile,如上例所示。
注意test()方法包括初始化各种I/O对象的时间,因此,即使建立映射文件的花费很大,但是整体受益比起I/O流来说还是很显著的。
new I/O:目的是为了提高速度,在新版本的JDK中,旧的I/O也使用nio重新实现,有性能提升。速度的提高来自于所使用的结构更接近于操作系统的执行I/O方式——通道和缓冲器。
流使用getChannel()方法会产生一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问(后面有提到)。
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class GetChannel {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
// Write a file:
FileChannel fc =
new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
// Add to the end of the file:
fc = new RandomAccessFile("data.txt", "rw").getChannel();
fc.position(fc.size()); // Move to the end
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
// Read the file:
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while(buff.hasRemaining())
System.out.print((char)buff.get());
}
}
将字节存放于ByteBuffer有2种方法:一是用put方法直接填充一个或多个字节,或基本数据类型;另一种是用warp方法将已存在的字节数组包装到ByteBuffer中。对于只读访问,我们必须显示的使用静态的allocate方法分配ByteBuffer。nio的目标就是快速移动大量数据,因此ByteBuffer的大小就很重要——需要在实际运行的程序中测试来找到最佳值。要达到更高的速度也有可能,方法就是使用allocateDirect()而不是allocate(),以产生一个与操作系统有更高耦合性的“直接”缓冲器,但效果需要实际测试一下。
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
if(args.length != 2) {
System.out.println("arguments: sourcefile destfile");
System.exit(1);
}
FileChannel in = new FileInputStream(args[0]).getChannel();
FileChannel out = new FileOutputStream(args[1]).getChannel();
//有一种特殊的办法,将2个通道直接相连
// in = transferTo(0, in.size(), out); 或者 out = transferFrom(in, 0, in.size());
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1) {
buffer.flip(); // Prepare for writing
out.write(buffer);
buffer.clear(); // Prepare for reading
}
}
}
BufferWriter转换成char型来操作,很不方便,我们利用CharBuffer的toString()方法来转换成String型就方便多了。
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.io.*;
public class BufferToText {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
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();
// Doesn't work:
System.out.println(buff.asCharBuffer());
// Decode using this system's default Charset:
buff.rewind();
String encoding = System.getProperty("file.encoding");
System.out.println("Decoded using " + encoding + ": "
+ Charset.forName(encoding).decode(buff));
// Or, we could encode with something that will print:
fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap(
"Some text".getBytes("UTF-16BE")));
fc.close();
// Now try reading again:
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
// Use a CharBuffer to write through:
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24); // More than needed
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
// Read and display:
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
-------------------------------------------------------------------------------------------------------------------------------------------
内存映射文件:它允许我们创建和修改那些因为太大而不能放入内存的文件。
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import static net.mindview.util.Print.*;
public class LargeMappedFiles {
static int length = 0x8FFFFFF; // 128 MB
public static void main(String[] args) throws Exception {
MappedByteBuffer out =
new RandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);
for(int i = 0; i < length; i++)
out.put((byte)'x');
print("Finished writing");
for(int i = length/2; i < length/2 + 6; i++)
printnb((char)out.get(i));
}
}
文件加锁:通过对FileChannel调用tryLock()和lock(),就可以获得整个文件的FileLock。启动tryLock()是非阻塞式的,它设法获取锁,但如果不能获得,将直接放方法调用返回。lock()是阻塞式的,它要阻塞线程直到获得锁,或者调用lock()的线程中断,或者调用lock()的通道关闭。
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;
public class FileLocking {
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();
if(fl != null) {
System.out.println("Locked File");
TimeUnit.MILLISECONDS.sleep(100);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
}
对映射文件的部分加锁:
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class LockingMappedFiles {
static final int LENGTH = 0x8FFFFFF; // 128 MB
static FileChannel fc;
public static void main(String[] args) throws Exception {
fc =
new RandomAccessFile("test.dat", "rw").getChannel();
MappedByteBuffer out =
fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
for(int i = 0; i < LENGTH; i++)
out.put((byte)'x');
new LockAndModify(out, 0, 0 + LENGTH/3);
new LockAndModify(out, LENGTH/2, LENGTH/2 + LENGTH/4);
}
private static class LockAndModify extends Thread {
private ByteBuffer buff;
private int start, end;
LockAndModify(ByteBuffer mbb, int start, int end) {
this.start = start;
this.end = end;
mbb.limit(end);
mbb.position(start);
buff = mbb.slice();
start();
}
public void run() {
try {
// Exclusive lock with no overlap:
FileLock fl = fc.lock(start, end, false);
System.out.println("Locked: "+ start +" to "+ end);
// Perform modification:
while(buff.position() < buff.limit() - 1)
buff.put((byte)(buff.get() + 1));
fl.release();
System.out.println("Released: "+start+" to "+ end);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
}
1,对程序语言设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的事。
2,File类:
它是能代表一个特定的文件的名称,又能代表一个目录下的一组文件的名称。如果它代表一个文件集,我们就可以对此集合调用list()方法,这个方法会返回一个字符的数组。
3,文件目录器
public class DirList {
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new DirFilter(args[0]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
}
class DirFilter implements FilenameFilter {
private Pattern pattern;
public DirFilter(String regex) {
pattern = Pattern.compile(regex);
}
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
} /* Output:
列出当前目录下所有文件。
下面改用匿名内部类:
public class DirList2 {
public static FilenameFilter filter(final String regex) {
// Creation of anonymous inner class:
return new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
}; // End of anonymous inner class
}
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(filter(args[0]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
} /* Output:
进一步改进:
public class DirList3 {
public static void main(final String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new FilenameFilter() {
private Pattern pattern = Pattern.compile(args[0]);
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
} /
代码更少,将解决特定问题的代码隔离、聚拢于一点。但是代码不易于阅读。应该谨慎使用。
4,目录实用工具
程序设计中一项常见的任务就是在文件集上执行操作,这些文件要么来自本地目录,要不遍布整个目录树中。下面这个工具
能够为你产生这个文件集,很有用。
package net.mindview.util;
import java.util.regex.*;
import java.io.*;
import java.util.*;
public final class Directory {
public static File[]
local(File dir, final String regex) {
return dir.listFiles(new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(
new File(name).getName()).matches();
}
});
}
public static File[]
local(String path, final String regex) { // Overloaded
return local(new File(path), regex);
}
// A two-tuple for returning a pair of objects:
public static class TreeInfo implements Iterable<File> {
public List<File> files = new ArrayList<File>();
public List<File> dirs = new ArrayList<File>();
// The default iterable element is the file list:
public Iterator<File> iterator() {
return files.iterator();
}
void addAll(TreeInfo other) {
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public String toString() {
return "dirs: " + PPrint.pformat(dirs) +
"\n\nfiles: " + PPrint.pformat(files);
}
}
public static TreeInfo
walk(String start, String regex) { // Begin recursion
return recurseDirs(new File(start), regex);
}
public static TreeInfo
walk(File start, String regex) { // Overloaded
return recurseDirs(start, regex);
}
public static TreeInfo walk(File start) { // Everything
return recurseDirs(start, ".*");
}
public static TreeInfo walk(String start) {
return recurseDirs(new File(start), ".*");
}
static TreeInfo recurseDirs(File startDir, String regex){
TreeInfo result = new TreeInfo();
for(File item : startDir.listFiles()) {
if(item.isDirectory()) {
result.dirs.add(item);
result.addAll(recurseDirs(item, regex));
} else // Regular file
if(item.getName().matches(regex))
result.files.add(item);
}
return result;
}
// Simple validation test:
public static void main(String[] args) {
if(args.length == 0)
System.out.println(walk("."));
else
for(String arg : args)
System.out.println(walk(arg));
}
} ///:~
“灵巧打印机”
package net.mindview.util;
import java.util.*;
public class PPrint {
public static String pformat(Collection<?> c) {
if(c.size() == 0) return "[]";
StringBuilder result = new StringBuilder("[");
for(Object elem : c) {
if(c.size() != 1)
result.append("\n ");
result.append(elem);
}
if(c.size() != 1)
result.append("\n");
result.append("]");
return result.toString();
}
public static void pprint(Collection<?> c) {
System.out.println(pformat(c));
}
public static void pprint(Object[] c) {
System.out.println(pformat(Arrays.asList(c)));
}
} ///:~
使用的例子:
public class DirectoryDemo {
public static void main(String[] args) {
// All directories:
PPrint.pprint(Directory.walk(".").dirs);
// All files beginning with 'T'
for(File file : Directory.local(".", "T.*"))
print(file);
print("----------------------");
// All Java files beginning with 'T':
for(File file : Directory.walk(".", "T.*\\.java"))
print(file);
print("======================");
// Class files containing "Z" or "z":
for(File file : Directory.walk(".",".*[Zz].*\\.class"))
print(file);
}
} /* Output: (Sample)
下面的工具,可以在目录中穿行,根据Strategy对象来处理这些目录中的文件。
package net.mindview.util;
import java.io.*;
public class ProcessFiles {
public interface Strategy {
void process(File file);
}
private Strategy strategy;
private String ext;
public ProcessFiles(Strategy strategy, String ext) {
this.strategy = strategy;
this.ext = ext;
}
public void start(String[] args) {
try {
if(args.length == 0)
processDirectoryTree(new File("."));
else
for(String arg : args) {
File fileArg = new File(arg);
if(fileArg.isDirectory())
processDirectoryTree(fileArg);
else {
// Allow user to leave off extension:
if(!arg.endsWith("." + ext))
arg += "." + ext;
strategy.process(
new File(arg).getCanonicalFile());
}
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
public void
processDirectoryTree(File root) throws IOException {
for(File file : Directory.walk(
root.getAbsolutePath(), ".*\\." + ext))
strategy.process(file.getCanonicalFile());
}
// Demonstration of how to use it:
public static void main(String[] args) {
new ProcessFiles(new ProcessFiles.Strategy() {
public void process(File file) {
System.out.println(file);
}
}, "xml").start(args);
}
} /* (Execute to see output) *///:~
5,InputStream类型
包括:ByteArrayInputStream:允许将内存缓冲区当做InputStream
StringBufferInputStream:将String转换成InputStream
FileInputStream:从文件中读取信息
PipedInputStream:产生用于写入相关PipedOutSream的数据。实现“管道化”概念
SequenceInputStream:将两个或多个InputSream转换为单个InputStream
FilterInputStream:抽象类,作为“装饰类”的接口
6,OutputStream类型
包括:
ByteArrayOutputStream:在内存中创建缓冲区,所有送往"流"的数据都要放置在此缓冲区。
FileOutputStream:将信息写到文件。
PipedOutputStream:任何写入其中的信息都会自动作为相关PipedInputSream的输出。实现“管道化”概念。
FilterOutputStream":抽象类,作为“装饰类”的接口。
7:文件读写的实用工具
一个常见的程序化任务就是读取文件到内存,修改,然后再写出。java I/O类库的问题之一就是:它需要编写相当多的代
码去执行这些常用操作。下面的工具能简化对文件的读写操作:
package net.mindview.util;
import java.io.*;
import java.util.*;
public class TextFile extends ArrayList<String> {
// Read a file as a single string:
public static String read(String fileName) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader in= new BufferedReader(new FileReader(
new File(fileName).getAbsoluteFile()));
try {
String s;
while((s = in.readLine()) != null) {
sb.append(s);
sb.append("\n");
}
} finally {
in.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
// Write a single file in one method call:
public static void write(String fileName, String text) {
try {
PrintWriter out = new PrintWriter(
new File(fileName).getAbsoluteFile());
try {
out.print(text);
} finally {
out.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
// Read a file, split by any regular expression:
public TextFile(String fileName, String splitter) {
super(Arrays.asList(read(fileName).split(splitter)));
// Regular expression split() often leaves an empty
// String at the first position:
if(get(0).equals("")) remove(0);
}
// Normally read by lines:
public TextFile(String fileName) {
this(fileName, "\n");
}
public void write(String fileName) {
try {
PrintWriter out = new PrintWriter(
new File(fileName).getAbsoluteFile());
try {
for(String item : this)
out.println(item);
} finally {
out.close();
}
} catch(IOException e) {
throw new RuntimeException(e);
}
}
// Simple test:
public static void main(String[] args) {
String file = read("src\\net\\mindview\\util\\TextFile.java");
write("test.txt", file);
TextFile text = new TextFile("test.txt");
text.write("test2.txt");
// Break into unique sorted list of words:
TreeSet<String> words = new TreeSet<String>(
new TextFile("src\\net\\mindview\\util\\TextFile.java", "\\W+"));
// Display the capitalized words:
System.out.println(words.headSet("a"));
}
} /* Output:
8,进程控制
你经常会需要在java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出。java类库提供了执行这些操作的
类。
public class OSExecuteException extends RuntimeException {
public OSExecuteException(String why) { super(why); }
} ///:~
package net.mindview.util;
import java.io.*;
public class OSExecute {
public static void command(String command) {
boolean err = false;
try {
Process process =
new ProcessBuilder(command.split(" ")).start();
BufferedReader results = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String s;
while((s = results.readLine())!= null)
System.out.println(s);
BufferedReader errors = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
// Report errors and return nonzero value
// to calling process if there are problems:
while((s = errors.readLine())!= null) {
System.err.println(s);
err = true;
}
} catch(Exception e) {
// Compensate for Windows 2000, which throws an
// exception for the default command line:
if(!command.startsWith("CMD /C"))
command("CMD /C " + command);
else
throw new RuntimeException(e);
}
if(err)
throw new OSExecuteException("Errors executing " +
command);
}
} ///:~
public class OSExecuteDemo {
public static void main(String[] args) {
OSExecute.command("javap bin/OSExecuteDemo");
}
} /* Output:
9,文件加锁
jdk1.4 引入了文件加锁机制,它允许我们同步某个作为共享资源的文件。
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;
public class FileLocking {
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().lock();
if(fl != null) {
System.out.println("Locked File");
System.out.println("isShared?"+fl.isShared());;
TimeUnit.MILLISECONDS.sleep(100);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
} /* Output:
通过对FileChannel调用tryLock()或lock()。前者是非阻塞的,后者是阻塞的。
10,压缩
java I/O类库中的类支持读写压缩格式的数据流。主要有如下类:
CheckedInputStream: GetCheckSum()为任何InputSream产生校验和(不仅是解压缩)
CheckedOutputSream:GetCheckSum()为任何OutputStream产生校验和(不仅是压缩)
DeflaterOutputStream:压缩类的基类
ZipOutputStream:一个DeflaterOutputStream,用于将数据压缩成Zip文件格式。
GZIPOutputStream:一个DeflaterOutputStream,用于将数据压缩成GZIP文件格式。
InflaterInputStream:解压缩类的基类
ZipInputStream:一个InflaterInputStream,用于解压缩Zip格式的文件数据
GZIPInoutStream:一个InflaterInputStream,用于解压缩GZIP格式的文件数据
11,用GZIP进行简单压缩
import java.util.zip.*;
import java.io.*;
public class GZIPcompress {
public static void main(String[] args)
throws IOException {
if(args.length == 0) {
System.out.println(
"Usage: \nGZIPcompress file\n" +
"\tUses GZIP compression to compress " +
"the file to test.gz");
System.exit(1);
}
BufferedReader in = new BufferedReader(
new FileReader(args[0]));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream("test.gz")));
System.out.println("Writing file");
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.close();
System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(
new InputStreamReader(new GZIPInputStream(
new FileInputStream("test.gz"))));
String s;
while((s = in2.readLine()) != null)
System.out.println(s);
}
} /* (Execute to see output) *///:~
12,用ZIP进行多个文件的保存:
import java.util.zip.*;
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class ZipCompress {
public static void main(String[] args)
throws IOException {
FileOutputStream f = new FileOutputStream("test.zip");
CheckedOutputStream csum =
new CheckedOutputStream(f, new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out =
new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
// No corresponding getComment(), though.
for(String arg : args) {
print("Writing file " + arg);
BufferedReader in =
new BufferedReader(new FileReader(arg));
zos.putNextEntry(new ZipEntry(arg));
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.flush();
}
out.close();
// Checksum valid only after the file has been closed!
print("Checksum: " + csum.getChecksum().getValue());
// Now extract the files:
print("Reading file");
FileInputStream fi = new FileInputStream("test.zip");
CheckedInputStream csumi =
new CheckedInputStream(fi, new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null) {
print("Reading file " + ze);
int x;
while((x = bis.read()) != -1)
System.out.write(x);
}
if(args.length == 1)
print("Checksum: " + csumi.getChecksum().getValue());
bis.close();
// Alternative way to open and read Zip files:
ZipFile zf = new ZipFile("test.zip");
Enumeration e = zf.entries();
while(e.hasMoreElements()) {
ZipEntry ze2 = (ZipEntry)e.nextElement();
print("File: " + ze2);
// ... and extract the data as before
}
/* if(args.length == 1) */
}
} /* (Execute to see output) *///:~
13:java档案文件
Zip格式也被应用于JAR格式的文件。jar cf myJarfile.jar *.class
14:对象序列化
java的对象序列化是将那些实现了Serializable接口的对象转
换成一个字节序列,并能够在以后将这个字节完整恢复为原来的对象。
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
class Data implements Serializable {
private int n;
public Data(int n) { this.n = n; }
public String toString() { return Integer.toString(n); }
}
public class Worm implements Serializable {
private static Random rand = new Random(47);
private Data[] d = {
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10))
};
private Worm next;
private char c;
// Value of i == number of segments
public Worm(int i, char x) {
print("Worm constructor: " + i);
c = x;
if(--i > 0)
next = new Worm(i, (char)(x + 1));
}
public Worm() {
print("Default constructor");
}
public String toString() {
StringBuilder result = new StringBuilder(":");
result.append(c);
result.append("(");
for(Data dat : d)
result.append(dat);
result.append(")");
if(next != null)
result.append(next);
return result.toString();
}
public static void main(String[] args)
throws ClassNotFoundException, IOException {
Worm w = new Worm(6, 'a');
print("w = " + w);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("worm.out"));
out.writeObject("Worm storage\n");
out.writeObject(w);
out.close(); // Also flushes output
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("worm.out"));
String s = (String)in.readObject();
Worm w2 = (Worm)in.readObject();
print(s + "w2 = " + w2);
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(bout);
out2.writeObject("Worm storage\n");
out2.writeObject(w);
out2.flush();
ObjectInputStream in2 = new ObjectInputStream(
new ByteArrayInputStream(bout.toByteArray()));
s = (String)in2.readObject();
Worm w3 = (Worm)in2.readObject();
print(s + "w3 = " + w3);
}
} /* Output:
15,序列化的控制
在特殊的情况下,这个实现了Externalizable接口。这个代替实现了Serializable接口,来对序列化过程进行控制。
这个Externalizable接口继承了Serializable接口。同时增加了两个方法。writeExternale()和readExternal()
这两个方法在序列化合反序列化还原的过程中被自动调用,以便执行一些特殊的操作。
import java.io.*;
import static net.mindview.util.Print.*;
class Blip1 implements Externalizable {
public Blip1() {
print("Blip1 Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip1.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip1.readExternal");
}
}
class Blip2 implements Externalizable {
public Blip2() {
print("Blip2 Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip2.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip2.readExternal");
}
}
public class Blips {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
print("Constructing objects:");
Blip1 b1 = new Blip1();
Blip2 b2 = new Blip2();
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blips.out"));
print("Saving objects:");
o.writeObject(b1);
o.writeObject(b2);
o.close();
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blips.out"));
print("Recovering b1:");
b1 = (Blip1)in.readObject();
// OOPS! Throws an exception:
print("Recovering b2:");
b2 = (Blip2)in.readObject();
}
} /* Output:
正式的调用序列化:
public class Blip3 implements Externalizable {
private int i;
private String s; // No initialization
public Blip3() {
print("Blip3 Constructor");
// s, i not initialized
}
public Blip3(String x, int a) {
print("Blip3(String x, int a)");
s = x;
i = a;
// s & i initialized only in non-default constructor.
}
public String toString() { return s + i; }
public void writeExternal(ObjectOutput out)
throws IOException {
print("Blip3.writeExternal");
// You must do this:
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
print("Blip3.readExternal");
// You must do this:
s = (String)in.readObject();
i = in.readInt();
}
public static void main(String[] args)
throws IOException, ClassNotFoundException {
print("Constructing objects:");
Blip3 b3 = new Blip3("A String ", 47);
print(b3);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blip3.out"));
print("Saving object:");
o.writeObject(b3);
o.close();
// Now get it back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blip3.out"));
print("Recovering b3:");
b3 = (Blip3)in.readObject();
print(b3);
}
}
16,transient(瞬时)关键字
如果我们正在操作一个Serializable对象,那么所有的序列化都会自动进行,为了能够予以控制,可以用transient关键字
逐个字段的关闭序列化,它的意思是:“不用麻烦你保存或者恢复数据--我自己会处理的”。
import java.util.concurrent.*;
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password;
public Logon(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
return "logon info: \n username: " + username +
"\n date: " + date + "\n password: " + password;
}
public static void main(String[] args) throws Exception {
Logon a = new Logon("Hulk", "myLittlePony");
print("logon a = " + a);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Logon.out"));
o.writeObject(a);
o.close();
TimeUnit.SECONDS.sleep(1); // Delay
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Logon.out"));
print("Recovering object at " + new Date());
a = (Logon)in.readObject();
print("logon a = " + a);
}
} /* Output: (Sample)
如果想序列化static的值,必须自己动手去实现:
import java.io.*;
import java.util.*;
abstract class Shape12 implements Serializable {
public static final int RED = 1, BLUE = 2, GREEN = 3;
private int xPos, yPos, dimension;
private static Random rand = new Random(47);
private static int counter = 0;
public abstract void setColor(int newColor);
public abstract int getColor();
public Shape12(int xVal, int yVal, int dim) {
xPos = xVal;
yPos = yVal;
dimension = dim;
}
public String toString() {
return getClass() +
"color[" + getColor() + "] xPos[" + xPos +
"] yPos[" + yPos + "] dim[" + dimension + "]\n";
}
public static Shape12 randomFactory() {
int xVal = rand.nextInt(100);
int yVal = rand.nextInt(100);
int dim = rand.nextInt(100);
switch(counter++ % 3) {
default:
case 0: return new Circle(xVal, yVal, dim);
case 1: return new Square(xVal, yVal, dim);
case 2: return new Line(xVal, yVal, dim);
}
}
}
class Circle extends Shape12 {
private static int color = RED;
public Circle(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
class Square extends Shape12 {
private static int color;
public Square(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
color = RED;
}
public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
class Line extends Shape12 {
private static int color = RED;
public static void
serializeStaticState(ObjectOutputStream os)
throws IOException { os.writeInt(color); }
public static void
deserializeStaticState(ObjectInputStream os)
throws IOException { color = os.readInt(); }
public Line(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
public class StoreCADState {
public static void main(String[] args) throws Exception {
List<Class<? extends Shape12>> shapeTypes =
new ArrayList<Class<? extends Shape12>>();
// Add references to the class objects:
shapeTypes.add(Circle.class);
shapeTypes.add(Square.class);
shapeTypes.add(Line.class);
List<Shape12> shapes = new ArrayList<Shape12>();
// Make some shapes:
for(int i = 0; i < 10; i++)
shapes.add(Shape12.randomFactory());
// Set all the static colors to GREEN:
for(int i = 0; i < 10; i++)
((Shape12)shapes.get(i)).setColor(Shape12.GREEN);
// Save the state vector:
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("CADState.out"));
out.writeObject(shapeTypes);
Line.serializeStaticState(out);
out.writeObject(shapes);
// Display the shapes:
System.out.println(shapes);
}
} /* Output:
恢复cad系统:
public class RecoverCADState {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("CADState.out"));
// Read in the same order they were written:
List<Class<? extends Shape>> shapeTypes =
(List<Class<? extends Shape>>)in.readObject();
Line.deserializeStaticState(in);
List<Shape> shapes = (List<Shape>)in.readObject();
System.out.println(shapes);
}
} /
17,Preferences
public class PreferencesDemo {
public static void main(String[] args) throws Exception {
Preferences prefs = Preferences
.userNodeForPackage(PreferencesDemo.class);
prefs.put("Location", "Oz");
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?", true);
int usageCount = prefs.getInt("UsageCount", 0);
usageCount++;
prefs.putInt("UsageCount", usageCount);
for(String key : prefs.keys())
print(key + ": "+ prefs.get(key, null));
// You must always provide a default value:
print("How many companions does Dorothy have? " +
prefs.getInt("Companions", 0));
}
} /
windows系统数据保存到注册表了。
1、Java中“流“类库让人迷惑的主要原因:创建单一的结果流,却需要创建多个对象。
2、使用层叠的数个对象为单个对象动态地、透明地添加职责的方式,称作“修饰器“模式。修饰器必须与其所修饰的对象具有相同的接口,这使得修饰器的基本应用具有透明性——我们可以想修饰过或没有修饰过的对象发送相同的消息。
3、为什么使用修饰器?
在直接使用扩展子类的方法时,如果导致产生了大量的、用以满足所需的各种可能的组合的子类,这是通常就会使用修饰器——处理太多的子类已经不太实际。
4、修饰器的缺点:增加代码的复杂性。
5、JavaI/O类库操作不便的原因在于:我们必须创建许多类——“核心”I/O类型加上所有的修饰器,才能得到我们所希望的单个I/O对象。
6、FilterInputStream和FilterOutputStream是用来提供修饰器类接口以控制特定输入流和输出流的俩个类。它们分别继承自I/O类库中的基类InputStream和OutputStream。
7、DataInputStream是FilterInputStream的直接子类,它允许我们读取不用的基本类型数据以及String对象。
8、InputStream和OutputStream是面向字节的,而Reader和Writer是面向字符与兼容Unicode。
9、在InputStream和OutputStream的继承层次结构仅支持8位的字节流,并且不能很好地处理16位的Unicode。由于Unicode用用于字符国际化,所以添加Reader和Write继承结构就是为了在所有的I/O操作中都支持Unicode。另外添加了Reader和Write的I/O流类库使得它的操作比旧类库更快。
10、但是,Reader和Write并不是用来取代nputStream和OutputStream的,因为InputStream和OutputStream在面向字节形式的I/O中仍然提供了极有价值的功能。
11、有时,我们需要把来自于“字节”层次结构的类和“字符”层次结构的类结合起来使用。这时,需要使用“适配器(adapter)”类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。
12、当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码。UTF-8是Unicode的变体,后者把所有字符都存储成两个字节的形式。而我们使用的只是ASCII或者几乎是ASCII字符(只占7位),这么做就显得极其浪费空间和宽带,所以UTF-8将ASCII字符编码成单一字节的形式,而非ASCII字符则编码成两到三个字节的形式。另外,字符串的长度存储在两个字节中。
13、System.in是一个没有被加工过的InputStream,所以在读取System.in之前必须对其进行加工。
14、标准I/O重定向:如果我们突然开始在显示器上创建大量输出,而这些输出滚动得太快以至于无法阅读是,重定向输出就显得极为有用。重定向有一下方法:
SetIn(InputStream)
SetOut(InputStream)
SetErr(InputStream)
注意:重定向操作的是字节流(InputStream、OutputStream),而不是字符流。
15、JDK1.4的java.nio.*包引入了新的JavaI/O类库,其目的在于提高速度。速度的提高来自于所使用的结构更接近操作系统的I/O的方式:通道和缓冲器。
我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互;只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer——也就是说,可以存储未加工字节的缓冲器。ByteBuffer是一个相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用以原始的字节形式或基本数据类型输出和读取数据。
16、FileInputStream、FileOutputStreaam以及用于既读又写的RandomAccessFile被使用nio重新实现过,都有一个getChannel()方法返回一个FileChannel(通道)。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。Reader和Writer这种字符模式类不能产生通道;但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Wirter。
以下
17、转换数据:缓冲器容纳的是普通的字节,为了把它们转化成字符,我们要么在输入它们的时候对其进行编码,要么在将其从缓冲器输出时对它们进行解码。请参看下面例子:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class GetChannel{
public static void main(String[] args) throws Exception{
//写入data.txt
FileChannel fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text".getBytes()));
fc.close();
//读取data.txt
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(1024);
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//使用系统默认的字符集进行解码(输出时对其进行解码)
buff.rewind();/返回数据开始的部分
String encoding = System.getProperty("file.encoding");
System.out.println("Decodedusing " + encoding + ":"
+ Charset.forName(encoding).decode(buff));
//再次写入(输入时对其进行编码)
fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE")));
fc.close();
//再次读取
fc = new FileInputStream("data.txt").getChannel();
buff = ByteBuffer.allocate(1024);
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
//再次写入(输入时对其进行编码)
fc = new FileOutputStream("data.txt").getChannel();
buff = ByteBuffer.allocate(24);
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
//再次读取
fc = new FileInputStream("data.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
运行结果:
18、获取基本类型:尽管ByteBuffer只能保存字节类型的数据,但是它具有从其所容纳的字节中产生出各种不同的类型值的方法。
import java.nio.ByteBuffer;
public class GetData{
private final static 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();
//char
bb.asCharBuffer().put("Howdy");
char c;
while((c = bb.getChar()) != 0){
System.out.print(c + " ");
}
System.out.println();
bb.rewind();
//short。是一个特例,必须加上(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();
}
}
运行结果:
19、视图缓冲器:可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
//asIntBuffer() 创建此字节缓冲区的视图,作为 int 缓冲区。
IntBuffer ib = bb.asIntBuffer();
20、不同机器可能会有不同的字节排序方法来存储数据。默认的ByteBuffer是以高位优先的顺序存储数据的。考虑下面两个字节:
如果我们以short(ByteBuffer.asShortBuffer)高位优先读出的是97(0000000001100001),若更改ByteBuffer更改为地位优先,则读出的是24832(0110000100000000)。再看下面例子:
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
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");
//array()只能对有数组支持的缓冲器调用
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()));
}
}
运行结果:
21、如果想把一个字节数组写到文件中去,那么应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel中去。
22、存储器映射文件:允许我们创建和修改那些因为太大而不能放入内存的文件。有了存储器映射文件,我们就可以假定整个文件都在内存中,而且可以完全把它当作非常大的数组来访问。
尽管“旧”的I/O在用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度。
23、文件加锁机制:允许我们同步访问某个作为共享资源的文件。文件锁对其它操作系统进程是可见的,因为Java的文件加锁之间映射到本地操作系统的加锁工具。另外,利用对映射文件的部分加锁,可以对巨大的文件进行部分加锁,以便其他进程可以对修改文件中未被加锁的部分。
24、压缩:Java压缩类库是按字节方式的,它们继承自InputStream和OutputStream。
1).GZIP:如果只想对单个数据流(而不是一系列互异数据)进行压缩,那么它是比较适合的选择。下面是对单个文件进行压缩的例子:
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GZIPcompress{
public static void main(String[] args) throws IOException{
//InputStreamReader中设置字符编码为GBK
BufferedReader in = new BufferedReader(new InputStreamReader(new
FileInputStream("test.txt"), "GBK"));
BufferedOutputStream out = new BufferedOutputStream((
new GZIPOutputStream(new FileOutputStream("test.txt.gz"))));
System.out.println("Writingfile");
int c;
while((c = in.read()) != -1){
//使用GBK字符编码将此 String编码到 out中
out.write(String.valueOf((char) c).getBytes("GBK"));
}
in.close();
out.close();
System.out.println("ReadingFile");
BufferedReader in2 = new BufferedReader((new InputStreamReader
(new GZIPInputStream(new FileInputStream("test.txt.gz")))));
String s;
while((s = in2.readLine()) != null){
System.out.println(s);
}
}
}
2).用Zip进行多文件压缩:对于每一个要加入压缩档案的文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。
25、对象的序列化:将那些实现Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。利用它可以实现“轻量持久性”。
1).“持久化”:意味一个对象的生存周期并不取决于程序是否正在执行。通过将一个序列化对象写入磁盘,然后再重新调用程序时恢复该对象,就能够实现持久性的效果。
2).“轻量级”:因为它不能用某种“persistent”(持久)关键字来简单定一个对象,并让系统自动维护其他细节问题。相反,对象必须在程序中显示地序列化和反序列化还原。
26、对象序列化的概念加入到语言中是为了支持两种主要特性:
1).Java的“远程方法调用”(Remote Method Invocation,RMI)。
2).Java Beans。
27、
1).序列化:创建OutputStream对象,封装到ObjectOutputStream,再调用writeObject()
2).反序列化:创建InputStream对象,封装到ObjectInputStream,再调用readObject()
3).Serializable的对象在还原的过程中,没有调用任何构造器。
28、对象序列化是面向字节的,因此采用InputStream和OutputStream层次结构。
29、序列化的控制:
1).实现Externalizable接口,接口继承自Serializable,添加了writeExternal()和readExternal()两个方法,它们会在序列化和反序列化还原的过程中被自动调用。
2).反序列过程中,对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不是调用构造器。而Externalizeble对象,普通的缺省构造函数会被调用。
3).transient(瞬时)关键字:只能和Serializable对象一起使用。
private transient String password;
password将不会被保存到磁盘中。
30、序列化/反序列化static成员:显示调用serializeStaticState()和deserializeStaticState()
31、Preferences:用于存储和读取用户的偏好(preferences)以及程序配置项的设置。只能用于小的、受限的数据集合——我们只能保存基本数据类型和字符串,并且每个字符串的存储类型长度不能超过8K(不是很小,单我们也不想用它来创建任何重要的东西)。它是一个键-值集合(类似映射),存储在一个节点层次结构中。
import java.util.Arrays;
import java.util.Iterator;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
public class PreferencesDemo{
public static void main(String[] args) throws BackingStoreException{
Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
prefs.put("日期", "2012年02月05日");
prefs.put("天气", "晴");
prefs.putInt("放假多少天了?", 24);
prefs.putBoolean("现在在家?", true);
int usageCount = prefs.getInt("寒假还剩多少天?", 16);
usageCount--;
prefs.putInt("寒假还剩多少天?", usageCount);
Iterator<String> it = Arrays.asList(prefs.keys()).iterator();
while(it.hasNext()){
String key = it.next().toString();
//null作为缺省
System.out.println(key + ":" + prefs.get(key, null));
}
System.out.println("放假多少天了?" + prefs.getInt("放假多少天了?", 0));
}
}
注意:每次运行,usageCount的值没都减少1。即每次打印出“寒假还剩多少天?”后面的数字都减少1。然而,在程序第一次运行之后,并没有任何本地文件出现。Preferences API利用合适的系统资源完成了这个任务,并且这些资源会随操作系统的不同而不同。例如在windows里,就使用注册表。
32、正则表达式:
1). 在Java ,“\\”意味“我正在插入一个正则表达式反斜杠”,那么随后的字符具有特殊意义。若想插入一个字面意义上的反斜杠,得这样子表示“\\\\”。
2).量词:
·贪婪的:竟可能的模式发现尽可能的匹配。
·勉强的:用问号来指定,匹配满足模式所需的最少字符数。
·占有的:只有在Java语言中才可用,并且它也更高效,常常用于防止正则表达式失控,因此可以是正则表达式执行起来更有效。
3).注意abc+与(abc)+的不同。
4).在java中,正则表达式是通过java.util.regex包里面的Pattern和Matcher类来实现的。
·Pattern对象表示一个正则表达式的编译版本。静态的complie()方法将一个正则表达式字符串编译成Pattern对象。
·Matcher对象有matcher()方法和输入字符串编译过的Pattern对象中产生Matcher对象。
5).split()分裂操作将输入字符串断开成字符串对象数组。
18.1File类
File既可以代表一个特定文件,又可以代表一个目录下的一组文件。
通过java.util.Arrays.sort()和String.CASE_INSENSITIVE.ORDERComparator,对文件名列表排序很容易实现。
目录列表器,两种方式获取File对象:1.调用不带参数的list()方法获取此File对象的所有列表;2.通过一个目录过滤器获取一个受限列表。
18.2输入和输出
Java中流的类库让人迷惑的真正原因是:创建单一的结果流,需要创建多个对象。
与输入有关的类都从InputStream继承,与输出相关的类都从OutputStream继承。
InputStream可能的数据源有:1.字节数组2.String队形3.文件4.管道5.一个从其他种类的流组成的序列6.其他数据源如Internet链接等7.FilterInputStream。每一种数据源都有相应的InputStream子类。
X
输出的主要目标是:1.字节数组2.文件3.管道
18.3添加属性和有用的接口
18.4Reader和Writer
InputStream和OutputStream在面向字节的I/O中发挥重要作用,Reader和Writer则提供unicode和面向字符的I/O。InputStreamReader可以把inputStream转化为Reader而outputStreamWriter可以把outputStream转化为Writer。
18.6I/O流的典型使用方式
18.6.1缓冲输入文件
Java中使用流来处理程序的输入和输出操作,流是一个抽象的概念,封装了程序数据于输入输出设备交换的底层细节。JavaIO中又将流分为字节流和字符流,字节流主要用于处理诸如图像,音频视频等二进制格式数据,而字符流主要用于处理文本字符等类型的输入输出。
1.字节输入流InputStream
输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。
常用字节输入流的主要类型:
(1).ByteArrayInputStream字节数组输入流:
主要功能:允许内存缓存作为输入流。
ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。
注意:关闭ByteArrayInputStream无效,该类中的方法在关闭此流之后仍可被调用,而不会产生任何的IOException。
(2).FileInputStream文件输入流:
主要功能:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。
(3).PipedInputStream管道输入流:
主要功能:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端。
管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,这些数据有某个线程从PipedInputStream对象中读取,并有其他线程将其写入到相应的PipedOutputStream对象中。
注意:不建议PipedInputStream和PipedOutputStream对象使用单线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可以在缓冲区限定范围内将读操作和写操作分离开,如果先连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
(4).SequenceInputStream顺序输入流:
重要功能:将两个或多个输入流对象转换为一个单个输入流对象。
SequenceInputStream表示其他输入流的逻辑串联关系,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
(5).FilterInputStream过滤输入流:
主要功能:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。
常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。
2.字节输出流OutputStream:
和字节输入流相对应,字节输出流负责字节类型数据想目标文件或设备的输出。常见的字节输出流如下:
(1).ByteArrayOutputStream字节数组输出流:
主要功能:在内存中创建一个缓冲区,将接收到的数据放入该内存缓冲区中。
ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。
注意:和ByteArrayInputStream类似,关闭ByteArrayOutputStream也是无效的,此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。
(2).FileOutputStream文件输出流:
主要功能:将数据写入到指定文件中。
文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。
(3).PipedOutputStream管道输出流:
主要功能:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。
(4).FilterOutputStream过滤输出流:
主要功能:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。
常用的FIlterOutputStream是DataOutputStream数据输出流,它允许程序以适当的方式将java基本数据类型写入输出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。
3.字符流:
Java中得字节流只能针对字节类型数据,即支持处理8位的数据类型,由于java中的是Unicode码,即两个字节代表一个字符,于是在JDK1.1之后提供了字符流Reader和Writer。
字符流相关常用类如下:
(1).Reader:
用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。
(2).InputStreamReader:
是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。
它使用的字符集可以有名称指定或显式给定,也可以使用平台默认的字符集。
(3).Writer:
用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。
(4).OutputStreamWriter:
是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。
4.综合使用java IO各种流:
Java IO中的各种流,很少单独使用,经常结合起来综合使用,既可以满足特定需求,又搞效。例子如下:
(1).使用缓冲流读取文件:
1 import java.io.*;
2
3 public class BufferedInputFile{
4 public static String read(String filename) throws IOException{
5 //缓冲字符输入流
6 BufferedReader in = new BufferedReader(new FileReader(filename));
7 String s;
8 StringBuilder sb = new StringBuilder();
9 //每次读取文件中的一行
10 While((s = in.readLine()) != null){
11 sb.append(s + “\n”);
12 }
13 in.close();
14 return sb.toString();
15 }
16 public static void main(String[] args) throws IOException{
17 System.out.println(read(“BufferedInputFile.java”));
18 }
19 }
(2).读取内存中的字符串:
1 import java.io.*;
2
3 public class MemoryInput{
4 public static void main(String[] args) throws IOException{
5 //将字符串包装为字符输入流
6 StringReader in = new StringReader(
7 BufferedInputFile.read(“BufferedInputFile.java”));
8 int c;
9 //读取字符输入流中的字符
10 while((c == in.read()) != -1){
11 System.out.println((char)c);
12 }
13 }
14 }
(3).数据输入/输出流:
1 import java.io.*;
2
3 public class DataInputOutput{
4 public static void main(String[] args) thows IOException{
5 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
6 new FileOutputStream(“Data.txt”)));
7 out.writeDouble(3.14159);
8 out.writeUTF(“That was pi”);
9 out.writeDouble(1.41413);
10 out.writeUTF(“Square root of 2”);
11 out.close();
12 DataInputStream in = new DataInputStream(new BufferedInputStream(
13 new FileOutputStream(“Data.txt”)));
14 System.out.println(in.readDouble());
15 System.out.println(in.readUTF());
16 System.out.println(in.readDouble());
17 System.out.println(in.readUTF());
18 }
19 }
输出结果:
3.14159
That was pi
1.41413
Square root of 2
(4).文本文件输出流:
1 import java.io.*;
2
3 public class TextFileOutput{
4 //输出文件名
5 static String file = “BasicFileOutput.out”;
6 public static void main(String[] args) throws IOException{
7 //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
8 BufferedReader in = new BufferedReader(new StringReader
9 (BufferedInputFile.read(“TextFileOutput.java”)));
10 //字符输出流
11 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
12 int lineCount = 1;
13 String s;
14 While((s = in.readLine()) != null){
15 out.println(lineCount++ + “: ” + s);
16 }
17 out.close();
18 }
19 }
(5).二进制文件读写:
1 import java.io.*;
2
3 public class BinaryFileOutput{
4 //输出文件名
5 static String file = “BinaryFileOutput.out”;
6 public static void main(String[] args) throws IOException{
7 //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流
8 BufferedInputStream in = new BufferedInputStream(
9 new FileInputStream(“TestFile.png”)));
10 //字符输出流
11 BufferedOutputStream out = new BufferedOutputStream (
12 new FileOutputStream(file));
13 byte[] buf = new byte[1024];
14 int n;
15 While((n = in.read(buf)) > 0){
16 out.write(buf, 0, n);
17 }
18 out.close();
19 }
20 }
,