ppe#标准I/O
一.从标准输入中读取
1.按照标准I/O模型,Java提供了System.in、System.out、System.err。其中System.out已经事先被包装成了PrintStream对象。System.err同样也是PrintStream,但是System.in却是一个没有被包装过的未经加工的InputStream。这意味着尽管我们可以立刻使用System.out和System.err,但是在读取System.in之前必须对其进行包装。
2.为了使用readLine()一行一行地读取,我们将System.in包装成BufferedReader来使用:
public class Echo {
public static void main(String... args) throws IOException {
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
String in;
while ((s = stdin.readLine()) != null && s.length != 0) {
System.out.println(s);
}
}
}
注意,System.in和大多数流一样,通常应该对它进行缓冲。
二.将System.out转换成PrintWriter
PrintWriter有一个可以接受OutputStream作为参数的构造器。因此,只要需要,就可以使用那个那个构造器把System.out转换成PrintWriter:
public class ChangeSystemOut {
public static void main(String... args) {
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello, world");
}
}
第二个参数需要设置为true,以便开启自动清空功能;否则,你可能看不到输出。
三.标准I/O重定向
1.Java的System类提供了一些简单的静态方法调用,以允许我们对标准I/O流进行重定向:
setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)
2.下面是简单实例:
public class Redirecting {
public static void main(String... args) throws IOException {
PrintStream console = System.out;
BufferedInputStream in = new BufferedInputStream(
new FileInputStream("Redirecting.java"));
PrintStream out = new PrintStream(
new BufferedOutputStream(
new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
String s;
while (s = br.readLine() != null) {
System.out.println(s);
}
out.close();
System.setOut(console);
}
}
I/O重定向操纵的是字节流,而不是字符流;因此我们使用的是InputStream和OutputStream,而不是Reader和Writer。
进程控制
1.对于需要在Java内部之行其他 操作系统程序的需求,Java类库提供了执行这些操作的类。
2.下面的程序的作用是运行程序,并将产生的输出发送到控制台:
class OSExecuteException extends RuntimeException {
public OSExecuteException(String why) { super(why); }
}
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()));
while ((s = results.readLine) != null){
System.err.println(s);
err = true;
}
} catch (Exception e) {
if (!command.startWith("CMD /C"))
command("CMD /C" + command);
else
throw new RuntimeException(e);
}
if (err) {
throw new OSExecuteException("Errors executing" + command);
}
}
}
要想运行一个程序,你需要传递一个字符串,它与你在控制台上运行该程序所键入的命令相同。这个命令被传递给java.lang.ProcessBuilder构造器,然后所产生的ProcessBuilder对象被启动。程序执行过程中调用getInputStream()和getErrorStream()获取标准输出流和标准错误流。
新I/O
1.JDK1.4引入了新的Java I/O类库java.nio.,其目的在于提高速度。速度的提高在文件I/O和网络I/O中都有实现,这里我们只研究前者。
2.速度的提高来自于所使用的结构:通道和缓冲器。但是,我们并没有必要直接和通道交互,我们只和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
3.唯一直接与通道交互的缓冲器是ByteBuffer:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法集,用于以原始的字节形式或几本数据类型输出和读取数据。
4.FileInputStream* 、FileOutputStream和RandomAccessFile提供了方法用以产生可写的、可读的及可读可写的通道:
public class GetChannel {
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 = RandomAccessFile("data.txt", "rw").getChannel();
fc.position(fc.size());
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while (buff.hasRemaining())
System.out.println((char) buff.get());
}
}
/*
Output:
Some text Some more
*/
5.通道时一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
6.将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。也可以使用warp()方法将已经存在的字节数组“包装到”ByteBuffer中。
7.对于只读访问,我们必须显式地使用静态的allocate()方法来分配ByteBuffer。
8.一旦调用read()方法来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备,如果我们打算使用缓冲器执行进一步的read()操作,我们也必须得调用clear()来为每个read()做好准备:
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();
out = new FileOutputStream(args[1]).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while (in.read(buffer) != -1) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
}
}
每次read()操作之后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲器中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接受数据的准备。
一.转换数据
1.缓冲器容纳的是普通字节,为了把它们转换成字符,我们要不在输入它们的时候对其进行编码,要么在将其从缓冲器输出对它们进行解码。可以使用java.nio.charset.Charset类实现这些功能,该类提供来把数据编码成多种不同类型的字符集的工具。如果我们想对缓冲器调用rewind()方法(该方法是为了回到数据开始的部分),接着使用平台的默认字符集对数据进行decode(),那么作为结果的CharBuffer可以很好地输出打印到控制台:
public class BufferToText {
private static final int BSIZE = 1024;
public static void main(String[] args) {
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.getProprety("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());
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(24);
buff.asCharBuffer().put("Some text");
fc.write(buff);
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
/*
Output:
????
Decoded using Cp1252: Some text
Some text
Some text
*/
2.System.getProperty("file.encoding")可以用来发现默认字符集,它会产生代表字符集名称的字符串。把该字符串传送给Charset.forName()用以产生Charset对象,可以用它对字符串进行解码。
二.获取基本类型
1.尽管ByteBuffer只能保存字节类型的数据,但是它可以具有可以从所容纳的字节中产生出各种不同基本类型值的get方法:
ByteBuffer bb = ByteBuffer.allocate(1024);
System.out.println(String.valueof(bb.getInt()));
2.向ByteBuffer插入基本数据类型的方法是:利用asCharBuffer()、asShortBuffer()等获得该缓冲器上的视图,然后使用视图的put()方法。此方法适用于所有基本数据类型的转换,唯一的例外就是使用asShortBuffer()方法等时候,需要进行数据类型转换:
bb.asCharBuffer().put("ByteBuffer");
bb.asShortBuffer().put((short) 1111111111);
bb.asIntBuffer().put(1);
3.ByteBuffer中提供了limit()方法,以便获取ByteBuffer可使用空间的上限。
4.当分配完一个ByteBuffer之后,缓冲器的分配方式会将其内容自动置零。
三.视图缓冲器
1.视图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,支持着前面的视图,因此,对视图的任何修改都会映射成对ByteBuffer中数据的修改:
public class IntBufferDemo {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer();
ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016});
System.out.println(ib.get(3));
ib.put(3, 1811);
ib.flip();
while (ib.hasRemaining()) {
int i = ib.get();
System.out.println(i);
}
}
}
/*
Output:
99
11
42
47
1811
143
811
1016
*/
先用重载后的put()方法存储一个数组,接着get()和put()方法调用直接访问底层ByteBuffer中的某个整数位置。
2.不同的机器可能会使用不同的字节排序方法来存储数据。“big endian”(高位优先)将最重要的字节存放在地址最低的存储器单元。而“little endian”(低位优先)则是将最重要的字节放在地址最高的存储器单元。因此,当存储量大于一个字节的时候,就要考虑字节的顺序问题了。如有两个字节b1:00000000,b2:01100001,如果我们以short(ByteBuffer.asShortBuffer())形式读取数据,得到的数字是97(二进制形式为000000000110010),如果在读取之前将排序方式改为低位优先,得到的数字为24832(二进制形式为011001000000000)。
3.改变排序方式可以使用order()方法,这里需要传入一个参数,为ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN。
四.用缓冲器操纵数据
1.五.缓冲器的细节
1.Buffer由数据和可以高效地访问及操纵这些数据的四个索引组成,这四个索引是:mark(标记),position(位置),limit(界限),和capacity(容量):
方法 | 描述 |
---|---|
capacity() | 返回缓冲器的容量 |
clear() | 清空缓冲区,将position设置为0,limit设置为容量。我们可以调用此方法覆盖缓冲区 |
flip() | 将limit设置为position,position设置为0.此方法用于准备葱缓冲区读取已经写入的数据 |
limit() | 返回limit值 |
limit(int num) | 设置limit的值 |
mark() | 将mark设置为position |
position() | 返回position()的值 |
position(int pos) | 设置position的值 |
remaining() | 返回(limit - positon) |
hasRemaining() | 若有介于position和limit之间的元素,则返回true |
六.内存映射文件
1.内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且王权可以把它当作非常大的数组来访问:
public class LargeMappedFiles {
static int length = 0x8FFFFFF;
public static void main(String... args) {
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');
}
fro (int i = length / 2; i < length / 2 + 6; i ++) {
System.out.print((char) out.get(i));
}
}
}
MappedByteBuffer由ByteBuffer继承而来,可以通过调用获取到的文件上的通道的map()方法获得,它具有ByteBuffer的所有方法。
2.尽管“映射写”似乎要用到FileOutputStream,但是映射文件中的所有输出必须使用RandomAccessFile。
3.尽管“旧”的I/O流在使用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度,即使简历映射文件的话费很大。
七.文件加锁
1.JDK1.4引入了文件加锁机制,它允许我们同步访问某个作为共享资源的文件。为了解决竞争统一文件的两个线程可能在不同的进程里的问题,文件锁被设定为对其他的操作系统的进程是可见的,因为Java的文件加锁直接映射到了本地操作系统的加锁工具。
2.通过对FileChannel调用tryLock()或lock(),就可以获得整个问价的FileLock。tryLock()是非阻塞式的,它设法获取锁,但是如果不能获得,它将直接从方法调用返回。lock()则是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。使用FileLock.release()可以释放锁。
3.tryLock()和lock()方法也有其重载方法提供使用对文件等一部分上锁:
tryLock(long position, long size, boolean shared)
lock(long position, long size, boolean shared)
4.文件映射通常应用于极大的文件。我们可能需要对这种巨大的文件进行部分加锁,以便其他进程可以修改文件中未被加锁的部分:
public class LockAndModify extends Thread {
private ByteBuffer buff;
private int start, end;
public LockAndModify(ByteBuffer mob, int start, int end) {
this.start = start;
this.end = end;
mbb.limit(end);
mbb.position(start);
buff = mbb.slice();
start();
}
public void run() {
try {
FileLock fl = fc.lock(start, end, false);
System.out.println("Locked: " + start + " to " + end);
while (buff.position() < buff.limit() - 1) {
buff.put((byte) (byte.get() + 1));
}
fl.release();
System.out.println("Release: " + start + " to " + end);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
在上面的程序中,线程类LockAndModify创建了缓冲区和用于修改的slice,然后在run()方法中,获得文件通道上的锁。