think in java i o_《Thinking in Java》学习——18章Java I/O系统(三)
ppe#標(biāo)準(zhǔn)I/O
一.從標(biāo)準(zhǔn)輸入中讀取
1.按照標(biāo)準(zhǔn)I/O模型,Java提供了System.in、System.out、System.err。其中System.out已經(jīng)事先被包裝成了PrintStream對象。System.err同樣也是PrintStream,但是System.in卻是一個沒有被包裝過的未經(jīng)加工的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和大多數(shù)流一樣,通常應(yīng)該對它進行緩沖。
二.將System.out轉(zhuǎn)換成PrintWriter
PrintWriter有一個可以接受OutputStream作為參數(shù)的構(gòu)造器。因此,只要需要,就可以使用那個那個構(gòu)造器把System.out轉(zhuǎn)換成PrintWriter:
public class ChangeSystemOut {
public static void main(String... args) {
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello, world");
}
}
第二個參數(shù)需要設(shè)置為true,以便開啟自動清空功能;否則,你可能看不到輸出。
三.標(biāo)準(zhǔn)I/O重定向
1.Java的System類提供了一些簡單的靜態(tài)方法調(diào)用,以允許我們對標(biāo)準(zhǔn)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重定向操縱的是字節(jié)流,而不是字符流;因此我們使用的是InputStream和OutputStream,而不是Reader和Writer。
進程控制
1.對于需要在Java內(nèi)部之行其他 操作系統(tǒng)程序的需求,Java類庫提供了執(zhí)行這些操作的類。
2.下面的程序的作用是運行程序,并將產(chǎn)生的輸出發(fā)送到控制臺:
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構(gòu)造器,然后所產(chǎn)生的ProcessBuilder對象被啟動。程序執(zhí)行過程中調(diào)用getInputStream()和getErrorStream()獲取標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯誤流。
新I/O
1.JDK1.4引入了新的Java I/O類庫java.nio.,其目的在于提高速度。速度的提高在文件I/O和網(wǎng)絡(luò)I/O中都有實現(xiàn),這里我們只研究前者。
2.速度的提高來自于所使用的結(jié)構(gòu):通道和緩沖器。但是,我們并沒有必要直接和通道交互,我們只和緩沖器交互,并把緩沖器派送到通道。通道要么從緩沖器獲得數(shù)據(jù),要么向緩沖器發(fā)送數(shù)據(jù)。
3.唯一直接與通道交互的緩沖器是ByteBuffer:通過告知分配多少存儲空間來創(chuàng)建一個ByteBuffer對象,并且還有一個方法集,用于以原始的字節(jié)形式或幾本數(shù)據(jù)類型輸出和讀取數(shù)據(jù)。
4.FileInputStream* 、FileOutputStream和RandomAccessFile提供了方法用以產(chǎn)生可寫的、可讀的及可讀可寫的通道:
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.通道時一種相當(dāng)基礎(chǔ)的東西:可以向它傳送用于讀寫的ByteBuffer,并且可以鎖定文件的某些區(qū)域用于獨占式訪問。
6.將字節(jié)存放于ByteBuffer的方法之一是:使用一種“put”方法直接對它們進行填充,填入一個或多個字節(jié),或基本數(shù)據(jù)類型的值。也可以使用warp()方法將已經(jīng)存在的字節(jié)數(shù)組“包裝到”ByteBuffer中。
7.對于只讀訪問,我們必須顯式地使用靜態(tài)的allocate()方法來分配ByteBuffer。
8.一旦調(diào)用read()方法來告知FileChannel向ByteBuffer存儲字節(jié),就必須調(diào)用緩沖器上的flip(),讓它做好讓別人讀取字節(jié)的準(zhǔn)備,如果我們打算使用緩沖器執(zhí)行進一步的read()操作,我們也必須得調(diào)用clear()來為每個read()做好準(zhǔn)備:
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()操作之后,就會將數(shù)據(jù)輸入到緩沖器中,flip()則是準(zhǔn)備緩沖器以便它的信息可以由write()提取。write()操作之后,信息仍在緩沖器中,接著clear()操作則對所有的內(nèi)部指針重新安排,以便緩沖器在另一個read()操作期間能夠做好接受數(shù)據(jù)的準(zhǔn)備。
一.轉(zhuǎn)換數(shù)據(jù)
1.緩沖器容納的是普通字節(jié),為了把它們轉(zhuǎn)換成字符,我們要不在輸入它們的時候?qū)ζ溥M行編碼,要么在將其從緩沖器輸出對它們進行解碼。可以使用java.nio.charset.Charset類實現(xiàn)這些功能,該類提供來把數(shù)據(jù)編碼成多種不同類型的字符集的工具。如果我們想對緩沖器調(diào)用rewind()方法(該方法是為了回到數(shù)據(jù)開始的部分),接著使用平臺的默認(rèn)字符集對數(shù)據(jù)進行decode(),那么作為結(jié)果的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")可以用來發(fā)現(xiàn)默認(rèn)字符集,它會產(chǎn)生代表字符集名稱的字符串。把該字符串傳送給Charset.forName()用以產(chǎn)生Charset對象,可以用它對字符串進行解碼。
二.獲取基本類型
1.盡管ByteBuffer只能保存字節(jié)類型的數(shù)據(jù),但是它可以具有可以從所容納的字節(jié)中產(chǎn)生出各種不同基本類型值的get方法:
ByteBuffer bb = ByteBuffer.allocate(1024);
System.out.println(String.valueof(bb.getInt()));
2.向ByteBuffer插入基本數(shù)據(jù)類型的方法是:利用asCharBuffer()、asShortBuffer()等獲得該緩沖器上的視圖,然后使用視圖的put()方法。此方法適用于所有基本數(shù)據(jù)類型的轉(zhuǎn)換,唯一的例外就是使用asShortBuffer()方法等時候,需要進行數(shù)據(jù)類型轉(zhuǎn)換:
bb.asCharBuffer().put("ByteBuffer");
bb.asShortBuffer().put((short) 1111111111);
bb.asIntBuffer().put(1);
3.ByteBuffer中提供了limit()方法,以便獲取ByteBuffer可使用空間的上限。
4.當(dāng)分配完一個ByteBuffer之后,緩沖器的分配方式會將其內(nèi)容自動置零。
三.視圖緩沖器
1.視圖緩沖器可以讓我們通過某個特定的基本數(shù)據(jù)類型的視窗查看其底層的ByteBuffer。ByteBuffer依然是實際存儲數(shù)據(jù)的地方,支持著前面的視圖,因此,對視圖的任何修改都會映射成對ByteBuffer中數(shù)據(jù)的修改:
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()方法存儲一個數(shù)組,接著get()和put()方法調(diào)用直接訪問底層ByteBuffer中的某個整數(shù)位置。
2.不同的機器可能會使用不同的字節(jié)排序方法來存儲數(shù)據(jù)。“big endian”(高位優(yōu)先)將最重要的字節(jié)存放在地址最低的存儲器單元。而“l(fā)ittle endian”(低位優(yōu)先)則是將最重要的字節(jié)放在地址最高的存儲器單元。因此,當(dāng)存儲量大于一個字節(jié)的時候,就要考慮字節(jié)的順序問題了。如有兩個字節(jié)b1:00000000,b2:01100001,如果我們以short(ByteBuffer.asShortBuffer())形式讀取數(shù)據(jù),得到的數(shù)字是97(二進制形式為000000000110010),如果在讀取之前將排序方式改為低位優(yōu)先,得到的數(shù)字為24832(二進制形式為011001000000000)。
3.改變排序方式可以使用order()方法,這里需要傳入一個參數(shù),為ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN。
四.用緩沖器操縱數(shù)據(jù)
1.
nio類之間的關(guān)系.png
五.緩沖器的細(xì)節(jié)
1.Buffer由數(shù)據(jù)和可以高效地訪問及操縱這些數(shù)據(jù)的四個索引組成,這四個索引是:mark(標(biāo)記),position(位置),limit(界限),和capacity(容量):
方法
描述
capacity()
返回緩沖器的容量
clear()
清空緩沖區(qū),將position設(shè)置為0,limit設(shè)置為容量。我們可以調(diào)用此方法覆蓋緩沖區(qū)
flip()
將limit設(shè)置為position,position設(shè)置為0.此方法用于準(zhǔn)備蔥緩沖區(qū)讀取已經(jīng)寫入的數(shù)據(jù)
limit()
返回limit值
limit(int num)
設(shè)置limit的值
mark()
將mark設(shè)置為position
position()
返回position()的值
position(int pos)
設(shè)置position的值
remaining()
返回(limit - positon)
hasRemaining()
若有介于position和limit之間的元素,則返回true
六.內(nèi)存映射文件
1.內(nèi)存映射文件允許我們創(chuàng)建和修改那些因為太大而不能放入內(nèi)存的文件。有了內(nèi)存映射文件,我們就可以假定整個文件都放在內(nèi)存中,而且王權(quán)可以把它當(dāng)作非常大的數(shù)組來訪問:
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繼承而來,可以通過調(diào)用獲取到的文件上的通道的map()方法獲得,它具有ByteBuffer的所有方法。
2.盡管“映射寫”似乎要用到FileOutputStream,但是映射文件中的所有輸出必須使用RandomAccessFile。
3.盡管“舊”的I/O流在使用nio實現(xiàn)后性能有所提高,但是“映射文件訪問”往往可以更加顯著地加快速度,即使簡歷映射文件的話費很大。
七.文件加鎖
1.JDK1.4引入了文件加鎖機制,它允許我們同步訪問某個作為共享資源的文件。為了解決競爭統(tǒng)一文件的兩個線程可能在不同的進程里的問題,文件鎖被設(shè)定為對其他的操作系統(tǒng)的進程是可見的,因為Java的文件加鎖直接映射到了本地操作系統(tǒng)的加鎖工具。
2.通過對FileChannel調(diào)用tryLock()或lock(),就可以獲得整個問價的FileLock。tryLock()是非阻塞式的,它設(shè)法獲取鎖,但是如果不能獲得,它將直接從方法調(diào)用返回。lock()則是阻塞式的,它要阻塞進程直至鎖可以獲得,或調(diào)用lock()的線程中斷,或調(diào)用lock()的通道關(guān)閉。使用FileLock.release()可以釋放鎖。
3.tryLock()和lock()方法也有其重載方法提供使用對文件等一部分上鎖:
tryLock(long position, long size, boolean shared)
lock(long position, long size, boolean shared)
4.文件映射通常應(yīng)用于極大的文件。我們可能需要對這種巨大的文件進行部分加鎖,以便其他進程可以修改文件中未被加鎖的部分:
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創(chuàng)建了緩沖區(qū)和用于修改的slice,然后在run()方法中,獲得文件通道上的鎖。
總結(jié)
以上是生活随笔為你收集整理的think in java i o_《Thinking in Java》学习——18章Java I/O系统(三)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字符串处理方法
- 下一篇: Showdoc 搭建项目 API 文档系