Java基础复习-IO流
本文仅对所学java知识的查缺补漏
File
File类的使用
1.File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
2.File类声明在java.io包下
3.路径分隔符与操作系统:
window和DOS系统默认使用 \ 表示
UNIX和URL使用 / 表示
4.Java程序支持跨平台运行,因此路径分隔符要慎用
5.为了解决这个隐患,File类提供了一个常量:
public static final String separator:根据操作系统,动态提供分隔符
File file = new File("C:"+File.separator+"test.txt");
6.File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
但并未涉及到写入或读取文件内容的操作,如果需要读取或写入文件内容,必须使用IO流完成
创建对象
File(String filePath);
File(String parentPath, String childPath);
File(File parentFile, String childPath);
常用方法
public String getAbsolutePath()
:获取绝对路径public String getPath()
:获取路径public String getName()
:获取名称public String getParent()
:获取上层文件目录路径。若无,返回nullpublic long length()
:获取文件长度(即:字节数)。不能获取目录长度public long lastModified()
:获取最后一次的修改时间,毫秒数public String[] list()
:获取指定目录下的所有文件或者文件目录的名称数组(适用目录)public File[] listFiles()
:获取指定目录下的所有文件或者文件目录的File数组(适用目录)public boolean reanmeTo(File dest)
:把文件重命名为指定的文件路径。- 比如:
file1.renameTo(file2)
,要想保证返回true,需要file1在硬盘中存在,而且file2在硬盘中不存在
- 比如:
public boolean isDirectory()
:判断是否是文件目录public boolean isFile()
:判断是否是文件public boolean exists()
:判断是否存在public boolean canRead()
:判断是否可读public boolean canWrite()
:判断是否可写public boolean isHidden()
:判断是否隐藏public boolean createNewFile()
:创建文件,若文件存在,则不创建,返回falsepublic boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层不存在,则创建失败public boolean mkdirs()
:创建文件目录。如果上层目录不存在,一并创建public boolean delete()
:删除文件或者文件夹(注意:Java中的删除不走回收站)
IO流
流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流(节点流的包装)
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
流的操作步骤(固定)
- 实例化
File
对象 - 实例化流对象
- 要进行流的操作
- 关闭流
例子:文本复制(字符流)
public class FileReaderWriterTest {
@Test
public void test(){
FileReader fileReader = null;
try {
//1.实例化File类对象,指明要操作的文件
File file = new File("src\\javabasic\\day26","hello.txt");
System.out.println(file.getAbsolutePath());
//2.提供具体的流
fileReader = new FileReader(file);
//3.数据的读入
//read():返回读入的一个字符。如果到达文件末尾,则返回-1
//read(char[] cbuf):返回每次读入cbuf数组中的字符个数。如果到达文件末尾,返回-1。这种方式,如果读入的个数比char[]数组的长度小,那么char[]数组中还会保留部分上一次读入的内容
int data;
while((data = fileReader.read()) != -1){
System.out.print((char)data);
}
}catch (IOException e){
e.printStackTrace();
}finally {
//4.关闭流,因为对于物理连接,比如:数据库连接,IO流,Socket的连接,JVM是关闭不了的,需要手动关闭
try {
if (fileReader != null)
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
例子:图片复制(字节流)
/**
* 图片复制:使用FileInputStream和FileOutputStream
*/
@Test
public void test3(){
File file = new File("src\\javabasic\\day26", "hello.jpg");
File file1 = new File("src\\javabasic\\day26", "hello1.jpg");
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = new FileInputStream(file);
outputStream = new FileOutputStream(file1);
byte[] bytes = new byte[5];
int len;
while((len = inputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意
- 对于文本文件(
.txt,.java,.c,.cpp
),使用**字符流(16bit)**处理 - 对于非文本文件(
.jpg,.mp3,.mp4,.avi,.doc,.ppt......
),使用**字节流(8bit)**处理 - 使用字节流处理文本文件可能会出现乱码(复制文件可以用字节流,相当于数据的搬运工)
缓冲流
BufferedInput / OutputStream
提高了字节流的读取和写入速度,
原因:底层有一个8k大小的byte
缓冲区:即byte[8192]
/**
* 处理流之一:缓冲流的使用
* @Author: fxx
* @Date: 2020/12/28 14:26
*/
public class BufferedTest {
@Test
public void test(){
//1.实例化File
File file1 = new File("src\\javabasic\\day26", "hello.jpg");
File file2 = new File("src\\javabasic\\day26", "hello1.jpg");
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
//2.实例化节点流
inputStream = new FileInputStream(file1);
outputStream = new FileOutputStream(file2);
//3.实例化处理流:包装节点流的流
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(outputStream);
//4.操作流
byte[] bytes = new byte[5]; //每次读进来的流字节,一般文件比较大的话会设置为1024
int len;
long start = System.currentTimeMillis();
while((len = bufferedInputStream.read(bytes)) != -1){
bufferedOutputStream.write(bytes, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end - start));
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.关闭流
//关闭流的操作是:先关闭外层处理流,再关闭内层节点流
//说明:关闭外层流的同时,内层流也会自动关闭,所以这里只需要关闭外层流
try {
if(bufferedInputStream != null)
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bufferedOutputStream != null)
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedReader / Writer
提高了字符流的读取和写入速度,
原因:底层有一个8k大小的char
数组缓冲区:即char[8192];
转换流
作用:提供字节流与字符流之间的转换
属于字符流(看名字后缀)
InputStreamReader
:将一个字节的输入流转换为字符的输入流,相当于解码OutputStreamWriter
:将一个字符的输出流转换为字节的输出流,相当于编码- 可以解码后重新编码
例子
@Test
public void test(){
FileInputStream fileInputStream = null;
InputStreamReader inputStreamReader = null;
try {
fileInputStream = new FileInputStream("src\\javabasic\\day26\\hello.txt");
//如果不指定字符集,用系统默认字符集
//解码的字符集使用什么,取决于原来的文件使用了什么字符集
inputStreamReader = new InputStreamReader(fileInputStream,"UTF-8");
char[] chars = new char[20];
int len;
while((len = inputStreamReader.read(chars)) != -1){
String str = new String(chars, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
标准输入输出流
* 标准的输入,输出流
* System.in:标准输入流,默认从键盘输入
* System.out:标准输出流,默认从控制台输出
* System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流:流重定向
*
* 练习:
* 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直到输入
* e或exit时,退出程序。
* 方式一:用Scanner实现,调用next()返回一个字符串
* 方式二:用System.in实现。System.in ----> 转换流 ----->BufferedReader的readLine()
* InputStreamReader inputStream = new InputStreamReader(System.in);
* BufferedReader bf = new BufferedReader(inputStream);
* 下面操作和之前的一样
打印流
实现将基本数据类型的数据格式转化为字符串输出
PrintStream
和PrintWriter
的输出不会抛出IOException
异常PrintStream
和PrintWriter
有自动flush功能PrintStream
打印的所有字符都会使用平台默认字符编码转换为字节,如果需要写入字符而不是字节,应使用PrintWriter
System.out
类返回的是PrintStream
实例
数据流
用于将内存中的基本数据类型存到磁盘中,或者从磁盘中读取对应的基本数据类型的数据到内存中
/**
* 数据流
* DataInputStream 和 DataOutputStream
* 作用:用于读取或写出基本数据类型的变量或字符串
* 需要注意的一点:读取的时候要按照存入的时候的顺序去读取,不能随机读取
*/
@Test
public void test(){
DataOutputStream outputStream = null;
DataInputStream inputStream = null;
try {
outputStream = new DataOutputStream(new FileOutputStream(new File("src\\javabasic\\day26\\hello.txt")));
inputStream = new DataInputStream(new FileInputStream(new File("src\\javabasic\\day26\\hello.txt")));
outputStream.writeUTF("小明");
outputStream.writeInt(90);
outputStream.writeBoolean(false);
System.out.println(inputStream.readUTF());
System.out.println(inputStream.readInt());
System.out.println(inputStream.readBoolean());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对象流
- 用于存储和读取基本数据类型或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来;
- 序列化:用
ObjectOutputStream
类保存基本数据类型或对象的机制 - 反序列化:用
ObjectInputStream
类读取基本数据类型或对象的机制 ObjectInputStream
和ObjectOutputStream
不能序列化static
和transient
修饰的成员变量
对象的序列化
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
- 序列化的好处在于可将任何实现了
Serializable
接口的对象转化为字节数据,使其在保存和传输时可被还原。 - 序列化是
RMI ( Remote Method Invoke -远程方法调用)
过程的参数和返回值都必须实现的机制,而RMI
是JavaEE
的基础。因此序列化机制是JavaEE
平台的基础。 - 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出
NotSerializableException
异常Serializable
Externalizable
自定义对象序列化
-
需要实现接口:
Serializable
-
当前类提供一个全局常量:
serialVersionUID
-
public static final long serialVersionUID = ...L;
-
-
除了当前的
Person
类需要实现Serializable
接口之外,还必须保证其内部所有属性也是可以序列化的(即:属性里可能含有其他类,需要保证这些类都可以序列化),默认情况下,基本数据类型是可序列化的。 -
static
和transient
修饰的成员变量不可序列化。
为什么要提供serialVersionUID
?
serialVersionUID
是一个版本号,相当于你那个类的版本:
- 如果提供了这个版本号,那么在序列化的时候,他会记下这个版本号,然后假如你的类的结构变了(比如加多了其他属性或方法,即修改了类的结构了),那么在反序列化的时候,因为有版本号,所以他会在反序列化的时候去找有相同版本号的类,将之前的数据赋给之前有的属性,而没有的属性会赋默认值,这样就不会发生序列化错误。
- 如果没有提供这个版本号,那么系统会自动生成一个版本号,序列化的时候也会记下这个版本号。
- 如果没有改动类的情况下,在反序列化的时候,反序列化时候的版本号是可以在内存中找到的,所以可以将属性都重新赋值,即:反序列化是成功的;
- 如果改动了类的话,在反序列化的时候,因为改动了类,所以系统生成了一个新的序列号了,此时新序列号可能与之前存储的序列号不同,导致找不到对应的类,此时反序列化失败;
RandomAccessFile
- 直接继承于
java.lang.Object
,实现了DataInput
和DataOutput
接口。 RandomAccessFile
既可以作为一个输入流,也可以作为一个输出流。- 如果
RandomAccessFile
作为输出流,写出到的文件如果不存在,则在执行过程中自动创建;如果写出到的文件存在,则会对原有文件内容进行覆盖(从头开始覆盖)。 - 构造器
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
- 创建
RandomAccessFile
类实例需要指定一个mode参数,该参数指定RandomAccessFile
的访问模式:- r:以只读方式打开
- rw:打开以便读取和写入
- rwd:打开以便读取和写入;同步文件内容的更新
- rws:打开以便读取和写入;同步文件内容和元数据的更新
- 如果模式为只读r。则不会创建文件,而是会去读取-一个已经存在的文件,如果读取的文件不存在则会出现异常。如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。
NIO
(非阻塞IO)
通道和缓冲区
Java NIO
系统的核心在于:通道(channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之:Channel
负责传输,Buffer
负责存储
缓冲区(Buffer)
/**
* 一、缓冲区(buffer):在java nio中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
*
* 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:
* ByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
*
* 上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区
*
*二、缓冲区存取数据的两个核心方法:
* put():存入数据到缓冲区中
* get():获取缓冲区中的数据
*
* 三、缓冲区中的四个核心属性:
* capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明就不能改变
* limit:界限,表示缓冲区中可以操作数据的大小。(limit以后的数据不能进行读写)
* position:位置,表示缓冲区中正在操作数据的位置。
* mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark的位置
*
* 0 <= mark <= position <= limit <= capacity
*
*
* hasRemaining():缓冲区中是否还有剩余可操作元素
* remaining():缓冲区中剩余可操作元素数量
*
* 四:直接缓冲区和非直接缓冲区
* 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中
* 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。
* 可以提高效率
*
* @Author: fxx
* @Date: 2021/1/2 16:15
*/
public class BufferTest {
//显示NIO的属性
private void show(Buffer buffer, String s){
System.out.println("-----------"+s+"--------------");
System.out.println("position:" + buffer.position());
System.out.println("limit:" + buffer.limit());
System.out.println("capacity:" + buffer.capacity());
}
@Test
public void test(){
String str = "abcde";
//1.分配一个指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
show(byteBuffer, "allocate");
//2.利用put()存入数据到缓冲区
byteBuffer.put(str.getBytes());
show(byteBuffer, "put");
//3.切换到读取数据的模式,上面的put是写数据模式
byteBuffer.flip();
show(byteBuffer, "flip");
//4.开始读数据
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println(new String(bytes, 0, bytes.length));
show(byteBuffer, "get");
//5.rewind():可重复读数据,即,position回到读数据之前的位置
byteBuffer.rewind();
show(byteBuffer, "rewind");
//6.clear():清空缓冲区,其实只是NIO的属性值变为初始值了,
// 缓冲区中依然还保留着原来的数据
byteBuffer.clear();
show(byteBuffer, "clear");
byteBuffer.put(str.getBytes());
//7.mark():标记position位置,可用reset()回到刚刚position位置
byteBuffer.flip();
byteBuffer.get(bytes, 0, 2);
show(byteBuffer, "flip-get");
System.out.println(bytes.length);
//标记一下
byteBuffer.mark();
byteBuffer.get(bytes, 2, 2);
System.out.println(bytes);
show(byteBuffer, "mark-get");
byteBuffer.reset();
show(byteBuffer,"reset");
//判断缓冲区中是否还有剩余的数据
if (byteBuffer.hasRemaining()){
//获取缓冲区中可以操作的数据容量
System.out.println("缓冲区中还可以操作的数据容量:"+byteBuffer.remaining());
}
}
@Test
public void test1(){
//开辟直接缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//判断是不是开辟的直接缓冲区
System.out.println(byteBuffer.isDirect());
}
}
NIO缓冲区的属性
直接缓冲区
缓冲区直接建立在物理内存上,不放内存中
非直接缓冲区
缓冲区建立在JVM内存中
通道(Channel)
通道:由java.nio.channels
包定义的。Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
/**
* 一、通道(Channel):用于源结点与目标结点的连接。在Java NIO中负责缓冲区中的数据传输。
* Channel本身不存储数据,因此需要配合缓冲区进行传输。
*
* 二、通道的主要实现类
* java.nio.channels.Channel 接口:
* 1.FileChannel
* 2.SocketChannel
* 3.ServerSocketChannel
* 4.DatagramChannel
*
* 三、获取通道
* 1.Java针对支持通道的类提供了getChannel()方法
* 本地IO:
* FileInputStream / FileOutputStream
* RandomAccessFile
*
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
*
* 2.在JDK1.7中的NIO.2针对各个通道提供了静态方法 open()
* 3.在JDK1.7中的NIO.2的 Files 工具类的 newByteChannel()
*
* 四、通道之间的数据传输
* transferFrom()
* transferTo()
*
* 五、分散(Scatter)与聚集(Gather)
* 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
* 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
*
*
* @Author: fxx
* @Date: 2021/1/2 17:54
*/
public class ChannelTest {
@Test
public void test4(){
FileChannel channel = null;
FileChannel channel1 = null;
try {
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
//1.获取通道
channel = randomAccessFile.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(100);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
//3.分散读取
ByteBuffer[] buffers = {byteBuffer1, byteBuffer2};
channel.read(buffers);
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
System.out.println(new String(buffers[0].array(), 0, buffers[0].limit()));
System.out.println("---------------");
System.out.println(new String(buffers[1].array(), 0, buffers[1].limit()));
//4.聚集写入
RandomAccessFile accessFile = new RandomAccessFile("2.txt", "rw");
channel1 = accessFile.getChannel();
channel1.write(byteBuffer2);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null){
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (channel1 != null){
try {
channel1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//通道之间的数据传输(直接缓冲区)
@Test
public void test3(){
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
inChannel = FileChannel.open(Paths.get("src\\javabasic\\NIO\\2.jpg"), StandardOpenOption.READ);
outChannel = FileChannel.open(Paths.get("src\\javabasic\\NIO\\2.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inChannel != null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel != null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//2.使用直接缓冲区完成文件复制(内存映射文件)
@Test
public void test2(){
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
inChannel = FileChannel.open(Paths.get("src\\javabasic\\NIO\\2.jpg"), StandardOpenOption.READ);
outChannel = FileChannel.open(Paths.get("src\\javabasic\\NIO\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] bytes = new byte[inMappedBuf.limit()];
inMappedBuf.get(bytes);
outMappedBuf.put(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inChannel != null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel != null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//1.利用通道完成文件的复制(非直接缓冲区)
@Test
public void test1(){
//1.拿到流
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
FileChannel fileInputStreamChannel = null;
FileChannel fileOutputStreamChannel = null;
try {
fileInputStream = new FileInputStream(new File("src\\javabasic\\NIO\\1.gif"));
fileOutputStream = new FileOutputStream(new File("src\\javabasic\\NIO\\2.gif"));
//2.通过流拿到通道
fileInputStreamChannel = fileInputStream.getChannel();
fileOutputStreamChannel = fileOutputStream.getChannel();
//3.拿到缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//4.通过通道操作缓冲区
while(fileInputStreamChannel.read(byteBuffer) != -1){
//切换为读模式
byteBuffer.flip();
fileOutputStreamChannel.write(byteBuffer);
//清空缓冲区
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStreamChannel != null){
try {
fileInputStreamChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStreamChannel != null){
try {
fileOutputStreamChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
非阻塞式网络编程-TCP
/**
* 非阻塞式IO:
* 1.通道(Channel):负责连接
* 2.缓冲区(Buffer):负责数据的存取
* 3.选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
*
* @Author: fxx
* @Date: 2021/1/3 15:01
*/
public class NonBlockingNIOTest {
@Test
public void client(){
SocketChannel socketChannel = null;
try {
//1.获取通道
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.切换成非阻塞模式
socketChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//4.发送数据给服务端
byteBuffer.put(new Date().toString().getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socketChannel != null){
try {
//5.关闭通道
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//try-catch-finally篇幅太长,这里用Exception取代
@Test
public void server()throws Exception{
//1.获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.切换成非阻塞模式
serverSocketChannel.configureBlocking(false);
//3.绑定连接端口
serverSocketChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
/**
* SelectionKey.OP_ACCEPT:接收数据操作
* SelectionKey.OP_CONNECT:连接操作
* SelectionKey.OP_READ:读操作
* SelectionKey.OP_WRITE:写操作
* 如果有多个事件,使用 | 连接,如:SelectionKey.OP_READ | SelectionKey.OP_WRITE
*/
//5.将通道注册到选择器上,并且指定“监听接收事件”
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式地获取选择器上已经“准备就绪”的事件
while(selector.select() > 0){
//7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
//8.获取准备就绪的事件
SelectionKey selectionKey = iterator.next();
//9.判断具体是什么事件准备就绪
if(selectionKey.isAcceptable()){ //如果接受就绪
//10.获取客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
//11.切换非阻塞模式
socketChannel.configureBlocking(false);
//12.将该通道注册到选择器上
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){ //如果已经读就绪
//13.获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) selectionKey.channel();
//14.读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while(sChannel.read(byteBuffer) != -1){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));
byteBuffer.clear();
}
}
//15.取消选择键:SelectionKey
iterator.remove();
}
}
}
}
非阻塞式网络编程-UDP
/**
* @Author: fxx
* @Date: 2021/1/4 15:38
*/
public class NonBlockingNIOUDPTest {
@Test
public void send()throws Exception{
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("123".getBytes());
byteBuffer.flip();
datagramChannel.close();
}
@Test
public void receive()throws Exception{
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.bind(new InetSocketAddress(9898));
Selector selector = Selector.open();
datagramChannel.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey next = iterator.next();
if (next.isReadable()){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
datagramChannel.receive(byteBuffer);
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));
byteBuffer.clear();
}
}
iterator.remove();
}
}
}
管道
/**
* 管道:单向数据发送
*
* @Author: fxx
* @Date: 2021/1/4 21:24
*/
public class PipeTest {
@Test
public void test()throws Exception{
//1.获取管道
Pipe pipe = Pipe.open();
//2.将缓冲区中的数据写入管道
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel = pipe.sink();
byteBuffer.put("通过单向管道发送数据".getBytes());
byteBuffer.flip();
sinkChannel.write(byteBuffer);
//3.读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
byteBuffer.flip();
int len = sourceChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array(), 0, len));
//关闭资源
sourceChannel.close();
sinkChannel.close();
}
}
Files、Path、Paths等
后面有看到再补充