IO流(字节流、字符流、缓冲流)

IO流

1. 什么是IO

生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。

我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input 和输出output ,即流向内存是输入流,流出内存的输出流。

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

2. IO的分类

2.1 根据数据的流向分为:输入流和输出流。

输入流 :把数据从其他设备上读取到内存中的流。

输出流 :把数据从内存 中写出到其他设备上的流。

2.2 格局数据的类型分为:字节流和字符流。

字节流 :以字节为单位,读写数据的流。

字符流 :以字符为单位,读写数据的流。

3. IO的流向说明图解

4. 顶级父类们

输入流 输出流

字节流 字节输入流 InputStream

字节输出流 OutputStream

字符流 字符输入流 Reader

字符输出流 Writer

以Stream后缀结尾就是字节流

以er后续结尾的就是字符流

字节流

1. 一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

小贴士

字节流可以操作任何格式的数据。

2. 字节输出流【OutputStream】

java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

public void close() :关闭此输出流并释放与此流相关联的任何系统资源。

public void flush()刷新此输出流并强制任何缓冲的输出字节被写出

public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。

public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。

public abstract void write(int b) :将指定的字节输出流。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

public void test() throws IOException {
		/*
		 1 写一个a.txt 到 c:/a.txt 写入内容为 一个字节,一个字节数组
		 */
		File file = new File("c:/a.txt");
		//判断
		if(!file.exists()) {
			file.createNewFile();
		}
		
		//创建输出流对象
		OutputStream os = new FileOutputStream(file);
		//写出一个字节的方法
		os.write(97);
		os.write(98);
		
		System.out.println("写出了一个字节");
		//写出一个字节数组
		byte[] b = {99,100,101,102,103};//98是b
		os.write(b);
		System.out.println("写出一个字节数组");
		
		//从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
		os.write(b, 2, 3);//abcdefgefg
	}
}
3. 【FileOutputStream】类

OutputStream有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

构造方法

public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件

public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

构造举例,代码如下:

public void test2() throws IOException {
		//写一个hello追加到a.txt的末尾处
		File file = new File("c:/a.txt");
		//输出流对象
		//FileOutputStream(File file, boolean append) 
		//OutputStream os = new FileOutputStream(file, true);
		OutputStream outputStream=new FileOutputStream(file);//会清空原来的文本数据
		//定义一个字符串
		String str = "hello";
		byte[] b = str.getBytes();
		//直接写一个字节数组
		os.write(b);
		
		//换行
		os.write("\r\n".getBytes());
		os.write(98);//abchellohello
		             //b
	}
小贴士:

虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。

流操作完毕后,必须释放系统资源,调用close方法,千万记得。

在JDK7以前和JDK7以后是两种方式释放系统资源的

JDK7前处理

之前的入门练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try…catch…finally 代码块,处理异常部分,代码使用演示:

 catch (Exception e) {
			e.printStackTrace();
		}finally {//总有一部分程序员会忘记在这里写上finally
			//能否最后关闭? 不能!!
			System.out.println("关闭之前打印...");
			r.close();
		}

JDK7的处理(扩展知识点)

还可以使用JDK7优化后的try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

try (创建流对象语句,如果多个,使用’;'隔开) {

​ // 读写数据

} catch (IOException e) {

​ e.printStackTrace();

}

