小tips:本文省略了FIle类的讲解,还有对一开始的简单字节,字符流都进行了简化讲解,如果你需要更基础的入门,可以参考我的知乎文章:IO流
Java输入/输出
字节流和字符流
你可以把Java的输出和输入流想象为我们在一个竹筒中取水,向竹筒中把水倒出来就是输出流。字节流的话就是一滴一滴地流出,字符流的话就是多滴多滴流出来。输入也是相同的,我们向竹筒中注水,字节流就是一滴一滴地注入,字符流就是多滴多滴地注入。
演示输出流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import java.io.FileInputStream; import java.io.IOException;
public class FileInputStreamTest { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("FileInputStreamTest.java"); byte[] bbuf = new byte[1024]; int hasRead = 0; while((hasRead = fis.read(bbuf))>0) { System.out.println(new String(bbuf,0,hasRead)); } fis.close(); } }
|
演示输入流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.io.FileInputStream; import java.io.FileOutputStream;
public class FileOutputStreamTest { public static void main(String[] args) { try( FileInputStream fis = new FileInputStream("FileOutputStreamTest.java"); FileOutputStream 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 (Exception e) { e.printStackTrace(); } } }
|
输入流/输出流体系
缓冲流,转换流,对象流均需要在黑色标注的基础流的基础上使用。相当于是对基础流的一层封装,让其实现更多功能。这里的话只对特殊的类(访问字符串,推回输入流),接触比较少的类进行介绍。其他类的话可以参考我的知乎文档。

打印流
演示打印流的程序,我们可以看到PrintStream ps = new PrintStream(fos)
是建立在访问文件的字节输入流中的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.io.FileOutputStream; import java.io.PrintStream;
public class PrintStreamTest { public static void main(String[] args) { try( FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos) ){ ps.print("普通字符串"); ps.println(new PrintStreamTest()); } catch (Exception e) { e.printStackTrace(); } } }
|
读取字符串
其中字符流中有一个比较特殊的流—StringWriter/StringRead
演示此类的demo程序,此流可以通过字符串来当作它的节点。不过用法上与其他流并无不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import java.io.StringReader; import java.io.StringWriter;
public class StringNodeTest { public static void main(String[] args) { String src = "从明天起,做一个幸福的人\n" + "喂马,劈柴,周游世界\n" + "从明天起,关心粮食和蔬菜\n" + "我有一所 房子,面朝大海,春暖花开\n" + "从明天起,和每一个亲人通信\n" + "告诉他们我的幸福\n"; char[] buffer = new char[32]; int hasRead = 0; try( StringReader sr = new StringReader(src)) { while((hasRead = sr.read(buffer))>0) { System.out.println(new String(buffer,0,hasRead)); } } catch (Exception e) { e.printStackTrace(); } try( StringWriter sw = new StringWriter(20) ) { sw.write("有一个美丽的新世界,\n她在远方等我,\n那里有天真的孩子,\n还有姑娘的酒窝\n"); System.out.println("----下面是sw字符串节点里的内容----"); System.out.println(sw.toString()); } catch (Exception e) { e.printStackTrace(); } } }
|
推回输入流
PushbackInputSteam 和 PushbackReader,他们都提供了三个特殊的方法
方法 |
描述 |
void unread( byte[]/ char[] buf) |
将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。 |
void unread( byte[]/ char[] b, int off, int len) |
将 一个 字节/ 字符 数组 里 从 off 开始, 长度 为 len 字节/ 字符 的 内容 推 回到 推 回 缓冲 区里, 从而 允许 重复 读取 刚刚 读取 的 内容。 |
void unread( int b) |
将 一个 字节/ 字符 推 回到 推 回 缓冲 区里, 从而 允许 重复 读取 刚刚 读取 的 内容。 |
是不是和InpuStream和Reader的read()方法很像,没错这三个方法就相当于反着的read。
通过这个方法,我们可以把一些数据重新冲入缓冲区,使用这个流的read方法时,会先把缓冲区的数据拿出来以后在从原输入流中读取数据。
下面的程序通过这个特殊的流实现了复制本代码程序一遍的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PushbackReader;
public class PushbackTest { public static void main(String[] args) { try( PushbackReader pr = new PushbackReader(new FileReader( "PushbackTest.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()); if(targetIndex > 32) { buf = new char[targetIndex]; } pr.read(buf,0,targetIndex); System.out.println(new String(buf,0,targetIndex)); System.exit(0); }else { System.out.println(lastContent); lastContent = content; } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }; } }
|
转换流
这两个特殊的流可以把字节流转换为字符流,当然是没有字符流转换成字节流的特殊流啦。
下面的程序通过包装实现把System.in(标准输入),然后在通过缓冲流实现一次读取一行的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.Reader;
public class KeyinTest { public static void main(String[] args) { try( Reader reader = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(reader); ){ String line = null; while((line = br.readLine())!= null) { if(line.equals("exit")) { System.exit(1); } System.out.println("输入地内容为:"+line); } } catch (Exception e) { e.printStackTrace(); } } }
|
重定向标准输入/输出流
上一节讲到,通过包装System.in来实现一次读取一行的操作。默认情况下,System.in和System.out分别代表键盘和显示器,在System类中提供了如下方法可以重新定义输入的对象,比如可以实现把输出定向到文件输出,而不是在屏幕上输出。
方法 |
描述 |
static void setErr( PrintStream err) |
重定向标准错误流 |
static void setIn( InputStream in) |
重定向标准输入流 |
static void setOut( PrintStream out) |
重定向标准输出流 |
重定向输入到文件代码demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.io.FileOutputStream; import java.io.PrintStream;
public class RedirectOut { public static void main(String[] args) { try( PrintStream ps = new PrintStream(new FileOutputStream("out.txt")) ) { System.setOut(ps); System.out.println("普通字符串"); System.out.println(new RedirectOut()); } catch (Exception e) { e.printStackTrace(); } } }
|
从文件中读取输入,而不是键盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.io.FileInputStream; import java.util.Scanner;
public class RedirectIn { public static void main(String[] args) { try( FileInputStream fis = new FileInputStream("RedirectIn.java")){ System.setIn(fis); Scanner sc = new Scanner(System.in); sc.useDelimiter("\n"); while(sc.hasNext()) { System.out.println("键盘输入的内容是:"+sc.next()); } } catch (Exception e) { e.printStackTrace(); } } }
|
java虚拟机读写其他进程的数据
使用Runtime对象的exec()
方法可以运行平台上的其他程序,该方法产生一个Process对象,这个对象代表由Java程序启动的子进程。这个类提供了如下方法用于让程序之间进行通信。
方法 |
描述 |
InputStream getErrorStream() |
获取子进程的错误流 |
InputStream getInputStream() |
获取子进程的输入流。 |
OutputStream getOutputStream() |
获取子进程 的输出流。 |
注意此处的输入输出均是以Java程序的角度来看,给Java程序的数据就是输入,Java程序给出的数据就是输出。
下面程序示范了读取其他进程(javac)的输出信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;
public class ReadFromProcess { public static void main(String[] args) throws IOException { Process p = Runtime.getRuntime().exec("javac"); try( BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream())) ){ String buff = null; while((buff = br.readLine())!= null) { System.out.println(buff); } } } }
|
下面程序演示了能帮我们运行Java程序的Java程序,以下程序运行后,会产生一个out.txt文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.Scanner;
public class WriteToProcess { public static void main(String[] args) throws IOException { Process p = Runtime.getRuntime().exec("java ReadStandard"); try( PrintStream ps = new PrintStream(p.getOutputStream())){ ps.println("普通字符串"); ps.println(new WriteToProcess()); } catch (Exception e) { e.printStackTrace(); } } }
class ReadStandard{ public static void main(String[] args) { try( Scanner sc = new Scanner(System.in); PrintStream ps = new PrintStream(new FileOutputStream("out.txt")) ) { sc.useDelimiter("\n"); while(sc.hasNext()) { ps.println("键盘输入的内容是:"+sc.next()); } } catch (Exception e) { e.printStackTrace(); } } }
|
RandomAccessFile(Java中功能丰富地读取文件类)
RandomAccessFile是Java中读取文件比较灵活的类,他能够自由定义从哪里开始读取文件,读取到哪里结束。比较特殊的是包含了两个方法来移动我们读取文件的指针
方法 |
描述 |
long getFilePointer() |
返回文件记录指针的当前位置 |
void seek( long pos) |
将文件记录指针定位到 pos 位置。 |
其他使用方法就和流的类似。还有一个特殊的点在于这个类的构造器,其中除了指定文件名以外还有一个参数要指定访问这个文件的mode,使用Linux系统的小伙伴就比较熟悉了。
值 |
描述 |
r |
以只读方式打开指定文件。 如果试图对该RandomAccessFile执行写入方法,都将抛出 IOException异常。 |
rw |
以读、写方式打开指定文件。 如果该文件尚不存在,则尝试创建该文件。 |
rws |
以读、写方式打开指定文件。相对于” rw” 模式,还要求对文件的内容或元数据的每个更新都同步写入 到底层存储设备。(不经常用,主要我也不太懂) |
rwd |
以读、 写方式打开指定文件。相对于” rw” 模式,还要求对文件内容的每个更新都同步写入到底层存储设备。(不经常用,主要我也不太懂) |
下面的程序可以访问文件的特定部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.io.RandomAccessFile;
public class RandomAccessFileTest { public static void main(String[] args) { try( RandomAccessFile raf = new RandomAccessFile("RandomAccessFileTest.java", "r")) { System.out.println("RandomAcessFile的文件指针的初始位置" + raf.getFilePointer()); raf.seek(300); byte[] bbuf = new byte[1024]; int hasRead = 0; while((hasRead = raf.read(bbuf))>0) { System.out.println(new String(bbuf,0,hasRead)); } } catch (Exception e) { e.printStackTrace(); } } }
|
下面的程序示范了如何向指定的文件后追加内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.io.RandomAccessFile;
public class AppendContent { public static void main(String[] args) { try( RandomAccessFile raf = new RandomAccessFile("out.txt","rw")) { raf.seek(raf.length()); raf.write("追加的内容!\n".getBytes()); } catch (Exception e) { e.printStackTrace(); } } }
|
下面程序实现了向指定文件、指定位置插入内容的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile;
public class InsertContent { public static void insert(String fileName,long pos,String insertContent) throws IOException { File tmp = File.createTempFile("tmp", null); tmp.deleteOnExit(); try( RandomAccessFile raf = new RandomAccessFile(fileName, "rw"); FileOutputStream tmpOut = new FileOutputStream(tmp); FileInputStream tmpIn = new FileInputStream(tmp)) { raf.seek(pos); byte[] bbuf = new byte[64]; int hasRead = 0; while((hasRead = raf.read(bbuf))>0) { tmpOut.write(bbuf,0,hasRead); } raf.seek(pos); raf.write(insertContent.getBytes()); while((hasRead = tmpIn.read(bbuf))>0) { raf.write(bbuf,0,hasRead); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { insert("InsertContent.java",45,"插入的内容\r\n"); } }
|
这个类给我们的启示是:我们可以用这个来实现类似于断点续传的问题。具体的示例我可以会放在我多线程的文章中。