zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java IO 体系学习及常用类学习

JAVA学习IO 常用 体系
2023-09-11 14:22:11 时间

在这里插入图片描述

一、前言引入

根据文件操作类的学习我们认识到,File可以操作文件,但是不能操作文件的内容,如果要操作文件的内容,可以通过字节流或者字符流。
一、前言引入
二、IO概念和概述讲解Java IO体系(部分)
三、具体内容讲解,文件操作应用小案例
四、总结

在这里插入图片描述

二、IO概念和概述讲解Java IO体系(部分)

2.1 基本概念阐述

  • 流的概念和作用

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

  • IO流的分类

根据处理数据类型的不同分为:字符流和字节流

根据数据流向不同分为:输入流和输出流

  • 字符流和字节流

字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:

  • 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
  • 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。

  • 输入流和输出流

对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

2.2 常用 IO 类的概述

  • 文件流:FileInputStream / FileOutputStream, FileReader / FileWriter

这四个类是专门操作文件流的,用法高度相似,区别在于前面两个是操作字节流,后面两个是操作字符流。它们都会直接操作文件流,直接与OS底层交互。因此他们也被称为节点流。

注意使用这几个流的对象之后,需要关闭流对象,因为java垃圾回收器不会主动回收。不过在Java7之后,可以在 try()括号中打开流,最后程序会自动关闭流对象,不再需要显示地close。

  • 转换流:InputStreamReader / OutputStreamReader

这两个类可以将字节流转换成字符流,被称为字节流与字符流之间的桥梁。我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类

  • 缓冲流:BufferedReader / BufferedWriter ,BufferedInputStream / BufferedOutputStream

没有经过Buffered处理的IO, 意味着每一次读和写的请求都会由OS底层直接处理,这会导致非常低效的问题。

经过Buffered处理过的输入流将会从一个buffer内存区域读取数据,本地API只会在buffer空了之后才会被调用(可能一次调用会填充很多数据进buffer)。
另外,BufferedReader提供一个readLine()可以方便地读取一行,而FileInputStream和FileReader只能读取一个字节或者一个字符,因此BufferedReader也被称为行读取器

接下来的内容将围绕上面提到的几个重要 IO 类展开

三、具体讲解(围绕以上常用的IO类展开)

如果要进行输入、输出操作一般都会按照如下的步骤进行(以文件操作为例):

  • 通过File类定义一个要操作文件的路径;

  • 通过字节流或字符流的子类对象为父类对象实例化;

  • 进行数据的读(输入),写(输出)操作;

  • 数据流属于资源操作,资源操作必须关闭;

3.1 字节输出流:OutputStream (重点)

OutputStream类是一个专门进行字节数据输出的一个类,这个类定义如下:

public abstract class OutputStream extends Object implements Closeable, Flushable

首先我们发现OutputStream类实现了两个接口:Closeable、Flushable,这两个接口定义如下:在这里插入图片描述
有意思的是JDK1.7的时候引入了一个自动关闭机制,所以Closeable有多继承了一个

AutoCloseable接口:
public interface AutoCloseable {
	public void close() throws Exception;
}

但是OutputStream类是在 JDK1.0 的时候就提供了,这个类中定义close()与flush()
两个方法,所以以上的两个接口几乎 我们可以不用在意了。

在OutputStream类里面提供有三个重要的方法:

方法释义
public abstract void write(int b) throws IOException输出单个字节
public void write(byte[] b) throws IOException输出全部字节数组
public void write(byte[] b,int off,int len) throws IOException输出部分字节数组

根据API的学习我们知道OutputStream是属于抽象类,如果想要为抽象类进行对象的实例化操作,本次由于是文件操作,就要使用其子类FileOutputStream。

构造方法释义
public FileOutputStream(File file) throws FileNotFoundException创建或覆盖已有文件
public FileOutputStream(File file,boolean append) throws FileNotFoundException文件内容的追加

示例代码:文件输出

