IO流相关概念
什么是IO流
- Java中I/O是以流为基础进行数据的输入输出的,所有数据被串行化(所谓串行化就是数据要按顺序进行输入输出)写入输出流。简单来说就是java通过io流方式和外部设备进行交互。
- 在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操
作,网络上的数据传输流,字符串流,对象流等等等。 - 比如程序从服务器上下载图片,就是通过流的方式从网络上以流的方式到程序中,在到硬盘中
I:Input 输入
O:Output 输出
IO流分类简答
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划(读取数据方式)分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流
同步与异步,阻塞与非阻塞的区别(重要)
- 同步,一个任务的完成之前不能做其他操作,必须等待(等于在打电话)
- 异步,一个任务的完成之前,可以进行其他操作(等于在聊QQ)
- 阻塞,是相对于CPU来说的, 挂起当前线程,不能做其他操作只能等待
- 非阻塞,,无须挂起当前线程,可以去执行其他操作
IO的五种设计模式
- 同步阻塞BIO(blocking I/O)
- 同步非阻塞NIO(noblocking I/O
- 异步非阻塞AIO(asynchronous I/O)
- 信号驱动IO(signal blocking I/O)
- IO多路转接(I/O multiplexing)
什么是Netty
- Netty是由JBOSS提供的一个Java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
- Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发
什么是内核空间
我们的应用程序是不能直接访问硬盘的,我们程序没有权限直接访问,但是操作系统
(Windows、Linux…)会给我们一部分权限较高的内存空间,他叫内核空间,和我们的实际硬
盘空间是有区别的,可以理解为应用程序空间和实际电脑硬盘内存之间的缓冲区
注意:我这里的用户空间就是应用程序空间
什么是比特(Bit),什么是字节(Byte),什么是字符(Char),它们长度是多少,各有什么区别
- Bit最小的二进制单位 ,是计算机的操作部分取值0或者1,是最小单位 计算机他只能认识0或者1
- Byte是计算机中存储数据的单元,8位的二进制数,Byte是8个字节,是给计算机看的(计算机内部,一个字节可表示一个英文字母,两个字节可表示一个汉字。) 取值(-128-127)
*Char是用户的可读写的最小单位,他只是抽象意义上的一个符号。如‘5’,‘中’,‘¥’ 等等等等。在
java里面由16位bit组成Char 取值 (0-65535)
字符 是看到的东西 一个字符=二个字节
IO流讲解
IO流的分类详解
一种方式是按照流的方向进行分类:
- 输入流:从文件读入到内存。只能进行读操作,以内存作为参照物,往内存中去,叫做输入(Input)。或者叫做读(Read)
- 输出流:从内存读出到文件。只能进行写操作,从内存中出来,叫做输出(Output)或者叫做写(Write)
- 输入流是得到数据,输出流是输出数据。
- 注意:输出流可以帮助我们创建文件,而输入流不会
另一种方式是按照读取数据方式不同进行分类:
- 字节流:是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制
(Java代码接收数据为一般为 char数组,也可以是别的 ) - 字符流:这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件
(Java代码接收数据只能为 byte数组 )
问:为什么图片、视频、音乐、文件等 都是要字节流来读取
这个很基础,你看看你电脑文件的属性就好了,CPU规定了计算机存储文件都是按字节算的
按照角色划分,节点流和处理流的区别(按照读写时是否直接与硬盘,内存等节点连接分)
- 节点流是底层流/低级流,直接和数据源相接。
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
- 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
处理流的功能主要体现在:
性能的提高,主要以增强缓冲的方式来提高输入输出的效率
操作的边界,处理流可能提供了一系列便捷方法来一次输入输出大批量的数据,使用更加灵活。
Java中流类的超类(四大家族)
Java I0流的40多个类都是从如下4个抽象类基类中派生出来的
超类代表顶端的父类(都是抽象类)
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()可能会导致丢失数据
IO的常用类分类图
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系,
IO流中需要掌握哪些流(16个)(重要)
- 文件专属流:(重点)
java.io.FileInputstream(掌握)
java.io.FileOutputstream(掌握)
java.io.FileReader
java.io.FileWri ter - 转换流:(将字节流转换成字符流)
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(掌握)
IO流常见流使用和注意事项
文件专属流:(带有File的流@重点)
FileInputstream使用和注意事项
文件字节输入流
从硬盘 到---->内存去读取
FileInputstream使用(循环读取 高效)
package com;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
FileInputStream :
1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读
2、字节的方式,完成输入的操作,完成读的操作(硬盘----> 内存)
*/
// FileInputStreamTest为IDEA中的当前路径
public class FileInputStreamTest {
public static void main(String[] args) {
// 创建FileInputStream字节流对象
FileInputStream fis =null;
try {
// IDEA当前路径默认在工程Project的根,当寻找路径文件的时候会首先在工程根的目录下开始往下一步一步的寻找文件
// 在自己包下的文件也要从根目录开始写"studyIo/src/com/tempFile.txt",不能直接写tempFile.txt,这样会报错
fis =new FileInputStream("studyIo\\src\\com\\tempFile.txt");
// 初始化一个数组
byte[] bytes =new byte[4];
while (true){
try {
// 读流 调用一个read()方法,相当于指针开始指向第一个元素,每调用一次,指针下移一次,读出相应的字节
// 当指针指向末尾之后,也就是最后一个元素后面,没了,返回-1,如果读取为-1代表为空即结束读取
// 读取的是一个字符,但是java虚拟机会自动将char类型数据转换为int数据
int fisCount =fis.read(bytes); // 拿到的是字节数量
if (fisCount==-1){ // 字节数量为-1的时候说明文件里面没有元素了(读到了0个字节)
break;
}
// 能运行到(还没到-1)这里说明文件还没有读取完.把读取到的byte数组转换成字符串,读到多少个转换多少个
System.out.println(new String(bytes,0,fisCount));
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fis != null) {
try {
// 用完一定要关闭
fis.close();
System.out.println("流关闭成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileInputStream类的其他常用方法:(文件内容不大时候)
int available() : 返回流当中剩余的没有读到的字节数量
long skip(long n) : 跳过几个字节不读
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest04 {
public static void main(String[] args) {
// 创建FileInputStream对象
FileInputStream fis =null;
try {
fis =new FileInputStream("chapter15/src/com/bjpowernode/java/io/UserFile");
// 还没有读的时候文件中的总字节数量:
// System.out.println("文件中的总字节数量:"+fis.available()); // 5
// 读一个字节
// int readData =fis.read();
// 文件中还剩下可以读的字节数量是: (文件中数据:abcde)
// System.out.println("剩下多少字节没有读:"+fis.available()); // 4
// int available() 这个方法可以怎么用呢?
// : 当文件中的数据不太大的时候可以直接把总字节数量放入一个数组里面一次读取完(注意:当文件过大时不能直接放到数组当中:扩容问题影响效率)
byte[] bytes =new byte[fis.available()]; // 直接把文件中的总字节数量放到数组里面
// 不需要循环了
// 直接一次读就行了
int readData1 =fis.read(bytes); // 读到数组里面 读到的是字节数量
System.out.println(readData1); // 5
System.out.println(new String(bytes)); //abcde 直接把数组转换成字符串
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
// 关闭流
try {
fis.close();
System.out.println("流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意事项
①目录问题
②要读取的文件一定要存在
FileOutStream使用和注意事项
文件字节输出流
从内存 ---->硬盘去写文件 保存下来
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutStreamTest01 {
public static void main(String[] args) {
FileOutputStream fos =null;
// 注意:这种方式谨慎使用,这种方式会先将原文件清空,然后重写写入
try {
// 新文件OutputStreamFile.txt
fos =new FileOutputStream("OutputStreamFile");
// 定义byte数组
byte[] bytes ={97,98,99,100,101};
// 全部写的方式
// 将byte数组全部写到硬盘,将数组里面的内容写入到”OutputStreamFile”文件里面
// (当文件不存在的时候自动新建到项目根目录)
fos.write(bytes);
// 部分写的方式
// 将byte数组的一部分写到硬盘,从0开始2个长度 ab
fos.write(bytes,0,2);
// 写完之后一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
System.out.println("流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意事项
①这种方式谨慎使用,当文件不存在的时候自动新建到项目根目录,如果原来有文件存在,先将原文件清空,然后重写写入 ,改进方式
以追加的方式在文件末尾写入,不会清空原文件内容
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
// 以追加的方式在文件末尾写入,不会清空原文件内容
public class FileOutStreamTest02 {
public static void main(String[] args) {
FileOutputStream fos =null;
try {
// 追加的构造方法,true是参数
fos =new FileOutputStream("chapter15/src/com/bjpowernode/java/io/tempFile.txt",true);
// 开始写
byte[] bytes ={97,98,99,100,101};
fos.write(bytes);
// 也可以写字符串形式,然后把字符串转换成数组格式
String s ="我是中国人,我爱中国,中国加油,中国之国~";
byte[] bytes1 =s.getBytes(); // 将字符串转换成数组
fos.write(bytes1); // 把该数组当中的内容写入到文件当中
// 刷新管道
fos.flush();
System.out.println("流刷新成功~");
}
catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
System.out.println("流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件复制(任何文件)(FileInputStream + FileOutputStream)
使用FileInputStream + FileOutputStream完成文件的拷贝
拷贝的过程应该是一边读,一边写
使用以上的字节流拷贝文件的时候,文件类型随意,万能的,什么样的文件都能拷贝
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyTest01 {
public static void main(String[] args) {
FileInputStream fis =null;
FileOutputStream fos =null;
try {
// 把D盘中的文件复制到C盘
// 创建一个输入流对象
fis =new FileInputStream("D:\\画图总部\\面向对象思想图.png");
// 创建一个输出流对象
fos =new FileOutputStream("C:\\面向对象思想图.png");
// 核心 : 一边读一边写
byte[] bytes =new byte[1024 *1024]; // 以1MB拷贝
int readCount =0; // 数组的字节数量
// 从文件中读进数组里面 只要不为-1就一直读,为-1说明读取完了
while ((readCount = fis.read(bytes))!=-1){
//写 边读边写入
fos.write(bytes,0,readCount); //
}
// 刷新,输出流最后要刷新
fos.flush();
System.out.println("输出流刷新成功~");
}
catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 注意:分开try,不要一起try
// 一起try的时候,其中一个出现异常,可能会影响到另外一个流的关闭
if (fis != null) {
try {
fis.close();
System.out.println("输入流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
System.out.println("输出流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileReader使用和注意事项
文件字符输入流,只能读取普通文本
读取文字内容时,比较方便,快捷
FileReader.read()方法的使用
要读取的文件FileReader fr = new FileReader(“D:\test.txt”);//文件必须存在
FileWriter使用和注意事项
运行该程序的时候 首先会把原文件中的内容清空写入
public static void main(String[] args) {
FileWriter fw =null;
try {
fw =new FileWriter("chapter15/src/com/bjpowernode/java/io/UserFile");
// 开始写
char[] chars ={'我','是','中','国','人'};
fw.write(chars); // 全部写入
fw.write(chars,2,3); //中国人 2是开始位置3是长度
// 注意这里: 再一次运行该程序的时候 首先会把原文件中的内容清空 然后把我是中国人中国人写入该文件当中
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fw != null) {
try {
fw.close();
System.out.println("流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
复制普通文本文件(FileReader+FileWriter)(只能是普通文本文件.txt)
FileReader+FileWriter使用好处是,快捷方便,坏处是只能处理字符类型,处理的文件只能是
普通文本文件
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTest02 {
public static void main(String[] args) {
FileReader fr =null;
FileWriter fw =null;
try {
// 创建字符输入流对象
fr =new FileReader("chapter15/src/com/bjpowernode/java/io/UserFile");
// 创建字符输出流对象
fw =new FileWriter("UserFile"); // 默认路径为根目录下
// 核心:一边读、一边写
char[] chars =new char[1024 *1024]; // 1MB
int readCount =0;
while ((readCount =fr.read(chars))!=-1){ // 读1MB的数据放到char[]数组当中
// 边读边写入文件
fw.write(chars,0,readCount); // 把每次写入到数组当中的数据写入文件当中
}
// 管道刷新
fw.flush();
System.out.println("管道刷新成功~");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 分开try
// 一起try的话如果第一个发生了异常 第二个字符输出流就关闭不了
if (fr != null) {
try {
fr.close();
System.out.println("字符输入流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
System.out.println("字符输出流关闭成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
缓冲流:(带有Buffer的流@重点)
缓冲流的概念和介绍
- 属于处理流(包装流)下的流:处理流·缓冲流
- 读取或者写入的文件存放咋缓冲区(内核空间)
- 有readLine()方法,readLine()方法读取一个文本行,但不带换行符,当没有读取到任何文字的时候返回null
File file = new File("d:/test2.txt");
try
{
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter (fw);
bw.write("你好");
bw.close();
fw.close();
}
catch (Exception e)
{
e.printStackTrace();
}
这里有一个 "修饰类 "的概念
FileWriter 是被修饰者
BufferedWriter 是修饰者
一般用法为
BufferedWriter bw = new BufferedWriter(new FileWriter( "filename "));
上面这个加了一个缓冲,缓冲写满后在将数据写入硬盘,这样做极大的提高了性能
如果单独使用 FileWriter也可以,只是你每写一个数据,硬盘就有一个写动作,性能极差
BufferedReader :缓冲输入字符流
package com;
import java.io.*;
/*
BufferedReader:
带有缓冲区的字符输入流
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲
public BufferedReader(Reader in) : 构造方法中Reader是一个抽象类,FileReader继承了该抽象类
*/
public class BufferReaderTest01 {
public static void main(String[] args) throws IOException {
// 创建子类FileReader对象,记住:FileReader:节点流 BufferedReader:包装流/处理流
FileReader Reader =new FileReader("UserFile");
// 多态 父类型引用指向子类型对象 (FileReader继承了Reader该抽象类) 相当于把文件夹传了过来
BufferedReader br =new BufferedReader(Reader);
// 读取文件夹
/*
读一行
String firstLine =br.readLine();
System.out.println(firstLine);
String secondLine =br.readLine();
System.out.println(secondLine);
*/
// 注意:br.readLine()方法读取一个文本行,但不带换行符(换行是因为我们输出的时候是println,ln换行)
// 当没有读取到任何文字的时候返回null
String s =null;
while ((s =br.readLine())!=null){ // 当读取文件内容不是null的时候 说明有数据
System.out.println(s); // 输出文件内容
}
// 关闭流
// 对于包装流来说,只需要关闭最外层流就可以,里面的节点流会自动关闭
br.close();
}
}
当输入字节流:FileInputStream对象的时候 怎么利用缓冲的方法读取文件
InputStreamReader转换流:(将字节流转换成字符流)
package com.bjpowernode.java.io;
import java.io.*;
public class BufferReaderTest02 {
public static void main(String[] args) throws IOException {
// 字节流
// FileInputStream fis =new FileInputStream("UserFile");
// 通过转换流转换(InputStreamReader将字节流转换成字符流)
// fis是节点流 reader是包装流
// InputStreamReader reader =new InputStreamReader(fis);
// 这里的BufferedReader类当中的构造方法只能传一个字符流,不能传字节流
/*
构造方法当中Reader是一个抽象类 InputStreamReader继承了该抽象类
所以可以把字节流先转换成字符流 然后该字符流继承了Reader抽象类 利用多态 能够传进去
*/
// BufferedReader br =new BufferedReader(reader);
// reader是节点流 br是包装流
// 合并上述代码
BufferedReader br =new BufferedReader(new InputStreamReader(new FileInputStream("UserFile")));
// 读
String s =null;
while ((s=br.readLine())!=null){
System.out.println(s);
}
// 关闭最外层
br.close();
}
}
BufferedWriter:缓冲字符输出流
BufferedWriter: 带有缓冲的字符输出流
OutputStreamWriter:转换流
package com.bjpowernode.java.io;
import java.io.*;
/*
BufferedWriter: 带有缓冲的字符输出流
OutputStreamWriter:转换流
*/
public class BufferedWriterTest01 {
public static void main(String[] args) throws IOException {
// 带有缓冲区的字符输出流
// BufferedWriter out =new BufferedWriter(new FileWriter("copy"));
BufferedWriter out =new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy"))); // 能个true追加
// 开始写
out.write("hello world");
out.write("\n"); // 换行符
out.write("kitty");
// 刷新
out.flush();
}
}
标准输出流
PrintStream的介绍使用
好处:
- 标准输出流不需要手动close()关闭
- 改变标准输出流的输出方向,标准输出流不再指向控制台,输出方向修改到“xxx.txt”文件
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamTest01 {
public static void main(String[] args) throws FileNotFoundException {
// 联合起来写
System.out.println("hello world"); // 将"hello world" 输出到控制台
// System.out 返回的是一个PrintStream
PrintStream ps =System.out;
// 分开写
ps.println("junker");
ps.println("kitty");
// 标准输出流不需要手动close()关闭
// 我们可以改变标准输出流的输出方向吗? 可以
// 标准输出流不再指向控制台,指向了“psFile”文件
PrintStream printStream =new PrintStream(new FileOutputStream("psFile"));
// 修改输出方向,将输出方向修改到“psFile”文件
System.setOut(printStream);
// 再输出
System.out.println("hello world");
System.out.println("hello kitty");
}
}
File类
File类和四大家族没有关系,所以File类不能完成文件的读和写
File对象代表什么?
文件和目录路径名的抽象表示形式
C:\Drivers这是一个File对象
C: \Drivers\Lan\Realtek\Readme.txt也是File对象。
一个File对象有可能对应的是目录(文件夹),也可能是文件
File只是一个路径名的抽象表示形式。
Files的常用方法都有哪些?
创建:
createNewFile()在指定位置创建一个空文件,成功返回true,如果已存在就不创建,返回false
mkdir() 在指定位置创建一个单级文件夹。
mkdirs() 在指定位置创建一个多级文件夹。
renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。
删除:
delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。
deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。
判断:
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
listFile() 获取当前目录下的所有子文件
获取:
getName() 获取文件或文件夹的名称,不包含上级路径。
getAbsolutePath()获取文件的绝对路径(D:\java course\day01\HelloWorld.java),与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent()和getParentFile() 返回此抽象路径名父目录的路径名字符串(D:\java course\day01);如果此路径名没有指定父目录,则返回null,
lastModified()获取最后一次被修改的时间。
文件夹相关:
static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
- 获取文件的父路径(两种获取方式) D:\java course\day01
①File.getParent()
②File.getParentFile() - File.getAbsoluteFile():获取绝对路径 D:\java course\day01\HelloWorld.java
网络操作IO讲解
BIO
什么是BIO
BIO:同步并阻塞,服务器实现一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,没处理完之前此线程不能做其他操作(如果是单线程的情况下,我传输的文件很大呢?),当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中
示例详解:
- A拿着一支鱼竿在河边钓鱼,并且一直在鱼竿前等,在等的时候不做其他的事情,十分专心。只有鱼上钩的时,才结束掉等的动作,把鱼钓上来。
- 在内核将数据准备好之前,系统调用会一直等待所有的套接字,默认的是阻塞方式
请求走向解析
BIO通信代码
步骤说明:写三个类,先模拟启动服务端,然后启动第一个客户端,模拟客户端操作未完成的时候再启动第二个客户端
TCP协议Socket使用BIO进行通信:服务端(先执行)
package com.test.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//TCP协议Socket使用BIO进行通信:服务端
public class BIOServer {
// 在main线程中执行下面这些代码
public static void main(String[] args) {
//使用Socket进行网络通信
ServerSocket server = null;
Socket socket = null;
//基于字节流
InputStream in = null;
OutputStream out = null;
try {
server = new ServerSocket(8000);
System.out.println("服务端启动成功,监听端口为8000,等待客户端连接...");
while (true){
socket = server.accept(); //等待客户端连接
System.out.println("客户连接成功,客户信息为:" +
socket.getRemoteSocketAddress());
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//读取客户端的数据
while ((len = in.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, len));
}
//向客户端写数据
out = socket.getOutputStream();
out.write("hello!".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP协议Socket使用BIO进行通信:客户端(第二执行)
package com.test.io;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//TCP协议Socket使用BIO进行通信:客户端
public class Client01 {
public static void main(String[] args) throws IOException {
//创建套接字对象socket并封装ip与port
Socket socket = new Socket("127.0.0.1", 8000);
//根据创建的socket对象获得一个输出流
//基于字节流
OutputStream outputStream = socket.getOutputStream();
//控制台输入以IO的形式发送到服务器
System.out.println("TCP连接成功 \n请输入:");
String str = new Scanner(System.in).nextLine();
byte[] car = str.getBytes();
outputStream.write(car);
System.out.println("TCP协议的Socket发送成功");
//刷新缓冲区
outputStream.flush();
//关闭连接
socket.close();
}
}
TCP协议Socket使用BIO进行通信:客户端(第三执行)
package com.test.io;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//TCP协议Socket:客户端
public class Client02 {
public static void main(String[] args) throws IOException {
//创建套接字对象socket并封装ip与port
Socket socket = new Socket("127.0.0.1", 8000);
//根据创建的socket对象获得一个输出流
//基于字节流
OutputStream outputStream = socket.getOutputStream();
//控制台输入以IO的形式发送到服务器
System.out.println("TCP连接成功 \n请输入:");
String str = new Scanner(System.in).nextLine();
byte[] car = str.getBytes();
outputStream.write(car);
System.out.println("TCP协议的Socket发送成功");
//刷新缓冲区
outputStream.flush();
//关闭连接
socket.close();
}
}
1.启动服务端
2.启动第一个客户端,发现服务器显示连接成功,先不要在控制台输入,模拟堵塞(代码输入了
就代表请求完成了)
3.启动第二个客户端, 发现服务端没效果,而客户端连接成功(在堵塞当中) 启动了俩个
Client,(这俩个代码是一样的)
4.第一个客户控制台输入,输入完后就会关闭第一个客户端, 在看服务端发现第二个客户端连接上来了
多线程解决BIO编程会出现的问题(线程池)
- 使用多线程是可以解决堵塞等待时间很长的问题,因为他可以充分发挥CPU
- 然而系统资源是有限的,不能过多的新建线程,线程过多带来线程上下文的切换,从来带来更大的性能损耗
多线程BIO代码示例:
①四个客户端,多复制了俩个一样客户端类
②服务端的代码(多线程的方式)
package com.test.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//TCP协议Socket使用多线程BIO进行通行:服务端
public class BIOThreadService {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(8000);
System.out.println("服务端启动成功,监听端口为8000,等待客户端连接... ");
while (true) {
Socket socket = server.accept();//等待客户连接
System.out.println("客户连接成功,客户信息为:" + socket.getRemoteSocketAddress());
//针对每个连接创建一个线程, 去处理I0操作
//创建多线程创建开始
Thread thread = new Thread(new Runnable() {
public void run() {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//读取客户端的数据
while ((len = in.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, len));
}
//向客户端写数据
OutputStream out = socket.getOutputStream();
out.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这种方式线程太多,线程池解决多线程BIO编程会出现的问题
服务端的代码的代码(线程池)
package com.test.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//TCP协议Socket使用线程池BIO进行通行:服务端
public class BIOThreadPoolService {
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(30);
try {
ServerSocket server = new ServerSocket(8000);
System.out.println("服务端启动成功,监听端口为8000,等待客户端连接...");
while (true) {
Socket socket = server.accept();//等待客户连接
System.out.println("客户连接成功,客户信息为:" +
socket.getRemoteSocketAddress());
//使用线程池中的线程去执行每个对应的任务
executorService.execute(new Thread(new Runnable() {
public void run() {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
//读取客户端的数据
while ((len = in.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, len));
}
//向客户端写数据
OutputStream out = socket.getOutputStream();
out.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
})
);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
缺点:线程池固然可以解决这个问题,万一需求量大还不够,还要再扩大线程池。这是靠着自己的思想完成的IO操作,Socket 上来了就去创建线程去抢夺CPU资源,线程都去做IO去了,CPU也忙不过来
NIO(重要)
什么是NIO
- NIO:同步非阻塞,服务器实现一个连接一个线程,即客户端发送的连接请求都会注册到多路复用器上(选择器(Selector)),多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂
- NIO使用中的几个概念:
通道(Channel),选择器(Selector),Buffer (缓冲区)
可以理解为把多个BIO封装使用,轮训分配执行多个BIO过程
示例详解: - B也在河边钓鱼,但是B不想将自己的所有时间都花费在钓鱼上,在等鱼上钩这个时间段中,B也在做其他的事情(一会看看书,一会读读报纸,一会又去看其他人的钓鱼等),但B在做这些事情的时候,每隔一个固定的时间检查鱼是否上钩。一旦检查到有鱼上钩,就停下手中的事情,把鱼钓上来。 B在检查鱼竿是否有鱼,是一个轮询的过程
NIO中的几个概念
什么是通道(Channel)
- Channel是一个对象,可以通过它读取和写入数据。 通常我们都是将数据写入包含一个或者多个字节的缓冲区,然后再将缓存区的数据写入到通道中,将数据从通道读入缓冲区,再从缓冲区获取数据。
- Channel 类似于原I/O中的流(Stream),但有所区别:
· 流是单向的,通道是双向的,可读可写。
· 流读写是阻塞的,通道可以异步读写。
什么是选择器(Selector)
- Selector可以称他为通道的集合,每次客户端来了之后我们会把Channel注册到Selector中并且我们给他一个状态,在用死循环来环判断( 判断是否做完某个操作,完成某个操作后改变不一样的状态 )状态是否发生变化,知道IO操作完成后在退出死循环
什么是Buffer (缓冲区)
- Buffer 是一个缓冲数据的对象, 它包含一些要写入或者刚读出的数据。
- 在普通的面向流的 I/O 中,一般将数据直接写入或直接读到 Stream 对象中。当是有了Buffer(缓冲区)后,数据第一步到达的是Buffer(缓冲区)中
- 缓冲区实质上是一个数组( 底层完全是数组实现的),通常它是一个字节数组,内部维护几个状态变量,可以实现在同一块缓冲区上反复读写(还不用清空数据再写)
NIO通信代码
①客户端还是上面的那几个
②服务端代码如下,步骤:
- 第一步 设置Channel(通道)
a.Service端的Channel,监听端口的
b.设置为非阻塞
c.赋值端口 - 第二步 selector选择器
a. Selector.open()声明selector选择器
b.serverChannel.register(selector, SelectionKey.OP_ACCEPT)
是把selector注册到Channel上面,
把客户端注册到Selector选择器上,默认状态是Accepted - 第三步 buffer缓冲区
创建buffer缓冲区,声明大小是1024,底层使用数组来实现的 - 第四步 轮询 连接 交互数据
a.服务端不断轮询,等待客户端的连接
b.如果有客户端轮询上来就取出对应的Channel,没有就一直轮询
c.使用SelectionKey来获取连接了客户端和服务端的Channel
d.判断SelectionKey中的Channel状态如何,如果是OP_ACCEPT就进入
e.到此轮训到的时候,if (key.isReadable())发现状态是read,开始进行数据交互
f.以buffer作为数据桥梁,数据要想读要先写,必须先读取到buffer里面进行操作
package com.test.io;
import com.lijie.iob.RequestHandler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
//111111111第一步 设置Channel
//Service端的Channel,监听端口的
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//设置为非阻塞
serverChannel.configureBlocking(false);
//nio的api规定这样赋值端口
serverChannel.bind(new InetSocketAddress(8000));
//显示Channel是否已经启动成功,包括绑定在哪个地址上
System.out.println("服务端启动成功,监听端口为8000,等待客户端连接..."+
serverChannel.getLocalAddress());
//22222222第二部 selector选择器
//声明selector选择器
Selector selector = Selector.open();
//这句话的含义,是把selector注册到Channel上面,
//每个客户端来了之后,就把客户端注册到Selector选择器上,默认状态是Accepted
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
//33333333第三步buffer缓冲区
//创建buffer缓冲区,声明大小是1024,底层使用数组来实现的
ByteBuffer buffer = ByteBuffer.allocate(1024);
RequestHandler requestHandler = new RequestHandler();
//444444444 轮询,服务端不断轮询,等待客户端的连接
//轮询,服务端不断轮询,等待客户端的连接
//如果有客户端轮询上来就取出对应的Channel,没有就一直轮询
while (true) {
int select = selector.select();
if (select == 0) {
continue;
}
//有可能有很多,使用Set保存Channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//使用SelectionKey来获取连接了客户端和服务端的Channel
SelectionKey key = iterator.next();
//判断SelectionKey中的Channel状态如何,如果是OP_ACCEPT就进入
if (key.isAcceptable()) {
//从判断SelectionKey中取出Channel
ServerSocketChannel channel = (ServerSocketChannel)
key.channel();
//拿到对应客户端的Channel
SocketChannel clientChannel = channel.accept();
//把客户端的Channel打印出来
System.out.println("客户端通道信息打印:" + clientChannel.getRemoteAddress());
//设置客户端的Channel设置为非阻塞
clientChannel.configureBlocking(false);
//操作完了改变SelectionKey中的Channel的状态OP_READ
clientChannel.register(selector, SelectionKey.OP_READ);
}
//到此轮训到的时候,发现状态是read,开始进行数据交互
if (key.isReadable()) {
//以buffer作为数据桥梁
SocketChannel channel = (SocketChannel) key.channel();
//数据要想读要先写,必须先读取到buffer里面进行操作
channel.read(buffer);
//进行读取
String request = new String(buffer.array()).trim();
buffer.clear();
//进行打印buffer中的数据
System.out.println(String.format("客户端发来的消息: %s : %s",
channel.getRemoteAddress(), request));
//要返回数据的话也要先返回buffer里面进行返回
String response = requestHandler.handle(request);
//然后返回出去
channel.write(ByteBuffer.wrap(response.getBytes()));
}
iterator.remove();
}
}
}
}
Netty(重要)
什么是Netty
- Netty是由JBOSS提供的一个Java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
- Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的Socket服务开发。
Netty是由NIO演进而来,使用过NIO编程的用户就知道NIO编程非常繁重,
Netty是能够能跟好的使用NIO
重点:
写NettyServer服务端类里面把他封装层一个HandLe类,NettyServerHandler
然后NettyServerHandler继承ChannelInboundHandlerAdapter类
Netty通信代码
重点:
①写NettyServer服务端类里面把他封装层一个HandLe类,NettyServerHandler
②然后NettyServerHandler继承ChannelInboundHandlerAdapter类
1.先添加依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.16.Final</version>
</dependency>
2.NettyServer 模板,看起来代码那么多, 其实只需要添加一行消息就好了
重点:把他封装成一个个的个Hand1e类就行了
//把他当成 SpringMVC的Controller
package com.lijie.iob;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel)
throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast(" decoder", new
io.netty.handler.codec.serialization.ObjectDecoder(Integer.MAX_VALUE,
ClassResolvers.cacheDisabled(null)));
//重点,其他的都是复用的
//这是真正的I0的业务代码,把他封装成一个个的个Hand1e类就行了
//把他当成 SpringMVC的Controller
pipeline.addLast(new NettyServerHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture f = b.bind(8000).sync();
System.out.println("服务端启动成功,端口号为:" + 8000);
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
3.需要做的IO操作,重点是继承ChannelInboundHandlerAdapter类就好了
package com.lijie.iob;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
RequestHandler requestHandler = new RequestHandler();
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(String.format("客户端信息: %s",
channel.remoteAddress()));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws
Exception {
Channel channel = ctx.channel();
String request = (String) msg;
System.out.println(String.format("客户端发送的消息 %s : %s",
channel.remoteAddress(), request));
String response = requestHandler.handle(request);
ctx.write(response);
ctx.flush();
}
}
4.客户端的代码还是之前NIO的代码
然后启动测试
AIO
什么是AIO
- AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理,AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用操作系统参与并发操作,编程比较复杂,JDK1.7之后开始支持。.
- AIO属于NIO包中的类实现,其实IO主要分为BIO和NIO,AIO只是附加品,解决IO不能异步的实现在以前很少有Linux系统支持AIO,Windows的IOCP就是该AIO模型。但是现在的服务器一般都是支持AIO操作
- AIO示例详解:
C也想钓鱼,但C有事情,于是他雇来了D、E、F,让他们帮他等待鱼上钩,一旦有鱼上钩,就打
电话给C,C就会将鱼钓上去,AIO操作先完成了一个线程请求资源的操作,再去通知服务器
当应用程序请求数据时,内核一方面去取数据报内容返回
另一方面将程序控制权还给应用进程,应用进程继续处理其他事情,是一种非阻塞的状态
信号驱动IO(signal blocking I/O)
G也在河边钓鱼,但与A、B、C不同的是,G比较聪明,他给鱼竿上挂一个铃铛,当有鱼上钩的时
候,这个铃铛就会被碰响,G就会将鱼钓上来
信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,
对SIGIO信号进行捕捉,并且调用我的信号处理函数来获取数据报
IO多路转接(I/O multiplexing)
- H同样也在河边钓鱼,但是H生活水平比较好,H拿了很多的鱼竿,一次性有很多鱼竿在等,H不断的查看每个鱼竿是否有鱼上钩。增加了效率,减少了等待的时间
IO多路转接是多了一个select函数,select函数有一个参数是文件描述符集合,
对这些文件描述符进行循环监听,当某个文件描述符就绪时,就对这个文件描述符进行处理
- IO多路转接是属于阻塞IO,但可以对多个文件描述符进行阻塞监听,所以效率较阻塞IO的高