Java 标准 IO 流编程一览笔录( 下 )

Java 标准 IO 流编程一览笔录( 下 )

8、回推流:PushbackInputStream与PushbackReader

PushbackInputStream/PushbackReader 用于解析InputStream/Reader内的数据,允许你读取字节/字符后,回推(pushback)到流中,而不破坏流。

PushbackInputStream类具有以下构造函数:

PushbackInputStream(InputStream inputStream)
PushbackInputStream(InputStream inputStream,int numBytes)

第一种形式创建的流对象允许将一个字节返回到输入流; 第二种形式创建的流对象具有一个长度为numBytes的回推缓存,从而允许将多个字节回推到输入流中。

提供了unread()方法,如下所示:

void unread(int b)
void unread(byte[] buffer)
void unread(byte[] buffer,int offset,int numBytes)

第一种形式回推b的低字节,这会使得后续的read()调用会把这个字节再次读取出来。第二种形式回推buffer中的字节。第三种形式回推buffer中从offset开始的numBytes个字节。当回推缓存已满时,如果试图回推字节,就会抛出IOException异常。

示例:

public static void main(String[] args) throws IOException {
		String filepath = "file.bin";
		java.io.OutputStream os = null;
		try {
			os = new FileOutputStream(filepath);
			os.write('#');
			os.write(new byte[]{'a', 'b', 'c', 'd'});
			os.flush();// 把缓冲区内的数据刷新到磁盘
		} finally {
			if (os != null) {
				os.close();// 关闭流
			}
		}
		/**
		 * 回推(pushback)
		 */
		PushbackInputStream pis = null;
		try {
			//pis = new PushbackInputStream(new FileInputStream(filepath));
			pis = new PushbackInputStream(new FileInputStream(filepath), 3);
			int len = -1;
			byte[] bytes = new byte[2];
			while ((len = pis.read(bytes)) != -1) {
				if ('b' == bytes[0]) {
					//pis.unread('U');
					//pis.unread(bytes);
					pis.unread(new byte[]{'1', '2', '3'});
				}
				for (int i = 0; i < len; i++) {
					System.out.print(((char) bytes[i]));
				}
			}
			System.out.println();
		} finally {
			if (pis != null)
				pis.close();
		}
		/**
		 * 会发现PushbackInputStream并没有改变目标介质的数据,不破坏流
		 */
		try {
			pis = new PushbackInputStream(new FileInputStream(filepath));
			int len = -1;
			byte[] bytes = new byte[2];
			while ((len = pis.read(bytes)) != -1) {
				for (int i = 0; i < len; i++) {
					System.out.print(((char) bytes[i]));
				}
			}
		} finally {
			if (pis != null)
				pis.close();
		}
	}

注:PushbackInputStream对象会使得InputStream对象(用于创建PushbackInputStream对象)的mark()或reset()方法无效。对于准备使用mark()或reset()方法的任何流来说,都应当使用markSupported()方法进行检查。

9、行数记录:LineNumberInputStream与LineNumberReader

LineNumberInputStream与LineNumberReader提供跟踪行号的附加功能。行是以回车符 (' ')、换行符 (' ') 或回车符后面紧跟换行符结尾的字节序列。在所有这三种情况下,都以单个换行符形式返回行终止字符。 行号以 0 开头,并在 read 返回换行符时递增 1。 

使用getLineNumber()可以获取当前读取所在行数。

示例:

public static void main(String[] args) throws IOException {
		String filepath = "file.txt";
		java.io.Writer w = null;
		try {
			w = new FileWriter(filepath);
			w.write("百世山河任凋换,一生意气未改迁。愿从劫火投身去,重自寒灰飞赤鸾。
");
			w.write("沧海桑田新几度,月明还照旧容颜。琴心剑魄今何在,留见星虹贯九天。 
");
			w.write("冰轮腾转下西楼,永夜初晗凝碧天。长路寻仙三山外,道心自在红尘间。 
");
			w.write("何来慧剑破心茧,再把貂裘换酒钱。回望天涯携手处,踏歌重访白云间。
");
			w.write("何以飘零去,何以少团栾,何以别离久,何以不得安? 
");
			w.flush();// 把缓冲区内的数据刷新到磁盘
		} finally {
			if (w != null) {
				w.close();// 关闭流
			}
		}
		/**
		 * LineNumberReader
		 */
		LineNumberReader lnr = null;
		try {
			lnr = new LineNumberReader(new FileReader(filepath));
			int len = -1;
			char[] chars = new char[2];
			//int lastLineNumber = -1;
			while ((len = lnr.read(chars)) != -1) {
				for (int i = 0; i < len; i++) {
					System.out.print(((char) chars[i]));
				}
				/*int lineNumber = lnr.getLineNumber();
				if (lineNumber != lastLineNumber) {
					System.out.println("---------------行数:" + lineNumber);
					lastLineNumber = lineNumber;
				}*/
			}
			int lineNumber = lnr.getLineNumber();
			System.out.println("行数:" + lineNumber);
			System.out.println();
		} finally {
			if (lnr != null)
				lnr.close();
		}
	}

10、StreamTokenizer的使用

 StreamTokenizer定义了几种基本的常量用于标识解析过程:TT_EOF(流结尾)、TT_EOL(行结尾)、TT_NUMBER(数字符号, 0 1 2 3 4 5 6 7 8 9 . -都属于数字语法)、TT_WORD(一个单词)。
ttype 在调用 nextToken 方法之后,此字段将包含刚读取的标记的类型。
nval 如果当前标记是一个数字,则此字段将包含该数字的值。
sval 如果当前标记是一个文字标记,则此字段包含一个给出该文字标记的字符的字符串。

public static void main(String[] args) throws IOException {
	StreamTokenizer tokenizer = new StreamTokenizer(new StringReader("Sven had 7 shining ring..."));
	while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {// 流末尾
		if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
			System.out.println(tokenizer.sval);
		} else if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
			System.out.println(tokenizer.nval);
		} else if (tokenizer.ttype == StreamTokenizer.TT_EOL) {// 行末尾
			System.out.println();
		}
	}
	//System.out.println(tokenizer.lineno());
}

基本方法介绍一下:

 nextToken()  - 从此标记生成器的输入流中解析下一个标记。

(1)标记注释

commenChar(int ch) - 指定某个字符为注释字符,此字符之后直到行结尾都被stream tokenizer忽略。

slashSlashComments(boolean flag) - 如果为true,则/*与*/之间的都被认为是注释,反之,不是。

slashStartComments(boolean flag) - 如果为true,则//之后到行结尾的所有都被认为是注释,反之,不是。 

(2)基本语义

eolIsSignificant(boolean flag) - 决定一个行结束符是否被当作一个基本的符号处理,如果是true,则被当作一个基本符号,不当作普通的分隔符,如果是false,则保持原义,即当作普通的分隔符。

lowerCaseMode(boolean flag) - 决定是否读取一个单词时是否转变成小写。

parseNumbers() - 当stream tokenizer遭遇到一个单词为双精度的浮点数时,会把它当作一个数字,而不是一个单词。

resetSyntax() - 重置语法表使所有的字符都被认为是“ordinary”。

(3)指定字符语义

ordinaryChar(int ch) - 指定字符在这个tokenizer中保持原义,即只会把当前字符认为普通的字符,不会有其他的语义。
ordinaryChars(int low, int hi) - 指定范围内的字符保持语义,同上

whitespaceChars(int low, int hi) - 字符low与hi之间的所有字符都被当作为空格符,即被认识为tokenzier的分隔符。
wordChars(int low, int hi) - 字符low与hi之间的所有字符都被当作为单词的要素。一个单词是由一个单词要素后面跟着0个或者更多个单词要素或者数字要素。

11、合并流SequenceInputStream

SequenceInputStream会将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的末尾为止。 合并流的作用是将多个源合并合一个源。

public static void main(String[] args) throws IOException {
		String filepath1 = "file1.txt";
		String filepath2 = "file2.txt";
		java.io.Writer w = null;
		try {
			w = new FileWriter(filepath1);
			w.write("百世山河任凋换,一生意气未改迁。愿从劫火投身去,重自寒灰飞赤鸾。
");
			w.write("沧海桑田新几度,月明还照旧容颜。琴心剑魄今何在,留见星虹贯九天。 
");
			w.write("冰轮腾转下西楼,永夜初晗凝碧天。长路寻仙三山外,道心自在红尘间。 
");
			w.write("何来慧剑破心茧,再把貂裘换酒钱。回望天涯携手处,踏歌重访白云间。
");
			w.flush();// 把缓冲区内的数据刷新到磁盘
		} finally {
			if (w != null) {
				w.close();// 关闭流
			}
		}
		try {
			w = new FileWriter(filepath2);
			w.write("何以飘零去,何以少团栾,何以别离久,何以不得安? ");
			w.flush();// 把缓冲区内的数据刷新到磁盘
		} finally {
			if (w != null) {
				w.close();// 关闭流
			}
		}
		java.io.Reader r = null;
		try {
			Vector<InputStream> v = new Vector<InputStream>(2);
			InputStream s1 = new FileInputStream(filepath1);
			InputStream s2 = new FileInputStream(filepath2);
			v.addElement(s1);
			v.addElement(s2);
			r = new BufferedReader(new InputStreamReader(new SequenceInputStream(v.elements())));
			
			char[] data = new char[256];
			int len = -1;
			while ((len = r.read(data)) != -1) {// -1 表示读取到达文件结尾
				//操作数据
				for (int i = 0; i < len; i++) {
					System.out.print(data[i]);
				}
			}
		} finally {
			if (r != null) {
				r.close();// 关闭流
			}
		}
	}

更多Demo:https://git.oschina.net/svenaugustus/MyJavaIOLab

本文只针对标准IO的知识总结,其他IO总结姊妹篇(NIO)请参见:
+ JavaNIO编程一览笔录: https://my.oschina.net/langxSpirit/blog/899954