public class Test2 {
	public static void main(String args[]) throws Exception {
		// 1. 定义文件要输出的路径
		File file = new File("E:" + File.separator + "demo" 
				+ File.separator + "my.txt");
		// . 此时由于目录不存在,所以文件不能够输出,那么应该创建文件的目录
		if (file.getParentFile().exists()) { // 文件目录不存在
			file.getParentFile().mkdirs();  // 创建目录
		}
		// 2. 应该使用OutputStream和其子类进行对象的实例化,此时目录存在,文件还不存在
		OutputStream output = new FileOutputStream(file);  // 当然了  1,2步骤可以合为一步
		// 3. 进行文件内容的输出
		String str = "好好学习,天天向上";
		byte[] data = str.getBytes();    // 将字符串变为字节数组
		output.write(data);          // 将内容输出 
		// 4. 将输出资源关闭
		output.close();
 	}
}

以上是将整个字节数据的内容进行了输出(第三种方式),并且发现,如果此时要输出的文件不存在,那么会自动进行创建,对于输出操作整个OutputStream类里面一共定义有三个方法。

范例:采用单个字节的方式输出

for (int x = 0; x < data.length; x++) {
			output.write(data[x]);
}

范例:输出部分字节的内容

output.write(data, 1, 4);

但是每一次都是内容的覆盖感觉不是很好,我们还可以进行内容的追加,只需要更换FileOutputStream()构造方法即可。

OutputStream output = new FileOutputStream(file, true);

第三种方式,就是整个字节数据的输出。 只要是程序输出内容,都可以利用OutputStream类完成。

3.2、字节输入流InputStream

