《学习笔记Day20》IO:字符流;转换流【超详细笔记!!!附代码学习】

一、字符流

使用字节流出现的问题:

1、使用字节流拷贝字符
1)如果是纯粹的拷贝不会出现任何问题
2)如果拷贝中途掺杂人为阅读,就会因为拷贝到的内容字节的问题,解码时产生乱码,如果是纯英文,我们还能按照一个字节进行解析,如果是纯中文,并且平台是GBK字符集,中文可以按照两个字节解析,如果是UTF-8中文按照3个字节解析,但是,如果中英文混杂,就不能按照固定套路解析了

public static void main(String[] args) throws IOException {
		//字节流在纯拷贝的时候不会有问题
		//如果在拷贝过程中掺杂人为阅读,就会有乱码问题,因为读取的字节信息不完整
		FileInputStream fis = new FileInputStream("Demo01.txt");
		FileOutputStream fos = new FileOutputStream("Demo01_copy1.txt");
		//定义一个小数组
		byte[] arr = new byte[2];
		
		int len;
		
		while((len = fis.read(arr)) != -1) {
			//因为时文字所以使用String
			System.out.println(new String(arr));
			fos.write(len);
		}
		
		fis.close();
		fos.close();
	}

解决方案
1、出现乱码问题的原因:
(1)中英文混杂,每次不知道到底应该读取到少个字节才算一个完整的字符
(2)但是我们代码中每次读取多少个字节信息又是写死的

2、解决:
(1)可以动态判断要读取的内容是什么,再决定一次读取几个字节
(2)不妨直接使用字符流,不仅能够动态判断每个读取到的内容是中文还是英文,而且能够识别语种

(三)字符流的使用

1、顶层抽象父类:Reader、Writer
2、常用方法:
(

1)Reader:

1)read() 读取一个字符信息并且返回字符信息的码值,如果到文件末尾就返回-1
2)read(char[] arr)将一部分字符读取进数组,返回读取到的字符个数,到达文件末尾返回-1 (

2)Writer:

1)write(int c) 写出一个字符信息
2)write(String str) 写出一个字符串 3)write(char[] arr) 将一个数组中所有的字符信息写出
4)write(char[] arr, int offset, int len) 将一个数组中一部分的字节信息写出

3、由于Reader、Writer都是抽象类,无法实例化,所以需要实现子类创建对象调用方法
4、实现子类:
(1)字符输入流:FileReader
(2)字符输出流:FileWriter

public class TestClass {

	@Test
	public void test1() throws IOException {
		/*
		 * 字符流在拷贝过程中掺杂人为阅读可以完美解决乱码问题
		 * */
		FileReader fr = new FileReader("Demo01.txt");
		FileWriter fw = new FileWriter("Demo01_copy2.txt");
		
		int ch;
		
		while((ch = fr.read()) != -1) {
			System.out.println((char)ch);
			fw.write(ch);
		}
		
		fr.close();
		fw.close();
	}
	
	@Test
	public void test2() throws IOException {
		/*
		 * 从效率角度测试字符流文件拷贝是否很快,来印证具有缓冲区
		 * 
		 * 确实印证了具有缓冲
		 * 
		 * 从不刷新缓冲区的角度也印证了确实具有缓冲区,所以字符流效率才非常高
		 * */
		FileReader fr = new FileReader("Demo02.txt");
		FileWriter fw = new FileWriter("Demo02_copy1.txt");
		
		int ch;
		
		long start = System.currentTimeMillis();
		
		while((ch = fr.read()) != -1) {
			fw.write(ch);
		}
		
		long end = System.currentTimeMillis();
		
		fr.close();
		fw.close();
		
		System.out.println(end - start);
	}
}

(四)字符流的拷贝

1、使用字符流进行文件拷贝,发现效率很高,效率高是因为底层具有char[],作为缓冲区
2、字符流拷贝的必要性:如果在拷贝过程中掺杂了人为的阅读,就需要使用字符流;如果仅仅是单纯拷贝,没有必要使用字符流,直接使用字节流即可。因为在字符流拷贝的过程中,本质上还是在借助字节流拷贝,只是字节流将字节信息读取进内存后,额外做了一套编码、解码的操作,如果不掺杂人为阅读,这个编解码操作是多余的,会较低系统效率。
在这里插入图片描述

(五)字符流是否能够拷贝非纯文本文件

1、非纯文本文件:文件中可以存储非字符的内容,例如:图片、视频、音频等
2、使用字符流不能拷贝非纯文本文件
3、原因:

当字符流借助字节流拷贝字节信息到内存之后,会先将字节信息按照字符集进行解码,但是如果拷贝的是一个图片,并不存在字符信息,一定按照字符集解析的时候,就会损坏原来的字节信息。进过一套编解码操作,原来的字节信息就损坏了,目标文件中得到的字节信息,可能与源文件不符,或者缺失,非纯文本文件不能正常显示,所以不能使用字符流拷贝非纯文本文件。

