Java基础I/O流中的字节流与字符流

Java基础I/O流中的字节流与字符流

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

前面在讲Java基础File类操作时提到文件夹的复制,由于那篇文章没有将到I/O流,所以没讲文件的复制,本文会把这个坑填上并且讲一下JavaI中/O流。


提示:以下是本篇文章正文内容,下面案例可供参考

一、I/O是什么?

I/O即input和output,输入与输出。
Java是面向对象的编程语言,其很多的方法都可以在现实中找到相似的实物。
水厂和蓄水池
硬盘和程序
看上面的两幅图,我们找一下I/O流在现实中的对应来加强对I/O流的理解。
输入输出流就是一种通道(水管),连接着程序(水厂)和磁盘(蓄水池)中的文件(水箱),磁盘中的文件(水箱)通过I/O流(输/送水管)和程序(水厂)进行数据(水)的传输。

二、I/O流的分类

字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

本文只讲它们的常用子类

字节流字符流
输入流FileInputStreamFileReader
输出流FileOutputStreamFileWriter

三、字节与字符的区别

1、读写单位不同,字节流以字节为单位(一个字节为8bit位),字符流以字符(char)为单位
        这就导致字节流无法处理中文这种多字节的文字乱码的问题
2、操作对象不同,字节流处理字节类型的数据,字符流处理字符类型数据

四、字节转换为字符方法:

编码表:(建立字符与字节的映射关系) ASCII(借助一个字节存储数据,标准ASCII码的一个字节为7个bit位)

编码简介
ASCII借助一个字节存储数据,标准ASCII码的一个字节为7个bit位
GBK《汉字内码扩展规范》主要处理中文,借助两个字节存储数据)
utf-8对Unicode编码格式的优化,一个字节存储
Unicode2个字节,但不是处理中文的

五、解码、编码类

charSet 类
更多的时候使用的是:
	String的构造方法		String(byte[] bytes, Charset charset)
		通过使用指定的 charset 解码指定的字节数组构造一个新的 StringString类的方法		byte[] getBytes(Charset charset)String 使用给定的 charset 编码为一个字节序列,
		结果存放到一个新的字节数组。		
URLEncode类,URLDecode类 网页交互相关编码解码类

字节流

文件字节输入流:FileInputStream
1、构造方法

FileInputStream(File file)
	直接使用File完成构建
FileInputStream(String name)
	使用文件的路径进行构建,文件由文件系统中的路径名name命名

2、常用方法:

int read()
	从输入流读取一个字节的数据。
	返回读取到的字节数据的ASCII码值
int read(byte[] b)
	从输入流读取b.length长度的数据字节到字节数组。
	本质是int read(byte[] b,0,b.length)
	返回值为读取到的字节个数,如果为-1则证明读到了末尾。
int read(byte[] b, int off, int len)
	从输入流读取len长度的数据字节到字节数组,并指定数组b以off为起始位置装填数据。
	例:read(b,4,5);//数组内容{0,0,0,0,69,70,71,72,73,0}
void close()
	关闭此文件输入流并释放与流关联的任何系统资源。				

对这些方法的说明和补充

read()方法一次只能读取一个字节,返回的int类型的数值有2中情况:
	1、数据的ASCII码;
	2-1,即文件没有可读取的数据了
read(byte[] b)方法一次可以读取b.length长度的数据到byte[]数组中去。
	byte[]数组称为缓冲区,阅读源码可知使用byte[] b作为参数
		其本质也是以循环read()方法将读取到的数据装入数组b中
		所以缓冲区的意义:
			1、并不是减少运行读取操作(read()方法)的运行次数
				而是有了一个方便可控的读取区,可实现对读取长度的控制,
				并且数组的内容会在程序运行期间一直留存在缓冲区(byte[])中,方便操作
			2、缓冲区在内存中,和内存进行数据交换比直接和硬盘来的快的多,提高速度。
	而且数组中留存的数据就可以为程序所用,不需要每次要用了就要再读一遍