如果程序需要进行数据的读取操作,可以利用InputStream类实现,此类定义如下:

 public abstract class InputStream extends Object implements Closeable
  • 在InputStream里面也定义有数据读取的方法:
    • 读取单个字节:public abstract int read() throws IOException返回的是int类型和writer接收参数保持一致;(吃花生,吃完就是为空,怎么在数字上描述“空”,无数据可读,返回“-1”,不知道读取多少次用while
      |----------返回值:返回数据的字节内容,如果现在没有内容,返回 -1
    • 将读取的数据保存在字节数组里:public int read(byte[] b) throws IOException(一池子水,文明人拿杯子,30升杯子,10升水。返回10升,100升水,返回30
      |----------返回值:返回数据的读取长度,读取到结尾返回-1;
    • 将读取的数据保存在部分字节数组里:public int read(byte[] b,int off,int len) throws IOException
      |-----------返回值:读取的部分数据长度,如果已经读取到结尾了,返回 -1;

InputStream是一个抽象类,要读取数据要使用FileInputStream子类,而这个子类构造方法:

构造方法释义
public FileInputStream(File file) throws FileNotFoundException该文件由文件系统中的File对象file命名
public FileInputStream(String name) throws FileNotFoundException文件由文件系统中的路径名name命名

示例代码:向数组里面读取数据

public class Test2 {
	public static void main(String args[]) throws Exception {
		// 1. 定义文件要输出的路径
		File file = new File("E:" + File.separator + "demo" 
				+ File.separator + "my.txt");
		// 判断文件是否存在后才可以读取
		if (file.exists()) {  // 文件存在
			// 2. 使用InputStream 进行读取
			InputStream input = new FileInputStream(file);  // 这里的 file 也可以是本地的文件名  
			// 3. 进行数据读取
			byte[] data = new byte[1024]; // 准备一个1024的数组
			int len = input.read(data); // 将内容保存到字节数组之中
			// 4. 关闭输入流
			input.close();
			System.out.println("【" + new String(data, 0, len)+ "】");
		}
 	}
}
  • 由于一个文件有很多的字节数据,所以如果要读取肯定采取循环的方式,由于不确定循环次数,我们采用do…while与while两种方式实现

示例代码(重点):利用while循环读取

public class Test2 {
	public static void main(String args[]) throws Exception {
		// 1. 定义文件要输出的路径
		File file = new File("E:" + File.separator + "demo" 
				+ File.separator + "my.txt");
		// 判断文件是否存在后才可以读取
		if (file.exists()) {  // 文件存在
			// 2. 使用InputStream 进行读取
			InputStream input = new FileInputStream(file);
			// 3. 进行数据读取
			byte[] data = new byte[1024]; // 准备一个1024的数组
			int foot = 0;  // 表示字节数组的操作脚标
			int temp = 0;  // 表示接收每次读取的字节数组
			/*
			 * 第一部分:(temp = input.read()),将read读取的字节数组给了temp变量,
			 * 第二部分:判断读取的内容是否是-1 ,如果不是将内容保存
			 */
			while ((temp = input.read()) != -1) {
				data[foot ++] = (byte) temp;
			}
			// 4. 关闭输入流
			input.close();
			System.out.println("【" + new String(data, 0, foot)+ "】");
		}
 	}
}

范例:拷贝文本文件

public class CopyTxtTest {
	public static void main(String[] args) throws IOException {
		//封装数据源
		FileInputStream fis = new FileInputStream("d:\\窗里窗外.txt");
		//封装目的地
		FileOutputStream fos = new FileOutputStream("林青霞.txt");
		
		//读写数据
		//方式1:一次读取一个字节
//		int by;
//		while((by=fis.read())!=-1) {
//			fos.write(by);
//		}
		
		//方式2:一次读取一个字节数组
		byte[] bys = new byte[1024];
		int len;
		while ((len = fis.read(bys)) != -1) {
			fos.write(bys, 0, len);
		}
		
		//释放资源
		fos.close();
		fis.close();
	}
}

3.3、字符输出流:Writer

Writer类里面定义了有如下的输出方法(部分):

方法释义
public void write(char[] cbuf) throws IOException输出全部字符数组
public void write(String str) throws IOException输出字符串

示例代码:输出流

public class Test2 {
	public static void main(String args[]) throws Exception {
		// 1. 定义文件要输出的路径
		File file = new File("E:" + File.separator + "demo" 
				+ File.separator + "my.txt");
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		// 2. 实例化了Writer 类的对象
		Writer out = new FileWriter(file);
		// 3. 将内容输出
		String str = "好好学习,天天向上,每天起来,拥抱太阳";
		out.write(str); // 输出字符串数据
		// 4. 关闭输出流
		out.close();
 	}
}

可以发现Writer作为字符输出流,可以直接进行字符串的输出,这一点就比OutputStream强了。

3.4、字符输入流:Reader

Reader是进行字符数据读取的输入流,其本身也是一个抽象类:

public abstract class Reader extends Object implements Readable, Closeable

在Reader类里提供了一系列的read()方法:

  • 读取内容到字符数组:public int read(char[] cbuf) throws IOException
    |-----------返回值:表示赌球的长度,如果已经读取到结尾了返回-1;
    为Reader类实例化的可以使用 FileReader 子类完成。

示例代码:使用Reader读取 数据

public class Test2 {
	public static void main(String args[]) throws Exception {
		// 1. 定义文件要输出的路径
		File file = new File("E:" + File.separator + "demo" 
				+ File.separator + "my.txt");
		if (file.exists()) {
			// 2. 为     Reader类对象实例化
			Reader in = new FileReader(file);
			// 3. 进行数据读取
			char[] data = new char[1024];
			int len = in.read(data);
			// 4. 关闭输入流
			in.close();
			System.out.println(new String(data, 0, len));
		}
 	}
}

与字节输入流几乎是一样的,只是数据类型由byte换为了char而已。
在这里插入图片描述

3.5 字节流与字符流的转换

字符虽然要需要缓冲区进行处理,但是有一个问题千万不能忽略,字符输出流有个特点就是可以直接输出字符串,所以有的时候就不得不进行字节流与字符流的操作转换。
在java.io包里面提供了两个类:InputStreamReader、OutputStreamWriter。我们来看一下这两个类的定义以及构造方法:
在这里插入图片描述
范例:字节流和字符流的转换

 public class Test {
	public static void main(String[] args) throws IOException {
	
		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"),"UTF-8"); // 可以指定编码方式 
		//调用写数据的方法
		osw.write("你好");
		//释放资源
		osw.close();
		System.out.println("------------------------");
		
		
		InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"),"UTF-8");
		//读数据:一次读取一个字符数据
		int ch;
		while((ch=isr.read())!=-1) {
			System.out.print((char)ch);
		}
		//释放资源
		isr.close();
	}
}
  • 使用FileWriter,FileReader 简化了OutputStreamWriter,InputStreamReader的使用当然了,从继承的关系来看FileWriter,FileReader是OutputStreamWriter,InputStreamReader的子类,体现了一代更比一代强。

3.6 字节缓冲流

字节流一次读写一个数组的速度比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想,所以提供了字节缓冲区流

  • 字节缓冲流 :

    • BufferedOutputStream:字节缓冲输出流

    • BufferedInputStream:字节缓冲输入流

代码示例:字节缓冲流

public class BufferedStreamDemo {
	public static void main(String[] args) throws IOException {
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
		// 方式1:一次读取一个字节
		// int by;
		// while((by  =bis.read()) != -1) {
		// System.out.print((char) by);
		// }

		// 方式2:一次读取一个字节数组
		byte[] bys = new byte[1024];
		int len;
		while ((len = bis.read(bys)) != -1) {
			System.out.print(new String(bys, 0, len));
		}
		bis.close();
	}
}

3.7 字符缓冲流

  • BufferedWriter------构造BufferedWriter(Writer out)

将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

  • BufferedReader-----构造BufferedReader(Reader in)

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

示例代码:

public class Test {

        // 创建字符缓冲输入流对象
		BufferedReader br = new BufferedReader(new FileReader("BufferedStreamDemo.java"));
		
		//方式1:一次读取一个字符
//		int ch;
//		while ((ch = br.read())!=-1) {
//			System.out.print((char)ch);
//		}
		
		//方式2:一次读取一个字符数组
		char[] chs = new char[1024];
		int len;
		while((len = br.read(chs))!=-1) {
			System.out.print(new String(chs,0,len));
		}
		
		// 释放资源
		br.close();
	}
}

3.7.1 字符缓冲区流的特殊功能

  • BufferedWriter-----void newLine():写入一个行分隔符,这个行分隔符是由系统决定的

  • BufferedReader-----String readLine():包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null

范例:字符缓冲区流的特殊功能复制Java文件

public class CopyJavaTest {
	public static void main(String[] args) throws IOException {
		//封装数据源
		BufferedReader br = new BufferedReader(new FileReader("yarn-site.xml"));
		//封装目的地
		BufferedWriter bw = new BufferedWriter(new FileWriter("kangll.txt"));
		
		//读写数据
		String line;
		while((line = br.readLine()) != null) {
			bw.write(line);
			bw.newLine();
			bw.flush();
		}
		
		//释放资源
		bw.close();
		br.close();
		
	}
}

复制结果
在这里插入图片描述

3.8 文件应用小案例

要求:把集合中的学生对象数据存储到文本文件,把ArrayList集合中的学生数据存储到文本文件,每一个学生数据作为文件中的一行数据

代码示例:

public class ArrayListToFileTest {
	public static void main(String[] args) throws IOException {
		// 创建集合对象
		ArrayList<Student> array = new ArrayList<Student>();

		// 创建学生对象,自己可以自行创建
		list.add(new Student("it001", "科比", 45, "纽约"));
    	list.add(new Student("it002", "詹姆斯", 35, "洛杉矶"));
    	list.add(new Student("it003", "伦纳德", 27, "洛杉矶"));

		// 把学生对象添加到集合中
		array.add(s1);
		array.add(s2);
		array.add(s3);

		// 创建字符缓冲输出流对象
		BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));

		// 遍历集合,得到每一个学生对象,然后把该对象的数据拼接成一个指定格式的字符串写到文本文件
		for (Student s : array) {
			StringBuilder sb = new StringBuilder();
			sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.getAge()).append(",")
					.append(s.getCity());
			bw.write(sb.toString());
			bw.newLine();
			bw.flush();
		}
		
		//释放资源
		bw.close();
	}
}

