Java输入/输出流

Java输入/输出流

一、流的概念

输入流只能读不能写,输出流只能写不能读。按照流中数据的处理单位不同,可将流分为字节流字符流。在字节流中,数据的组织和操作的基本单位是字节;在字符流中,数据的组织和操作的基本单位是字符。综上,流可分为字节输入流,字节输出流,字符输入流,字符输出流。

Java是面向对象的语言,使用类来表示各种流

使用流来进行数据输入的具体步骤为

  1. 创建适当的输入流类的对象,建立与输入设备(如磁盘或者文件)的连接
  2. 调用相应的read()方法读入数据
  3. 关闭流,释放相关的系统资源

使用流来进行数据输出的具体步骤为

  1. 创建适当的输出流类的对象,建立与输出设备(如屏幕或文件)的连接
  2. 调用相应的write()方法输出数据
  3. 关闭流,释放相关的系统资源

二、I/O类体系

为了实现对各种输入/输出设备的操作,Java提供了丰富的基于流的I/O类,这些类位于java.io包中。其中有四个抽象类尤为重要,分别是InputStream(字节输入流)类、OutputStream(字节输出流)类、Reader(字符输入流)类和Writer(字符输出流)类。所有字节流的类都是InputStream类或OutputStream类的子类,所有字符流的类都是Reader类或Writer类的子类。

2.1 字节流

在这里插入图片描述

2.2 字符流

在这里插入图片描述

三、文件流

File类的构造方法

方法原型 说明
public File(File parent, String child) 根据文件对象和字符串创建一个新的File实例
public File(String pathName) 根据路径名字符串创建一个新的File实例
public File(String parent, String child) 根据两个字符串创建一个新的File实例
public File(URL uri) 使用给定的统一资源定位符来创建一个新的File实例

File类的常用成员方法

方法原型 说明
public boolean exists() 判断文件是否存在,存在返回true,否则返回false
public boolean isFile() 判断是否为文件
public boolean isDirectory() 判断是否为目录
public String getName() 返回文件或者目录的名称
public String getAbsolutePath() 返回文件的绝对路径(包含文件名)
public long length() 如果是文件,返回文件的长度(字节数),如果是目录,返回值不确定
public boolean creatNewFile() throws IOException 创建新文件,若文件已存在,返回false;该方法只能创建文件,不能创建目录
public boolean delete() 删除当前文件或目录,如果为目录,则目录必须为空时才能删除
public String[] list() 返回当前目录下所有的文件和目录名称

3.1 FileInputStream

FileInputStream类继承于InputStream类,是进行文件读取操作的最基本的类,它的作用是将文件中的数据读入到内存中。

使用FileInputStream类读取文件内容的步骤为—>

//(1)创建流,将程序与文件连接起来,具体方法是实例化FileInputStream类的对象
FileInputStream fileInputStream = new FileInputStream("D:\\test\\data.txt");
//或者,
File myFile = new File("D:\\test\\data.txt");
FileInputStream fileInputStream = new FileInputStream(myFile);
//注意,创建流时,可能抛出异常,必须进行处理--------------------------------

//(2)调用read()方法读取流中的数据,read()方法有如下三种形式,根据需要选用
public int read();
public int read(byte b[]);
public int read(byte[] b, int off, int len);

//(3)读取完毕后要关闭流
fileInputStream.close();

【示例】文件字节输入流应用示例,从文本文件中读取数据并显示在屏幕上

public class Main {
    
    
	public static void main (String[] args) {
    
    
		try {
    
    
			File file = new File(".//great//data.txt");
			FileInputStream fileInputStream = new FileInputStream(file);
			char ch;
			for(int i = 0; i < file.length(); i++) {
    
    
				ch = (char)(fileInputStream.read());
				System.out.print(ch);
			}
			fileInputStream.close();
		} catch (FileNotFoundException e) {
    
    
			e.printStackTrace();
		} catch (IOException e) {
    
    
			e.printStackTrace();
		}
	}
}

字节流中数据操作的单位是字节。由于一个汉字占两个字节,因此使用字节流读写汉字时,可能出现乱码现象。

从文件中读取数据时,可以逐个字节读取,也可以一次读取多个字节存入字节数组中。以文件的长度来定义字节数组的长度,一次读取全部数据存入数组,修改后的代码为

byte[] buf = new byte[(int)(file.length())];   //定义字节数组,以文件的长度确定数组的长度
fileInputStream.read(buf);     //一次性读取文件所有数据存放到字节数组中
String str = new String(buf);  //利用字节数组创建字符串
System.out.println(str);       //将文件内容以字符串形式输出

当然,这种一次读取全部数据的方式只适合小文件,当读取较大文件时,宜采用分块读取的方式,即一次读取固定长度的数据,多次读取,到达文件尾时结束,读取数据的代码修改为