(六)字符流使用小数组拷贝

1、使用FileReader中的read(char[] arr)方法,可以一次性将多个字符读取到数组中,返回的值表示本次读取到的有效字符个数,到达文件末尾返回-1
2、使用FileWriter中write(char[] cbuf, int off, int len)方法,可以一次性输出多个字符,当我们上一次读取到的字符有多少个有效的,就输出多少个有效的
3、说明:不使用小数组拷贝,发现效率也非常高,是因为在FileWriter中,也有一个缓冲区数组,所以写出效率就比较高。使用小数组之后,效率可以变得更高

(七)高效缓冲字符流

1、顶层抽象父类:BufferedReader、BufferedWriter

2、BufferedReader、BufferedWriter本身也不具备读写功能,而是对具有读写功能流对象的一个加强,并且他们分别为Reader和Writer的子类,所以可以直接使用其父类的方法

3、高效的原因:

(1)BufferedReader:每次调用read方法,只有第一次从磁盘读取8192个字符,存到该对象的缓冲区数组中,将一个字符返回给调用者,再次调用read方法的时候,就不需要访问磁盘了,而是从缓冲数组中获取一个字符,效率提升。
(2)BufferedWriter:每次调用write方法,不会直接将字符输出到磁盘中,而是将字符存入字符数组中,等到缓冲区存满,才访问一次磁盘,减少了和磁盘的交互,提高了效率

4、特有方法:

(1)BufferedReader:readLine() 一次读取一个文本行。到达文件末尾返回null
(2)BufferedWriter:newLine() 换行

@Test
	public void bufferedTest() throws IOException {
		BufferedReader br = new BufferedReader(new FileReader("Demo03.txt"));
		BufferedWriter bw = new BufferedWriter(new FileWriter("Demo03_copy.txt"));
		
		//int i;
		String str;
		
		long start = System.currentTimeMillis();
		
		while((str = br.readLine()) != null) {
			bw.write(str);
			bw.newLine();
		}
		
		long end = System.currentTimeMillis();
		
		br.close();
		bw.close();
		
		System.out.println(end - start);
	}

(八)练习

键盘录入一个文件夹的路径,将改文件反转
反转:第一行变成最后一行,最后一行变成第一行

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

/**
 * 键盘录入一个文件的路径,将该文件反转
	反转:第一行变成最后一行,最后一行变成第一行
 * 
 * 思路:反转,在数组,集合,字符串缓冲区中接触过
 * 		结合反转数组的思路,第一行变最后一行,最后一行遍地一行,所以,行作为元素,被操作
 *      进而,我们读取文件的时候按行来读
 * 		因为要结合反转,所以按行读取后,存入一个容器,因为文件行数不一致,所以最好使用集合
 * 		
 * 
 * 注意事项:使用IO流,遵循晚创建早关闭的原则
 * @author Zihuatanejo
 *
 */
public class Demo02_Exercise {

	public static void main(String[] args) throws IOException {
		File filePath = getFilePath();
		System.out.println(filePath);
		
		//创建集合,用于将文件中的每一行作为一个元素,将来反转集合,就将行的关系反转了
		ArrayList<String> list = new ArrayList<>();
		
		//1.使用高效缓冲字符输入流按行读取文件
		BufferedReader br = new BufferedReader(new FileReader(filePath));//1.java
		
		String str;
		
		while((str = br.readLine()) != null) {
			list.add(str);
		}
		
		br.close();
		
		//2.反转集合
		Collections.reverse(list);
		
		//使用高效缓冲字符输出流将反转后的行输出并且换行
		BufferedWriter bw = new BufferedWriter(new FileWriter(filePath));//1.java
		
		//3.遍历集合,获取反转后的每一行
		for (String line : list) {
			bw.write(line);
			bw.newLine();
		}
		
		bw.close();
	}
	
	public static File getFilePath() {
		Scanner sc = new Scanner(System.in);
		
		System.out.println("请录入一个文件的路径:");
		
		while (true) {
			
			String strPath = sc.nextLine();
			
			//1.将String的文件路径封装为File对象
			File file = new File(strPath);
			
			if (file.isFile()) {
				return file;
			} else {
				System.out.println("重录:");
			} 
		}
	}
}

二、转换流

(一)编码表
1、GBK:国标码,定义了英文和中文字符。在GBK编码表中,英文占1个字节,中文占2个字节

2、UTF-8:万国码,定义了全球所有语言符号,英文占1个字节,中文占3个字节

(二)转换流