反过来 ,把文本文件中的学生对象数据读取到集合

需求:从文本文件中读取学生数据到ArrayList集合中,并遍历集合,每一行数据作为一个学生元素。

代码示例 :

public class FileToArrayListTest {
	public static void main(String[] args) throws IOException {
		// 创建字符缓冲输入流对象
		BufferedReader br = new BufferedReader(new FileReader("students.txt"));

		// 创建集合对象
		ArrayList<Student> array = new ArrayList<Student>();

		// 读取数据,每一次读取一行数据,把该行数据想办法封装成学生对象,并把学生对象存储到集合中
		String line;
		while ((line = br.readLine()) != null) {
		
			String[] strArray = line.split(",");

			Student s = new Student();
			s.setSid(strArray[0]);
			s.setName(strArray[1]);
			s.setAge(Integer.parseInt(strArray[2]));
			s.setCity(strArray[3]);

			array.add(s);
		}

		// 释放资源
		br.close();

		// 遍历集合
		for (Student s : array) {
			System.out.println(s.getSid() + "---" + s.getName() + "---" + s.getAge() + "---" + s.getCity());
		}
	}
}

3.9 PrintWriter 使用(IO类的使用,Socket应用)

使用TCP的 Socket编程实现 客户端和 服务器的通信,客户端发送 用户名和密码 服务器 验证是否正确,并且返回验证信息。

