java io字符流_java基础io流——字符流的变革(深入浅出)

乱码导火索:

在io流里,先诞生了字节流,但是字节流读取数据会有乱码的问题(读中文会乱码)。比如:

FileInputStream fis = new FileInputStream("a.txt");

// int by = 0;

// while ((by=fis.read() )!= -1) {

// System.out.print((char)by);//bcdbcdbcdbcdbcdbcdhello 中�

// //因为还正常,中文就乱码了,有什么办法解决吗,有,就是有点麻烦

// }

从文件中读取中文会有乱码,当然字节流有解决措施。

FileInputStream fis = new FileInputStream("a.txt");

byte[] bytes = new byte[1024];

int len = 0;

while ((len = fis.read(bytes)) != -1) {

System.out.println(new String(bytes,0,len));//bcdbcdbcdbcdbcdbcdhello 中国

//查看new String()的源码,this.value = StringCoding.decode(bytes, offset, length);

//点进decode,循序渐进发现,默认编码是UTF-8

//通过源码,也看到了这个方法public String(byte bytes[], int offset, int length, String charsetName)

}

但是解码这并不是字节流做的,而是String的功能。查看String的源码,构造方法有解码功能,并且默认编码是utf-8。

聊到了乱码,我觉得有必要聊一下编码的知识。

String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组

byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组

编码:把看得懂的变成看不懂的

String -- byte[]

解码:把看不懂的变成看得懂的

byte[] -- String

因此字节流读取的数据是编码过的数据,我们解码就行了。

编码问题简单,只要编码解码的格式是一致的。

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。

ASCII:美国标准信息交换码。

用一个字节的7位可以表示。

ISO8859-1:拉丁码表。欧洲码表

用一个字节的8位表示。

GB2312:中国的中文编码表。

GBK:中国的中文编码表升级,融合了更多的中文文字符号。

GB18030:GBK的取代版本

BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”。

Unicode:国际标准码,融合了多种文字。

所有文字都用两个字节来表示,Java语言使用的就是unicode

UTF-8:最多用三个字节来表示一个字符。

UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:

它将Unicode编码为00000000-0000007F的字符,用单个字节来表示�

它将Unicode编码为00000080-000007FF的字符用两个字节表示

它将Unicode编码为00000800-0000FFFF的字符用3字节表示

示例:

String s = "你好";

byte[] bytes1 = s.getBytes();

System.out.println(Arrays.toString(bytes1));//[-28, -67, -96, -27, -91, -67] 默认编码utf-8

byte[] bytes2 = s.getBytes("GBK");

System.out.println(Arrays.toString(bytes2));//[-60, -29, -70, -61]

byte[] bytes3 = s.getBytes("UTF-8");

System.out.println(Arrays.toString(bytes3));//[-28, -67, -96, -27, -91, -67]

String s1 = new String(bytes1);

System.out.println(s1);//你好

String s2 = new String(bytes2,"GBK");

System.out.println(s2);//你好

String s3 = new String(bytes2,"gbk");

System.out.println(s3);//你好

String s4 = new String(bytes3);

System.out.println(s4);//你好

String s5 = new String(bytes3,"gbk");

System.out.println(s5);//浣犲ソ

虽然字节流有解决乱码的方案,但并不方便,所以java io流就设计出了转换流,一场乱码引发的变革。

(OutputStreamWriter、InputStreamReader)

OutputStreamWriter(OutputStream out):根据默认编码把字节流的数据转换为字符流

OutputStreamWriter(OutputStream out,String charsetName):根据指定编码把字节流数据转换为字符流

把字节流转换为字符流。

//创造对象

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"));//查看源码,java8 默认utf-8

//写数据

osw.write("中国");

//释放资源

osw.close();//close包含了close和flush的作用

一般默认编码就够了。

查看源码,发现OutputStreamWriter有5个write方法。

/*

* OutputStreamWriter的方法:

* public void write(int c):写一个字符

* public void write(char[] cbuf):写一个字符数组

* public void write(char[] cbuf,int off,int len):写一个字符数组的一部分

* public void write(String str):写一个字符串

* public void write(String str,int off,int len):写一个字符串的一部分

*

* 面试题:close()和flush()的区别?

* A:close()关闭流对象,但是先刷新一次缓冲区。关闭之后,流对象不可以继续再使用了。

* B:flush()仅仅刷新缓冲区,刷新之后,流对象还可以继续使用。

*/

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("c.txt"));//查看源码,java8 默认utf-8

