首先需要明确的一点是输入流输出流的输入输出是站在内存的角度看的,读取文件,把文件内容写到内存中,是输入流;写文件,把内存中的数据写到文件中,是输出流。
IO操作主要有4个抽象类:
字节输入输出流:InputStream、OutputStream,操作的是字节byte。
字符输入输出流:Reader、Writer,操作的是字符char。
最常用的子类有FileInputStream 、FileOutputStream、InputStreamReader、OutputStreamWriter、FileReader、FileWriter、BufferedReader、BufferedWriter
FileInputStream是InputStream的直接子类,其最常用的构造器是FileInputStream(File file),参数传一个File对象,即获得了此File对象对应的字节输入流,常用的方法是:int read(byte b[]);从字节输入流中读取数据到字节数组中,在读的过程中会阻塞线程,返回值是实际读取到的字节的长度,如果什么都没读到,会返回-1。示例:
public static void main(String[] args) { File file = new File("D:/tmp.txt"); InputStream input = null; try { input = new FileInputStream(file);
//每次读1024个字节 byte[] b = new byte[1024]; int len = -1; while ((len = input.read(b)) != -1) { System.out.println(len); String info = new String(b, 0, len); System.out.println(info); } } catch (Exception e) { e.printStackTrace(); } finally { if (input != null) { try { input.close(); } catch (Exception e) { e.printStackTrace(); } } } }
需要注意的是,流资源是很昂贵的资源,用完之后要关闭。通常在finally块中关闭。
FileOutputStream是OutputStream的直接子类,其最常用的构造器有两个,用于得到一个File对象对应的字节输出流:
FileOutputStream(File file);只需传一个File对象
FileOutputStream(File file, boolean append);不仅要传一个File对象,还需要传一个布尔值,如果这个布尔值是true的话,则会在原文件中追加内容,如果是false的话,效果等同于第一个构造器,即会覆盖原文件
字节输出流常用的方法是write(byte[] b);把b字节数组所有的字节写到输出流中。示例:
public static void main(String[] args) { File file = new File("D:/tmp.txt"); OutputStream output = null; try { output = new FileOutputStream(file); output.write("welcome to u".getBytes()); } catch (Exception e) { e.printStackTrace(); } finally { if (output!= null) { try { output.close(); } catch (Exception e) { e.printStackTrace(); } } } }
值得注意的是,字节输出流write完之后不用调flush方法去手动刷缓存区,实测,只要执行完write()方法之后,文件中就会有内容。当然最终还是得调用close方法关闭流,免得资源浪费。
如果想在文件中输入换行,可以用String separator = System.getProperty("line.separator");拿到换行符,在不同的系统中换行符是不一样的,所以不能写死,必须得通过这种方式得到。得到换行符之后,想换行就简单了,调用字节输出流的write(separator.getBytes())方法就好了。示例:
public static void main(String[] args) { String separator = System.getProperty("line.separator"); File file = new File("D:/tmp.txt"); OutputStream output = null; try { output = new FileOutputStream(file, true); output.write((separator + "thank u").getBytes()); } catch (Exception e) { e.printStackTrace(); } finally { if (output != null) { try { output.close(); } catch (Exception e) { e.printStackTrace(); } } } }
InputStreamReader是Reader的直接子类,是字节输入流转变成字符输入流的桥梁,也被称为转换流。主要用到的构造器有两个:
InputStreamReader(InputStream in);传入一个InputStream对象,使用当前IDE workspace的编码格式
InputStreamReader(InputStream in, String charsetName);传入一个InputStream对象和一个指定编码集的字符串。建议使用这种方式,尽量让结果与IDE的设置无关,这样代码具有可移植性。
最常用的方法还是 int read(char cbuf[]);不过注意的是,这里操作的是字符数组了,从字符输入流中读取数据到字符数组中,在读的过程中会阻塞线程。
示例:
public static void main(String[] args) { InputStream input = null; InputStreamReader inputStreamReader = null; try { input = new FileInputStream(new File("/tmp.txt")); inputStreamReader = new InputStreamReader(input, "GBK"); char[] cbuf = new char[10240]; int len = -1; while ((len = inputStreamReader.read(cbuf)) != -1) { System.out.println(len); String str = new String(cbuf, 0, len); System.out.println(str); } } catch (Exception e) { e.printStackTrace(); } finally { if (inputStreamReader != null) { try { inputStreamReader.close(); } catch (Exception e) { e.printStackTrace(); } } } }
在简体中文windows系统上创建文本文件,打开,另存为时发现编码是ANSI,ANSI在简体中文windows系统上就是GBK,在繁体中文Windows系统上是Big5。
上例中创建InputStreamReader实例时用了InputStreamReader(InputStream in, String charsetName)构造器,指定读取文件时用GBK解码(数据在计算机中存储的是二进制,按照GBK规则解码成我们能理解的字符)。如果不指定GBK,则会因为我们一般把IDE的workspace的编码设为UTF-8而乱码。(实测)
OutputStreamWriter是Writer的直接子类,是字节输出流转变成字符输出流的桥梁,也称为转换流。常用的构造器也有两个:
OutputStreamWriter(OutputStream out);
OutputStreamWriter(OutputStream out, String charsetName);推荐用这种,在创建时指定编码格式,否则会用IDE workspace的编码格式。
常用的方法是:
write(char cbuf[], int off, int len);用于从文件中读取数据并保存到另一文件的情况
write(String str, int off, int len);用于想把一个字符串保存到一个文件的情况
flush();把数据从缓冲区中刷新到指定文件中。不管是用的write()方法的哪种重载,执行完之后,数据是在缓冲区中(在内存中),需要把缓冲区中的数据手动刷到指定目的地。close()方法内部也调用了flush()方法,如果用close()刷数据的话,刷之后会关闭流,流关闭后再write()就会报错了,所以根据实际需求选择合适的刷新方法。其实,最好不要在最后才刷缓冲区,因为如果数据很大的话,不刷缓冲区会占用很多内存(在内存存储者那些数据),即时不报内存溢出错误也是属于很严重的资源浪费。
示例:
public static void main(String[] args) { OutputStreamWriter writer = null; InputStreamReader reader = null; try { writer = new OutputStreamWriter(new FileOutputStream(new File("/tmp.txt")), "UTF-8"); reader = new InputStreamReader(new FileInputStream(new File("/1.txt")), "GBK"); char[] cbuf = new char[1024]; int len = -1; while ((len = reader.read(cbuf)) != -1) { writer.write(cbuf, 0, len); writer.flush(); } writer.write("你好吗?"); writer.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (Exception e) { e.printStackTrace(); } } if (writer != null) { try { writer.close(); } catch (Exception e) { e.printStackTrace(); } } } }
上例中,1.txt是在windows系统下建的一个文件文件,随便写点内容。上例代码是将1.txt的内容复制到tmp.txt文件中,并在后面追加了一些内容。
FileReader是InputStreamReader的子类,与InputStreamReader只有一点不同,那就是固定了编码为当前IDE workspace的编码集,改不了。不推荐使用。同理,FileWriter也不推荐使用。
public static void main(String[] args) { FileReader reader = null; FileWriter writer = null; try { reader = new FileReader(new File("/1.txt")); writer = new FileWriter(new File("/2.txt")); char[] cbuf = new char[1024]; int len = -1; while ((len = reader.read(cbuf)) != -1) { writer.write(cbuf, 0, len); writer.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (Exception e) { e.printStackTrace(); } } if (writer != null) { try { writer.close(); } catch (Exception e) { e.printStackTrace(); } } } }
上例中用了FileReader、FileWriter,复制1.txt的内容到2.txt文件中。1.txt是在简体中文windows系统上创建的一个文本文件,GBK编码,上面代码运行完后,查看2.txt的内容,不会乱码。但是如果把1.txt另存为UTF-8编码的,再运行上面代码,2.txt内容会乱码,只要不改变IDE的workspace的编码,问题就解决不了。所以还是推荐使用InputStreamReader、OutputStreamWriter。
如果使用字节流的话,没有必要使用缓冲流(对应的缓冲流有BufferedInputStream、BufferedOutputStream)。使用字符流时可以使用缓冲字符流BufferedReader、BufferedWriter,其中BufferedReader的地位同InputStreamReader一样,也是Reader的直接子类,BufferedWriter的地位同OutputStreamWriter一样,也是Writer的直接子类。
BufferedReader常用的构造器是BufferedReader(Reader in);创建一个默认大小(8192字符)缓冲区的缓冲字符输入流对象,需要传入一个Reader对象,这个Reader对象通常是一个InputStreamReader实例。
BufferedReader对象常用的方法除了read()的各种重载外,最重要的是多了一个String readLine()方法,用来读取一行,返回值是这一行的数据,如果读到文件尾了,会返回null。其实,如果不逐行读取的话,也没必要用缓冲字符流,用转换流就足够了。
BufferedWriter常用的构造器是BufferedWriter(Writer out);创建一个默认大小(8192字符)缓冲区的缓冲字符输出流对象,需要传入一个Writer对象,这个Writer对象通常是一个InputStreamWriter实例。
BufferedWriter对象常用的方法除了write()的各种重载外,最重要的是多了一个newLine()方法,用于输出一个换行,相当于调用字节输出流对象的write(System.getProperty("line.separator").getBytes())方法或者调用字符输出流对象的write(System.getProperty("line.separator"))方法。
缓冲流用法示例:
public static void main(String[] args) { BufferedReader reader = null; BufferedWriter writer = null; String str = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("/1.txt")), "GBK")); writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/2.txt"), "UTF-8")); while ((str = reader.readLine()) != null) { writer.write(str); writer.newLine(); writer.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (Exception e) { e.printStackTrace(); } } if (writer != null) { try { writer.close(); } catch (Exception e) { e.printStackTrace(); } } } }
上例中,逐行读取文件,并逐行写到新文件,且原文件是GBK编码的,新文件是UTF-8编码的。