快速了解IO流
本文转载于
https://blog.csdn.net/qq_44543508/article/details/102831084
作者:宜春
一.常用的编码方式
1.ASCII码
标准ASCII 码也叫基础ASCII码,使用7位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符
2.Unicode字符集
统一码(Unicode),也叫万国码、单一码,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求
3.UTF-8编码规则
UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码
比如这句话:CSDN内容
,如何让计算机识别呢?
通过Unicode字符集,可以知道,这句话对应的编码
C : 0043
S : 0053
D : 0044
N : 004E
内:5185
容:5BB9
丰:4E30
富:5BCC
其转化为二进制如下:
C : 00000000 01000011
S : 00000000 01010011
D : 00000000 01000100
N : 00000000 01001110
内 : 00101000 10000101
容 : 01011011 10111001
字符串总共占用了12个字节,可以发现,英文前8位都是0,浪费了空间,怎解决这个问题呢?UTF
UTF-8是这样做的
1.单字节的字符,字节的第一位设为0,对于英语文本,UTF-8码只占用一个字节,和ASCII码完全相同
2.n个字节的字符(n>1),第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足
3.这样就形成了如下的UTF-8标记位:
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
于是,CSDN内容
就变成了这样
分析一下内字,内的Unicode码为01010001 10000101,因为是UTF-8(通用多八位字符集传输格式),所以应该是3个字节,故应为1110 xxxx | 10xx xxxx |10xx xxxx
依次填入内的Unicode码,变为11100101 10000110 10000101
C : 01000011
S : 01010011
D : 01000100
N : 01001110
内 : 11100101 10000110 10000101
容 : 11100101 10101110 10111001
可以看出,UTF-8的规则可以让字符串变为10字节,节省了空间
4.GBK汉字编码字符集
GBK全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification),中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司、电子工业部科技与质量监督司1995年12月15日联合以技监标函1995 229号文件的形式,将它确定为技术规范指导性文件。2000年已被GB18030-2000《信息交换用 汉字编码字符集 基本集的扩充》国家强制标准替代。 2005年GB18030-2005发布,替代了GB18030-2000
GBK对应GBK编码方式
二.File类
java.io.File是专门对文件进行操作的类,只能对文件本身进行操作,不能对文件内容进行操作
常用API
- public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
- public File(String parent,String child) :从父路径名字符串和子路径名字符串创建新的 File实例
- public File(File parent,String child) :从父抽象路径名和子路径名字符串创建新的 File实例
=================================================================
-
public String getAbsolutePath() :返回此File的绝对路径名字符串
-
public String getPath() :将此File转换为路径名字符串
-
public String getName() :返回由此File表示的文件或目录的名称
-
public long length() :返回由此File表示的文件的长度字节
-
public boolean exists() :此File表示的文件或目录是否实际存在
-
public boolean isDirectory() :此File表示的是否为目录
-
public boolean isFile() :此File表示的是否为文件
public class FileTest {
public static void main(String[] args) {
// 文件路径名
String path = "F:\\input\\wordcount\\word.txt";
File f = new File(path);
System.out.println("文件绝对路径:"+f.getAbsolutePath());
System.out.println("文件构造路径:"+f.getPath());
System.out.println("文件名称:"+f.getName());
System.out.println("文件长度:"+f.length()+"字节");
// 判断是否存在
System.out.println("是否存在:"+f.exists());
// 判断是文件还是目录
System.out.println("文件?:"+f.isFile());
System.out.println("目录?:"+f.isDirectory());
}
}
- public boolean createNewFile() :文件不存在,创建一个新的空文件并返回true,文件存在,不创建文件并返回false
- public boolean delete() :删除由此File表示的文件或目录
- public boolean mkdir() :创建由此File表示的目录
- public boolean mkdirs() :创建多级目录
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
// 文件的创建
File f = new File("./data/test.txt");
System.out.println("是否存在:"+f.exists()); // false
System.out.println("是否创建:"+f.createNewFile()); // true
System.out.println("是否创建:"+f.createNewFile()); // 以及创建过了所以再使用createNewFile返回false
System.out.println("是否存在:"+f.exists()); // true
// 目录的创建
File f2= new File("newDir");
System.out.println("是否存在:"+f2.exists());// false
System.out.println("是否创建:"+f2.mkdir()); // true
System.out.println("是否存在:"+f2.exists());// true
// 创建多级目录
File f3= new File("newDira\\newDirb");
System.out.println(f3.mkdir());// false
File f4= new File("newDira\\newDirb");
System.out.println(f4.mkdirs());// true
// 文件的删除
System.out.println(f.delete());// true
// 目录的删除
System.out.println(f2.delete());// true
System.out.println(f4.delete());// true 删除的是newDirb
}
}
三.字节流和字符流概述
字节:1byte = 8bit
字符:1字符 = 2byte = 16bit
字节流操作的基本单位是字节
字符流操作的基本单位是字符
Java的I/O系统中
面向字节流的是InputStream(输入) 和 OutPutStream(输出)
面向字符的是Reader(输入) 和 Writer(输出)
两者之间的桥梁为InputStremReader 和 OutputStreamWriter,也称之为转换流
四.字节流
我们必须明确一点的是,一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据
4.1 字节输出流(OutputStream)
java.io.OutputStream抽象类是表示字节输出流的所有类的超类(父类),将指定的字节信息写出到目的地
常用方法
-
public void close() :关闭此输出流并释放与此流相关联的任何系统资源
-
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
-
public void write(byte[] b):将 b.length个字节从指定的字节数组写入此输出流。
-
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量off开始输出到此输出流。 也就是说从off个字节数开始读取一直到len个字节结束
-
public abstract void write(int b) :将指定的字节输出
以上五个方法则是字节输出流都具有的方法,由父类OutputStream定义提供,子类都会共享以上方法
FileOutputStream类
OutputStream有很多子类,我们从最简单的一个子类FileOutputStream开始。看名字就知道是文件输出流,用于将数据写出到文件
FileOutputStream构造方法
-
public FileOutputStream(File file):根据File对象为参数创建对象
-
public FileOutputStream(String name): 根据名称字符串为参数创建对象
FileOutputStream写出字节数据
使用FileOutputStream写出字节数据主要通过Write方法,而write方法分如下三种
示例
展示了文件追加,文件内容换行,以及创建流、写数据的三种方式
public class FileOutputStreamTest {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
// 使用文件名称创建流对象,没有则创建,有则会清空文件数据,第二个参数可以设置,设置为true表示可追加
fos = new FileOutputStream("F:\\input\\test.txt",true);
// 第一种写的方式,输入一个ASCII方式编码的字符,占一个字节
fos.write(97);
// \r\n一个换行一个回车 占用两个字节
fos.write("\r\n".getBytes(StandardCharsets.UTF_8));
// 第二种写的方式,字节数组
byte[] bytes = "Java".getBytes(StandardCharsets.UTF_8);
fos.write(bytes);
fos.write("\r\n".getBytes(StandardCharsets.UTF_8));
// 第三种写的方式,字节数组,并设置范围
byte[] bytes1 = "Hadoop and Spark".getBytes(StandardCharsets.UTF_8);
fos.write(bytes1,0,6);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2 字节输入流(InputStream)
java.io.InputStream抽象类是表示字节输入流的所有类的超类(父类),可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法
字节输入流的基本共性功能方法:
-
public void close() :关闭此输入流并释放与此流相关联的任何系统资源
-
public abstract int read(): 从输入流读取数据的下一个字节
-
public int read(byte[] b): 该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
FileInputStream类
java.io.FileInputStream类是文件输入流,从文件中读取字节
FileInputStream的构造方法
-
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名
-
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名name命名
FileInputStream读取字节数据
示例
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("F:\\input\\test.txt");
// 一.读取一个字节
int i = inputStream.read();
System.out.println((char)i);
// 二.使用字节数组
int len;
byte[] bys = new byte[1024];
while ((len = inputStream.read(bys)) != -1) {
System.out.println(new String(bys,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五.字符流
为什么需要字符流?
字节流读取中文字符时,可能不会显示完整的字符,那是因为一个中文字符占用多个字节存储
5.1 字符输出流(Writer)
java.io.Writer抽象类是字符输出流的所有类的超类(父类),将指定的字符信息写出到目的地。它同样定义了字符输出流的基本共性功能方法
字符输出流的基本共性功能方法:
- void write(int c) 写入单个字符
- void write(char[] cbuf) 写入字符数组
- abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
- void write(String str) 写入字符串
- void write(String str,int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
- void flush()刷新该流的缓冲
- void close() 关闭此流,但要先刷新它
FileWriter类
java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区
构造方法
- FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象
- FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称
FileWriter写出数据
关闭流时,与FileOutputStream不同。 如果不关闭,数据只是保存到缓冲区,并未保存到文件
关闭close和刷新flush
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了
flush :刷新缓冲区,流对象可以继续使用
close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了
示例
public class FileWriterTest {
public static void main(String[] args) {
// 使用文件名称创建流对象
FileWriter fw = null;
try {
fw = new FileWriter("F:\\input\\filewritertest.txt");
// 一.写出一个ASCII码的字符
fw.write(97);
// 二.写出字符串
fw.write("Hadoop汉语版");
// 三.写出字符数组
char[] chars = "Spark大数据".toCharArray();
fw.write(chars);
// 四.写出范围字符串
fw.write("Flink",0,3);
// 五.字符数组的范围
fw.write(chars,0,5);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
assert fw != null;
fw.flush(); // 将缓冲区的数据保存到文件
// 关闭资源时,与FileOutputStream不同。 如果不关闭,数据只是保存到缓冲区,并未保存到文件
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.2 字符输入流(Reader)
java.io.Reader抽象类是字符输入流的所有类的超类(父类),可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法
字符输入流的共性方法:
- public void close() :关闭此流并释放与此流相关联的任何系统资源
- public int read(): 从输入流读取一个字符
- public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
FileReader类
java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区
构造方法
- FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象
- FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的字符串名称
FileReader读取字符数据
public class FileReaderTest {
public static void main(String[] args){
// 使用文件名称创建流对象
FileReader fr = null;
try {
fr = new FileReader("F:\\input\\filewritertest.txt");
// 一.读一个字符 a
System.out.println((char) fr.read());
// 二.读一个字符数组
char []chars = new char[6];
int len;
while((len = fr.read(chars)) != -1){
System.out.println(new String(chars,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
// 关闭资源
try {
assert fr != null;
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五.缓冲流
读取到一个字节/字符,先不输出,等凑足了缓冲的最大容量后一次性写出去,减少了读写次数,提高了效率
5.1 字节缓冲流
构造方法
-
public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流,注意参数类型为InputStream
-
public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流,注意参数类型为OutputStream
public class BufferedDemo {
public static void main(String[] args) throws IOException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("F:\\input\\test.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("F:\\input\\testCopy.txt"));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
}
}
5.2 字符缓冲流
构造方法
-
public BufferedReader(Reader in) :创建一个新的缓冲输入流,注意参数类型为Reader
-
public BufferedWriter(Writer out): 创建一个新的缓冲输出流,注意参数类型为Writer
字符缓冲流特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,这里不再阐述,我们来看字符缓冲流具备的特有方法
- BufferedReader:public String readLine(): 读一行数据。 读取到最后返回null
- BufferedWriter:public void newLine(): 换行,由系统属性定义符号
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("F:\\input\\test.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("F:\\input\\testCopy.txt"));
// 定义字符串,保存读取的一行文字
String line = null;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {
bw.write(line);
bw.newLine();
}
// 释放资源
br.close();
bw.close();
}
}
六.转换流
众所周知,计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象
InputStreamReader类-----(字节流到字符的桥梁)
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("F:\\input\\chinese.txt") , "GBK");
OutputStreamWriter类-----(字符到字节流的桥梁)
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK")
七.打印流
打印流
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式
打印流分类:
-
字节打印流PrintStream
-
字符打印流PrintWriter
打印流特点:
A:只操作目的地,不操作数据源
B:可以操作任意类型的数据
C:如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新
D:可以直接操作文件
字符输出流
public class PrintTest {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("F:\\input\\test.txt"));
PrintWriter pw=new PrintWriter("F:\\input\\testPrint.txt");
String line;
while((line=br.readLine())!=null) {
pw.println(line);
}
br.close();
pw.close();
}
}
字节输出流
public class PrintTest {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("F:\\input\\test.txt"));
PrintStream ps=new PrintStream("F:\\input\\testPrint.txt");
String line;
while((line=br.readLine())!=null) {
ps.println(line);
}
br.close();
ps.close();
}
}
八.序列化
这篇文章把序列化和反序列化总结的很好
https://blog.csdn.net/weixin_41427129/article/details/105756415
九.BIO NIO AIO
我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。
并且,用户空间的程序不能直接访问内核空间
当想要执行 IO 操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成
因此,用户进程想要执行 IO 操作的话,必须通过 系统调用 来间接访问内核
我们在平常开发过程中接触最多的就是 磁盘 IO(读写文件) 和 网络 IO(网络请求和响应)
从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的
当应用程序发起 I/O 调用后,会经历两个步骤:
内核等待 I/O 设备准备好数据
内核将数据从内核空间拷贝到用户空间
BIO 同步阻塞IO
同步:请求只能一次一次发送,上一次请求完成后才可以发送下一次请求
阻塞:这个请求收不到数据会一直处于等待状态
阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误才返回。进程从调用recvfrom开始到它返回的整段时间内是被阻塞的
NIO 同步非阻塞IO
当一个应用进程像这样对一个非阻塞描述字循环调用recvfrom时,我们称之为轮询(polling)。应用进程持续轮询内核,以查看某个操作是否就绪
IO多路复用 异步阻塞
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的
AIO 异步非阻塞IO
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作