//写一个字符

osw.write('a');

osw.write(97);

osw.write('中');

// 为什么数据没有进去呢?

// 原因是:字符 = 2字节

// 文件中数据存储的基本单位是字节。

// void flush()

osw.flush();

//写一个字符数组

char[] chars = {'a','b','中','国'};

osw.write(chars);

osw.flush();

//写一个字符数组的一部分

osw.write(chars,2,2);

osw.flush();

//写一个字符串

osw.write("\n\r中国");

//写一个字符串的一部分

osw.write("中国你好",2,2);

osw.close();

InputStreamReader(InputStream is):用默认的编码读取数据,默认utf-8

InputStreamReader(InputStream is,String charsetName):

用指定的编码读取数据

//创建对象

InputStreamReader isr = new InputStreamReader(new FileInputStream("b.txt"));//默认编码utf-8

InputStreamReader isr1 = new InputStreamReader(new FileInputStream("b.txt"),"gbk");//可指定编码

//读数据

int ch = 0;

while ((ch = isr.read()) != -1) {

System.out.print((char)ch);//中国

}

//释放资源

isr.close();

//只有文档的编码和读取的编码一致才不会乱码。

查看源码知道InputStreamReader有2个read方法。

/*

* InputStreamReader的方法:

* int read():一次读取一个字符

* int read(char[] chs):一次读取一个字符数组

*/

InputStreamReader isr = new InputStreamReader(new FileInputStream("c.txt"));

//读一个字符

// int ch = 0;

// while ((ch = isr.read()) != -1) {

// System.out.print((char) ch);//9797200139798200132226920013222691020013222692032022909/

// //aa中ab中国中国

// //中国你好

// }

//isr.close();

//读一个字符数组

char[] chars =new char[1024];

int len = 0;

while ((len = isr.read(chars)) != -1) {

System.out.println(chars.length);//1024

System.out.println(new String(chars,0,len));

//aa中ab中国中国

//中国你好

}

isr.close();

现在我们可以通过转换流升级字节流复制文件的方式了。

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e:\\a.txt"));

char [] chars = new char[1024];

int len = 0 ;

while ((len = isr.read(chars)) != -1) {

osw.write(chars,0,len);

osw.flush();

}

osw.close();

isr.close();

字符流的诞生:

转换流已经是字符流了,但是他们的名字太长了,Java就提供了其子类供我们使用。

FileWriter 、FileReader 继承了其父类的所有方法。

字符流=字节流+编码表

OutputStreamWriter = FileOutputStream + 编码表(GBK)

FileWriter = FileOutputStream + 编码表(GBK)

InputStreamReader = FileInputStream + 编码表(GBK)

FileReader = FileInputStream + 编码表(GBK)

复制改写:

FileWriter fw = new FileWriter("e:\\b.txt");

FileReader fr = new FileReader("b.txt");

char[] chars =new char[1024];

int len = 0;

while ((len = fr.read(chars)) != -1) {

fw.write(chars,0,len);

fw.flush();

}

fw.close();

fr.close();

/**

* 总结,到现在为止,加上字节流,复制文本的方式有8种,但是复制图片视频等文件只有四种字节流的方式,因为不能用字符流复制图片视频mp3等

*/

字符流学习前辈字节流的经验,也设计了字符缓冲流。

BufferedReader、

BufferedWriter

和字节缓冲流的设计基本一样,也有两个构造方法,但是我们只要默认的缓冲大小的构造方法就可以了。

用字符缓冲流改写复制功能:

BufferedReader br = new BufferedReader(new FileReader("c.txt"));

BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\d.txt"));

char[] chars = new char[1024];

int len = 0 ;

while ((len = br.read(chars)) != -1) {

bw.write(chars,0,len);

bw.flush();

}

br.close();

bw.close();

然而字符缓冲流还有自己特殊的读写功能。

BufferedWriter :void newLine() 换行

BufferedReader :String readLine() 读一行数据

public static void main(String args[]) throws IOException {

// write();

read();

}

private static void read() throws IOException {

BufferedReader br = new BufferedReader(new FileReader("bw.txt"));

String line = null;

while ((line = br.readLine()) != null) {

System.out.println(line);

//readLine不会读取换行符

//读到末尾返回null

}

br.close();

}

private static void write() throws IOException {

BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

for (int i = 0; i <10 ; i++) {

bw.write("你好哈哈哈哈哈");

bw.newLine();//换行,并且自动检测不同系统的换行符

bw.flush();

//一般三个连用

}

bw.close();

}

