IO流概述
IO流:存储和读取数据的解决方法。用于读写文件中的数据(可以读写文件,或者网络中的数据)
在IO流中以程序为参照进行读写操作,即程序对文件进行读取和写入。
IO流的分类:
-
按照流的方向分为:
输入流:读取本地文件中的数据。
输出流:写入本地文件中的数据。
-
按照操作文件类型分为:
字节流:可以操作所有类型的文件。
字符流:只能操作纯文本文件。
IO流的体系
IO
字节流
字节流使用场景:拷贝任意类型的文件。
FileOutputStream
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。
书写步骤:
- 创建字节输出流对象
- 写数据
- 释放资源
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
FileOutputStream out=new FileOutputStream("a.txt");//这个构造方法可以是File对象,也可也是path路径
out.write(65);//写入数据
out.close();//关闭输出传输通道
}
}
FileOutputStream第一步创建对象,就是把对应路径的文件和我们所创建的对象建立连接通道
第二部写入数据,就是通过这个数据传输通道把指定数据传输进去
第三步就是关闭这个数据传输通道
书写细节:
-
创建字节输出流对象:
细节1:参数是字符串表示的路径,也可也是File对象表示的路径
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3:如果文件已经存在,构造方法会情况文件内部数据。
-
写数据
细节1:write方法的参数是整数,单实际是这个字符在ASCII表上的字符
-
释放资源
每次使用完流后都要释放资源。
用处就是解除资源占用。
FileOutputStream写数据的3中方法
方法名 | 说明 |
---|---|
void write(int b) | 一次写一个字节数据 |
void write(byte[] b) | 一次写一个字节数组的数据 |
void write(byte[] b,int off,int len) | 一次写一个字节数组中从off开始的长度为len的数据 |
换行写
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
FileOutputStream out=new FileOutputStream("a.txt");
String str1="hello world \r\n";
String str2="hi world";
out.write(str1.getBytes());
out.write(str2.getBytes());
out.close();
}
}
不同的操作系统换行符号不一样:
windows:\r\n
Linux:\n
Mac:\r
但是,在Windows操作系统中,java对回车换行进行了优化,虽然完整的是\r\n但是只需要写一个\r或\n即可。
java在底层会自动补全。
续写
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
FileOutputStream out=new FileOutputStream("a.txt",true);//在创建对象的时候,把续写开关打开即可
String str1="hello world \n";
String str2="hi world";
out.write(str1.getBytes());
out.write(str2.getBytes());
out.close();
}
}
FileInputStream
作用:操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中。
和FileOutputStream一样,创建对象的时候可以传递Stream类型的oath地址,或者File类型的path地址
书写步骤:
- 创建字节输入流对象
- 读取数据
- 释放数据
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
FileInputStream f=new FileInputStream("a.txt");//建立连接通道
int read = f.read();//读取数据
System.out.println(read);
f.close();//关闭连接通道
}
}
FileInputStream书写细节
-
创建字节输入流对象
细节:如果文件不存在,则直接报错。
-
读取数据
细节1:一次读一个字节,读出来是数据在ASCII表上对应的数字
细节2:读到文件末尾了,read方法返回-1
细节3:read表示读取数据,而且读取一个数据就移动一次指针。
-
释放资源
细节:每次使用完流后都需要释放资源
FileInputStream循环读取
import java.io.FileInputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
FileInputStream f=new FileInputStream("a.txt");
int b;
while( (b=f.read()) != -1){
System.out.print((char)b);
}
f.close();
}
}
文件拷贝
思路:通过FileInputStream和FileOutputStream进行文件拷贝。
//1.小文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
//创建对象
String path1="C:\\Users\\WDADWA\\Desktop\\learn_file\\java\\jdk1.8中文版.CHM";
String path2="C:\\Users\\WDADWA\\Desktop\\learn_file\\jdk1.8中文版.CHM";
FileInputStream in=new FileInputStream(path1);
FileOutputStream out=new FileOutputStream(path2);
//拷贝
//核心思想:边读边写
int b;
while((b=in.read()) != -1){
out.write(b);
}
//释放资源
//规则:先开的流最后关闭
out.close();
in.close();
}
}
文件拷贝速度很慢的原因:FileInputStream一次只读取一个字节,当字节数量为100的时候就代表要循环100次.
解决方法:FileInputStream一次性读取多个字节
方法名 | 说明 |
---|---|
public int read() | 一次读取一个字节数据 |
public int read(byte[] buffer) | 一次读取一个字节数字的数据 |
public int read(byte[] buffer,int off,int len) | 一次读取一个字节数字的数据中起始位置长度为len的数据 |
public int read(byte[] buffer)
的细节:
-
一次读取一个字节数组的数据,每次读取会尽可能把数组装满。
-
故数组越长读取越快,但是数组太长会导致内存存不下,导致崩溃。
-
一般创建数组会使用1024的整数倍
-
read方法返回的是读取数据的长度,数组直接存储到了buff数组中
-
在buff数组中多次读取的时候,最新一次读取的数据会覆盖掉上一次读取的数据。
假设读取abcde字符串,且byte数组长度为2
第一次:ab
第二次:cd
第三次:ed(采用覆盖读取,此时只读取到了e,故覆盖了0索引)
解决方法:使用
public int read(byte[] buffer,int off,int len)
public int read(byte[] buffer,0,len)
这样即可解决第三次出现d的情况了 -
规定了read方法没有读取到数据的时候返回-1
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
//创建对象
String path1="C:\\Users\\WDADWA\\Desktop\\learn_file\\java\\java语言学习\\0正则表达式.md";
String path2="C:\\Users\\WDADWA\\Desktop\\learn_file\\0正则表达式.md";
FileInputStream in=new FileInputStream(path1);
FileOutputStream out=new FileOutputStream(path2);
//拷贝
//核心思想:边读边写
int b;
byte[] buff=new byte[1024*1024*5];//创建了5MB数组
while((b=in.read(buff)) != -1){
out.write(b);
}
//释放资源
//规则:先开的流最后关闭
out.close();
in.close();
}
}
IO流中不同版本JDK捕获异常的方式
在以后编写代码的时候一般不会使用throws抛出异常,而是采用捕获异常的方式处理。
try...catcg异常处理
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
public static void main(String[] args)throws IOException {
//创建对象
FileOutputStream fos=null;//这里使用外界定义是因为如果定义在try中那么这个是局部变量,则无法使用在finally中
try{
fos=new FileOutputStream("a.txt");
fos.write(97);
}catch(IOException e){
e.printStackTrace();
}finally{
if(fos != null){//这里使用if是因为可能fos找不到对应的文件,则会为null,这样会报错
try {//这里使用try catch包裹是因为close方法也是存在异常的
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上述代码过于复杂阅读性很低,故java在不同版本中给出了方法来简化。
java推出了一个AutoCloseable
接口
特点:凡是实现了这个接口的类,在特定情况下,可以自动释放资源。
-
基本做法(上述写法)
try{ 可能出现异常的代码; }catch(异常类名 变量名){ 异常处理代码; }finally{ 执行资源释放操作; }
-
JDK7方案(这个方式使得括号内部代码太复杂,故jdk9简化了)
try(创建流对象1;创建流对象2;...;创建流对象n){ 可能出现异常的代码; }catch(异常类名 变量名){ 异常的处理代码 } //资源用完后会自动释放
上述代码简化为:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOtext { public static void main(String[] args)throws IOException { try( FileOutputStream f= new FileOutputStream("a.txt")){ f.write(97); }catch(IOException e){ e.printStackTrace(); } }
-
JDK9方案
创建流对象1; 创建流对象2; ... 创建流对象n; try(流1;流2;...;流n){ 可能出现异常的代码; }catch(异常类名 变量名){ 异常的处理代码; } //资源用完后会自动释放
上述代码简化为
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOtext { public static void main(String[] args)throws IOException { FileOutputStream f= new FileOutputStream("a.txt"); try(f){ f.write(97); }catch(IOException e){ e.printStackTrace(); } } }
字符集
ASCII字符集
在ASCII编码表中记录了128条数据,这128条数据对于欧洲国际来说够用了。这玩意主要给欧美那边用的。
计算机在存储英文的时候,只需要一个字节就足够了。
编码的原因是:计算机中最小存储单位是1个字节。
GB2312字符集
1980年发布,1981年5月1日实施的简体中文汉字编码国家标准。收录了7447个图形字符,包括6763个简体汉字。
BIG5字符集
台湾地区繁体中文标准字符集,收录13053个中文汉字,1984年实施。
GBK字符集
2000年3月17日发布,收录21003个汉字,包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。
windows系统默认使用的就是GBK,但系统显示:ANSI。因为每个操作系统使用的编码是不同的。
GBK完全兼容ASCII,故GBK存储英文和ASCII一样。
GBK的规则:
规则1:汉字用两个字节存储,第一个字节叫高位字节,第二个字节叫低位字节。(因为汉字太多了一个字节不够放)
规则2:高位字节二进制一定用1开头,转成十进制后是一个负数(目的是为了和英文区分)、
总结:
GBK中一个英文字母占一个字节,二进制第一位是0.
GBK中一个汉字占两个字节,二进制第一位是1
Unicode字符集
Unicide字符集:又叫万过码,国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言,跨平台的文本信息交换。
英文借助Unicide字符集存储方式:
Unicode完全兼容AscII字符集,但是编码的时候不一样。常用的分为三种:
-
UTF-8编码规则:用1~4个字节保存(最常用)
-
UTF-16编码规则:用2~4个字节保存
-
UTF-32编码规则:使用固定4个字节保存
UTF-8编码规则规定:
- 如果是ASCII里面的英文字母,统一使用1个字节表示
- 如果是叙利亚,阿拉伯等文字,统一使用2个字节表示
- 如果是中日韩,中东,东南亚文字,统一使用3个字节表示。
- 其他语言使用4个字节表示。
UTF-8对于不同的字节,采取不同的编码规则
1个字节的首位必须为0,其他位置为查询Unicode对应的二进制数。
2个字节的首位为110,第二个字节首位必须为10,其他位置为查询Unicode对应的二进制数。
3个字节的高位字节首位为1110,其他字节首位为10,其他位置为查询Unicode对应的二进制数。
4个字节的高位字节首位为11110,其他字节首位为10,其他位置为查询Unicode对应的二进制数。
故对于英文:
对于中文:
红色的是UTF-8编码规定的,黑色的由对应编码补齐。
乱码
乱码出现原因:
- 读取数据时没有读完整个汉字。
- 编码和解码方式不统一。
防止乱码产生:
- 不要用字节流去读取文本文件。
- 编码和界面时使用同一个码表,同一个编码方式。
java中编码和解码的代码实现
-
java中编码的方法
String类中的方法 说明 public byte[] getBytes() 使用默认方式进行编码 public byte[] getBytes(String charsetName) 使用指定方式进行编码 idea默认使用Unicode中的UTF-8进行编码
Eclipse默认使用GBK进行编码,charsetName是编码的名字,如UTF-16
-
java中解码的方法
String类中的方法 说明 String(byte[] bytes) 使用默认方式进行解码 String(byte[] bytes,String charsetName) 使用指定方式进行解码
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Text {
public static void main(String[] args) throws UnsupportedEncodingException {
//1.编码
String str="你好a";
byte[] bytes1 = str.getBytes();
byte[] bytes2 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes1));
System.out.println(Arrays.toString(bytes2));
//2. 解码
String str1=new String(bytes1);
String str2=new String(bytes2,"GBK");
System.out.println(str1);
System.out.println(str2);
}
}
字符流
字符流使用场景:操作文本文件。
乱码产生的原因之一是:读取数据时未读取整个汉字。
java推出了字符流,当读取字母时,一个字节一个字节读取,当读取汉字时,根据对应的编码所决定一次读2个字节还是3个字节。
字符流的底层就是字节流。
字符类=字节流+字符集
特点:
- 输入流:一次读一个字节,遇到中文时,一次读多个字节。
- 输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。
字符流使用场景:对于纯文本进行读写操作。
字符流继承体系结构
FileReader
创建方法和使用方法都是和字节流一样的。
使用步骤
-
创建字符流输入对象
构造方法 说明 public FileReader(File file) 创建字符输入流关联本地文件 public FileReader(String pathName) 创建字符输入流关联本地文件 public FileReader(String pathName,Charset charsetName) 创建字符输入流关联本地文件并且使用指定字符编码 第三个Charset是一个工具类。(JDK11新特性)
new FileReader("a.txt",Charset.forName("GBK"));//这样来指定编码读取数据
如果读取文件不存在,报错。
-
读取数据
成员方法 说明 public int read() 每次读取1字节数据,读到末尾时返回-1 public int read(char[] buffer) 每次读取多个数据到buff数组中,读到末尾时返回-1 public int read(char[] buffer,int off,int len) 每次读取多个数据到buff数组中len长度,读到末尾时返回-1 空参read细节:
- 默认也是一个字节一个字节的读取,如果遇到中文就一次读取多个字节。
- 在读取之后,方法底层还会进行解码并转成十进制。最终把这个十进制作为返回值,这个十进制也表示在字符集上对应得数字。
有参的read细节:
- 读取数据,解码,强转三步合并了,把强转之后的字符放入char数组中了。
-
释放资源
方法名 说明 public int close() 关闭流
FileWrite
创建方法和使用方法都是和字节流一样的。
使用步骤
-
创建字符流输入对象
构造方法 说明 public FileWriter(File file) 创建字符输出流关联本地文件 public FileWriter(String pathName) 创建字符输出流关联本地文件 public FileWriter(File file,boolean append) 创建字符输出流关联本地文件,续写 public FileWriter(String pathName,boolean append) 创建字符输出流关联本地文件,续写 public FileWriter(String pathName,Charset charsetName) 创建字符输出流关联本地文件,续写+使用指定编码 public FileWriter(String pathName,Charset charsetName,boolean append) 创建字符输出流关联本地文件,续写+使用指定编码 细节1:参数是字符串表示的路径还是File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
细节3:如果文件已经存在,则会清空文件,如果不想情况就在append中写入true打开续写开关。
-
读取数据
成员方法 说明 void write(int c) 写出一个字符 void write(String str) 写出一个字符串 void write(String str,int off,int len) 写出一个字符串的一部分 void write(char[] cbuf) 写出一个字符数组 void write(char cbuf,int off,int len) 写出一个字符数组的一部分 如果write方法的参数是整数,实际上写到本地文件的是整数在字符集上对应的字符。
-
释放资源
方法名 说明 public int close() 关闭流
字符流原理解析
输入流:
-
创建字符输入流对象
底层:关联文件,并创建缓冲区(长度为8192的字节数组)
-
读数据
底层:
-
判断缓冲区中是否有数据可以读取
-
缓存区中没有数据:
- 就从文件中获取数据,装到缓存区中,每次尽可能的装满缓冲区。
- 如果文件中也没有数据了,就返回-1
-
缓存区中有数据:就冲缓存区中读取数据。
空参的read方法,一次读取一个字节,遇到中文一次读取多个字节,把字节解码为十进制返回。
有参的read方法:把读取字节,解码,强转三部合并了,强转之后的字符放到数组中。
-
FileWriter的关联会删除文件,但是reader同时对这个文件进行读取的时候可以读取到缓冲区中的数据哦~
(必须先Reader,再关联writer)
输出流:
-
创建字符输出流对象
底层:关联文件,并创建缓冲区(长度为8192的字节数组)
-
写数据
底层:把所有数据按照utf-8进行编码,并先写入缓冲区中。
三个情况把缓冲区数据写入文件中。
- 当缓冲区装满了
- 手动刷新(使用flush方法)
- 释放资源时
fluse和clone的区别:flush刷新之后还可继续往文件中写数据。