int n = 1024, count = 0;
byte[] buf = new byte[n];
while((count = fileInputStream.read(buf)) != -1) {
    
      //读取数据粗如数组
	//将字节数组转换为字符串后输出
	System.out.print(new String(buf, 0, count));
}

3.2 FileOutputStream

创建FileOutputStream类的对象时,如果文件不存在,系统会自动创建该文件,但是当文件路径中包含不存在的目录时创建失败会抛出异常

使用FileOutputStream类将数据输出到文件的步骤为—>

/*(1)创建流,将程序与文件连接起来,具体方法是实例化FileOutputStream类的对象。将数据写入文件时,有覆盖和追加两种方式。覆盖是指清除文件的原有数据,写入新的数据;追加是指保留文件的原有数据,在原有数据的末尾写入新的数据。默认是覆盖方式(false)
*/
FileOutputStream fileOutputStream = new FileOutputStream("D:\\test\\data.txt");
//或者
File myFlie = new File("D:\\test\\data.txt");
FileOutputStream fileOutputStream = new FileOutputStream(myFile, true);
//注意,创建输出流时,可能抛出异常,必须进行处理-----------------------------------

//(2)调用write()方法将数据输出到文件,write()方法有以下三种形式
public void write(int b);
public void write(byte b[]);
public void write(byte b[], int off, int len);

//读取完毕后关闭流
fileOutputStream.close();

【示例】从键盘输入一串字符,并将其追加至文件中

public class Main {
    
    
	public static void main (String[] args) {
    
    
		Solution so = new Solution();
		System.out.println(""  );
		try {
    
    
			FileOutputStream fileOutputStream = new FileOutputStream(".\\great\\data.txt", true);
			System.out.println("请输入一行字符:");
			Scanner sc = new Scanner(System.in);
			String str = sc.nextLine();       //输入一行字符
			byte[] buffer = str.getBytes();   //将字符串转换为字节数组
			fileOutputStream.write(buffer);   //写入输入流
			fileOutputStream.close();         //关闭输入流
		} catch (FileNotFoundException e) {
    
    
			e.printStackTrace();
		} catch (IOException e) {
    
    
			e.printStackTrace();
		}
	}
}

将逐个字符追加写入文件

String str = "Hello World!";
	for(int i = 0; i < str.length(); i++) {
    
    
	fileOutputStream.write(str.charAt(i));
}

3.3 FileReader

【示例】文件字符输入流示例

public class Main {
    
    
	public static void main (String[] args) throws IOException {
    
    
		Solution so = new Solution();
		System.out.println(""  );
		File file = new File(".//great//data.txt");
		FileReader fileReader = new FileReader(file);
		char ch;
		for(int i = 0; fileReader.ready(); i++) {
    
    
			ch = (char)(fileReader.read());
			System.out.print(ch);
		}
		fileReader.close();
	}
}

3.4 FileWriter

不同的操作系统下,回车换行符并不相同,调用System类的getProperty("line.separator")方法能够获取当前系统的回车换行符。但是在Windows系统下,回车换行符是"\r\n"。

【示例】文件字符输出流应用示例。输入多个字符串,以”#"结束,将这些字符串写入文件中,要求一个字符串占一行

public class Main {
    
    
	public static void main (String[] args) throws IOException {
    
    
		Solution so = new Solution();
		System.out.println(""  );
		FileWriter fileWriter = new FileWriter(".//great//data.txt", true);
		Scanner sc = new Scanner(System.in);
		String ch = System.getProperty("line.separator");
		while(!sc.hasNext("#")) {
    
    
			String str = sc.nextLine();
			fileWriter.write(str);
			fileWriter.write(ch);
		}
		fileWriter.close();
		System.out.println("已保存至.//great//data.txt");
	}
}

四、实体流和装饰流

按照是否直接连接实际数据源(例如文件)将流划分为实体流和装饰流。

实体流能够直接连接数据源,可单独使用实现对数据源的读写,如文件流。装饰流不能直接连接数据源,不能单独使用,必须在其他流(实体流或其他装饰流)的基础上使用。常用的有缓冲流、数据流和对象流等。例如访问文件,既可以只使用文件流来读写,也可以在文件流的基础上配合使用装饰流。

由于装饰流是在其他流的基础上创建的,这种创建流的方式称作“流的嵌套”。在流的嵌套中,各个流的性质必须相同,也就是流的读写单位(字节/字符)、流的方向(输入/输出)都要一致。装饰流不改变实体流中的数据内容,只是对实体流做了一些功能上的增强

五、缓冲流

使用缓冲流,当输入数据时,数据以块为单位读入缓冲区,此后如有读操作,则直接访问缓冲区;当输出数据时,先将数据写入缓冲区,当缓冲区的数据满时,才将缓冲区中的数据写入输出流中,提高了输入/输出的效率。

5.1 缓冲字节流

