java I/O流基础(知识+代码示例)

Posted 知道什么是码怪吗?

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java I/O流基础(知识+代码示例)相关的知识,希望对你有一定的参考价值。

目录 

IO流原理

流的分类

文件处理常用流的基础操作(节点流)

FileInputStream文件输入流

FileOutputStream文件输出流

FileReder文件输入流

FileWriter文件输出流

节点流和处理流的区别

 BufferedReader处理流

BufferedWriter处理流

BufferedInputStream处理流

BufferedOutputStream处理流

标准输入输出流

序列化和反序列化

使用Serializable接口序列化

 反序列化

 序列化和反序列化注意事项

转换流

InputSteamReder转换流

OutputStreamWriter转换流

Properties类


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流基础(知识+代码示例)的主要内容,如果未能解决你的问题,请参考以下文章

java从基础知识I/O

Java笔记:流I/O

Java NIO 入门

Java实战-基于I/O流设计的图书馆管理系统项目总结

java网络编程基础(知识+代码示例)

I/O流