java I/O流基础(知识+代码示例)
Posted 知道什么是码怪吗?
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java I/O流基础(知识+代码示例)相关的知识,希望对你有一定的参考价值。
目录
IO流原理
输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的分类
①按操作数据单位不同分为:字节流(8bit),字符流(按字符为单位)。
②按数据流的流向不同分为:输入流、输出流。
③按流的角色的不同分为:节点流、处理流。
字节流操作二进制文件较好,字符流操作文本文件较好。
(以下两张图片来自B站韩顺平老师整理)
文件处理常用流的基础操作(节点流)
FileInputStream文件输入流
import java.io.FileInputStream;
public class FileInputStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\FileIntext.txt";
FileInputStream fileInputStream = new FileInputStream(filePath);
int readlength = 0;// 表示实际读取的字节数
// read()方法默认读取一个字节,并返回实际读取到的字节数
// 如果读取到了文件末尾,则返回-1
System.out.println("单个字节读取方式:");
while ((readlength = fileInputStream.read()) != -1)
// 这里以int的类型读取单个字符,输出的时候需要转换成char类型
System.out.print((char) readlength);
// 将上面的读取方式注释掉,再使用字节读取,否则上面的已经将文件读取完毕
byte[] bytes = new byte[8];// 字节数组
System.out.println("字节数组读取方式:");
while ((readlength = fileInputStream.read(bytes)) != -1)
System.out.print(new String(bytes, 0, readlength));// 将字节数组装换成字符串
fileInputStream.close();// 关闭输入流
文件内容如下:
FileOutputStream文件输出流
import java.io.FileOutputStream;
public class FileOutputStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\FileOutText.txt";// 字节流输出到文件的路径,如果没有这一文件,将会创建一个
FileOutputStream fileOutputStream1 = new FileOutputStream(filePath);
byte[] bytes1 = "hello,world!".getBytes();// 获取对应字符串的字节
fileOutputStream1.write(bytes1);// 写入数据到到文件当中
fileOutputStream1.close();// 关闭输出流
// 多次执行上面的代码,发现总是覆盖掉之前的内容。如果我们想在文件尾继续追加内容,则可以在实例化的时候选择另一种方式
FileOutputStream fileOutputStream2 = new FileOutputStream(filePath, true);// 第二个参数为true,表示从文件尾继续写入数据
byte[] bytes2 = "hello,java!".getBytes();// 获取对应字节
fileOutputStream2.write(bytes2);// 写入数据到文件当中
// 如果我们不想将字符串的数据完全写入,我们可以这样使用write方法
fileOutputStream2.write('\\n');// 换行
fileOutputStream2.write(bytes2, 6, 4);// 第二个参数表示从下标为6的字节开始写入,写入字符长度为4
fileOutputStream2.write('\\n');// 换行
fileOutputStream2.write(bytes2, 0, bytes2.length);
fileOutputStream2.close();// 关闭输出流
执行完上面这一段代码,同一文件夹下就会出现一个名为FileOutText的文本文件,内容如下:
FileReder文件输入流
使用方法和FileInputStream大同小异
import java.io.FileReader;
public class FileReaderDemo
public static void main(String[] args) throws Exception
// 和字节流文件读取大同小异
String filePath = "src\\\\FileReaderText.txt";
FileReader fileReader = new FileReader(filePath);
int readlength = 0;
// 读取一个字符
while ((readlength = fileReader.read()) != -1)
System.out.print((char) readlength);
// 将读取一个字符的方法注释之后执行下面的代码,字符数组读取
char[] chars = new char[8];// 字符数组,一次读入8个字符
while ((readlength = fileReader.read(chars)) != -1)
System.out.print(new String(chars, 0, readlength));
fileReader.close();
FileWriter文件输出流
write的各种方法如下
import java.io.FileWriter;
public class FileWriterDemo
public static void main(String[] args) throws Exception
// 和文件字节输出流用法大同小异
String filePath = "src\\\\FileWriterText.txt";
FileWriter fileWriter1 = new FileWriter(filePath);
fileWriter1.write("hello,world!");
// 文件尾写入数据
FileWriter fileWriter2 = new FileWriter(filePath, true);
fileWriter2.write("\\n");// 换行
fileWriter2.write(65);
fileWriter2.write("\\n");
fileWriter2.write("hello,java!");
fileWriter2.write("\\n");
fileWriter2.write("你好,世界", 0, 2);// 从下标为0的字符开始输出,输出长度为2个字符
fileWriter1.close();// close()源码中关闭输出流时才是真正的写入内容,一定要记得关闭流
fileWriter2.close();
写入文件内容如下:
节点流和处理流的区别
(图片来自B站韩顺平老师整理)
①节点流时底层流,直接跟数据源相接。
②处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
③处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连。
④处理流以缓冲的方式提高了输入输出的效率(但是某些时候使用处理流并不一定比节点流快)。
⑤处理流提供了一系列便捷的方法来一次输入输出大量的数据,使用更加灵活方便。
BufferedReader处理流
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedReaderText
public static void main(String[] args) throws Exception
String filePath = "src\\\\FileInText.txt";//被读取文件的路径
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));//处理流包装FileReader
String readString;
//处理流不仅具有节点流自带的各种方法,还具有了其他更加方便的方法
while ((readString = bufferedReader.readLine()) != null) //一次读取一行
System.out.println(readString);
bufferedReader.close();//关闭外层流,内层流也自动关闭
文件内容:
输出内容:
BufferedWriter处理流
import java.io.BufferedWriter;
import java.io.FileWriter;
public class BufferedWriterText
public static void main(String[] args) throws Exception
String filePath = "src\\\\BufferedWriter_1.txt";// 文件路径
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("知道什么叫码怪吗?");
bufferedWriter.newLine();// 换行
bufferedWriter.write("hello,world!");
bufferedWriter.newLine();
bufferedWriter.close();
/*
* 如果我们使用包装流来包装FileWriter,想要在文件末尾追加内容而不是覆盖 可以使用FileWriter的另一个构造方法
* FileWriter(File file,Boolean ppend); BufferedWriter bufferedWriter = new
* BufferedWriter(new FileWriter(filePath,true));
*/
BufferedWriter bufferedWriter2 = new BufferedWriter(new FileWriter(filePath, true));
bufferedWriter2.write("这是追加的内容");
bufferedWriter2.close();// 记得一定要关闭流,不然数据无法写入文件当中
文件写入数据如下:
BufferedInputStream处理流
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class BufferedInputStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\BufferedInText.txt";
//处理流包装文件字节输入流,除了FileInputStream自身具备的方法,还有其他扩充的方法
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
byte[] bytes = new byte[1024];
int readlen = 0;
while ((readlen = bufferedInputStream.read(bytes)) != -1)
System.out.print(new String(bytes));
bufferedInputStream.close();//关闭外层流
文件内容如下:
BufferedOutputStream处理流
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
public class BufferedOutputStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\BufferedOutText.txt";
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytes = "hello,world!".getBytes();
bufferedOutputStream.write(bytes, 1, bytes.length - 1);// 从下标为1开始写入length-1个字节
bufferedOutputStream.write('\\n');// 换行
bufferedOutputStream.write(65);
bufferedOutputStream.write('\\n');
bufferedOutputStream.write(bytes);
bufferedOutputStream.close();// 关闭流执行的才是真正的写入
// 写入到文件末尾
BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(new FileOutputStream(filePath, true));
bufferedOutputStream1.write("hello,java".getBytes());
bufferedOutputStream1.close();
写入内容如下:
标准输入输出流
使用java的过程中,我们经常使用System.out.println()来输出内容到屏幕上,也经常使用Scanner(System.in)来读取键盘输入的内容。了解了javaI/O流之后,会对其有一个更深刻的理解。
System.out.println(System.in.getClass());
System.out.println(System.out.getClass());
输出内容:
而我们再追踪到其源码会发现,System.in源码定义如下:
System.out源码定义如下:
可见,标准输入流编译类型为:InputStream,实际运行的类型为:BufferedInputStream。
标准输出流编译类型和实际运行类型都为PrintStream。
示例:更改标准输出流的输出位置到文件当中
import java.io.PrintStream;
public class PrintStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\PrintText.txt";
PrintStream printStream = System.out;// 获取输出流
printStream.println("hello,java!");// 默认输出位置为显示器
printStream.write("hello,world!".getBytes());
printStream.close();
System.setOut(new PrintStream(filePath));// 更改输出流的输出位置
System.out.println("hello,world! hello,java!");//输出到文件当中
文件输出如下:
序列化和反序列化
①序列化就是在保存数据时,保存数据的值和数据类型。
②反序列化就是在恢复数据时,恢复数据的值和数据类型。
为什么需要用到序列化和反序列化?
当我们存放数据的时候,如果需要将这个数据的数据类型也记录下来,就需要用到序列化。例如:将字符串”12345“存放到文件当中,文件内直接写入的是12345,而我们需要记录的数据类型是字符串类型。又或者说,我们需要将一个对象保存到文件当中,并且能够从文件当中恢复。实现这些需求,就需要用到序列化和反序列化操作。
怎么序列化和反序列化?
方法一:实现Serializable接口,推荐使用Serializable接口实现序列化,因为Serializable接口是一格标记接口,里面没有任何待实现的方法。
方法二:实现Externalizable接口,如果我们选择实现Externalizable接口,那么我们必须实现Externalizable接口中的方法。但是一般情况下我们都不会用到实现出来的方法。
下面以实现Serializable接口为例,来演示如何序列化。
使用Serializable接口序列化
查看Serializable的源码我们可以看到,Serializable接口内部没有任何内容,只是作为一个可序列化的标记接口。
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Person implements Serializable // 一个实现了Serializable接口的类
private String name;
private String phoneNumber;
public Person()
public Person(String name, String phoneNumber)
this.name = name;
this.phoneNumber = phoneNumber;
public String getName()
return name;
public String getPhoneNumber()
return phoneNumber;
public class ObjectOutStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\ObjectOutText.txt";// 保存的位置,但是序列化后的文件格式其实并不是txt文件
// ObjectOutputStream为处理流,这里包装了FileOutputStream
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeInt(12345);// 保存为int类型,Integer类实现了Serializable接口
objectOutputStream.writeDouble(1.33333);// 保存为double类型,Double类实现了Serializable接口
objectOutputStream.writeChar('A');// 保存为Char类型,Charecter类实现了Serializable接口
objectOutputStream.writeUTF("hello,world!");// 保存为String类型,String类也实现了Serializable接口
objectOutputStream.writeBoolean(false);// 保存为Boolean类型
objectOutputStream.writeObject(new Person("小明", "123456"));// 将Person对象保存进文件当中,Person类实现了Serializable接口
objectOutputStream.close();// 关闭流
查询JDK文档可见Integer、String、 Character等均实现了Serializable接口,所以这些类型可以序列化,而我们写的一个类想要实现序列化,也需要实现Serializable接口。
此时我们再来看看我们保存的txt文件,会发现文件内容变得不一样了。
所以,实现序列化之后保存的格式实际上是经过java特殊处理过后的。
反序列化
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class ObjectInputStreamText
public static void main(String[] args) throws Exception
String filePath = "src\\\\ObjectOutText.txt";
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filePath));
// 读取的顺序需要和序列化时的顺序一致,不然会发生错误
System.out.println(objectInputStream.readInt());// 以int类型读取
System.out.println(objectInputStream.readDouble());// 以double类型读取
System.out.println(objectInputStream.readChar());// 以char类型读取
System.out.println(objectInputStream.readUTF());// 以UTF编码形式读取字符串
System.out.println(objectInputStream.readBoolean());// 以boolean类型读取
// System.out.println(objectInputStream.readObject());// 读取一个对象
Person person = (Person) objectInputStream.readObject();// 以Object类型读取,返回的也是一个object类型。强制转换为Person类赋给person
System.out.println(person.getName() + " " + person.getPhoneNumber());// 输出内容
objectInputStream.close();// 关闭流
class Person implements Serializable // 一个实现了Serializable接口的类
private String name;
private String phoneNumber;
public Person()
public Person(String name, String phoneNumber)
this.name = name;
this.phoneNumber = phoneNumber;
public String getName()
return name;
public String getPhoneNumber()
return phoneNumber;
输出内容如下:
序列化和反序列化注意事项
①读写顺序要一致。
②需要序列化或反序列化的对象,需要实现Serializable接口或Externalizable接口。
③序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员,static修饰的变量天然就是不可序列化的,而transient作为java的关键字用来表示一个成员变量不是该对象序列化的一部分。
④序列化某一对象时,要求里面属性的类型也需要实现序列化接口,举一个简单的例子:下面代码中有两个类,Student类和Person类,Student类没有实现序列化。如果Person类实例化了Student类,那么Person类无法序列化,只有当Student类也实现序列化之后,才不会报错。但是如果Person类没有实例化Student类,仅仅作为一个属性,那么序列化不会报错。
class Student
class Person implements Serializable // 一个实现了Serializable接口的类
public String name;
public String phoneNumber;
public Student student = new Student();// 这种情况下无法序列化,需要Student类序列化
// public Student student;// 这种情况下序列化不会报错,因为student为空
public Person()
public Person(String name, String phoneNumber)
this.name = name;
this.phoneNumber = phoneNumber;
⑤序列化具有可继承性,如果某类实现了序列化,那么它的子类也默认实现了序列化。
转换流
为什么需要用到转换流?
当我们用字符流读取文件时,会因为其编码方式的不同,导致我们读取时可能会产生乱码。
例如文件中的内容为:
但是可能因为读取的编码方式不同,导致输出为这样
这个时候就需要用到转换流。我们可以用转换流将一个字节流转换成字符流,而字节流可以指定读取的编码方式,从而让字符流以特定的编码方式读取文件。
InputSteamReder转换流
JDK文档说明如下:
其构造方法如下:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class InputStreamRederText
public static void main(String[] args) throws Exception
// 转换流
String filePath = "src\\\\ReadText.txt";// 文件路径
// 将FileInputStream字节流转换成字符流,指定读取文件的编码方式
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "UTF-8");
// 然后使用处理流包装转换流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//当然,我们也可以将上面两步合在一起写
// BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"UTF-8"));
String readString;
while ((readString = bufferedReader.readLine()) != null) // 一次读取一行
System.out.println(readString);
bufferedReader.close();// 关闭最外层的流
OutputStreamWriter转换流
JDK文档说明如下:
其构造方法如下:
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class OutputStreamWriterText
public static void main(String[] args) throws Exception
String filePath = "src\\\\WriterText.txt";
// 转换流,将指定编码方式的字节流转换为字符流
BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(filePath), "UTF-16"));
bufferedWriter.write("hello,world!");
bufferedWriter.close();// 关闭最外层流
打开文件查看编码方式:
Properties类
Properties类是为了方便我们配置文件而产生的一个类,这个类在读取、修改、删除配置文件时较为方便。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
public class PropertiesText
public static void main(String[] args) throws Exception
// Properties类简单样例
Properties properties = new Properties();
FileOutputStream fileOutputStream = new FileOutputStream("src\\\\re.properties");
// 写入配置文件
properties.setProperty("charset", "UTF-8");// 指定编码方式
properties.setProperty("user", "Tom");// 添加user=Tom
properties.setProperty("password", "123456");// 添加password=123456
// 第一个参数指定流的类型和文件地址,第二个参数指定注释
properties.store(fileOutputStream, "写入配置文件");// 开始写入
// 读取配置文件
// Key-val ,根据指定的Key找到val,读取不用按照顺序读取
properties.load(new FileInputStream("src\\\\re.properties"));// 读取配置文件
String user = properties.get("user").toString();// 读取user对应的val
String password = properties.get("password").toString();// 读取password对应的val
String charset = properties.get("charset").toString();// 读取charset对应的val
System.out.println("charset = " + charset);
System.out.println("user = " + user);
System.out.println("password = " + password);
// 修改配置文件
// setProperty底层使用Hashtable,修改和添加方式也类似
// 修改方式:如果存在这一键值,就修改这一键值对应的值,如果不存在这一键值,就创建这一键值将值加入进去
properties.setProperty("user", "Bob");
properties.setProperty("password", "987654");
properties.store(fileOutputStream, "修改配置文件");// 开始写入
System.out.println("user = " + properties.get("user"));
System.out.println("password = " + properties.get("password"));
// 删除键值
properties.remove("user");
properties.store(fileOutputStream, "删除键值");// 开始写入
配置文件内容如下:
以上是关于java I/O流基础(知识+代码示例)的主要内容,如果未能解决你的问题,请参考以下文章