BufferedInputStream类是InputStream类的子类

列如,在文件流基础上使用缓冲流进行文件数据的读取,创建流的代码为

FileInputStream inOne = new FileInputStream("data.txt");    //创建文件流
BufferedInputStream inTwo = BufferedInputStream(intOne);    //以文件流为参数创建缓冲流

BufferedOutputStream类是OutputStream类的子类

BufferedOutputStream in = new BufferedOutputStream(new FileOutputStream("data.txt"));

5.2 缓冲字符流

BufferedReader类是Reader类的子类,增加了读取一行字符的方法readLine()

方法原型 说明
public BufferedReader(Reader in) 构造方法,创建具有默认大小缓冲区的缓冲字符输入流
public BufferedReader(Reader in, int size) 构造方法,创建具有指定缓冲区大小的缓冲字符输入流
public String readLine() throws IOException 从缓冲输入流中读取一行字符,以字符串的形式返回(不包括回车符),如果已到达流末尾,则返回null

BufferedWriter类是Writer类的子类

bw.write("\r\n")bw.newLine()等价

File file = new File("data.txt");
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);

六、数据流

Java提供了两个数据流类,DataInputStream和DataOutputStream,用来输入/输出各种类型的数据。使用数据流需要注意以下几点:

  • 数据流属于装饰流,不能单独使用
  • 使用DataOutputStream流输出的数据,必须使用DataInputStream流进行读取,这两个类必须配合使用,否则会发生数据错误。因为使用DataOutputStream流输出数据时,除了数据以外,还加上了特定的格式
  • 读取数据时,数据的类型和顺序必须与输出的数据的类型和顺序保持一致

下面只介绍数据输出流DataOutputStream类的方法,数据输入流相应的方法与之对应

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0QXxa4u-1609296044884)(…\JAVA基础\great\DataOutputStream.png)]

【示例】读写学生信息

class StudentInfo {
    
    
	String name;
	int age;
	double score;
	StudentInfo(String name, int age, double score){
    
    
		this.name = name;
		this.age = age;
		this.score = score;
	}
}

public class Main {
    
    
	public static void main (String[] args) throws IOException {
    
    
		StudentInfo[] stu = new StudentInfo[5];
		Scanner sc = new Scanner(System.in);
		
		//写入文件
		FileOutputStream fos = new FileOutputStream(".//great//data.txt");
		DataOutputStream dos = new DataOutputStream(fos);
		System.out.println("请输入学生的姓名、年龄、成绩:");
		for(int i = 0; i < 3; i++) {
    
    
			stu[i] = new StudentInfo(sc.next(), sc.nextInt(), sc.nextDouble());
			dos.writeUTF(stu[i].name);
			dos.writeInt(stu[i].age);
			dos.writeDouble(stu[i].score);
		}
		dos.close();     //关闭流
		
		//读出数据
		DataInputStream dis = new DataInputStream(new FileInputStream(".//great//data.txt"));
		System.out.println("文件中的内容:");
		for(int i = 0; i < 3; i++) {
    
    
			System.out.println(dis.readUTF() + " " + dis.readInt() + " " + dis.readDouble());
		}
		dis.close();
		
	}
}

七、对象流与对象序列化

对象序列化是指将一个对象的属性和方法转化为一种序列化的格式用于存储和传输。在需要的时候,再将对象重构出来,这个过程称为反序列化。若要将对象能够进行序列化,其所属的类必须实现Serializable接口。Serializable接口是一个空接口,不包含任何方法,实现这个接口只是一个标志,表示该类的对象可以进行序列化。Serializable接口在java.io包中

Serializable】接口

public Myclass implements Serializable {
    
    
    //----
}

对象序列化时,不保存对象的transient变量(临时变量)和static变量(静态变量)

同数据流一样,读取对象时,数据的类型和顺序必须与输出的数据的类型和顺序保持一致

Externalizable】接口

当实现序列化时,如果要自行确定哪些变量要保存,以及具体的顺序或者除了成员变量,还需要保存其他的数据,就要通过实现Externalizable接口来进行。

一个类实现Externalizable接口以后,当采用ObjectOutputStream流输出对象时,会自动调用writeExternal()方法,按照方法规定的逻辑保存对象数据。当采用ObjectInputStream流输入对象时,会自动调用readExternal()方法

示例】读写用户信息

class UserInfo implements Externalizable {
    
    
	public String userName;
	public String userPass;
	public int userAge;
	public UserInfo() {
    
    
		
	}
	public UserInfo(String username, String userpass, int userage) {
    
    
		this.userName = username;
		this.userPass = userpass;
		this.userAge = userage;
	}
	
	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    
    