1、InputStreamReader 是字节流通向字符流的桥梁,可以指定编码形式的字符集
(1)构造方法:InputStreamReader(InputStream in, String charsetName)
(2)创建一个转换流对象,可以把将来方法中接受到的字符,通过指定的charsetName解码成字节信息
2、OutputStreamWriter 是字符流通向字节流的桥梁,可以指定编码形式的字符集
(1)构造方法:OutputStreamWriter(OutputStream out, String charsetName)
(2)创建一个转换流对象,可以把将来内存中的字符,按照指定的字符集编码,将编码后的字节信息输出到指定文件中
在这里插入图片描述
4、总结:在跨字符集拷贝文件的时候,要想不出现乱码,读取的时候,源文件是什么字符集,就按照什么字符集去解码,输出到时候,目标文件是什么字符集,就按照什么字符集编码,之后输出对应的字节信息。就不会造成乱码

public class Demo03_Transition {

	public static void main(String[] args) throws IOException {
		//InputStreamReader isr = new InputStreamReader(new FileInputStream("x.txt"), "GBK");
		
		FileReader fr = new FileReader("x.txt");
		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("y.txt"), "utf-8");
		
		int i;
		
		while((i = fr.read()) != -1) {
			osw.write(i);
		}
		
		fr.close();
		osw.close();
	}

	public static void test2() throws FileNotFoundException, IOException {
		FileReader fr = new FileReader("x.txt");
		FileWriter fw = new FileWriter("y.txt");
		
		int i;
		
		while((i = fr.read()) != -1) {
			fw.write(i);
		}
		
		fr.close();
		fw.close();
	}
	
	//因为x.txt是GBK编码,每个汉字占2个字节,一共四个汉字有8个字节
	//y.txt是utf-8编码,每个汉字占3个字节
	//直接使用字节流将原本8个字节拷贝到y.txt中,如果正常解析4个汉字要12个字节,但是现在只有8个,所以乱码
	public static void test1() throws FileNotFoundException, IOException {
		FileInputStream fis = new FileInputStream("x.txt");
		FileOutputStream fos = new FileOutputStream("y.txt");
		
		int i;
		
		while((i = fis.read()) != -1) {
			fos.write(i);
		}
		
		fis.close();
		fos.close();
	}
}

三、Properties类

(一)概述
1、Properties对象表示一个持久的属性集。

(1)属性集:还是一个双列集合,表示的是属性名和属性值的对应关系
(2)持久:可以将磁盘上存储的文件用流读取进Properties,也可以将Properties中的属性集通过流输出到文件中

2、Properties没有泛型,是因为Properties的键值全都是String,所以不需要写泛型
3、Properties是Hashtable的子类,也可以充当普通的map集合

**

(二)Properties中的特有方法

**

1、String getProperty(String key) 根据字符串类型的键,获取其对应的字符串的值
2、setProperty(String key, String value) 调用Hashtable的put方法
3、stringPropertyNames() 返回键集

public class Demo03_Properties {

	public static void main(String[] args) {
		test1();
		
	}

	public static void test2() {
		Properties p = new Properties();
		
		p.put("className", "com.offcn.demos.Person");
		p.put("name", "zhangsan");
		p.put("age", "23");
		
		System.out.println(p.getProperty("className"));
	}

	public static void test1() {
		//Properties实际上什么类型的键值对都能存储
		//Properties一般不会当做map来使用
		//Properties更多场景下实在加载配置文件,配置文件中的内容都是字符串,所用键值对都作为字符串
		Properties p = new Properties();
		
		p.put(123, 3.5);
		p.put("北京", "1");
		p.put("郑州", "2");
		p.put("兰州", "3");
		p.put("额尔古纳", "4");
		p.put("银川", "13");
		
		//Set<Entry<Object,Object>> set = p.entrySet();
		
		Set<String> set = p.stringPropertyNames();
		
		Iterator<String> it = set.iterator();
		
		while(it.hasNext()) {
			String key = it.next();
			
			System.out.println(key + "..." + p.getProperty(key));
		}
	}
}

(三)Properties中的配置文件交互方式

1、load(InputStream inStream) 将配置文件中的键值对信息读取到Properties对象中
2、store(OutputStream out, String comments) 将Properties对象中的属性集和制定的注释输出到配置文件中

public class Demo04_LoadPropertiesFile {

	public static void main(String[] args) throws FileNotFoundException, IOException {
		Properties p = new Properties();
		
		//1.使用load方法将属性集加载到Properties对象中
		p.load(new FileInputStream("config.properties"));
		
		//2.使用getProperty(String key)获取属性集中键所对应的值
		System.out.println(p.getProperty("className"));
		System.out.println(p.getProperty("name"));
		System.out.println(p.getProperty("age"));
		
		//修改配置文件
		//3.针对Properties对象中的属性集做出修改
		p.setProperty("name", "lisi");
		p.setProperty("age", "24");
		
		//4.store(OutputStream os, String comments)将配置文件中的属性集进行修改
		p.store(new FileOutputStream("config.properties"), "update name zhangsan to lisi, age 23 to 24");
		
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿福真的不想掉头发

大爷?赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值