数据传输操作,可以看作一种数据的流动,按照流动的方向分为输入Input和输出Output
Java中的IO操作主要指的是java.io下的一些常用类的使用,通过这些类对数据进行读取(输入Input)和写入(输出Output)
按照流的方向分类:输入流和输出流
按照流动的数据类型,可以分为:字节流和字符流
字节流:
字符流:
一切皆字节,计算机中的任何数据,文本,图片,视频等都是以二进制存储的,8个二进制位为一个字节。在数据传输时也都是以二进制的形式存储的。
任何流在传输时底层都是二进制
close()
关闭此输出流并释放与此流关联的所有系统资源,一定要关闭
flush()
刷新此输出流并强制写出任何缓冲的输出字节
nullOutputStream()
返回一个新的输出流,丢弃所有字节
write(byte[] b)
将字节数组写入输出流
write(byte[] b, int off, int len)
将从偏移量off开始的指定字节数组中的len长度写入此输入法
write(int b)
将指定的字节写入此输出流(虽然传入的是int,但写入的是低8位(范围为0~255))
构造器 | 描述 |
---|---|
FileOutputStream(File file) | 创建文件输出流以写入由指定的 File对象表示的文件。 |
FileOutputStream(FileDescriptor fdObj) | 创建要写入指定文件描述符的文件输出流,该文件描述符表示与文件系统中实际文件的现有连接。 |
FileOutputStream(File file, boolean append) | 创建文件输出流以写入由指定的 File对象表示的文件。append为true指的是在原文件末尾加入,为false指的是删去原文,重新写入 |
FileOutputStream(String name) | 创建文件输出流以写入具有指定名称的文件。 |
FileOutputStream(String name, boolean append) | 创建文件输出流以写入具有指定名称的文件。append为true指的是在原文件末尾加入,为false指的是删去原文,重新写入 |
//写入的对象为fos.txt,如果计算机中没有该文件则创建该文件 FileOutputStream fos = new FileOutputStream("G:/test/fos.txt"); //写入65,即'A' fos.write(65); //关闭输出流,且关闭后无法再写入 fos.close(); System.out.println("已经写入"); //成功在fos.txt中写入A
此时重新创建该流,并写入字节数组。由于上面的构造函数没有指定append为true,重新创建输出流会覆盖原文件的内容,运行后文件内容为"ABCDEF"。
FileOutputStream fos = new FileOutputStream("G:/test/fos.txt"); byte[] bytes = {65, 66, 67, 68, 69, 70}; //也可以采用字符串转字节数组的方式:byte[] bytes = "ABCDEF".getBytes(); fos.write(bytes); fos.close(); System.out.println("已经写出");
需要注意的是,上面重写文件内容只会出现在重建输出流时,如果在一个输出流关闭之前多次写入,还是会追加的。如下面程序运行后 fos.txt 内容为"ABCDEFABCDEF"。
FileOutputStream fos = new FileOutputStream("G:/test/fos.txt"); byte[] bytes = {65, 66, 67, 68, 69, 70}; fos.write(bytes); byte[] bytes1 = {65, 66, 67, 68, 69, 70}; fos.write(bytes1); fos.close();
write()
还可以对任意范围的字符串写入
byte[] bytes = "ABCDEF".getBytes(); fos.write(bytes, 1, 2); //从位置1索引开始,写入2个字节(“BC”)
将硬盘中的文件中的内容读取到内存中
读方法 | 描述 |
---|---|
abstract int read() | 从输入流中读取下一个数据字节。只返回一个字节,所以int返回值为0~255,如果读取的字节为空,返回-1 |
int read(byte[] b) | 从输入流中读取一些字节数并将它们存储到缓冲区数组b。返回值为被读取的字节数,0为没读取字节,-1为文章末尾没有可用字节。 |
int read(byte[] b, int off, int len) | 从输入流 len最多 len字节的数据读入一个字节数组。 |
read()单个字节读取
FileInputStream fis = new FileInputStream("g:/test/a.txt"); while(true){ byte b = (byte) fis.read(); if(b == -1) break; System.out.println((char)b); }
read(byte[] b) 分组读取
byte[] bytes = new byte[10]; fis.read(bytes); System.out.println(new String(bytes)); fis.read(bytes); System.out.println(new String(bytes)); fis.read(bytes); System.out.println(new String(bytes)); fis.close(); // abcdefghij // klmnopqrst // uvwxyzqrst //最后一行出现问题,应该只输出6个但是却输出了10个,这是由于只有前6个字节被新字节覆盖 byte[] bytes = new byte[10]; int len = fis.read(bytes); System.out.println(new String(bytes, 0, len)); len = fis.read(bytes); System.out.println(new String(bytes, 0, len)); len = fis.read(bytes); System.out.println(new String(bytes, 0, len)); fis.close(); //使用read的返回值可以得知有效读取的字节数目,进而只输出有效内容
通常使用一组输入的读取方式,减少IO调用的频率
但是汉字如何写入?
如果仍然以上的方法会出现乱码,因为汉字采用的时UTF-8编码,编码的长度是可变的,因此限制每组读取的长度可能会发现读取一个字的部分编码的情况。
解决方法:
以字符为单位避免上面汉字读写出现乱码的情况,但字符流只能操作文字,而字节流能操作任何文件,因此应用较少。
FileWriter fw = new FileWriter("g:/test/a.txt", true); fw.write('b'); fw.write("锄禾日当午,汗滴禾下土"); fw.append("汗滴禾下土"); //该方法会返回 fw.append("汗滴禾下土") 本身,也是writer类的对象,这意味着可以连续使用,即追加,如下所示: fw.append("锄禾日当午").append(",").append("汗滴禾下土"); fw.close();
注意:字符流如果写入后没有运行fw.close(),则字符会存在缓冲区,等候后面的写入,所以不会成功写入文件,但是通过运行fs.flush()执行缓冲区刷新操作可以强制写入
FileReader fr = new FileReader("g:/test/a.txt"); int c = fr.read(); System.out.println(c); //38148 System.out.println((char)c); //锄 fr.close();
为了将文件中的全部文字输出,可能会使用以下的方法,使用100长度的字符数组存储。但需要注意的是,在创建该数组时,实际上是由100个0组成,最后转换为字符串输出时也会输出100个字符(文件中的字符+空格)。
char[] chars = new char[100]; fr.read(chars); String text = new String(chars); System.out.println(text); //锄禾日当午,汗滴禾下土 System.out.println(text.length()); //100 fr.close();
解决办法:与字节流类似,规定字符数组向字符串转换的长度 new String(chars[], offset, len)
FileInputStream fis = new FileInputStream("g:/test/a.txt"); //参数1,要转换的字节流 //参数2, 指定编码名称 InputStreamReader isr = new InputStreamReader(fis,"UTF-8"); while(true){ int c = isr.read(); if(c == -1){ break; } System.out.println((char)c); }
OutputStreamWriter osw = new OutputStreamWriter(fos); osw.write("床前明月光"); osw.flush(); osw.close();
PrintStream ps = new PrintStream("g:/test/a.txt"); ps.println("锄禾日当午"); ps.println("锄禾日当午"); ps.println("锄禾日当午"); //与在控制台的输出效果类似,在a.txt打印了三行文字 ps.close();
PrintWriter pw = new PrintWriter("g:/test/a.txt"); pw.println("锄禾日当午"); pw.println("锄禾日当午"); pw.println("锄禾日当午"); pw.flush();
与字符流相同,打印流必须使用flush()或close()强制输出缓冲内容,才能成功写入
FileOutputStream fos = new FileOutputStream("g:/test/a.txt"); PrintWriter pw = new PrintWriter(fos); pw.println("锄禾日当午"); pw.println("锄禾日当午"); pw.println("锄禾日当午"); pw.flush();
将字符输入流转换成带有缓存的,可以一次读取一行的缓存字符读取流
FileReader fr = new FileReader("g:/test/a.txt"); BufferedReader br = new BufferedReader(fr); String text = br.readLine(); System.out.println(text);
try{ String s = null; s.toString(); }catch(Exception e){ PrintWriter pw = new PrintWriter("g:/test/bug.txt"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); pw.println(sdf.format(new Date())); e.printStackTrace(pw); pw.close(); }
存储在.properties文件中
Properties类基于Map接口,存储的是键值对
向Properties类中加入内容仍然用 put(K key, V val)
,将Map存入文件用 store(FileWriter fw, String comment)
,结束后关闭字符输出流
Properties ppt = new Properties(); ppt.put("name", "xxx"); ppt.put("info", "xxxq"); FileWriter fw = new FileWriter("g:/test/book.properties"); ppt.store(fw, "kankan"); fw.close();
读取Properties文件用 load(FileReader fr)
,获取Map中的内容,可以用 get(K key)
或 getProperty(K key)
Properties ppt = new Properties(); Reader r = new FileReader("g:/test/book.properties"); ppt.load(r); System.out.println(ppt.getProperty("name")); System.out.println(ppt.getProperty("info"));
将对象存储到文件中
public static void main(String[] args) throws IOException { Book b = new Book("金苹果","xxx"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("g:/test/book.txt")); oos.writeObject(b); oos.close(); } //需要继承Serializable接口 static class Book implements Serializable { private String name; private String info; public Book(String name, String info) { this.name = name; this.info = info; } @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", info='" + info + '\'' + '}'; } }
注意:当类中的属性没有被序列化标记,则该类实现的对象也不能被序列化(String类时被序列化的)
将写在文件中的对象输出
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("g:/test/book.txt")); Object o = ois.readObject(); System.out.println(o); Book b1 = (Book) ois.readObject(); System.out.println(b1.getInfo());
private transient String number; //number不会被序列化
private static String number; //number不会被序列化
private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeObject(number); out.writeObject(name); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { number = (String) in.readObject(); name = (String) in.readObject(); }
相比于Serializable,Externalizable的实现比较复杂,需要开发者自己完成,但是速度提升,存储空间减少
public class Person implements Externalizable { private String name; private String age; //可以在writeExternal和readExternal方法中自定义想要序列化和反序列化的方法 @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeObject(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); age = (String) in.readObject(); } }
操作需要关闭的资源时(输入输出流),经常需要释放资源。
使用try-with-resources,只要try()中的内容继承了Closeable()或AutoCloseable()类,那么该对象一定可以通过close()关闭,也就一定会在finally自动关闭。
try(FileReader fr = new FileReader("g://test/d16.txt")) { int c = fr.read(); System.out.println((char)c); } catch (IOException e) { e.printStackTrace(); }
FileReader fr = new FileReader("g://test/d16.txt"); PrintWriter pw = new PrintWriter("g://test/d16.txt"); try(fr;pw){ int c = fr.read(); System.out.println((char)c); }catch(IOException e){ e.printStackTrace(); }
FileReader fr = new FileReader("g://test/d16.txt"); PrintWriter pw = new PrintWriter("g://test/d16.txt"); try(fr;pw){ int c = fr.read(); System.out.println((char)c); }catch(IOException e){ e.printStackTrace(); }