		Date date = (Date)in.readObject();
		System.out.println(date);
		this.userName = (String)in.readObject();
		this.userAge  = in.readInt();
	}
    
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
    
    
		Date date = new Date();
		out.writeObject(date);
		out.writeObject(userName);
		out.writeInt(userAge);
	}
    
	public String toString() {
    
    
		String str = "用户名: " + this.userName + ", 密码: " + this.userPass 
				          +", 年龄: " + this.userAge;
		return str;
	}
}

public class Main {
    
    
	public static void main (String[] args) throws IOException, ClassNotFoundException {
    
    
		//写入文件(序列化)
		FileOutputStream fout = new FileOutputStream(".//great//data.txt");
		ObjectOutputStream oout = new ObjectOutputStream(fout);
		UserInfo user = new UserInfo("飞流", "123456", 18);
		oout.writeObject(user);
		oout.close();     //关闭流
		System.out.println("数据写入成功!");
		
		//读出数据(反序列化)
		FileInputStream fin = new FileInputStream(".//great//data.txt");
		ObjectInputStream oin = new ObjectInputStream(fin);
		UserInfo user1 = (UserInfo)oin.readObject();
		oin.close();
		System.out.println(user1.toString());
	}
}

八、标准输入/输出

System类功能强大,利用它可以获得很多Java运行时的系统信息。System类的属性和方法都是静态的,System.inSystem.out就是System类的两个静态属性,分别对应了系统的标准输入和标准输出。

System.in是InputStream类的对象,当程序需要从键盘输入数据时,只需要调用System.in.read()方法即可。不过要注意,System.in是字节对象流,只能输入字节类型的数据。System.out是PrintStream类的对象,有方法print(),println(),printf()可以输出各种类型的数据。

System.err是标准错误输出流。它是PrintStream类的对象,有两个方法print()和println(),调用这两个方法可以在屏幕上输出错误或者警示信息,如

System.err.println("IOException");

九、桥接流

在流的嵌套中,各个流的性质必须相同。为了解决InputStream和Reader两个体系流的嵌套问题,引入了InputStreamReaderOutputStreamWriter,用于实现字节流向字符流的转换,在字节流与字符流之间搭建了沟通的桥梁,这两个类被形象地称为“桥接流”

相应的构造方法如下

public InputStreamReader(InputStream in);     //将字节输入流转换为字符输入流
public OutputStreamWriter(OutputStream out);  //将字节输出流转换为字符输出流
//将字节流转换为字符流
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
//上述字符流效率较低,可以使用缓冲流来提高输入效率
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

【示例】将标准输入转换为字符流后再进行数据的输入

public class Main {
    
    
	public static void main (String[] args) throws IOException {
    
    
		//将字节流转换为字符流
		InputStreamReader inputStreamReader = new InputStreamReader(System.in);
		//上述字符流效率较低,可以使用缓冲流来提高输入效率
		BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
		System.out.println("请输入学生姓名:");
		String name = bufferedReader.readLine();
		System.out.println("请输入学生成绩:");
		double score = Double.parseDouble(bufferedReader.readLine());
		bufferedReader.close();
        
		System.out.println("姓名: " + name + ", 成绩:" + score);
	}
}

十、流的关闭

10.1 finally中关闭流

在之前的例子中,流的声明,创建以及关闭都是在try块中进行的,如果在执行过程中抛出异常,那么流的关闭语句有可能得不到执行,无法释放占用的资源。在finally中关闭流,无论是否抛出异常流都会被关闭

InputStreamReader isr = null;
try {
    
    
	isr = new InputStreamReader(System.in);
	int x = (int)isr.read();
	//······
} catch (IOException e) {
    
    
	e.printStackTrace();
} finally {
    
    
	try {
    
    
		if(isr != null) isr.close();
	} catch (IOException e) {
    
    
		e.printStackTrace();
	}
}

10.2 自动关闭流

使用finally块程序显得有些冗长

try-with-resources语句的形式为

try (资源的声明和创建) {
    
    
    //使用资源
}
  • 在括号()内创建的资源对象在try块结束时会自动释放,不需要再显式调用close()方法
  • 在括号()内创建的资源对象时try块的局部变量,作用域只限于try块内部
  • 后面可接catch块和finally块
  • 只能用于实现了java.lang.AutoCloseable接口的那些资源。java.io.Closeable接口继承了AutoCloseable接口,所有流类都实现了这两个接口,可以使用try-with-resources语句
try(InputStreamReader isr = new InputStreamReader(System.in);
       BufferedReader br = new BufferedReader(isr)) {
    
    
	String str = br.readLine();
	//·······
} catch (IOException e) {
    
    
	e.printStackTrace();
}

try-with-resources语句不仅可以用于流的操作,也可以应用于数据库编程和网络编程中所涉及的资源,只要资源实现了AutoCloseable接口即可


参考资料:《Java语言程序设计实用教程》主编 王素琴

猜你喜欢

转载自blog.csdn.net/weixin_44368437/article/details/111948214