用字符缓冲流的特殊方式升级复制功能:

BufferedReader br = new BufferedReader(new FileReader("bw.txt"));

BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\bw.txt"));

String line = null;

while ((line = br.readLine()) != null) {

bw.write(line);

bw.newLine();

bw.flush();

}

bw.close();

br.close();

//总结 字符流复制文本有5种方式

字符流的基础基本就聊完了,还有一个比较常用的类LineNumberReader

查看源码可知LineNumberReader继承自BufferedReader,并且增加了行号的操作。

LineNumberReader lnr = new LineNumberReader(new FileReader("a.txt"));

String line = null;

while ((line = lnr.readLine()) != null) {

System.out.println(lnr.getLineNumber()+":"+line);

}

1:A

2:S

3:F

但是LineNumberWriter。源码很简单,更多的操作请看源码。

io流总结:

9f3fd98ec28b

image

查看api源码,深入理解io设计理念。

io流面试题:

题一:复制单级文件夹

/**

* 封装

* 新建文件夹

* 获得源文件夹下文件列表

* 复制文件到新文件夹

*/

public class copyFolder {

public static void main(String args[]) throws IOException {

File file1 = new File("F:\\汤包\\IT时代\\java基础\\day21\\code\\demo");

File file2 = new File("e:\\demo");

//判断文件夹是否存在

if (!file2.exists()){

file2.mkdir();

}

//获取源文件夹下文件列表

File[] files = file1.listFiles();

for (File file : files) {

File newfile = new File(file2,file.getName());

copyFun(file,newfile);

}

}

private static void copyFun(File file1,File file2) throws IOException {

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1));

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2));

byte[] bytes = new byte[1024];

int len = 0;

while ((len = bis.read(bytes)) != -1) {

bos.write(bytes,0,len);

}

bis.close();

bos.close();

}

}

题二:复制多极文件夹

/*

* 需求:复制多极文件夹

*

* 数据源:F:\汤包\IT时代\java基础\day21\code\demos

* 目的地:E:\\

*

* 分析:

* A:封装数据源File

* B:封装目的地File

* C:判断该File是文件夹还是文件

* a:是文件夹

* 就在目的地目录下创建该文件夹

* 获取该File对象下的所有文件或者文件夹File对象

* 遍历得到每一个File对象

* 回到C

* b:是文件

* 就复制(字节流)

*/

public class copyFolder2 {

public static void main(String args[]) throws IOException {

File file1 = new File("F:\\汤包\\IT时代\\java基础\\day21\\code\\demos");

File file2 = new File("e:\\");

copyFolder(file1,file2);

}

private static void copyFolder(File srcFile, File destFile) throws IOException {

if (srcFile.isDirectory()){

File newFolder = new File(destFile, srcFile.getName());

newFolder.mkdir();

File[] files = srcFile.listFiles();

for (File file1 : files) {

copyFolder(file1,newFolder);

}

}else {

File newFile = new File(destFile,srcFile.getName());

copyFile(srcFile,newFile);

}

}

private static void copyFile(File file1,File file2) throws IOException {

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1));

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2));

byte[] bytes = new byte[1024];

int len = 0;

while ((len = bis.read(bytes)) != -1) {

bos.write(bytes,0,len);

}

bis.close();

bos.close();

}

}

复制多级文件夹,构思用到了递归,可知递归真的很重要,之后也会总结一下递归的知识。

题三:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低打印到控制台。

Student.java