public void test3() throws IOException {
		File file = new File("c:/test.txt");
		//字符输入流
		
		//try(流对象){}  try...with...resource语法,自动关闭流对象,作用只在try块里面
		try(Reader r = new FileReader(file)) {
			//Map
			Map<String,Integer> map = new HashMap<>();
			
			int len;
			while((len = r.read()) > 0) {
				char c = (char)len;
				//判断
				if(map.containsKey(c+"")) {
					map.put(c+"", map.get(c+"") + 1);
				}else {
					map.put(c+"", 1);
				}
			}
			
			int i = 5/0;
			
			//打印
			Set<String> keySet = map.keySet();
			for (String key : keySet) {
				System.out.println("字符:" + key + "出现" + map.get(key)+"次");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

数据追加续写

经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?

答:public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。

public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:

public void test2() throws IOException {
		//写一个hello追加到a.txt的末尾处
		File file = new File("c:/a.txt");
		//输出流对象
		//FileOutputStream(File file, boolean append) 
		OutputStream os = new FileOutputStream(file, true);//加ture后不会清除原来的数据,表示在后尾追加内容
		//OutputStream outputStream=new FileOutputStream(file);//会清空原来的文本数据
		//定义一个字符串
		String str = "hello";
		byte[] b = str.getBytes();
		//直接写一个字节数组
		os.write(b);
		
		//换行
		os.write("\r\n".getBytes());
		os.write(98);//abchellohello
		             //b
	}
小贴士换行符:

Windows系统里,换行符号是\r\n 。把

回车符\r和换行符\n :

回车符:回到一行的开头(return)。

换行符:下一行(newline)。

系统中的换行:

Windows系统里,每行结尾是 回车+换行 ,即\r\n;

Unix系统里,每行结尾只有 换行 ,即\n;

Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。

4. 字节输入流【InputStream】

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  1. public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

  2. public abstract int read(): 从输入流读取数据的下一个字节。

  3. public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

package com.gec.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import org.junit.Test;

public class InputStreamDemo2 {
	
	@Test
	public void test() throws IOException {
		//创建文件实例
		File file = new File("c:/a.txt");
		
		//创建输入对象
		InputStream is = new FileInputStream(file);
		//输出
		System.out.println(is);
		
		//read() 每次只返回一个字节
		// is.read():会自动提升为int
		int hasRead = is.read();
		System.out.println((char)hasRead);//a
		System.out.println((char)is.read());//b
		System.out.println((char)is.read());//c
		System.out.println(is.read());//-1 代表读取到内容的末尾
	}
	
	@Test
	public void test2() throws IOException {
		//创建文件实例
		File file = new File("c:/a.txt");
		
		//创建输入对象
		InputStream is = new FileInputStream(file);
		//输出
		System.out.println(is);
		
		int b;
		//循环
		/*
		 * is.read() :每次只读一个字节,然后赋值给b
		 */
		//while((b= is.read()) != -1) {
		while((b = is.read()) >0) {	
			System.out.println((char)b);
		}
	}
}

例题:

		//要copy的文件
		File srcFile =new File("d:/s.jpg");
		
		File desFile = new File("d:/s2.jpg");
		
		//输入流
		InputStream is = new FileInputStream(srcFile);
		
		//输出流对象
		OutputStream os = new FileOutputStream(desFile);
		
		int hasRead = 0;
		byte[] b = new byte[1024];
		
		int count = 0;
		/*
		 is.read(b):每次读1024字节赋值给hasRead
		 第14次读取的字节:13741-13312 = 429
		 */
		while((hasRead = is.read(b)) != -1) {
			//每次写一个字节到b.txt中
			os.write(b,0,hasRead);
			count++;
		}
		System.out.println("一共读写了" + count + "次");
		System.out.println("写好了");
5. 【FileInputStream】类

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

构造方法

FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException 。

InputStream is = new FileInputStream(srcFile);
小贴士:

使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。

.io.FileInputStream 类是文件输入流,从文件中读取字节。

构造方法

FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException 。

InputStream is = new FileInputStream(srcFile);
小贴士:

使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。

1.字符流

为什么要有字符流?

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。

Java中字符是采用Unicode标准(内存中),Unicode 编码中,一个英文为一个字节,一个中文为两个字节。

而在UTF-8编码中,一个中文字符是3个字节。例如下面图中,“云深不知处”5个中文对应的是15个字节:-28-70-111-26-73-79-28-72-115-25-97-91-27-92-124

如果使用字节流处理中文,如果一次读写一个字符对应的字节数就不会有问题,一旦将一个字符对应的字节分裂开来,就会出现乱码了。为了更方便地处理中文这些字符,Java就推出了字符流。以字符为单位读写数据,专门用于处理文本文件。

1. 字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

public void close() :

关闭此流并释放与此流相关联的任何系统资源。

public int read():

从输入流读取一个字符。

public int read(char[] cbuf):

从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。

读取字符数据

读取字符:read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取,

小贴士:虽然读取了一个字符,但是会自动提升为int类型。

使用字符数组读取:read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1

//创建File实例
		File file = new File("d:/abc.txt");
		//创建字符输入流对象
		Reader r = new FileReader(file);
		
		//调用read
		int hasRead = r.read();
		System.out.println((char)hasRead);
		hasRead = r.read();

		//使用char[]数组来读取
	  int len = 0;
		char[] cs = new char[1024];
		while((len = r.read(cs)) >0) {
			//组装字符串
			String str = new String(cs,0,len);
			System.out.println(str);
		}

StringBuffer

public void test3() throws IOException {
		//组装一个字符串
		StringBuffer sb = new StringBuffer();
		sb.append("从明天开始");
		//sb.append("一夜暴富");
		//sb.append("财富自由");
		//海子
		sb.append("喂马,辟柴");
		sb.append("面朝大海,春暖花开");
		
		StringReader reader = new StringReader(sb.toString());
		//使用StringReader读取这个sb,把这个字符串输出来
		char[] cs = new char[1024];
		int hasRead = 0;
		while((hasRead = reader.read(cs)) >0) {
			System.out.println(new String(cs,0,hasRead));
		}
		
	}

2. FileReader类

java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

  1. FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
    2. FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
    当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
//创建File实例
   	File file = new File("d:/abc.txt");
   	//创建字符输入流对象
   	Reader r = new FileReader(file);
   	Reader r = new FileReader("d:/abc.txt");
小贴士:

字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。

3.字符输出流【Writer】

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  1. void write(int c) 写入单个字符。

  2. void write(char[] cbuf) 写入字符数组。

  3. abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引, len写的字符个数

  4. void write(String str) 写入字符串。

  5. void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。

  6. void flush() 刷新该流的缓冲。

  7. void close() 关闭此流,但要先刷新它。

@Test
	public void test() throws IOException {
		//写若干个字符到d:/ab.txt中  你 你好  你们好
		File file = new File("d:/ab.txt");
		//创建字符输出流对象
		Writer w = new FileWriter(file);
		
		//调用方法 写一个int
		w.write(97);//a
		
		//写一个字符出去
		w.write('你');
		
		//写一个字符数组出去
		char[] cs = {'你','好'};
		w.write(cs);
		
		//写一整个字符串出去
		w.write("你们好");
		
		//定义一个字符串
		char[] cs2 = {'你','好','中','国','人'};
		//写入指定数组,从0开始,写3个
		w.write(cs2,0,3);
		
		//刷新
		w.flush();
		
		System.out.println("写出ok...");
	}

4. FileWriter类

java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

构造方法

  1. FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。

  2. FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

File file = new File("d:/ab.txt");
		//创建字符输出流对象
		Writer w = new FileWriter(file);
小贴士:

虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。

未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。

关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

flush() :刷新缓冲区,流对象可以继续使用。

close ():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流

字符串输出流

StringWriter

继承Writer,处理字符串的流

2.属性集(Properties)

1. 概述

java.util.Properties 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties 方法就是返回一个Properties对象。

注意:在泛型异常笔记也有一些概述代码

2. Properties类

构造方法

public Properties() :创建一个空的属性列表。

基本的存储方法

public Object setProperty(String key, String value) : 保存一对属性。

public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。

public Set stringPropertyNames() :所有键的名称的集合。

与流相关的方法

public void load(InputStream inStream): 从字节输入流中读取键值对。

参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。文本数据格式:

filename**=**a.txt

length**=**209385038

location**=**D:\a.txt

加载代码演示:

小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。

具体演示代码如下:

package com.gec.ioex;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;
import org.junit.Test;
public class PropertiesDemo2 {
	@Test
	public void test() {
		Properties p = new Properties();	
		//  /test.properties eclipse下的src下的文件  idea: 建议在工程创建一个文件夹,后面给文件夹标志为resource,灰黄色
		// idea:test.properties直接放文件夹里面  读取文件时不要加斜杠
		try(InputStream is = PropertiesDemo2.class.getResourceAsStream("/test.properties")){
			
			//调用load
			p.load(is);
			
			//获取属性集
			Set<String> propertyNames = p.stringPropertyNames();
			for (String name : propertyNames) {
				System.out.println(name + "=" + p.getProperty(name));
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

3.缓冲流

1. 概述

我们知道,程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。

联想一下生活中的例子,我们搬砖的时候,一块一块地往车上装肯定是很低效的。我们可以使用一个小推车,先把砖装到小推车上,再把这小推车推到车前,把砖装到车上。这个例子中,小推车可以视为缓冲区,小推车的存在,减少了我们装车次数,从而提高了效率

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

字节缓冲流:BufferedInputStream(创建一个 新的缓冲输入流),BufferedOutputStream(创建一个新的缓冲输出流。)

字符缓冲流:BufferedReader,BufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

2. 字节缓冲流构造方法

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

3. 字符缓冲流构造方法

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

字符流特有方法。

BufferedReader:public String readLine(): 读一行文字。

BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

缓冲流总体代码:

package com.gec.ioex;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.junit.Test;

public class CopyJdkDemo4 {

	@Test
	public void test() {
		File srcFile = new File("D:\\JAVASE\\day15\\jdk-8u191-windows-x64.exe");
		File desFile = new File("D:\\JAVASE\\day15\\b.exe");
		File desFile2 = new File("D:\\JAVASE\\day15\\c.exe");
		
		//时间戳
		long start = System.currentTimeMillis();
		//使用
		copyFile(srcFile,desFile);
		
		//copyFile2(srcFile,desFile2);
		
		//缓冲流copy
		long end = System.currentTimeMillis();
		
		System.out.println("copy jdk使用时间 :" + (end -start) + "毫秒");
	}
	
	private void copyFile2(File srcFile, File desFile) {
		try(
			InputStream is = new FileInputStream(srcFile);	
			//带缓冲的输入流
			BufferedInputStream bis = new BufferedInputStream(is);
			OutputStream os = new FileOutputStream(desFile);
			//带缓冲的输出流
			BufferedOutputStream bos = new BufferedOutputStream(os)
			) {
			int len = 0;
			byte[] b = new byte[1024];
			while((len = bis.read(b)) >0) {
				bos.write(b, 0, len);
			}
			
			bis.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void copyFile(File srcFile, File desFile) {
		try(
			InputStream is = new FileInputStream(srcFile);	
			OutputStream os = new FileOutputStream(desFile)
			) {
			int len = 0;
			byte[] b = new byte[1024];
			while((len = is.read(b)) >0) {
				os.write(b, 0, len);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

写入和读取对象

写入

读取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值