目录
转换流(InputStreamReader | 字符编码格式)
什么是IO?
-
I:Input(输入) ;O:Output(输出) ;Stream(流) 表示数据的传输。
-
通过IO可以完成硬盘文件的读和写。
-
IO流的分类
-
一种方式是按照流的方向进行分类:
-
以内存作为参照物,往内存中去,叫做输入(Input)。或者叫做读(Read)。
-
从内存中出来,叫做输出(Output),或者叫做写(Write)。
-
-
一种方式是按照读取数据方式不同而进行分类:
-
有的流是按照字节的方式读取数据,一次读取一个字节(byte),等同于一次读取8个二进制位。这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频频文件等等..
-
有的流是按照字符的方式读取数据的,一次读取一个字符。这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件,只能读取纯文本文件,连word文件都无法读取。
-
-
-
综上所述,流的分类:输入流、输出流、字节流、字符流。
-
Java中的IO流都已经写好了,我们程序员不需要关心,我们最主要需要掌握在java中已经提供了哪些流,每个流的特点是什么,每个流上的常用方法有哪些。
-
根据角色不同,分为节点流、处理流。
-
节点流 : 直接从数据来源来读写数据。
-
处理流 : 对节点流的包装处理,目的是让节点流更高效或更满足实际需求。
-
-
Java中所有的流都在:Java.io.*;下
- java IO流有四大家族,他们的首领都是抽象类(abstract class)
- java.io.InputStream 字节输入流
- java.io.OutputStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
- 注:在java中"类名"以Stream结尾的都是字节流,以"Reader/Writer"结尾的都是字符流
- 所有的流都实现了:java,io.Closeable接口,都是可关闭的,有close()方法。流是是内存和硬盘之间的通道,用完流一定要关闭,不然会占用很多资源
- 所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要flush()刷新一下,将通道/管道当中剩余未输出的数据强行输出完(清空管道)。如果没有flush,可能会导致丢失数据。
- java.io包下需要掌握的流有16个:
- 文件专属:
- java.io.FileInputStream;(掌握) //文件字节输入流
- java.io.FileOutputStream;(掌握) //文件字节输出流
- java.io.FileReader 字符流 = 字节流 + 编码(默认的)
- java.io.FileWriter
- 转换流专属:(将字节流转换成字符流)
- java.io.InputStreamReader 转换流 = 字节流 + 编码(自己指定的)
- java.io.OutputStreamWriter
- 缓冲流专属:
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
- 数据流专属:
- java.io.DataInputStream
- java.io.DataOutputStream
- 标准输出流:
- java.io.PrintWriter
- java.io.PrintStream (掌握)
- 对象专属流:
- java.io.ObjectInputStream(掌握)
- java.io.ObjectOutputStream(掌握)
FileInputStream(文件字节输入流) 重点
- 常用方法
int | available() 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。 |
int | read() 从此输入流中读取一个数据字节。 |
int | read(byte[] b) //返回读到的字节数 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 |
int | read(byte[] b, int off, int len) 从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。 |
long | skip(long n) 从输入流中跳过并丢弃 n 个字节的数据。 |
FileInputStream fis = null; //在try外面定义,否则finally无法close
try {
// 创建文件字节输入流对象,以下都是采用绝对路径的方式 文件内容:abcd
// 文件路径:E:\JavaSE\IOfile\temp (IDEA会自动把\变成\\,因为java中\表示转义)
//将\\写成/也是可以的
fis = new FileInputStream("E:/JavaSE\\IOfile\\temp");
//开始读
int readData = fis.read(); //这个方法的返回值是:读取到的"字节"本身。
System.out.println(readData); //97
System.out.println(fis.read()); //98
System.out.println(fis.read()); //99
System.out.println(fis.read()); //100
//读到到文件末尾,再读读不到任何数据,返回-1
System.out.println(fis.read()); //-1
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally { //在finally语句块中确保流一定关闭
if(fis != null){ //关闭流的前提是:流不是空,避免空指针异常
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
最终版
FileInputStream fis = null;
try {
//相对路径 从工程Project的根开始
fis = new FileInputStream("IOfile\\temp");
//准备一个byte数组
byte[] bytes = new byte[4];
int readCount = 0;
//读取后!= -1,说明还有数据,继续读取
while((readCount = fis.read(bytes))!=-1){
//把byte转换为字符串,读到多少转换多少个
System.out.print(new String(bytes,0,readCount));
}
/*while(true){
int readCount = fis.read(bytes);
if(readCount == -1){
break;
}
//把byte转换为字符串,读到多少转换多少个
System.out.println(new String(bytes,0,readCount));
}*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream(文件字节输出流) 重点
- 常用方法
|
|
protected void | finalize() 清理到文件的连接,并确保在不再引用此文件输出流时调用此流的 close 方法。 |
void | write(byte[] b) 将 b.length 个字节从指定 byte 数组写入此文件输出流中。 |
void | write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。 |
void | write(int b) 将指定字节写入此文件输出流。 |
FileOutputStream fos = null;
try {
//myfile文件不存在时,会自动新建
//这种方式会先将原文件清空,然后重新写入 谨慎使用!!
//fos = new FileOutputStream("IOfile\\myfile");
//以追加的方式在文件末尾写入,不会清空原文件内容
fos = new FileOutputStream("IOfile\\myfile",true);
//开始写
byte[] bytes = {97,98,99,100};
//将byte数组全部写出
fos.write(bytes);
//将byte数组一部分写出
fos.write(bytes,0,2);
//将字符串转为byte数组,转换后再写出
String s = "你好";
byte[] bs = s.getBytes();
fos.write(bs);
//写完之后,一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader(文件字符输入流)
FileReader只能读纯文本文件
大同小异,由通过byte[]数组读写,改为char[]数组读写
- 常用方法
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader。 |
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader。 |
abstract void | close() 关闭该流并释放与之关联的所有资源。 |
int | read() //达到-1表示读到文件结尾 读取单个字符。 |
int | read(char[] cbuf) 将字符读入数组。 |
abstract int | read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。 |
void | reset() 重置该流。 |
long | skip(long n) 跳过字符。 |
FileReader reader = null;
try {
//创建文件字符输入流
reader = new FileReader("IOfile\\FileReaderTest.txt");
//开始读
char[] chars = new char[4]; // 一次读取4字符
int readCount = 0;
while ((readCount = reader.read(chars)) !=-1 ){
//转为字符串,读多少转多少
System.out.print(new String(chars,0,readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(reader!=null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter(文件字符输出流)
FileWriter只能写纯文本文件
字符输出流默认内置一个8k大小的字符缓冲区,当缓冲区满或调用flush时,一次性写出文件(为了减少IO操作)。
close 关闭前会调用 flush方法。
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象。 |
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。 |
Writer | append(char c) 将指定字符添加到此 writer。 |
Writer | append(CharSequence csq) 将指定字符序列添加到此 writer。 |
Writer | append(CharSequence csq, int start, int end) 将指定字符序列的子序列添加到此 writer.Appendable。 |
abstract void | close() //刷新并关闭 关闭此流,但要先刷新它。 |
abstract void | flush() 刷新该流的缓冲。 |
void | write(char[] cbuf) 写入字符数组。 |
abstract void | write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 |
void | write(String str) 写入字符串。 |
void | write(String str, int off, int len) 写入字符串的某一部分。 |
FileWriter out = null;
try {
//创建文件字符输出流对象 如不想清空源文件,可在目录后加上true
out = new FileWriter("IOfile\\fileWriterTest.txt");
//开始写
char[] chars = {'哈','哈','哈','哈'};
out.write(chars,0,2);
out.write("你好世界");
//刷新
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(out !=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件复制(拷贝)
FileInputStream fis =null;
FileOutputStream fos = null;
try {
//创建一个输入流对象
fis = new FileInputStream("E:\\JavaSE\\200F6093053-7.jpg");
//创建一个输出流对象
fos = new FileOutputStream("IOfile\\temp.jpg");
//最核心的问题:一边读一边写
byte[] bytes = new byte[1024*1024] ; //一次最多拷贝1mb 1024byte =1kb
int readCount = 0; //记录读到多少
while ((readCount = fis.read(bytes))!=-1){ //读到bytes数组内
fos.write(bytes,0,readCount); //读到多少写多少
}
//刷新 输出流最后要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
//一起try其中一个出现异常,会影响另一个流的关闭
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符输入输出流 拷贝大同小异
FileReader in = null;
FileWriter out = null;
try {
//读
in = new FileReader("");
//写
out = new FileWriter("");
//一边读一边写
char[] chars = new char[1024 * 512] //1mb
int readCount = 0;
while ((readCount = in.read(chars))!=-1){
out.write(chars,0,readCount);
}
//刷新
out.flush();
} catch (FileNotFoundException e) {....
缓冲流(BufferedReader)
缓冲流,又称高效流,种处理流。默认内置8K的缓冲区(8192),可传参修改;
为了减少IO操作,提升效率,减少硬盘读写。
- BufferedReader
带缓冲的字符输入流
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。 |
BufferedReader(Reader in, int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流。 |
int | read() 读取单个字符。 |
String | readLine() //注意返回null,读取一行 读取一个文本行。 |
void | reset() 将流重置到最新的标记。 |
long | skip(long n) 跳过字符。 |
public static void main(String[] args) throws Exception{//偷懒,上抛异常
FileReader reader = new FileReader("基础语法\\src\\com\\IO\\Copy01.java");
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
// 外部负责包装的这个流叫做:包装流/处理流
//当前程序来说,FileReader就是一个节点流,BuffereReader就是包装流/处理流
BufferedReader br = new BufferedReader(reader);
/*//读一行
String firstLine = br.readLine();
System.out.println(firstLine);
//再读几行
System.out.println(br.readLine());
System.out.println(br.readLine());
System.out.println(br.readLine());*/
//br.readLine()读取一个文本行,但不带换行符
String s = null;
while ((s = br.readLine())!= null){
System.out.println(s);
}
//关闭流
//对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭(源代码分析)
br.close();
}
- BufferedWriter
void | newLine() 写入一个行分隔符。 |
void | write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 |
void | write(int c) 写入单个字符。 |
带缓冲的字符输出流
public static void main(String[] args) throws Exception {
BufferedWriter out = new BufferedWriter((new FileWriter("temp")),true);
out.write("Hello World");
//刷新
out.flush();
//关闭
out.close();
}
- java.io.BufferedInputStream 省略 大同小异
- java.io.BufferedOutputStream 省略 大同小异
转换流(InputStreamReader | 字符编码格式)
InputStreamReader 使用指定的
读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。(字符编码格式转换)charset
- InputStreamReader
InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。 |
InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader。 |
public static void main(String[] args) throws Exception{
//字节流
FileInputStream in = new FileInputStream("基础语法\\src\\com\\IO\\Copy01.java");
//这个构造方法只能传一个字符流,不能传字符流。
//BufferedReader br = new BufferedReader(in);
//通过转换流转换(将字节流转换为字符流。)in为节点流,reader为包装流
InputStreamReader reader = new InputStreamReader(in);
//节点包装流是相对而言的 reader是节点流,br是包装流
BufferedReader br = new BufferedReader(reader);
//以上三行代码可合并
//BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("")));
String line = null;
while ((line = br.readLine())!= null){
System.out.println(line);
}
//关闭最外层
br.close();
}
- OutputStreamWriter 省略
- 字符编码转换
/* 把今天的作业文件夹下的《我想对你说.txt》字符编码为GBK,
复制到当前项目的testIO文件夹下的《柴老师的话.txt》字符编码为UTF-8。*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("E:\\学习资料\\day20\\homework\\尚硅谷_19_IO流_homework\\我想对你说.txt");
InputStreamReader isr = new InputStreamReader(fis,"GBK"); //转为字符编码GBK
FileOutputStream fos = new FileOutputStream("HomeWork\\src\\day20IOStream\\File\\柴老师的话.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");//转为字符编码UTF-8
char[] cs = new char[24];
int readCound;
while ((readCound=isr.read(cs))!=-1){
osw.write(cs,0,readCound);
}
isr.close();
osw.close();
}
- JIS编码转UTF8编码 --解决游戏乱码
InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"),"JIS");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test1.txt"),"UTF8");
char[] cs = new char[24];
int cound;
while ((cound = isr.read(cs))!=-1){
osw.write(cs,0,cound);
System.out.println(cs);
}
isr.close();
osw.close();
关于字符集编码
在Java内存中使用Unicode编码。当文件通过IO存储到磁盘或硬盘才考虑编码规则,
Unicode 不是单个编码,只有提 UTF-8/16/32 编码的字节数才有意义。
英文字母和中文汉字在不同字符集编码下的字节数
英文字母:
·字节数 : 1;编码:GB2312
字节数 : 1;编码:GBK
字节数 : 1;编码:GB18030
字节数 : 1;编码:ISO-8859-1
字节数 : 1;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE
中文汉字:
字节数 : 2;编码:GB2312
字节数 : 2;编码:GBK
字节数 : 2;编码:GB18030
字节数 : 1;编码:ISO-8859-1
字节数 : 3;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE
美国人首先对其英文字符进行了编码,也就是最早的ascii码,用一个字节的低7位来表示英文的128个字符,高1位统一为0;
后来欧洲人发现你这128位哪够用,比如法国人字母上面的还有注音符,这个怎么区分,于是把高1位编进来吧,这样欧洲普遍使用一个全字节进行编码,最多可表示256位。
但是即使位数少,不同国家地区用不同的字符编码,虽然0–127表示的符号是一样的,但是128–255这一段的解释完全乱套了,即使2进制完全一样,表示的字符完全不一样,比如135在法语,希伯来语,俄语编码中完全是不同的符号;
更麻烦的是,这编码传到中国后,中国人发现我们有10万多个汉字,你们欧美这256字塞牙缝都不够。于是就发明了GB2312这些汉字编码,典型的用2个字节来表示绝大部分的常用汉字,最多可以表示65536个汉字字符,这样就不难理解有些汉字你在新华字典里查得到,但是电脑上如果不处理一下你是显示不出来的了吧。
这下各用各的字符集编码,这世界咋统一?俄国人发封email给中国人,两边字符集编码不同,尼玛显示都是乱码啊。为了统一,于是就发明了unicode,将世界上所有的符号都纳入其中,每一个符号都给予一个独一无二的编码,现在unicode可以容纳100多万个符号,每个符号的编码都不一样,这下可统一了,所有语言都可以互通,一个网页页面里可以同时显示各国文字。
然而,unicode虽然统一了全世界字符的二进制编码,但没有规定如何存储啊。x86和amd体系结构的电脑小端序和大端序都分不清,别提计算机如何识别到底是unicode还是acsii了。如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,文本文件的大小会因此大出二三倍,这对于存储来说是极大的浪费。这样导致一个后果:出现了Unicode的多种存储方式。
互联网的兴起,网页上要显示各种字符,必须统一。utf-8就是Unicode最重要的实现方式之一。另外还有utf-16、utf-32等。UTF-8不是固定字长编码的,而是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。这是种比较巧妙的设计,如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。
注意unicode的字符编码和utf-8的存储编码表示是不同的,例如”严”字的Unicode码是4E25,UTF-8编码是E4B8A5,这个7里面解释了的,UTF-8编码不仅考虑了编码,还考虑了存储,E4B8A5是在存储识别编码的基础上塞进了4E25。
UTF-8 使用一至四个字节为每个字符编码。128 个 ASCII 字符(Unicode 范围由 U+0000 至 U+007F)只需一个字节,带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及马尔代夫语(Unicode 范围由 U+0080 至 U+07FF)需要二个字节,其他基本多文种平面(BMP)中的字符(CJK属于此类-Qieqie注)使用三个字节,其他 Unicode 辅助平面的字符使用四字节编码。
- ASCII
ASCII(American Standard Code for Information Interchange):美国信息交换标准代码,适用于所有拉丁文字字母
ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符
- GBK(中文2字节)
GBK(即“国标”、“扩展”汉语拼音的第一个字母),汉字编码字符集。2000年已被GB18030-2000国家强制标准替代。 2005年GB18030-2005发布,替代了GB18030-2000。
GBK使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。
- GB2312
GB2312(信息交换用汉字编码字符集)是由中国国家标准总局1980年发布。基本集共收入汉字6763个和非汉字图形字符682个。GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
- Unicode
Unicode只是一组字符设定或者说是从数字和字符之间的逻辑映射的概念编码,但是它并没有指定代码点如何在计算机上存储。UCS4、UTF-8、UTF-16(UTF后的数字代表编码的最小单位,如UTF-8表示最小单位1字节,所以它可以使用1、2、3字节等进行编码,UTF-16表示最小单位2字节,所以它可以使用2、4字节进行编码)都是Unicode的编码方案。UTF-8因可以兼容ASCII而被广泛使用。
- UTF-8*(中文3字节)
UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码,也叫万国码、统一码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。
- UTF-16
UTF-16是Unicode的其中一个使用方式。UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节(2字节)储存,但UTF-16却无法兼容于ASCII编码。
- Big5
Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。
Big5虽普及于台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家/地区标准或官方标准,而只是业界标准。倚天中文系统、Windows繁体中文版等主要系统的字符集都是以Big5为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。
- ISO-8859-1
ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
ISO码表:HTML ISO-8859-1 参考手册
- JIS (Shift_JIS)
日文编码