public class Student {

// 姓名

private String name;

// 语文成绩

private int chinese;

// 数学成绩

private int math;

// 英语成绩

private int english;

public Student() {

super();

}

public Student(String name, int chinese, int math, int english) {

super();

this.name = name;

this.chinese = chinese;

this.math = math;

this.english = english;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getChinese() {

return chinese;

}

public void setChinese(int chinese) {

this.chinese = chinese;

}

public int getMath() {

return math;

}

public void setMath(int math) {

this.math = math;

}

public int getEnglish() {

return english;

}

public void setEnglish(int english) {

this.english = english;

}

public int getSum() {

return this.chinese + this.math + this.english;

}

}

StendentDemo.java

/*

* 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低存入文本文件

*

* 分析:

* A:创建学生类

* B:创建集合对象

* TreeSet

* C:键盘录入学生信息存储到集合

* D:遍历集合,把数据写到文本文件

*/

public class StendentDemo {

public static void main(String args[]) throws IOException {

TreeSet students = new TreeSet<>(new Comparator() {

@Override

public int compare(Student s1, Student s2) {

//s1-s2升序,s2-s1降序

int num = s2.getSum() - s1.getSum();

int num1 = num == 0 ?s2.getChinese()-s1.getChinese():num;

int num2 = num1 ==0 ?s2.getMath()-s1.getMath():num1;

int num3 = num2 ==0 ?s2.getEnglish()-s1.getEnglish():num2;

int num4 = num3 == 0?s1.getName().compareTo(s2.getName()):num3;//按字母顺序

return num4;

}

});

for (int i = 0; i <5 ; i++) {

Scanner sc = new Scanner(System.in);

System.out.println("录入学生成绩:");

System.out.println("姓名:");

String name = sc.nextLine();

System.out.println("语文成绩:");

int chinese = sc.nextInt();

System.out.println("数学成绩:");

int math = sc.nextInt();

System.out.println("英语成绩:");

int english = sc.nextInt();

Student student = new Student(name,chinese,math,english);

students.add(student);

}

BufferedWriter bw = new BufferedWriter(new FileWriter("grade.txt"));

bw.write("学生信息如下:");

bw.newLine();

bw.flush();

bw.write("姓名,语文成绩,数学成绩,英语成绩");

bw.newLine();

bw.flush();

for (Student student : students) {

StringBuilder sb = new StringBuilder();

sb.append(student.getName()).append(student.getChinese()).append(student.getMath()).append(student.getEnglish());

bw.write(sb.toString());

bw.newLine();

bw.flush();

}

bw.close();

}

}

题四:已知s.txt文件中有这样的一个字符串:“hcexfgijkamdnoqrzstuvwybpl”,请编写程序读取数据内容,把数据排序后写入ss.txt中。

/*

* 已知s.txt文件中有这样的一个字符串:“hcexfgijkamdnoqrzstuvwybpl”

* 请编写程序读取数据内容,把数据排序后写入ss.txt中。

*

* 分析:

* A:把s.txt这个文件给做出来

* B:读取该文件的内容,存储到一个字符串中

* C:把字符串转换为字符数组

* D:对字符数组进行排序

* E:把排序后的字符数组转换为字符串

* F:把字符串再次写入ss.txt中

*/

public class StringDemo {

public static void main(String[] args) throws IOException {

// 读取该文件的内容,存储到一个字符串中

BufferedReader br = new BufferedReader(new FileReader("s.txt"));

String line = br.readLine();

br.close();

// 把字符串转换为字符数组

char[] chs = line.toCharArray();

// 对字符数组进行排序

Arrays.sort(chs);

// 把排序后的字符数组转换为字符串

String s = new String(chs);

// 把字符串再次写入ss.txt中

BufferedWriter bw = new BufferedWriter(new FileWriter("ss.txt"));

bw.write(s);

bw.newLine();

bw.flush();

bw.close();

}

}

题五:用Reader模拟BufferedReader的readLine()功能

/*

* 用Reader模拟BufferedReader的readLine()功能

*

* readLine():一次读取一行,根据换行符判断是否结束,只返回内容,不返回换行符

*/

public class MyBufferedReader {

private Reader reader;

public MyBufferedReader(Reader reader){

this.reader=reader;

}

public String readLine() throws IOException {

StringBuilder sb = new StringBuilder();

int ch = 0;

while((ch=reader.read())!=-1) {

if (ch=='\r'){

continue;

}

if (ch=='\n'){

return sb.toString();

}else {

sb.append((char)ch);

}

}

if (sb.length()>0){

return sb.toString();

}

return null;

}

public void close() throws IOException {

this.reader.close();

}

}

测试:

MyBufferedReader mbr = new MyBufferedReader(new FileReader("a.txt"));

String line = null;

while ((line = mbr.readLine()) != null) {

System.out.println((line));

}

mbr.close();

通过自己实现readLine()方法,并且查看源码可知,字符缓冲流的readLine()也用到了StringBuilder,并且也要判断\n和\r,最后关闭的流就是Reader。

以上是本人学习笔记整理,重温java经典,欢迎各位同道中人批评指正。

学习心得:

再次学习io流,看了好多源码,体验了其架构思想的强大,逻辑的缜密。发现io流的读和写都是线程安全的。所以线程得再次研究下,设计模式得了解下。

梦回io流完整目录:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值