Skip to content

IO流

3644字约12分钟

javaio

2024-10-24

1.定义

I/O流就是Java用来操作和传输数据的技术。(形象的把数据流比作水流,像通过某种管道运输)

2.分类

1.方向

以Java程序为基准

输入流 读入 Datasrc---->JavaProgram

输出流 写出 JavaProgram---->Datasrc

2.处理宽带

字节流

字符流

3.功能

节点流

处理流

3.四个抽象基类

输入输出
字节流InputStreamOutPutStream
字符流ReaderWriter

4.流的操作

1.字节输入流的操作

1.创建流

	private static void test1() {
		// try-resource写法,jdk8以后支持
		// 创建了字节流输入对象
		try(InputStream inputStream = new FileInputStream("./test/test.txt")) {
			// 读取字节流内容
//			System.out.println(inputStream.read());
			// 将读取的int数字转为char
//			System.out.println((char)inputStream.read());
			// 一次读取全部内容
			int out;
			while((out = inputStream.read()) != -1) {
				System.out.print((char)out);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

2.操作流

1.inputStream.read()读取字节流内容的一个字节

循环遍历一次读取全部内容

2.read(byte[])使用byte数组批量读取

3.inputStream.skip(int n)跳过n个字节读取

4.read(byte[],offset,len)使用byte数组批量读取并跳过offset个数组元素存储len个字节

	private static void test2() {
		try(InputStream inputStream = new FileInputStream("./test/test.txt")) {
			// read(byte[])使用byte数组批量读取
			// 读取并存储10个字节
			byte[] buffer = new byte[10];
//			inputStream.read(buffer);
			// read(byte[],offset,len)使用byte数组批量读取并跳过offset个数组元素存储len个字节
//			inputStream.read(buffer, 2, 2);
			// 跳过n个字节读取
			inputStream.skip(2);
			inputStream.read(buffer);
			for (int i = 0; i < buffer.length; i++) {
				System.out.print((char)buffer[i]);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

注:

1.每次按数组大小批量读取

2.返回数据表示成功读取到的字节数

3.数组不会被自动清空,上次读取到的数据依然存在

4.数据读取完毕返回-1

5.offset表示数组中存储的偏移值而不是读取指定的值,len表示读取长度

6.skip(long n)表示跳过n个字节开始读取

7.close()关闭流

8.输出流,字符流类似

2.字节输出流的操作

1.创建流

1.OutputStream outputStream = new FileOutputStream("./test/test1.txt") 文件不存在会自动创建,但不会创建路径

注:

1.构造方法参数可以是路径也可以是对应路径的文件对象

2.构造方法可以有两个参数,第二个为append的boolean值

true表示写入内容不会覆盖文件原本内容,false表示写入内容覆盖原本内容

默认为false

2.操作流

1.outputStream.write(int code)写入一个字节

2.outputStream.write(byte[] buffer)写入一个byte数组

3.outputStream.write(buffer,offset,len)写入并跳过offset个数组元素存储len个字节

4.outputStream.append(int code)在原来的内容上追加一个字符

5.outputStream.flush()强制将缓冲区内容写入到磁盘(安全写法)

6.outputStream.close()关闭流

	private static void test1() {
		try(OutputStream outputStream = new FileOutputStream("./test/test1.txt")) {
			// 文件不存在会自动创建,但不会创建路径
			// 写入一个字节
//			outputStream.write('a');
//			outputStream.write(65);
			// 写入一个byte数组
			byte[] buffer = {65,97,72,101};
//			outputStream.write(buffer);
			// 写入并跳过offset个数组元素存储len个字节
			outputStream.write(buffer,2,2);
			System.out.println("写入成功");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

3.字符输入流的操作

1.创建流

Reader reader = new FileReader("./test/test2.txt")

构造方法参数可以是路径也可以是对应路径的文件对象

2.操作流

1.reader.read()读取一个字符,结果为int

2.循环遍历一次读取全部内容

3.reader.read(char[] chs)以char数组形式读取文件内容

4.read(char[],offset,len)使用char数组批量读取并跳过offset个数组元素存储len个字节

5.reader.skip(int n)跳过n个字符读取

6.reader.close()关闭流

	private static void test1() {
		try(Reader reader = new FileReader("./test/test2.txt")) {
			// 读取一个字符,结果为int
//			System.out.println((char)reader.read());
			// 读取全部
			int out;
			while((out = reader.read()) != -1) {
				System.out.print((char)out);
			}
  		    // 以char数组形式读取文件内容
			char[] chs = new char[15];
			// 读取15个字符
//			reader.read(chs);
//			System.out.println(Arrays.toString(chs));
			// read(char[],offset,len)使用char数组批量读取并跳过offset个数组元素存储len个字节
//			reader.read(chs,2,5);
			// 跳过n个字符读取
//			reader.skip(3);
//			reader.read(chs);
//			System.out.println(Arrays.toString(chs));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

3.字符输出流的操作

1.创建流

Writer writer = new FileWriter("./test/test3.txt")

注:

1.构造方法参数可以是路径也可以是对应路径的文件对象

2.构造方法可以有两个参数,第二个为append的boolean值

true表示写入内容不会覆盖文件原本内容,false表示写入内容覆盖原本内容

默认为false

同字节流

2.操作流

1.writer.write('A')写入一个字符

2.writer.write(char[] chs)以字符数组形式写入

3.writer.write("你好")以字符串形式写入

4.writer.append('世')追加字符

5.writer.flush()强制从缓冲区写入磁盘

6.writer.close()关闭流

	private static void test1() {
		try(Writer writer = new FileWriter("./test/test3.txt")) {
			// 写入一个字符
//			writer.write('A');
			// 以字符数组形式写入
			char[] chs = {'','',''};
//			writer.write(chs);
			// 以字符串形式写入
			writer.write("你好");
			// 追加字符
			writer.append('');
			writer.append('');
			writer.append((char) 183);
			// 强制从缓冲区写入磁盘
			writer.flush();
			System.out.println("写入成功");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

4.流操作中的注意点

1.创建流对象之后在使用完流对象必须关闭

/*
1.当我们new一个java流对象之后,不仅在计算机内存中创建了一个相应类的实例对象。而且,还占用了相应的系统资源,比如:文件句柄、端口、数据库连接等。在内存中的实例对象,当没有引用指向的时候,java垃圾收集器会按照相应的策略自动回收,但是却无法对系统资源进行释放。
2.我们需要主动调用close()方法释放java流对象。
3.在java7之后,可以使用try-with-resources语句来释放java流对象,从而避免了try-catch-finally语句的繁琐,尤其是在finally子句中,close()方法也会抛出异常。
一般来说,需要自己close的东西,都是用了虚拟机之外的资源,例如端口,显存,文件等,虚拟机无法通过垃圾回收释放这些资源,只能显式调用close方法来释放。比如释放占用的端口,文件句柄,网络操作数据库应用等。
4.一般来说,IO操作的底层就是这个步骤进行实现的,只要存在读写操作,无论使用到的是缓存IO,文件IO,还是块IO,都是需要和计算机内文件打交道的。清理计算机硬件上面的垃圾,虚拟机的本职能就没有这个任务。
gc()有下面的特性:
1)gc只能释放内存资源,而不能释放与内存无关资源。
2)gc回收具有不确定性,你根本不知道它什么时候会回收。
而对于需要程序员手动回收的资源往往具有这样的特点:
1)资源开销大,不用需要立即释放;
2)资源是系统唯一的,不释放会导致别的程序也无法使用该资源。
也就是说,对于具有这些特点的资源就必须保证不使用后能够立即释放出这部分资源,而不能把这么重要的事情交给一个具有不确定性不靠谱的gc来完成。
*/

2.为避免使用try-catch-finally语句的繁琐在使用流操作时流对象的创建一般在try-resourse中,无论有无手动关闭流都会自动关闭流,从而释放系统资源

try-resourse从JDK1.8开始支持

实质是编译器自动生成了try-catch-finally语句

1.try-with-resources语句是一个声明一个或多个资源的 try 语句。一个资源作为一个对象,必须在程序结束之后随之关闭。

2.try-with-resources语句确保在语句的最后每个资源都被关闭 。任何实现了 java.lang.AutoCloseable的对象, 包括所有实现了 java.io.Closeable 的对象, 都可以用作一个资源。

5节点流和处理流

节点流

对应资源的流

文件资源(File)

内存资源(char、string、byte)

进程间的通讯

网络通讯

键盘输入

控制台输出

处理流

采用装饰器模式

1.缓冲流(Buffered-xxxx-In/OutputStream)

特点:带缓冲(读写效率高)它依托于字符输入输出流

1.创建流

它依托于字符输入输出流

Reader reader = new FileReader("./test/test4.txt"); BufferedReader bufferedReader = new BufferedReader(reader);

2.操作流

1.bufferedReader.readLine()读取一行

2.循环读取整个内容

3.bufferedReader.skip(2)跳过一行中的n个字符

	private static void test1() {
		try(
			Reader reader = new FileReader("./test/test4.txt");
			BufferedReader bufferedReader = new BufferedReader(reader);
			) {
			// 读取一行
//			System.out.println(bufferedReader.readLine());// 第一行
//			System.out.println(bufferedReader.readLine());// 第二行
//			System.out.println(bufferedReader.readLine());// 第三行......
			// 读取整个内容
//			String s = "";
//			while((s = bufferedReader.readLine()) != null) {
//				System.out.println(s);
//			}
			// 跳过一行中的n个字符
			bufferedReader.skip(2);
//			System.out.println(bufferedReader.read());
			System.out.println(bufferedReader.readLine());
//			bufferedReader.skip(3);
//			System.out.println(bufferedReader.readLine());
//			bufferedReader.skip(3);
//			System.out.println(bufferedReader.readLine());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

2.转换流(一定是字符流)

它依托于字节输入输出流

作用:字节流转换为字符流

字节流一般不需要转字符流(除非需要显式控制输)

字符集:

ISO-8859-1:西欧文字

中文:GB2312、GBK(简体中文国标)、BIG5(繁体中文,台湾)

UTF-8(1-4字节变长字符集)兼容世界各国语言,支持中文

1.输入转换流(InputStreamRander)

1.创建流

InputStream inputStream = new FileInputStream("./test/test.txt"); InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

2.操作流

1.inputStreamReader.read()读取一个字符

2.inputStreamReader.read(char[] chs)读取字符数组

3.System.in.read()从系统输入流读取

4.指定字符编码读取

构造方法第二个参数可以指定字符编码

inputStreamReader = new InputStreamReader(inputStream, "GBK");

	public static void test1() throws IOException {
		InputStream inputStream = new FileInputStream("./test/test.txt");
		InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
		
		//  	
//			System.out.println(inputStreamReader.read());
		// 读取字符数组
		char[] chs = new char[10];
//			inputStreamReader.read(chs);
//			System.out.println(Arrays.toString(chs));
		// 从系统输入流读取
//			System.out.println(System.in.read());
//			byte[] bytes = new byte[10];
//			System.in.read(bytes);
//			System.out.println(Arrays.toString(bytes));
//		inputStreamReader = new InputStreamReader(s
		// 指定字符编码读取
		inputStream = new FileInputStream("./test/test3.txt");
//		inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
		inputStreamReader = new InputStreamReader(inputStream, "GBK");
		inputStreamReader.read(chs);
		System.out.println(Arrays.toString(chs));
		inputStreamReader.close();
		inputStream.close();
	}

2.输出转换流(OutputStreamRander)

1.创建流

OutputStream outputStream = new FileOutputStream("./test/test5.txt"); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);

2.操作流

1.outputStreamWriter.write("你好")写出到文件

2.以指定编码写入

构造方法第二个参数可以指定字符编码

outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");

	private static void test1() throws IOException {
		OutputStream outputStream = new FileOutputStream("./test/test5.txt");
		OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
		// 写出到文件
//		outputStreamWriter.write("你好");
		// 以指定编码写入
		outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
		outputStreamWriter.write("你好");
		outputStreamWriter.flush();
		outputStreamWriter.close();
		outputStream.close();
		System.out.println("ok");
	}

3.转换

使用输入转换流(InputStreamRander)以指定字符编码读入再使用输出转换流(OutputStreamRander)以指定字符编码写出即可完成文件的编码转换

3.打印流(PrintStream)

系统打印流

1.System.err

2.System.out

特点:

1.重写了大量的print()方法

2.自身处理异常,不影响显示

3.自动执行flush()方法

1.创建流

OutputStream outputStream = new FileOutputStream("./test/test6.txt"); PrintStream printStream = new PrintStream(outputStream);

2.操作流

1.printStream.println("你好时间")写入一行字符到文件

2.通过设置系统打印流实现系统函数写入文件内容

PrintStream syso = System.out;

System.setOut(printStream); System.out.println("哈哈哈哈哈"); System.setOut(syso); System.out.println("切回来");

	private static void test1() throws IOException {
		OutputStream outputStream = new FileOutputStream("./test/test6.txt");
		PrintStream printStream = new PrintStream(outputStream);
		// 写入一行字符到文件
		printStream.println("你好时间");
		// 存储系统打印流
		PrintStream syso = System.out;
		// 设置系统打印流
		System.setOut(printStream);
		System.out.println("哈哈哈哈哈");
		System.setOut(syso);
		System.out.println("切回来");
		outputStream.close();
		printStream.close();
		System.out.println("ok");
	}

4.数据流(DataInputStream)

好处:非常方便的读写各种数据类型

不需要按单个字节进行读写操作。可以按照数据类型读写

1.数据输出流(DataInput/OutputStream)

1.创建流

使用文件创建输出流OutputStream out = new FileOutputStream("./test/test7.txt"); 使用系统输出流创建输出流OutputStream out = System.out; 使用byte数组流创建输出流ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(out);

2.操作流

1.dataOutputStream.writeChars("DataOutputStraem测试")写入指定数据类型的数据

2.dataOutputStream.writeByte(byte[] b)写入byte数组

	private static void test1() throws IOException {
//		OutputStream out = new FileOutputStream("./test/test7.txt");
		OutputStream out = System.out;
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(out);
		dataOutputStream = new DataOutputStream(arrayOutputStream);
		// 写入指定数据类型的数据
		dataOutputStream.writeChars("DataOutputStraem测试");
		// 写入byte数组
		String b = "1235698";
		dataOutputStream.writeBytes(b);
		byte[] r = arrayOutputStream.toByteArray();
		System.out.println(Arrays.toString(r));
		out.close();
		arrayOutputStream.close();
		System.out.println("ok");
	}

注:当使用系统输出流创建输出流时,进行的数据写入操作会直接显示在控制台

2.数据输入流(DataInputStream)

1.创建流

byte[] buf = {1,2,3}; ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(buf); DataInputStream dataInputStream = new DataInputStream(arrayInputStream);

2.操作流

1.dataInputStream.readByte()读取一个byte类型的数

2.dataInputStream.read(byte[] b)读取到byte数组

	private static void test1() throws IOException {
		byte[] buf = {1,2,3};
		ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(buf);
		DataInputStream dataInputStream = new DataInputStream(arrayInputStream);
		// 读取一个byte类型的数
//		System.out.println(dataInputStream.readByte());
//		System.out.println(dataInputStream.readByte());
//		System.out.println(dataInputStream.readByte());
		// 读取到byte数组
		byte[] r = new byte[20];
		dataInputStream.read(r);
		System.out.println(Arrays.toString(r));
	}

5.对象流(ObjectInput/OutputStream)

注意

需要序列化(串化)

序列化:把对象转换成数据流(一个个的字节)用来保存到磁盘或者网络上传输

反序列化:把字节流转换成内存中的对象

序列化接口:Serializable,标志性接口

表示该类的对象具有序列化和反序列化的能力不实现接口

在序列化过程中可能会产生java.io.NotSerializableException

只要实现了Serializable接口的类的对象都可以序列化

SerialVersionUID可防止读取不合适的数据

transient关键字

transient关键字修饰的变量(字段)在对象序列化时不进行序列化,可局部控制序列化的顺序

Externalizable接口

可指定某个字段序列化

	// 从文件读取对象
	private static void test1() throws IOException, ClassNotFoundException {
		InputStream inputStream = new FileInputStream("./test/studnet.dat");
		ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
//		// 读取文件中的student对象
//		Student student = (Student) objectInputStream.readObject();
//		System.out.println(student);
		Object students =  objectInputStream.readObject();
		System.out.println(students);
		objectInputStream.close();
		inputStream.close();
	}
	// 将对象写入文件
	private static void test2() throws IOException {
		OutputStream outputStream = new FileOutputStream("./test/studnet.dat");
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
		// 写入一个序列化的对象数据
		Student student = new Student(1001, "张三", 16);
//		objectOutputStream.writeObject(student);
		System.out.println("ok");
		// 写入一个list对象
		List<Student> students = new LinkedList<Student>();
		students.add(student);
		students.add(new Student(1002));
		students.add(new Student(1003));
		objectOutputStream.writeObject(students);
		objectOutputStream.close();
		outputStream.close();
		System.out.println("ok");
	}