认识IO(输入输出流)
在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流....流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。
Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均由它们派生出来的。
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; public class InputStreamTest { /** * @param args */ public static void main(String[] args) { String fileName = "D:/newTemp.txt"; InputStreamTest.readFileByBytes(fileName); InputStreamTest.readFileByChars(fileName); } /** * 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。 * * @param fileName */ public static void readFileByBytes(String fileName) { File file = new File(fileName); InputStream in = null; try { System.out.println("以字节为单位读取文件内容,一次读一个字节:"); // 一次读一个字节 in = new FileInputStream(file); int tempbyte; while ((tempbyte = in.read()) != -1) { System.out.write(tempbyte); } // 关闭文件输入流 in.close(); } catch (IOException e) { e.printStackTrace(); } try { System.out.println("以字节为单位读取文件内容,一次读多个字节:"); // 一次读多个字节,创建一个长度为1024的字节数组来存取 byte[] tempbytes = new byte[1024]; // 用于保存实际读取的字节数 int byteread = 0; in = new FileInputStream(fileName); // 读入多个字节到字节数组中,byteread为一次读入的字节数 // 使用循环来进行重复读取 while ((byteread = in.read(tempbytes)) != -1) { System.out.write(tempbytes, 0, byteread); } } catch (Exception e) { e.printStackTrace(); } finally { if (in != null) { try { // 关闭文件输入流 in.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 以字符为单位读取文件,常用于读文本,数字等类型的文件 * * @param fileName */ public static void readFileByChars(String fileName) { File file = new File(fileName); Reader reader = null; try { System.out.println("以字符为单位读取文件内容,一次读一个字节:"); // 一次读一个字符 reader = new InputStreamReader(new FileInputStream(file), "gbk"); int tempchar; while ((tempchar = reader.read()) != -1) { // 对于windows下,/r/n这两个字符在一起时,表示一个换行。 // 但如果这两个字符分开显示时,会换两次行。 // 因此,屏蔽掉/r,或者屏蔽/n。否则,将会多出很多空行。 if (((char) tempchar) != '\r') { System.out.print((char) tempchar); } } // 关闭文件输入流 reader.close(); } catch (Exception e) { e.printStackTrace(); } try { System.out.println("以字符为单位读取文件内容,一次读多个字节:"); // 一次读多个字符 char[] tempchars = new char[30]; int charread = 0; reader = new InputStreamReader(new FileInputStream(fileName), "gbk"); // 读入多个字符到字符数组中,charread为一次读取字符数 while ((charread = reader.read(tempchars)) != -1) { System.out.println(new String(tempchars, 0, charread)); } } catch (Exception e1) { e1.printStackTrace(); } finally { if (reader != null) { try { // 关闭文件输入流 reader.close(); } catch (IOException e1) { } } } } }
上面要注意一个问题:字节流是根据字节来读取的,而一个中文是占两个字节的,如果包含很多中文的文件被字节流分多次进行读取,可能会造成乱码,因为有可能会导致刚好将一个中文分两次读取,这样就会乱码了,因此如果中文包含多的话还是使用字符流比较好。
在字节流与字符流之间选择的规律:如果需要进行输入/输出的内容是文本内容,则应该考虑使用字符流,如果需要进行输入/输出的是二进制内容,则应该考虑使用字节流,因为字节流的功能比字符流强大,计算机中所有的数据都是二进制的,而字节流可以处理所有的二进制文件。
与输入流一样,下面是输出流的演示:
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; public class OutputStreamTest { /** * @param args */ public static void main(String[] args) { OutputStreamTest.writeFileByBytes(); OutputStreamTest.writeFileByChars(); } /** * 以字节为单位输出 */ private static void writeFileByBytes() { // 创建字节输入流 FileInputStream fis = null; // 创建字节输出流 FileOutputStream fos = null; try { fis = new FileInputStream("D:/newTemp.txt"); fos = new FileOutputStream("D:/newTemp2.txt"); // 一次读多个字节,创建一个长度为40的字节数组来存取 byte[] bytes = new byte[40]; // 用于保存实际读取的字节数 int hasRead = 0; // 循环从输入流中读取数据 while ((hasRead = fis.read(bytes)) != -1) { // 每读取一个,即写入文件输出流,读了多少就写多少 fos.write(bytes, 0, hasRead); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭输入输出流 if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 以字符为单位输出 */ public static void writeFileByChars() { // 创建字符输出 FileWriter fw = null; try { fw = new FileWriter("D:/newTemp3.txt"); fw.write("Hello world!\r\n"); fw.write("JAVA"); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭输出流 if (fw != null) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } // 同样可以这样 // 创建字符输出 OutputStreamWriter osw = null; try { //创建一个节点输出流 FileOutputStream fos = new FileOutputStream("D:/newTemp4.txt"); //以OutputStreamWriter处理流来包装FileOutputStream节点流 osw = new OutputStreamWriter(fos); osw.write("Hello world!\r\n"); osw.write("Hello world!\r\n"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭输出流 if (osw != null) { try { osw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
上面出现了节点流与处理流,区分节点流于处理流的方法是:只要流的构造器的参数不是一个物理节点,而是已存在的流,那这个流一定是处理流,因为所有的节点流都是直接以物理io节点作为构造器的参数。
将节点流封装成处理流很简单,只需调用处理流的构造方法来传入节点流就可以了;而且看到上面流的关闭只是关闭了处理流而未去关闭节点流,这样做是完全正确的,以后我们在关闭流的时候只需要关闭最上层的处理流即可。