客户端代码实现

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {
	public static void main(String[] args) throws IOException {
		// 创建客户端 Socket 对象
		Socket s = new Socket("172.20.10.3",2345);
		
		// 从控制台获取 用户名和密码
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		System.out.println("请输入用户名:");
		String username = br.readLine();
		System.out.println("请输入密码:");
		String password = br.readLine();
		
		// 获取输出流对象
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		
		// 写出数据
		out.println(username);
		out.println(password);
		
		// 获取输入流对象,将服务端的  信息返回
		BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
		// 获得服务器返回的值
		String result = serverBr.readLine();
		System.out.println(result);
		s.close();
	}
}

服务器代码实现

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;

public class Server {
	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(2345);
		while (true) {
			// 监听
			Socket s = ss.accept();
			// 获取输入流对象
			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
			String username = br.readLine();
			// String username1 = new BufferedReader(new InputStreamReader(s.getInputStream())).readLine();
			String password = br.readLine();
			boolean flag = false;
			List<User> users = UserDB.getUserInstance();
			User user = new User(username, password);
			if (users.contains(user)) {
				flag = true;
			}
			// 获取输出流对象
			PrintWriter out = new PrintWriter(s.getOutputStream(), true);
			// 返回判断信息
			if (flag) {
				out.println("登录成功!");
			} else {
				out.println("登录失败!");
			}
		}
	}
}

四、总结

总结上面几种流的应用场景:

  • FileInputStream / FileOutputStream 需要逐个字节处理原始二进制流的时候使用,效率低下
  • FileReader / FileWriter 需要组个字符处理的时候使用
  • InputStreamReader/OutputStreamReader , 字节和字符的转换桥梁,在网络通信或者处理键盘输入的时候用
  • BufferedReader/BufferedWriter ,
    BufferedInputStream/BufferedOutputStream ,
    缓冲流用来包装字节流后者字符流,提升IO性能,BufferedReader还可以方便地读取一行,简化编程。

参考:https://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html
https://www.cnblogs.com/wsg25/p/7499227.html
https://www.cnblogs.com/fysola/p/6123947.html