前言:提到文件先了解一下编码
- 在windows下,直接创建txt文本文件,我的默认是ANSI编码,但文本文件编码可任意修改;
- 我使用的是IDEA,所以我的项目默认编码是UTF-8编码,UTF-8编码中,中文占3byte,英文占1byte;
根据结构图理解
IO流的三种分类方式:
- 按照流的方向分为:输入流和输出流
- 按照流的数据单位:字节流和字符流
- 按照流的不同功能:节点流和处理流
我是按照字节流和字符流的结构顺序来逐个理解相关概念及用法(图源网络)
一、File:
- java.io.file类代表系统文件(文件和目录)
- 访问文件属性步骤:
- 先创建/获取文件对象
File file = new File("D:\\Program Files (x86)\\Java\\TestIO\\test2.txt");//way1
File file = new File(new File("D:\\Program Files (x86)\\Java"),"TestIO\\test3.txt");//way2
URI uri = tmp.toURI();
File file = new File(uri);//way3
- 然后调用方法
- 常用方法:
(1)boolean exists() //表示当前路径是否存在
(2)boolean isFile() //判断当前路径是否是文件
(3)boolean isDirectory() //判断当前是否为目录
(4)boolean isHidden() //判断是否是隐藏文件
(5)void deleteOnExit() //在程序退出时删除
(6)boolean delete() //直接删除
(7)boolean createNewFile() //创建新文件 在当前文件不存在的情况下,创建成功返回true,文件存在返回false
(8)Mkdirs() 和mkdir() //创建目录
Mkdirs 和mkdir之间的关系
new File("/tmp/one/two/three").mkdirs();
执行后, 会建立tmp/one/two/three父级目录
new File("/tmp/one/two/three").mkdir();
则不会建立任何目录, 因为找不到/tmp/one/two目录, 结果返回false
(9)file.getName();// 获取文件名
(10)File.getPath(); //获取相对路径
(11)getParent()//获取文件的父路径
(12)file.lastModified() //获取文件最后一次修改的时间
(13)file.getAbsolutePath();//获取绝对路径
(14)File.Length() //获取文件长度
(15)public boolean renameTo(File dest)
上面是对文件的基本操作,下面再了解对文件内容的基本读写操作!
二、字节流:
- 输入流(InputStream):
输入流下有FileInputStream类,用来将文件内容读取到Java程序中:
public class fileInputStreamTest {
public static void main(String[] args) {
FileInputStream in = null;
try {
in = new FileInputStream("D:\\Program Files (x86)\\Java\\TestIO\\test4.txt");
int i = 0;
byte[] bytes = new byte[9];
//in.read();会乱码,中文占两个字节,这样每次读半个字
while(in.read(bytes) != -1){
System.out.print(new String(bytes,"gbk"));//gbk是编码形式
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
//防止多线程情况下引发的异常
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 输出流(OutputStream):
输出流下有FileOutputStream类,将内容从Java程序写入指定文件中:
public class FileOutStreamTest {
public static void main(String[] args) {
OutputStream out = null;
try {
//第二个参数不写或者写为false,会覆盖之前的内容;如果写为true的话,会追加在原文档的后面
out = new FileOutputStream("D:\\Program Files (x86)\\Java\\TestIO\\test.txt",true);
String str = "写入文件的内容啊";
out.write(str.getBytes("utf-8"));//插入的类型与源文件编码形式不同会出现乱码
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上两种方法是将内容直接写入或者读出的,只要运行程序,相应的IO操作就会完成,但是我们知道的,IO操作很耗时,如果频繁的进行IO操作,事实上很浪费时间,因此我们引入缓冲区的概念。
缓冲区的作用:
1、IO耗时,使用缓冲区减少IO次数,一次读出缓冲区的大小的内容,再次去读的时候操作的是缓冲区,将缓冲区内容读完后会刷新缓冲区的数据。–flush():在写入数据过程中,写了一部分就要同步到文件里 --close():在操作结束时调用;
2、字节和字符间的转换在缓冲区实现;
- 带有缓冲区的输入流(BufferedInputStream):
public class BufferInputStreamTest {
public static void main(String[] args) {
try {
BufferedInputStream in = new BufferedInputStream(new FileInputStream( "D:\\Program Files (x86)\\Java\\TestIO\\test.txt"));
byte[] bytes = new byte[2];
int read = in.read(bytes);//将文件中内容读出来,并且放到数组里
System.out.println(read);
System.out.println(Arrays.toString(bytes));
while(in.read(bytes) != -1){
System.out.print(Arrays.toString(bytes));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 带缓冲区的输出流(BufferedOutputStream):
public class bufferedOutStreamTest {
public static void main(String[] args) {
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(
new FileOutputStream("D:\\Program Files (x86)\\Java\\TestIO\\test.txt",true) );
String str = "是不是傻";
out.write(str.getBytes());
out.flush();//flash写完后被读之后还能继续向里面写内容,但close之后想要再写的话就还需要重新开启资源
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(out != null){
try {//如果没有写满8M的话,就要调用close方法或者flash
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字符和字节本质传输都是字节;
区别是:字节流以一个字节为单位进行数据传输;而字符流以两个字节二为单位进行数据传输。
三、字符流
-
转换流:字节流与字符流之间的桥梁
InputStreamReader按照指定编码将字节流转换成字符流 解决读里的乱码问题
InputStreamWriter按照指定编码将字符流转换成字节流 解决写里的乱码问题 -
输入流(reader):从文件读取内容转换为字节传输到Java程序再转换为字符输出,所以字符的输入输出流中间都会经过一个转换流
public class FileReaderTest {
public static void main(String[] args) {
FileReader fileReader = null;
try {
fileReader = new FileReader("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt");
char[] chars = new char[10];
int len = 0;
while((len = fileReader.read(chars)) > 0) {
for(int i = 0;i < len;i++) {
System.out.print(chars[i]);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fileReader != null){
try {
fileReader.close();
fileReader = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 输出流(writer):
public class FileWriterTest {
public static void main(String[] args) {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt",true);
String str = "也罢--鲁向卉";
fileWriter.write(str);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
fileWriter = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 带缓冲区的输入流:
public class BufferedReaderTest {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt"),1024);
String str = null;
reader.skip(2);//跳过前n个字符从第n+1个开始读
while((str=reader.readLine()) != null){
System.out.println(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 带缓冲区的输出流:
public class BufferedWriterTest {
public static void main(String[] args) {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt"));
// writer.write(123);
writer.write("-我们的总和-");
String str = "-也罢-";
writer.write(str,0,3);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(writer != null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
四、序列化和反序列化
ObjectInputStream:
ObjectOutputStream:
序列化:对象–>字节序列的过程 存放在文件中
反序列化:字节序列–>对象的过程
- 序列化和反序列化的作用:
(1)实现对象持久化。对象–>字节序列的过程 存放在文件中
对象==》项目终止 对象也就随之消失,无法实现反复利用 例如:MySQL
(2)网络传输。不能将消息直接从A转到B的,需要对它序列化再进行网络传输 - 序列化/反序列化流程:
(1)让序列化对象实现Serializable接口
(2)序列化调用ObjectOutputStream—out.writeObject( p)方法;
(3)反序列化调用ObjectInputStream—in.readObject( p)方法; - transient:可以让指定关键字不被序列化
- transient关键字的作用:
(1)避免不必要的序列化操作
(2)避免敏感信息被序列化
常见的I/O模型
(可能理解还不成熟,有错还望指出)
I/O分两步
- 用户层PAI的调用
- 内核层完成系统调用
I/O模型的分类
- 阻塞IO模型
- 非阻塞IO模型
- IO复用模型
- 信号驱动IO模型
- 异步IO模型
阻塞 IO模型(BIO)
阻塞IO模型先是等待数据就绪,然后将内核空间的数据拷贝一份到用户空间;
它的特点是一个用户单独创建一个线程,比如说小李子去火车站买票,但暂时不售票,他在火车站等了三天,然后买到票回去;它的缺点是:用户请求量和服务端线程成正比关系,服务端线程是并发的瓶颈,限制了用户数量;
非阻塞IO模型
soctet设置为NONBLOCK,当IO请求无法完成时,先返回一个错误码,然后IO测试操作持续进行,直到数据准备好,然后再将数据拷贝到内核空间;就像小李子去买票,但还未开始售票,他先回去隔两小时再去火车站买一次,直到买到票;
IO复用模型
IO复用模型用了select和poll函数,这两个函数会使线程阻塞,但与BIO的区别是,IO多复用模型可以同时对多个线程阻塞,还可以同时对多个文件IO数据读写操作进行检测,直到有数据准备就绪,才调用真正的IO函数。
过程是:调用select/poll方法由一个用户线程轮流查询多个Channel,直到某个阶段数据准备就绪,再通知实际用户执行下一阶段的拷贝,通过一个专职的用户进程执行非阻塞IO轮流查询,模拟实现阶段1的异步化。就像小李子想买票,他让黄牛去买,然后等黄牛买到后他再去黄牛那里拿票。
信号驱动IO模型首先socket进行信号驱动IO,并安装一个信号处理函数,线程继续运行并不阻塞,当数据准备好时,线程会收到一个信号,可以在信号处理函数中调用IO操作函数处理数据。就像小李子想去买票,他给售票处打电话,等由票的时候通知他,然后他再去火车站直接买好票回来;
异步IO模型
调用aio_read函数,告诉内核描述字,缓冲区大小和缓冲区指针,文件偏移和通知的方式,然后立即返回,当内核数据拷贝到缓冲区后,再调用应用程序。就像小李子买票的时候,他告诉售票员他的具体信息,等票出售的时候,售票员把他的票送货上门。所以异步IO模式下,阶段1和阶段2皆由内核完成,用户不参与。