回顾:
1、已经熟悉了字节流输入(InputStream)和输出(OutputStream)
2、解决了文件的操作(FileInputStream,FileOutputStream)
3、同时,提高了效率(BufferedInputStream,BufferedOutputStream)。
1、字节流读取字符的问题
在操作数据中,字节流可以操作所有数据,现在有新的需求。
比如:一篇文章中出现了多少个好字。读取数据。判断好字并计数。
package IO_charstream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class charStreamDemo {
public static void main(String[] args) throws IOException{
//通过流写一个文章,里面有中文,你好。
writeAartical();
//文章中出现了多少个好字,读取数据,判断好字并计数
readAarticl();
}
private static void readAarticl() throws IOException {
FileInputStream fis = new FileInputStream("F:\\javatest\\nihao.txt");
//一次读一个字节,对中文是无法判断的,一个中文默认为两个字节。
//读取所有字节,存储起来,变成字符串,再找指定的字
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf))!=-1){
String str = new String(buf,0,len);
System.out.println(str);
}
// int ch = 0;
// while ((ch = fis.read())!=-1){
// System.out.println(ch);
// }
fis.close();
}
private static void writeAartical() throws IOException{
FileOutputStream fos = new FileOutputStream("F:\\javatest\\nihao.txt");
String content= "你好,九月,请对我好一点。";
byte[] hello = content.getBytes();
fos.write(hello);
fos.close();
}
}
2、编码表
就是生活中文字和计算机二进制文字的对应表。
1、ASCII码 :一个字节中的7位就可以表示。对应的字节都是正数。
2、iso8859-1 :拉丁码表。用了一个字节用的八位。对应的字节都是负数。
3、GB2312 :简体中文码表。用两个字节表示。两个字节都是开头为1,两个字节都是负数。
4、GBK :目前最常用的中文码表。2万的中文和符号。用两个字节表示,一部分文字第一个字节开头是一,第二个字节开头是0。
5、unicode :国际标准码表。无论是什么文字,都用两个字节存储。java中的char类型就是用的这个码表。
6、utf-8 :基于unicode,一个字节就可以存储数据。不要用两个字节存储。而且这个码表更加标准化,在每一个字节头,都加入了编码信息。
3、字符流--FileReader
了解到操作文字数据需要编码表配合。
查阅FileInputstream api中是否有相关的提示?发现一个新名词。
字符流:之前用的是字节流,用于操作的都是字节数据。字符流,专门用于操作字符数据的流对象。
类FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区的大小都是适当的。
使用FileReader时,了解它的功能,看它所属的体系顶层。---Reader类
Reader:读取字符流的抽象超类。
read(); 读取单个字符并返回。
read(char []) ; 将数据读取到数组中。
package IO_charstream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
public class charStreamDemo {
public static void main(String[] args) throws IOException{
//通过流写一个文章,里面有中文,你好。
//writeAartical();
//文章中出现了多少个好字,读取数据,判断好字并计数
readAarticl();
System.out.println("______________________________");
//使用Filereader 解决需求:一篇文章中出现了多少个好字。读取数据。判断好字并计数
readCNTextByReader();
}
private static void readCNTextByReader() throws IOException{
//创建一个读取字符文件的读取流对象FileReader
FileReader fr = new FileReader("F:\\javatest\\nihao.txt"); //这个流的底层用的是FileInputStream
// int ch = fr.read();
// System.out.println("读取一个字符:"+(char)ch);
// int ch1 = fr.read(); //一次读取一个中文,读取多个字节查表转成中文
// System.out.println("读取一个字符:"+(char)ch1);
int ch = 0;
int count = 0;
while ((ch = fr.read())!=-1){
if(ch == '好'){
count++;
}
}
System.out.println(count);
fr.close();
}
private static void readAarticl() throws IOException {
FileInputStream fis = new FileInputStream("F:\\javatest\\nihao.txt");
int ch = fis.read();
System.out.println("读取一个字节:"+ch);
int ch1 = fis.read();
System.out.println("读取下一个字节:"+ch1);
fis.close();
}
private static void writeAartical() throws IOException{
FileOutputStream fos = new FileOutputStream("F:\\javatest\\nihao.txt");
String content= "你好,九月,请对我好一点。";
byte[] hello = content.getBytes();
fos.write(hello);
fos.close();
}
}
4、字符流--FileWriter
FileReader:字节读取流+默认编码表
字符流:为了便于操作数据中的字符数据。原理:字节流+编码表。
字符流的两个基类:
Reader(读取字符流的抽象类)
read(); 读取字符
Writer(写入字符流的抽象类)
字节流操作的是字节数组,字符流操作的是字符数组。
package IO_charstream;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
//用于操作文件的便捷类
FileWriter fw = new FileWriter("F:\\javatest\\filewriter.txt");
fw.write("九月,请对我好一点");//这些文字都先要编码。都写入到流的缓冲区中了
// fw.flush();
fw.close(); //具有刷新功能。
System.out.println("done");
}
}
Flush和close的区别?
flush();将流中缓冲区缓冲的数据刷新到目的地中,刷新后,流还可以继续使用。
close();关闭资源,但是关闭前会将缓冲区的数据先刷新到目的地,否则丢失数据。
如果写入数据多,一定要一边写一边刷新,最后一次可以不刷新。由close来完成刷新并关闭。
5、字符流-OutputStreamWriter-按照指定编码写中文
能识别中文的码表:GBK UTF-8。正因为识别中文码表不唯一,涉及到了编码解码问题。
outputstreamWriter是字符流通向字节流的桥梁。
它的作用就是将字符串按照指定的编码表转成字节,通过字节流写出去。
package IO_charstream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutStreamWriterDemo {
public static void main(String[] args) throws IOException{
/*
需求:识别中文的码表由两个
能不能将中文数据按照UTF-8的方式进行文件的存储
还能使用FileWrite吗?不能。因为默认的是GBK。
通过FileWriter的api描述,要制定编码表这些值,需要OutputStreamWriter
*/
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("F:\\javatest\\outputstreamwriter.txt"),"utf-8");
osw.write("你好");
osw.close(); //关的是字节流
}
}
6、字符流-InputStreamReader-按照指定编码读取中文
package IO_charstream;
import java.io.*;
public class OutStreamWriterDemo {
public static void main(String[] args) throws IOException{
writeCN();
readCN();
}
private static void readCN() throws IOException{
//读取中文
//如果使用FileReader就读不出来,因为文件是UTF-8编码。读取字节时,应该指定用UTF-8来解码。。FileReader默认为GBK码表。
InputStreamReader isr = new InputStreamReader(new FileInputStream("F:\\javatest\\outputstreamwriter.txt"),"utf-8");
char[] buf = new char[1024];
int len = isr.read(buf);
System.out.println(new String(buf,0,len));
isr.close();
}
public static void writeCN() throws IOException{
/*
需求:识别中文的码表由两个
能不能将中文数据按照UTF-8的方式进行文件的存储
还能使用FileWrite吗?不能。因为默认的是GBK。
通过FileWriter的api描述,要制定编码表这些值,需要OutputStreamWriter
*/
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("F:\\javatest\\outputstreamwriter.txt"),"utf-8");
osw.write("你好");
osw.close(); //关的是字节流
}
}
7、转换流和子类的区别
继承关系是这样的:
OutputStreamWrite
子类为:FileWriter
InputstreamReader
子类为:FileReader
父类和子类的功能有什么区别?
opsw和ipsr是字符和字节的桥梁:也可以称之为字符转换流
字符转换流原理:字节流+编码表
FileWriter和FileReader:最为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件使用的是默认编码表时,可以不用父类,直接用子类就可以完成操作,简化了代码。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt")); //默认字符集
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),gbk); //gbk字符集
FileReader fr = new FileReader("a.txt");
这三句代码的功能是一样的,其中第三句最为便捷,一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。
使用子类的条件:
1、操作的是文件
2、使用的是默认编码
8、复制文本文件
package IO_charstream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class copy {
/*
需求:字符流复制文件
思路:既然是文本,就涉及编码表,需要用到字符流;操作的是文件,涉及硬盘;有指定码表吗?
操作的是文件,使用的默认编码表,使用FileReader即可
*/
public static void main(String[] args) throws IOException {
copyTextFile();
}
private static void copyTextFile() throws IOException {
//1、明确源和目的
FileReader fr= new FileReader("F:\\javatest\\nihao.txt");
FileWriter fw = new FileWriter("F:\\javatest\\nihao_copy.txt");
//2、循环读取操作
int ch = 0;
while ((ch = fr.read())!=-1){
fw.write(ch);
}
fr.close();
fw.close();
}
}
上述方法循环次数太多,效率低。
package IO_charstream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class copy {
/*
需求:字符流复制文件
思路:既然是文本,就涉及编码表,需要用到字符流;操作的是文件,涉及硬盘;有指定码表吗?
操作的是文件,使用的默认编码表,使用FileReader即可
*/
public static void main(String[] args) throws IOException {
copyTextFile();
}
private static void copyTextFile() throws IOException {
//1、明确源和目的
FileReader fr= new FileReader("F:\\javatest\\nihao.txt");
FileWriter fw = new FileWriter("F:\\javatest\\nihao_copy.txt");
//2、为了提高效率,自定义缓冲区
char[] buf = new char[1024];
int len = 0;
while ((len = fr.read(buf))!=-1){
fw.write(buf,0,len);
}
//关闭资源
fr.close();
fw.close();
}
}
使用字符流缓冲区对象来复制文本文件。
bufferedReader
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够了。
package IO_charstream;
import java.io.*;
public class copy_buff {
public static void main(String[] args) throws IOException {
/*
字符流中提供了缓冲区,自定义数据就可以解决这个问题。
为什么还要用流中的缓冲区对象呢?因为缓冲区对象中除了封装数组以外,还提供了更多的操作缓冲区数据的方法
BufferdReader BufferedWriter
操作字符数据时,有一个文本特有的 表现形式:行
BufferedReader:readLine(); 一次读取一行
BufferedWriter:newLine(); 一次写一行 必须使用flush
*/
copytextbybuffer();
writerText();
readText();
}
public static void writerText() throws IOException{
BufferedWriter bfw = new BufferedWriter(new FileWriter("F:\\javatest\\nihao.txt"));
for(int x = 0;x<4;x++) {
bfw.write(x + "-itcast");
bfw.newLine(); //相当于一个换行符
bfw.flush();
}
bfw.close();
}
public static void readText() throws IOException{
BufferedReader bufr = new BufferedReader(new FileReader("F:\\javatest\\nihao.txt"));
String line = null;
while ((line = bufr.readLine())!=null){
System.out.println(line);
}
bufr.close();
}
private static void copytextbybuffer() throws IOException{
//读取一次
BufferedReader bufr = new BufferedReader(new FileReader("F:\\javatest\\nihao.txt"));
BufferedWriter buwr = new BufferedWriter(new FileWriter("F:\\javatest\\nihao_buff_copy.txt"));
String line = null;
while ((line = bufr.readLine())!=null){
buwr.write(line);
buwr.newLine();
buwr.flush();
}
bufr.close();
buwr.close();
}
}
9、编码解码
字符串---》编码(getBytes();)---》字节数组
字节数组---》解码(new String (byte[]))---》字符串
package IO_charstream;
import java.io.UnsupportedEncodingException;
public class encodingDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "你好";
//对字符串进行编码
//你好 对应的字节为-28 -67 -96 -27 -91 -67
//你好 GBK编码为-60 -29 -70 -61
byte[] encodingStr = str.getBytes("GBK");
// for(byte b:encodingStr){
// System.out.print(b);
// }
//对字节数组进行解码
String decoding = new String(encodingStr,"GBK");
System.out.println(decoding);
}
}
练习:
1、对字符串按照字节截取,“abc你好”有5个字符,7个字节。按照3个字节截取abc,按照四个字节截取abc和你字的一般,如果出现中文一半舍弃。
package IO_charstream;
import java.io.UnsupportedEncodingException;
public class practice_1 {
public static void main(String[] args) throws UnsupportedEncodingException {
/*
1、对字符串按照字节截取,“abc你好”有5个字符,7个字节。按照3个字节截取abc,按照四个字节截取abc和你字的一般,如果出现中文一半舍弃。
思路:一个中文gbk两个字节,都是负数;在截取时,先看最后一位是负数吗?不是,直接截取,如果是,不要舍弃,看一下该负数之前连续出现了几个负数;
因为中文两个字节,出现的负数个数是偶数,不需要舍弃,是奇数,则需要舍弃
*/
String str = new String("abc你好cd谢谢");
byte[] buf = str.getBytes("GBK");
for(int i = 0;i < buf.length;i++){
String s = cutString(str,i+1);
System.out.println(str+",截取"+(i+1)+"个结果是"+s);
}
int len = 5;
}
public static String cutString(String str,int len) throws UnsupportedEncodingException {
//1、将字符串编码成字节数组
byte[] buf = str.getBytes("GBK");
int count = 0;
//2、对数组进行遍历,从截取位往前遍历
for(int i = len-1; i >= 0; i--){
//判断最后截取位上是否为负数
if(buf[i]<0){
count++;
}else break;
}
//判断奇偶数
if(count%2==0){
return new String(buf,0,len,"GBK");
}else {
return new String(buf,0,len-1,"GBK");//舍弃最后一个
}
}
}
2、联通的问题。
新建两个文本文档,一个内容为移动,另一个内容为联通。内容为联通的文档已经乱码。
utf-8编码方式
package IO_charstream;
public class unicom_problem {
public static void main(String[] args) {
String str = "联通" ; //11101000 10000001 10010100 11101001 10000000 10011010
byte[] buf = str.getBytes();
for(byte b:buf){
System.out.print(Integer.toBinaryString(b&255)+" ");
}
}
}
联通的GBK编码二进制正好符合了utf-8的编码规律,所以记事本在解析这段二进制时,就启动了utf-8的码表来解析这个数据,就出现了乱码。
10、字符流缓冲区以及readLine();方法原理
缓冲区的原理:使用了底层流对象从具体设备上获取数据,并将存储到缓冲区中的数组中。通过缓冲区的read方法从缓冲区来获得具体的字符数据,这样就提高了效率。如果用read方法读取字符数据并存储到另一个容器中,直到读取到了换行符时,另一个容器中的临时存储的数据转称字符串返回,就形成了readline功能。
11、自定义缓冲区
package IO_charstream;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/*
自定义一个字符流缓冲区
用于缓冲字符数据从而提高操作效率Z,并提供更多操作缓冲区数据的方法,需要使用具体的流对象来完成数据的获取
分析:
1、必须要有数组
2、需要对数组进行操作,对数组操作一定要有索引。
*/
public class mybufferedreader {
private Reader reader;
//字符数组
private char[] buf = new char[1024];
//索引,用于操作数组中的元素
private int index = 0;
//定义一个变量,用于定义读取字符的个数
private int count = 0;
//对外提供一个可以从缓冲区中读取一个字符的方法
public int read() throws IOException {
if (count == 0){
count = reader.read(buf);
//每取一次新的数据,就需要将角标归0
index = 0;
}
if (count<0){
return -1;
}
//从缓冲区中取出一个字符
char ch = buf[index];
//角标自增
index++;
//计数器要自建
count-- ;
return ch;
}
/*
基于高效的read方法,写一个一次可以读取一行数据的方法,将行终止符之前的数据转成字符串返回
*/
public String readLine() throws IOException {
StringBuilder sb = new StringBuilder();
//调用本类的read方法从缓冲区读取一个字符,存储到临时容器中
int ch = 0;
while ((ch=this.read())!=-1){
if(ch == '\r'){
continue;
}
if(ch == '\n'){
return sb.toString();
}
sb.append((char)ch);
}
//万一文本中最后一行没有行终止符
if(sb.length()!=0){
return sb.toString();
}
return null;
}
//需要一初始化就具备一个流对象
public mybufferedreader(Reader reader){
this.reader = reader;
}
//关闭流资源
public void close() throws IOException {
reader.close();
}
}
测试程序
package IO_charstream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class mybufferedreadertest {
public static void main(String[] args) throws IOException {
mybufferedreader mybufferedreader = new mybufferedreader(new FileReader("F:\\javatest\\student.txt"));
String buf = null;
while ((buf = mybufferedreader.readLine())!=null){
System.out.println(buf);
}
mybufferedreader.close();
}
}