如果暂时无法理解那么参照示例代码自己敲一遍看看方法参数的改变会对结果有什么影响。
3、示例代码

FileInputStream fis = null;
try {
	fis = new FileInputStream(new File("E:\\test.txt"));
	byte[] bytes = new byte[10];//一个长度为10的字节数组充当缓冲区
	
	//假设文档内容为:12345abcde
	//循环read()方法读取整个文档,单独首次使用read()方法一定读取文档内部
	//的第一个字节,
	//read()方法读取完一个字字节会返回当前字符的ASCII码值,并且指向下一个
	//待读取的字节
	//读到文档末尾时会返回-1作为结束标志
	//-------------------------------------
	int len= 0;
	while ((len=fis.read())!=-1){
		System.out.print((char)len);
	}
	//-------------------------------------
	//或
	//-------------------------------------
	while ((len=fis.read(bytes))!=-1){
		for (byte b:bytes)
		System.out.print((char)b);
	}
	//-------------------------------------
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally {
//这里进行流的关闭,释放资源
	if (fis!=null){
		try {
			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

4、解决使用read(byte[] b)读取最后一次读取多读的问题

read(byte[] b)的本质之前说了是read(b,0,b.length)
	读取时10字节长度的数组读取了5字节的数据,那么后面的第5,6,7,8,9位的数据
	不会被刷新,依旧保存着上一次read()读取的数据
	假设文本内容为:“Hello World!”,共12个字节(空格也算一个字节)
	如果使用示例代码中的读取方式读取的结果为:
	Hello World!llo Worl
	原因是第一次向数组中装数据只能装10个字节
	byte[] b的内容为{'H','e','l','l','o',' ','W','o','r','l'}
	使用read(byte[] b)进行第二次读取的时候
	byte[] b的内容为{'d','!','l','l','o',' ','W','o','r','l'}
	从下标为2处开始就没有数据可以读,但是由于方法
	read(byte[] b)=read(b,0,b.length),所以0~b.length都需要装上数据
	即下标2之后的数据不会被清除,遍历的时候没有加以限制
	就导致了多读。

解决方法:

根据	
	read(byte[] b)方法的返回值(读取到的数据个数)new String(byte[] bytes, int offset, int length) 方法 根据bytes来构建字符串
	byte b = new byte[10];
	int len = 0;
	
	 while (-1 != (len = read(b))){
		System.out.println((new String(flush,0,len)));
	}
	/*
	len = read(b);//len为read()方法读取到的有效数据个数。
	*/

代码示例:

byte bytes = new byte[10];
int len = 0;
//这里将读取到的数据个数作为第三个参数加以限制即可解决
while((len = read(bytes,0,len))!=-1){
	for(byte b:bytes){
		System.out.print((char)b);
	}
}

文件字节输出流:FileOutputStream

1、构造方法:

FileOutputStream(File file) 
FileOutputStream(String name)
			
----下面的两个构造方法,可以设置是否向文件中追加数据--------
FileOutputStream(File file, boolean append) 
FileOutputStream(String name, boolean append) 
--------------------------------------------------------
true为追加模式,在文件的末尾写数据
	(理解为水厂向蓄水池的水箱中加水)
true为覆写模式,重写文件,对文件原内容进行覆盖
	(理解为水厂向蓄水池中的水箱加水前把水箱清空)

2、常用方法:

void write(int b)	调用一次写入一个数据字节
void write(byte[] b)	调用一次,可以把一个byte数组中的数据写入
void write(byte[] b, int off, int len)	调用一次,把b数组中的一部分数据写入
							
注意: FileOutputStream 会自动创建一个文件
	(如果文件不存在,但是文件的路径存在)

3、代码示例:

FileOutputStream fos=null;
try {
	fos=new FileOutputStream(new File(path));
	Byte[] bytes = {'a','b','c','d','e','f','g','h','i','j'};
	fos.write(str.getBytes());
	//可以在这里试试前面所讲的字符的编解码
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally {
	if (fos!=null){
		try {
			fos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

字节流总结

流程
	1、搞清楚是需要读文件还是写文件  ---> 选择什么流
	2、明确读/写哪个文件
	3、创建一个输入(输出)流的对象(目的是为了调用其中的read()(write())方法)
	4、调用读取(写入)方法获得(写入)数据
	5、关闭流资源 通过流对象来调用close方法(流关闭之后是不能够在操作的-读取(写入))
	6、字节流是无法读取中文的,因为read()方法是一个字节一个字节地读取,
		而中文无论在那种编码下都是2个字节或以上构成的,一个一个的读取就会造成乱码。
		中文的读写将使用字符流,字符流的使用和字节流十分相似。

字符流

文件输入字符流:FileReader
1、常用构造方法

FileReader(File file)File构建一个新的FileReader
FileReader(String fileName) 
	用文件名字构建一个新的FileReade

2、常用方法:

int read() 
	读取单个字符。
int read(char[] cbuf) (和字节流的byte[]区分)
	将字符读入一个数组。
abstract int read(char[] cbuf, int off, int len)
	将字符读入一个数组的一部分。
abstract void close()
	关闭流并释放与它相关联的任何系统资源。 

3、示例代码

FileReader:
FileReader fileReader = null;
try {
	fileReader = new FileReader(new File(path));
	char[] chars = new char[10];
		  
//   int len = fileReader.read();
//   System.out.println((char)len);
//
//   int len = 0;
//   while ((len=fileReader.read(chars))!=-1){
//   	for (char c:chars){
//    	    System.out.print(c);
//   	}
//	 }
	int len = 0;
	while ((len=fileReader.read(chars))!=-1){
		System.out.print(new String(chars,0,len));
	}
} catch (IOException e) {
	e.printStackTrace();
}finally {
	if (null!=fileReader){
		try {
			fileReader.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

文件输出字符流
1、构造方法

FileWriter(File file)File构建一个新的FileWriter
FileWriter(String fileName)FileName构建一个新的FileWriter
	
FileWriter(File file, boolean append)File构建一个新的FileWriter,指示是否附加写入的数据。  
FileWriter(String fileName, boolean append)FileName构建一个新的FileWriter,指示是否附加写入的数据。  

2、常用方法

void write(char[] cbuf) 
	写一个字符数组。  
abstract void write(char[] cbuf, int off, int len) 
	写入一个字符数组的一部分。  
void write(int c) 
	写一个字符。  
void write(String str) 
	写一个字符串。  
void write(String str, int off, int len) 
	写入字符串的一部分。  
abstract void close() 
	关闭流,并且进行一次刷新操作
abstract void flush() 
	刷新

3、示例代码

FileWriter fileWriter = null;
try {
	fileWriter = new FileWriter(path);
	fileWriter.write(str);
	fileWriter.flush();
} catch (IOException e) {
	e.printStackTrace();
}finally {
	if (null!=fileWriter){
		try {
			fileWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

字符流总结即注意事项:

IO流中每一个类都实现了Closeable接口,它们进行资源操作之后都需要执行close()方法将流关闭 。

字节流与字符流的不同之处在于:字节流是直接与数据产生交互,而字符流在与数据交互之前要经过一个缓冲区 。如果不进行flush()的话,数据会留在缓冲区。
	字节输出流  --》程序 --》文件
	字符输出流  --》程序 --》缓存 ---》文件
clos()方法也会进行一次flush()。
如果在程序中使用write()方法而不进行flush()或者close()的话,你会发现数据并没有写入文件中去。
	先使用write()方法将数据放入缓冲区
	再使用flush()将缓冲区的数据写入文件
flush()的使用时机:
	使用write()方法如果缓冲区被装满的话,会自动将缓冲区中的数据写入文件
					如果没装满而且需要写入文件的话就需要flush()
					所以在最后一步进行一次flush()即可
字符流的输出流可以直接写一个字符串

总结

字节流或字符流的选择:
  文本文件优先选择字符流,因为大概率会涉及中文的读写。
  除文本文件外的其他文件,例如图片文件、视频文件选择字节流,保证数据不失真。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值