java.io
通过数据流、序列化和文件系统提供系统输入和输出。
我有一个习惯,在学习新的知识体系时,总是先将它的类层次结构搞清楚,这样可以对类有一个全局观。下面先看一下 java IO 系统的类关系图
字符流:
字节流:
一、File 类
File 类是文件和目录路径的抽象表示形式。File 类定义了一些与平台无关的方法来操纵文件,例如:创建、删除和重命名文件。
编程举例:判断某个文件是否存在,存在则删除,不存在则创建。
public class Demo { public static void main(String[] args) throws Exception { File file = new File("C://Users//Administrator//Desktop//File.txt"); if (file.exists()) file.delete(); else file.createNewFile(); System.out.println("文件名:" + file.getName()); System.out.println("相对路径:" + file.getPath()); System.out.println("绝对路径:" + file.getAbsolutePath()); System.out.println("父目录:" + file.getParent()); System.out.println("是否存在:" + (file.exists() ? "存在" : "不存在")); System.out.println("是否可读:" + (file.canRead() ? "可读" : "不可读")); System.out.println("是否为目录:" + (file.isDirectory() ? "是目录" : "不是目录")); System.out.println("上次修改时间:" + (new Date(file.lastModified()))); } } // 文件名:File.txt // 相对路径:C:/Users/Administrator/Desktop/File.txt // 绝对路径:C:/Users/Administrator/Desktop/File.txt // 父目录:C:/Users/Administrator/Desktop // 是否存在:存在 // 是否可读:可读 // 是否为目录:不是目录 // 上次修改时间:2011-06-25 // 文件名:File.txt // 相对路径:C:/Users/Administrator/Desktop/File.txt // 绝对路径:C:/Users/Administrator/Desktop/File.txt // 父目录:C:/Users/Administrator/Desktop // 是否存在:不存在 // 是否可读:不可读 // 是否为目录:不是目录 // 上次修改时间:1970-01-01
二、流
流是字节序列的抽象概念。文件是数据的静态存储形式,而流是指数据传出时的形态。
三、字节流
1、InputStream 类
程序可以冲中连续读取字节的对象叫输入流,在 Java 中,用 InputStream 类来描述所有输入流的抽象概念。
InputStream 类的方法:
InputStream 是抽象类,实际使用到的是 InputStream 的子类,但并不是所有的子类对象都具有上面的方法 。
(1)FileInputStream 用来创建磁盘文件的输入流对象,通过 FileInputStream 的构造函数来指定文件路径和文件名。创建 FileInputStream 实例对象时,指定的文件应该是存在的和可读的。对于一个磁盘文件创建 FileInputStream 对象的两种方式:
1、FileInputStream in = new FileInputStream("fileName");
2、File f = new File("fileName");
FileInputStream in = new FileInputStream(f);
编程举例:用 FileOutputStream 类向文件中写入一个字符串,然后用 FileInputStream 读出写入的内容。
public class Demo { public static void main(String[] args) throws Exception { String writeWords = "DriverKing_斌"; byte[] writeBytes = writeWords.getBytes("gb2312"); FileOutputStream fos = new FileOutputStream( "C://Users//Administrator//Desktop//字节流.txt"); fos.write(writeBytes); fos.close(); FileInputStream fis = new FileInputStream( "C://Users//Administrator//Desktop//字节流.txt"); byte[] readBytes = new byte[1024]; int len = 0; len = fis.read(readBytes); String readWords = new String(readBytes, 0, len, "gb2312"); System.out.println(readWords); fis.close(); } } // 输出:DriverKing_斌
(2)PipedInputStream 和 PipedInputStream 用于在应用程序中创建管道通信。一个 PipedInputStream 对象必须与一个 PipedOutputStream 对象进行连接产生一个通信管道。PipedOutoutStream 对象负责向管道中写入数据, PipedInputStream 对象负责从管道中读取数据。主要完成线程之间的通信。一个线程的 PipedInputStream 能从另一个线程的 PipedOutputStream 对象中读取数据。
编程举例:实现两个线程之间的通信
public class Demo { public static void main(String[] args) { Demo demo = new Demo(); Send send = demo.new Send(); Receive receive = demo.new Receive(); PipedOutputStream pOut = send.getPipedInputStream(); PipedInputStream pIn = receive.getPipedInputStream(); send.start(); receive.start(); try { pOut.connect(pIn); } catch (IOException e) { e.printStackTrace(); } } class Send extends Thread { private PipedOutputStream pOut = new PipedOutputStream(); public PipedOutputStream getPipedInputStream() { return this.pOut; } @Override public void run() { try { pOut.write(new String("DriverKing_斌").getBytes()); } catch (IOException e) { e.printStackTrace(); } } } class Receive extends Thread { private PipedInputStream pIn = new PipedInputStream(); public PipedInputStream getPipedInputStream() { return this.pIn; } @Override public void run() { byte[] bytes = new byte[1024]; int len = 0; try { len = pIn.read(bytes); } catch (IOException e) { e.printStackTrace(); } System.out.println("接收了:" + new String(bytes, 0, len)); } } } // 输出:接收了:DriverKing_斌
使用管道流类,可以实现各个程序模块之间的松耦合通道。
(3)ByteArrayInputStream 用于以 IO 流的方式来完成对字节数组内容读写,来支持类似内存虚拟文件或者内存映像文件的功能。
构造函数:
1、ByteArrayInputStream(byte[] b)
2、ByteArrayInputStream(byte[] b, int offset, int length)
编程举例:编写一个函数,把输入六中所有英文字母编程大写字母后,将结果写入带一个输出流对象,用这个函数来将一个字符串中的所有字符转换成大写。
public class Demo { public static void main(String[] args) throws Exception { String words = "werkyhksdbnwkejryskafjksdf"; byte[] bytes = words.getBytes(); ByteArrayInputStream in = new ByteArrayInputStream(bytes); ByteArrayOutputStream out = new ByteArrayOutputStream(); toUpper(in, out); System.out.println(out.toString()); out.close(); in.close(); } public static void toUpper(InputStream in, OutputStream out) throws Exception { int ch = 0; while ((ch = in.read()) != -1) { out.write(Character.toUpperCase(ch)); } } } // 输出:WERKYHKSDBNWKEJRYSKAFJKSDF
(4)System.in 和 System.out System.in 连接到键盘,是 InputStream 类型的实例对象。System.out 连接到显示器,是 PrintStream 类的实例对象。
编程举例:借助上一个杉树,将键盘上输入的内容转为大写字母后打印在屏幕上。
public class Demo { public static void main(String[] args) throws Exception { toUpper(System.in, System.out); } public static void toUpper(InputStream in, OutputStream out) throws Exception { int ch = 0; while ((ch = in.read()) != -1) { out.write(Character.toUpperCase(ch)); } } }
(5)DataInputStream 并没有对应到任何具体的流设备,一定要给它传递一个对应具体流设备的输入流对象。DataInputStream 为包装类。构造 DataInputStream 的方法:
DataInputStream(InputStream in)
DateInputStream 提供了一个读取字符串的方法:
1、public final String readUTF();
DateOutputStream 提供了三个写入字符串的方法:
1、public final void writeBytes(String s);
2、public final void writeChars(String s);
3、public fianl void writeUTF(String s);
方法:
boolean |
readBoolean () 参见 DataInput 的 readBoolean 方法的常规协定。 |
byte |
readByte () 参见 DataInput 的 readByte 方法的常规协定。 |
char |
readChar () 参见 DataInput 的 readChar 方法的常规协定。 |
double |
readDouble () 参见 DataInput 的 readDouble 方法的常规协定。 |
float |
readFloat () 参见 DataInput 的 readFloat 方法的常规协定。 |
int |
readInt () 参见 DataInput 的 readInt 方法的常规协定。 |
long |
readLong () 参见 DataInput 的 readLong 方法的常规协定。 |
short |
readShort () 参见 DataInput 的 readShort 方法的常规协定。 |
(6)BufferedInputStream Java 提供的两个缓冲区包装类,不管底层系统是否使用了缓冲区,这两个类在自己的实例对象中创建缓冲区。依然是用 InputStream 类的实例来构造自己。
流栈的包装过程:
编程实例:分别使用 DataOutputStream 类的 writeUTF、writeBytes、writeChars 方法,比较这几个方法的差异。
public class Demo { public static void main(String[] args) throws Exception { FileOutputStream fo = new FileOutputStream("C://Users//Administrator//Desktop//a.txt"); BufferedOutputStream bos = new BufferedOutputStream(fo); DataOutputStream dos = new DataOutputStream(bos); dos.writeUTF("DriverKing_斌"); dos.writeBytes("DriverKing_斌"); dos.writeChars("DriverKing_斌"); dos.close(); FileInputStream fs = new FileInputStream("C://Users//Administrator//Desktop//a.txt"); BufferedInputStream bs = new BufferedInputStream(fs); DataInputStream ds = new DataInputStream(bs); System.out.println(ds.readUTF()); byte[] bytes = new byte[1024]; int len = ds.read(bytes); System.out.println(new String(bytes,0,len)); ds.close(); } }
(7)ObjectInputStream ObjectInputSteam 包装类用于从底层输入流中读取对象类型的数据和将对象类型的数据写入到底层输出流。只要把一个对象中的所有成员变量存储起来就等于保存了这个对象。ObjectInputStream 所要写的对象必须实现了 Serializable 接口,Serializable 接口中没有方法,是一个空的接口,它的作用就是为编译器做标记。对象中的 transient(临时变量标记,例如线程对象可以被 transient 修饰,因为没有保存的必要)和 static 类型的成员变量不会被写入。
编程举例:创建一个可序列化的学生对i向,并用 ObjectOutputStream 类把它存储到一个文件中,然后再用 ObjectInputStream 类把存储的数据读取到一个学生对象中,即反序列化。
public class Demo { public static void main(String[] args) throws Exception { Student s1 = new Student("张三", 23); Student s2 = new Student("DriverKing", 22); //写 FileOutputStream fos = new FileOutputStream("C://Users//Administrator//Desktop//Stu.data"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(s1); os.writeObject(s2); os.close(); //读 FileInputStream fis = new FileInputStream("C://Users//Administrator//Desktop//Stu.data"); ObjectInputStream ois = new ObjectInputStream(fis); Student s_1 = (Student)ois.readObject(); Student s_2 = (Student)ois.readObject(); ois.close(); s_1.sayHi(); s_2.sayHi(); } } class Student implements Serializable { public Student(String name, int age) { this.setName(name); this.setAge(age); } private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void sayHi() { System.out.println("姓名:" + this.getName() + "年龄:" + this.getAge()); } }
2、OutputStream 类
程序可以向其中连续写入字节的对象叫输出流,在 Java 中,用 OutputStream 类来描述所有输出流的抽象概念。
OutputStream 类的方法:
(1)FileOutputStream 用来创建磁盘文件的输出流对象,通过 FileOutputStream 的构造函数来制定文件路径和文件名。 创建 FileOutputStream 实例对象时,指定的文件应该是存在的和可写的,并且没有被其它应用程序占用的。对于一个磁盘文件创建 FileOutputStream 对象有以下方式:
1、 FileOutputStream out = new FileOutputStream ("fileName");
2、 FileOutputStream out = new FileOutputStream ("fileName", true); //是否追加
3、 File f = new File("fileName");
FileOutputStream out = new FileOutputStream (f);
FileOutputStream out = new FileOutputStream (f ,true);//是否追加
内存缓冲区的作用: 在应用程序和 I/O 设备之间,通常会有内存缓冲区。这是由于计算机访问外部设备的速度,要比直接访问内存的速度慢得多,如果在应用程序中的每一次 write 方法中都直接将数据写入到外部设备中,cpu 就要花费更多的时间等待数据写完。如果在程序中开辟了一个内存缓冲区,程序的每一次 write 方法的调用都是先将数据写入到缓冲区中,只有缓冲区被填满后,cpu 踩会将缓冲区的内容一次性的写入到外部设备中。使用内存缓冲区有两个方面的好处:1、提高了 cpu 的使用率。2、 write 方法并没有真正写入到外部设备,程序还有机会撤销操作。对于输入流也可以使用内存缓冲区,可以将外部大量数据读取到内存缓冲区中,然后再从内存缓冲区中读 取到应用程序中来。
(2)ByteArrayOutStream 参照 ByteArrayInputStream。
(3)DataOutputStream 参照 DataInputStream。
(4)BufferedOutputStream 参照 BufferedInputStream。
(8)ObjectOutputStream 参照 ObjectInputStream。
四、字符流
1、Reader 类
用于读取字符流的抽象类,读取文本格式的内容。Java 中的字符是采用 Unicode 编码的,是双字节的。而字节流中要读取字符串时,我们需要将它转换为字节后再读取数据。Java 中提供了单独的字符流类。
二进制文件与文本文件的区别: 在不考虑正负数的情况下,每个字节中的数据在0~255之间 ,它们在内存中都是以 二进制的形式存在。文件就是内存中的数据复制到硬盘上的存储形式,文件中的每个字节的数据也都是二进制形式的,所以严格的说磁盘上的每个文件都是二进制文 件。各种文本字符都是由一个或多个字节组成的,但是组成这些字符的字节不能是0~255之间的任意数,而是特定的数,有些数是不能存在于任何字符中的。如 果一个文件中所有每相邻的字节数据都可以表示成一个字符,那我们就称这个文件为文本文件。文本文件只是二进制文件的一种特例,为了与文本文件区别,人们又 把除了文本文件以外的文件称为二进制文件。
(1)FileReader 用来读取字符文件的便捷类。
(2)InputStreamReader 用来读取磁盘数据的便捷类。是字节流通向字符流的桥梁
编程举例: 用 FileWriter 类向文件中写入一个字符串,然后用 FileReader 独处写入的内容。
public class Demo { public static void main(String[] args) throws Exception { FileWriter fw = new FileWriter( "C://Users//Administrator//Desktop//字符流.txt"); fw.write("DriverKing_斌"); fw.close();// 如果不调用close,则缓冲区内容没有写到文件中 FileReader fr = new FileReader( "C://Users//Administrator//Desktop//字符流.txt"); char[] ch = new char[1024]; int len = 0; len = fr.read(ch); System.out.println(new String(ch, 0, len)); fr.close(); } } // 输出:DriverKing_斌
(3)PipedReader 和 PipedWriter 对照 PipedInputStream 和 PipedOutputStream
public class Demo { public static void main(String[] args) { Demo demo = new Demo(); Send send = demo.new Send(); Receive receive = demo.new Receive(); PipedWriter pw = send.getPipedWriter(); PipedReader pr = receive.getPipedReader(); try { pw.connect(pr); } catch (IOException e) { e.printStackTrace(); } send.start(); receive.start(); } class Send extends Thread { private PipedWriter pw = new PipedWriter(); public PipedWriter getPipedWriter() { return this.pw; } @Override public void run() { try { pw.write("DriverKing_斌"); } catch (IOException e) { e.printStackTrace(); } } } class Receive extends Thread { private PipedReader pr = new PipedReader(); public PipedReader getPipedReader() { return this.pr; } @Override public void run() { char[] ch = new char[1024]; int len = 0; try { len = pr.read(ch); } catch (IOException e) { e.printStackTrace(); } System.out.println("接收了:" + new String(ch, 0, len)); } } } // 输出:接收了:DriverKing_斌
(4)StringReader 以字符 IO 流的方式处理字符串
(5)BufferedReader 参照 BufferedInputStream。
2、Writer 类
用于写入字符流的抽象类,写入文本格式的内容。
(1)FileWriter 用来写入字符文件的便捷类。
(2)OutStreamWriter 用来写入磁盘数据的便捷类。是字节流通向字符流的桥梁
(3)StingWriter 参照 StringReader。
(4)BufferedWriter 参照 BufferedOutputtStream。
五、字节流与字符流之间的转换
public class Demo { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println(br.readLine()); br.close(); } }
InputStreamReader 和 OutputStreamWriter ,是用于将字节流转换成字符流的两个类, InpuStreamReader 可以将一个字节流中的字节解码成字符后读取,OutStreamWriter 将字符解码成字节后写入到一个字节流中。为了避免繁琐的在字节与字符之间转换,最好不要直接使用 InputStreamReader 和 OutputStreamWriter ,应该尽量使用 BufferedWriter 和 BufferedReader 来包装它们。
六、Java 程序与其它进程的数据通信
在 Java 中可以使用 Process 类的实例对象来表示子进程,子进程的标准输入和输出不再连接到键盘和显示器,而是以管道流的形式连接到父进程的一个输出流和输入流对象上。调用 Process 类的 getOutputStream 和 getInputStream 方法可以获得连接到子进程的输出流和输入流对象。
public class Demo implements Runnable { private Process p = null; public Demo() throws IOException { p = Runtime.getRuntime().exec("java ChildProcess"); new Thread(this).start(); } public static void main(String[] args) throws IOException { Demo demo = new Demo(); demo.send(); } public void send() throws IOException { OutputStream os = p.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); while (true) { bw.write("DriverKing_斌"); //System.out.println("发送:DriverKing_斌"); } } public void run() { InputStream in = p.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); while (true) { try { String getMsg = br.readLine(); System.out.println("返回: " + getMsg); } catch (IOException e) { e.printStackTrace(); } } } }
public class ChildProcess { public static void main(String[] args) throws IOException { while (true) { System.out.print(("接收:" + new BufferedReader(new InputStreamReader( System.in)).readLine())); } } }
七、特殊的 RandomAccessFile 类
RandomAccessFile 类提供了众多的文件访问方法。 RandomAccessFile 类支持随机访问方式,也就是可以跳转到文件的任意位置开始读写数据。 RandomAccessFile 实例对象中有个指示器,它可以跳转到文件任意位置 。 RandomAccessFile 读写操作都是从指示器所指示的当前位置开始读写,当读写 N 个字节以后,文件指示器将指向 N 个字节后的下一个字节处。 RandomAccessFile 类在随机(相对于顺序读取而言)读写等长记录格式的文件时有恨大的优势。 RandomAccessFile 类仅限于操作文件,不能访问其它的 IO 设备。
RandomAccessFile 类两种构造方法:
1、new RandomAccessFile(file,"rw"); //读写方式
2、new RandomAccessFile(file,"r"); //只读方式
八、总结
字节流输入流(InputStream)
FileInputStream、 ObjectInputStream、 PipedInputStream、 ByteArrayInputStream、 DataInputStream、 BufferedInputStream、 FilterInputStream
字节输出流(OutputStream)
FileOutputStream、ObjectOutputStream、PipedOutputStream、ByteArrayOutputStream、DataOutputStream、BufferedOutputStream、FilterOutputStream、PrintStream
字符输入流(Reader)
FileReader、CharArrayReader、StringReader、PipedReader、FilterReader、BufferedReader、InputStreamReader
字符输出流(Writer)
FileWriter、 CharArrayWriter、 StringWriter、 PipedWriter、 FilterWriter、 BufferedWirter、 OutputStreamReader