字节流和字符流
InputStream和Reader
InputStream和Reader两个抽象类是所有输入流的基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板.他们的方法是所有输入流都可用的方法.
InputStream中包含如下三个方法
-
int read()
从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转 换为int类型). -
int read(byte[] b)
从输入流中读取最多b.lenght
个字节数据,并将其存贮在数组b中,返回实际读取的字节数. -
int read(byte[] b,int off,int len)
从输入流中读取最多的len个字符的数据,并存在数组b中,放入数组b中时,从off的位置开始,返回实际读取的字节数.
Reader中包含如下三个方法
-
int read()
从输入流中读取单个字符,返回所读取的字符数据(字符数可以直接转换为int类型) -
int read(char[] cbuf)
从输入流中读取最多的cbuf.length个字符数据,并将其存储在字符数组cbuf中,返回实际读取的字符数. int read(char cbuf,int off,int len)
从输入流中读取最多len个字符的数据,并将其存在cbuf中,从off位置开始,返回实际的读取的字符数.使用FileInputStream读取自身
java
public void readFile() throws IOException { //创建字节输入流 FileInputStream fis = new FileInputStram("FileInputTest.java"); //创建一个长度为1024的缓冲数组 byte[] bbuf = new byte[1024]; //保存实际读取的字节数 int hasRead = 0; //循环读取文件内容 while ((hasRead = fis.read(bbuf)) > 0) { System.out.println(new String(bbuf,0,hasRead)); } //文件IO资源不属于内存资源 //垃圾回收无法回收该资源 //必须显式关闭 fis.close(); }
- 使用FileReader读取文件自身
public void readFile() throws IOException {
FileReader fr = new FileReader("FileReaderTest.java");
char[] cbuf = new char[32];
int hasRead = 0;
while ((hasRead = fr.read(cbuf)) > 0) {
System.out.println(new String(cbuf,0,hasRead));
}
fr.clse();
}
此外,InputStream和Reader还支持如下几个方法来支持指针的移动void mark(int readAheadLimit)
在记录指针当前位置记录一个标记(mark).boolean markSupported()
判断此输入流是否支持mark()
操作void reset()
将此流的记录指针重新定位到上一次记录标记的位置(mark).long skip(long n)
记录指针向前移动n个字节/字符.
OutputStream和Writer
OutputStream和Writer也非常相似,两个流都提供了如下三个方法:
-
void write(int c)
将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字符. -
void write(byte[]/char[] buf)
将字节数组/字符数组中的数据输出到指定输出流中. -
void write(byte[]/char[] buf,int off,int len)
将字节数组/字符数组从off位置开始,长度为len的字节/字符输出到输出流中.
Writer里还包含如下两个方法:
* void write(String str)
将str字符串输出到指定输出流中
* void write(String str,int off,int len)
将str字符串里从off位置开始,长度为len输出到指定输出流中.
下面的程序使用FileInputStream
来执行输入,通过FileOutputStream
执行输出,实现复制文件的功能
java
public void copyFile() throws IOException { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("outfile.txt"); fos = new FileOutputStream("newfile.txt"); byte[] bbuf = new byte[32]; int hasRead = 0; while ((hasRead =fis.read(bbuf)) > 0) { fos.write(bbuf,0,hasRead); } } catch(IOException ioe) { ioe.printStatckTrance(); } finally { if (fis != null) { fis.close(); } if(fos != null) { fos.close(); } } }
如果希望直接输出字符串内容,使用Writer更为方便.
java
public void out() { FileWriter fw = null; try { fw = new FileWrite("file.txt"); fw.write("但我不能放歌,\n"); fw.write("悄悄是别离的笙箫;\n"); fw.write("夏虫也为我沉默,\n"); fw.write("沉默是今晚的康桥!\n"); } catch (IOException ioe) { } finally { if(null != fw) { fw.close(); } } }
上面程序会在当前目录下输出file.txt,文件内容就是程序中输出内容.
输入/输出流体系
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedRead | BufferedWriter |
转换流 | InputStreamReader | OutputStreamReader | ||
对象流 | ObjectInputStream | ObjectOutput | ||
抽象基类 | FilterInputStream | FilterOutPutStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
上面表中粗体字标出类的代表节点流,必须直接与指定物理节点关联;斜体字标出的为抽象基类,无法创建实例.
字节流比字符流功能强大,因为计算机中所有数据都是二进制的.字节流可以处理所有的二进制文件.如果使用字节流来处理文本文件时,需要使用合适的方式把字节转换成字符,无形中增加了编程的复杂程度.所以通常有个规则,如果需要进行输入/输出的是文本内容,应考虑使用字符流,如果需要进行输入/输出的二进制内容,应考虑使用字节流.
使用字符串作为物理节点:
java
public void StringNodeTest() { String src = "软泥上的青荇,\n" +"油油的在水底招摇;\n" +"在康河的柔波里,\n" +"我甘心做一条水草。"; StringReader sr = new StringReader(src); char[] cbuf = new char[32]; int hasRead = 0; try { while((hasRead =sr.read(cbuf)) > 0) { System.out.println(new String(cbuf,0,hasRead)); } } catch (IOException ioe) { ioe. } finally { if (null != sr){ sr.close(); } } }
转换流
IO体系中提供了2个转换流,用于实现将字节流转换成字符流.InputStreamReader
将字节输入流转换成字符输入流,OutputStreamWriter
将字节输出流转换成字符输出流.
Java并没有提供字符流转换成字节流接口,看似设计遗漏.考虑下字符流和字节流的差别,字节流比字符流使用范围更广,但字符流操作比字节流方便,如果已经有一个字符流,为什么还要换成字节流,既然知道字节流的内容是文本,那么转换成字符流更方便一些,所以Java没有提供字符流到字节流的转换.
下面的例子通过使用键盘输入(System.in
).这个标准的输入流是InputStream实例,使用起来不太方便,键盘输入的都是纯文本,使用InputStreamReader
将其转换为字符输入流,普通Reader
读起来依旧不方便,于是再转换成BufferedReader
,利用它的readLine()
方法可以一次读一行.
java
public void convInput() { BufferedReader br = null; try { //System.in转换成Reader对象 InputStreamReader reader = new InputStreamReader(System.in); //将Reader包装成BufferedReader br = new BufferedReader(reader); String buffer = null; While((buffer = br.readLine()) != null) { if("exit".equals(buffer)) { System.exit(1); } System.out.println("输入内容为:" + buffer); } } catach (IOException ioe) { } finally { try { if (null != br) { br.close(); } } catach (IOException ioe) { } } }
推回输入流
在IO体系中,有2个特殊的流.PushbackInputStream和PushbackReader.提供如下三个方法:
* void unread(byte[]/char[] buf)
将一个字节数组内容推回到推回缓冲区里,从而允许重复读取刚刚的内容.
* void unread(byte[]/char[] b,int off,int len)
将一个字节/字符数组从off开始,长度为len字节/ 字符的内容推回到推回缓冲区里,从而允许重复读取刚刚的内容.
* void unread(int b)
将一个字节/字符推回到推回缓冲区,从而允许重复读取刚刚读取的内容.
这三个方法和InputStream和Reader里三个read方法一一对应,这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread方法时,系统会把指定数组的内容推回到该缓冲区里,从而允许重复读取刚刚读取的内容.
java
/**省略无数代码**/ PushbackReader pr = null; try { pr = new PushbackReader(new FileReader("E:\\workplace1\\review\\src\\io\\PushbackReaderTest.java"), 64); char[] buf = new char[32]; //保存上次读取的字符串内容 String lastContent = ""; int hasRead = 0; while ((hasRead = pr.read(buf)) > 0) { //读取到内容转换成字符串 String content = new String(buf,0,hasRead); int targetIndex = 0; //将上一次内容和本次内容拼起来 检查是否包含目标字符串 //如果包含目标字符串 if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0) { //将本次内容上次内容一起退回到推回缓冲区 pr.unread((lastContent + content).toCharArray()); //再次读取指定程度的内容(目标字符串之前的内容) pr.read(buf,0,targetIndex); //打印读取内容 System.out.println(new String(buf,0,targetIndex)); System.exit(0); } else { System.out.println(lastContent); lastContent = content; } } /**省略无数代码**/
重定向标准输入/输出
Java的标准输入输出分别通过System.in和System.out来代表,默认情况下他们分别代表键盘和显示器.
System类中提供三个重定向输入/输出标准的方法:
* static void setErr(PrintStream err)
重定向"标准"错误输出流
* static void setIn(InputStream in)
重定向"标准"输入流
* static void setOut(PrintStream out)
重定向"标准"输出流
下面的程序通过重定向标准输出流,将System.out的输出重定向到到文件输出:
java
/**省略无数代码**/ PrintStream ps = null; try { //创建重定向需要的输出流 ps = new PrintStream(new FileOutputStream("out.txt")); //将标准输出流重定向到ps输出流 System.setOut(ps); //测试输出 System.out.println("普通字符串"); System.out.println(new RedirectOut()); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (null != ps) { ps.close(); } } /**省略无数代码**/
下面的程序通过重定向标准输入流,将System.in重定向到指定文件而不是键盘:
java
/**省略无数代码**/ FileInputStream fis = null; try { fis = new FileInputStream("E:\\workplace1\\review\\src\\io\\RedirectIn.java"); //将标准输入重定向到fis输入流 System.setIn(fis); //获取标准输入 Scanner sc = new Scanner(System.in); //设置只把回车作为分隔符 sc.useDelimiter("\n"); while (sc.hasNext()) { System.out.println("键盘输入内容为:" + sc.next()); } /**省略无数代码**/