Test:
package com.zhao.io;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo01 {
public static void main(String[] args) throws IOException {
// hello.txt 内容:返回
FileInputStream fis = new FileInputStream("/Users/apple/IdeaProjects/Netty-Demo/hello.txt");
int by;
while ((by=fis.read())!=-1) {
System.out.print(by+" "); // 232 191 148 229 155 158
}
//释放资源
fis.close();
}
}
hello.txt 文件内容
返回
打印的结果是 232 191 148 229 155 158,可有对这个结果感到不解?
我们平时存储数据都是以二进制文件存储的(包括汉字),但是一堆 0 和 1,普通人都看不懂那是什么,所以我们定制了 ASCLL 码表,例如 单词字母 a,对应的就是 97,数字 1 对应的 49,以此类推。但是光是中文汉字就高达十万个,更别说别国文字了。于是 UTF8 规定 英文字母占一个字节,汉字占用三个字节,例如 “返” 字就用 232 191 148 这三个数字对应的二进制表示。但是三个字节会耗内存。可以粗略的算一下,与GBK相比一个汉字多占一个字节,1024 个汉字就要多占 1m 内存,1024 * 1024 个汉字就多占用一个 G 的内存。具体选用哪种编码还是需要权衡
public class InPutStreamTest {
public static void main(String[] args) throws IOException {
// 文件输入流
FileInputStream inputStream = new FileInputStream(new File("/Users/apple/IdeaProjects/Netty-Demo/hello.txt"));
// 文件输出流
FileOutputStream outputStream = new FileOutputStream("/Users/apple/IdeaProjects/Netty-Demo/hello2.txt",true);
// 每次读取的元素容量
byte[] bytes = new byte[1024];
int len;
// 把每次读取的元素个数赋值给变量 len。输入流理论上每次读取(1024)个元素
// 判断是否读到末尾
while((len = inputStream.read(bytes)) != -1 ){
// 输出流理论上每次写入 1024 个元素,从 0 开始到 len 结束
outputStream.write(bytes,0,len);
}
// 关闭流
inputStream.close();
outputStream.close();
}
}
NIO
三大组件:Channel & Buffer & Selector
ByteBuffer 的简单使用
package com.zhao.io;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Auther: HackerZhao
* @Date: 2021/10/26 21:23
* @Description: ByteBuffer 的简单使用
*/
public class ChannelDemo {
private static Logger LOGGER = LoggerFactory.getLogger(ChannelDemo.class);
public static void main(String[] args) {
try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
do {
// 从 channel 读取数据,向 buffer 写入
int len = channel.read(buffer);
LOGGER.debug("读取到的字节数 {}", len);
if (len == -1) {
break;
}
// 打印 buffer 的内容
buffer.flip(); // 切换至读模式
while (buffer.hasRemaining()) {
byte b = buffer.get();
LOGGER.debug("实际字节 {}", (char)b);
}
buffer.clear(); // 切换至写模式
} while (true);
} catch (IOException e) {
}
}
}
ByteBuffer 的读写模式
package com.zhao.io;
import java.nio.ByteBuffer;
/**
* @Auther: HackerZhao
* @Date: 2021/10/27 15:51
* @Description: ByteBuffer 的读写模式
*/
public class TestByteBufferReadWrite {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
// 写入
buffer.put((byte) 97);
ByteBufferUtil.debugAll(buffer); // 'a'
buffer.put(new byte[]{98,99,100}); // 'b','c','d'
ByteBufferUtil.debugAll(buffer);
// 获取
// System.out.println( buffer.get()); // 0,原因是未切换至读模式,position: [4] 位于索引 4
// 读模式
buffer.flip();
System.out.println( (char) buffer.get());
ByteBufferUtil.debugAll(buffer); // 获取一个元素后,position 指针移动至 索引1 的位置
buffer.compact();
ByteBufferUtil.debugAll(buffer); // 清除已读过的内存,把元素向前移动,原有的元素不会清除,后续写入会覆盖
}
}
get 方法会让 position 读指针向后走,如果想重复读取数据
-
可以调用 rewind 方法将 position 重新置为 0
-
或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针
ByteBuffer 的内存位置
package com.zhao.io;
import java.nio.ByteBuffer;
/**
* @Auther: HackerZhao
* @Date: 2021/10/27 16:10
* @Description: ByteBuffer 的内存位置
*/
public class TestByteBufferAllocate {
public static void main(String[] args) {
System.out.println(ByteBuffer.allocate(16).getClass()); // class java.nio.HeapByteBuffer
System.out.println(ByteBuffer.allocateDirect(16).getClass()); // class java.nio.DirectByteBuffer
/**
* class java.nio.HeapByteBuffer - java 堆内存,读写效率低,受到 GC 影响
* class java.nio.DirectByteBuffer - 直接内存,读写效率高(少一次拷贝),不会受 GC 影响,分配的效率低
*/
}
}
ByteBuffer 的标记与回归
package com.zhao.io;
import java.nio.ByteBuffer;
/**
* @Auther: HackerZhao
* @Date: 2021/10/27 16:21
* @Description: ByteBuffer 的标记与回归
*/
public class TestByteBufferRead {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a','b','c','d'}); // 写模式
buffer.flip(); // 读模式
// rewind 从头开始读
buffer.get(new byte[4]);
ByteBufferUtil.debugAll(buffer);
buffer.rewind();
System.out.println((char) buffer.get()); // 'a',本来读取的应该为空, rewind 方法重置了指针的位置
// mark & reset
// mark 做一个标记,记录 position 的位置,reset 是将 position 重置到 mark 的位置
System.out.println((char) buffer.get()); // 'b'
// 加标记,索引为2 的位置
buffer.mark();
System.out.println((char) buffer.get()); // 'c'
System.out.println((char) buffer.get()); // 'd'
// 将position 重置到索引2 的位置
buffer.reset();
System.out.println((char) buffer.get()); // 'c'
System.out.println((char) buffer.get()); // 'd'
// get(i) 不会改变索引的位置
System.out.println((char) buffer.get(3)); // 'c'
ByteBufferUtil.debugAll(buffer);
}
}
ByteBuffer 与字符串的转换
package com.zhao.io;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* @Auther: HackerZhao
* @Date: 2021/10/27 16:45
* @Description: ByteBuffer 与字符串的转换
*/
public class TestByteBufferString {
public static void main(String[] args) {
ByteBuffer buffer1 = ByteBuffer.allocate(16);
// 字符串 转 Buffer
// 方式1:字符串 转 Byte 数组 转 Buffer,position 指针不会回归
buffer1.put("hello".getBytes());
ByteBufferUtil.debugAll(buffer1);
// 方式2:字符串 转 Charsets 转 Buffer,position 指针会自动切换至读模式
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
ByteBufferUtil.debugAll(buffer2);
// 方式3:字符串 转 Byte 数组 转 Buffer,position 指针会自动切换至读模式
ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
ByteBufferUtil.debugAll(buffer3);
// Buffer 转 字符串
String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();
System.out.println(str1);
// 方式1 转换成字符串需要先切换成 读模式
buffer1.flip();
String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
System.out.println(str2);
}
}
ByteBuffer 的分散读 集中写
package com.zhao.io;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Auther: HackerZhao
* @Date: 2021/10/27 17:17
* @Description: 分散读
*/
public class TestScatteringReads {
public static void main(String[] args) {
try (RandomAccessFile file = new RandomAccessFile("word1.txt", "r")) {
FileChannel channel = file.getChannel();
ByteBuffer a = ByteBuffer.allocate(3);
ByteBuffer b = ByteBuffer.allocate(3);
ByteBuffer c = ByteBuffer.allocate(5);
channel.read(new ByteBuffer[]{a, b, c});
a.flip();
b.flip();
c.flip();
ByteBufferUtil.debugAll(a);
ByteBufferUtil.debugAll(b);
ByteBufferUtil.debugAll(c);
} catch (IOException e) {
e.printStackTrace();
}
}
}
ByteBuffer 的分散读
package com.zhao.io;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
/**
* @Auther: HackerZhao
* @Date: 2021/10/27 17:25
* @Description: 集中写
*/
public class TestGatheringWrites {
public static void main(String[] args) {
ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
try (FileChannel channel = new RandomAccessFile("word2.txt", "rw").getChannel()) {
channel.write(new ByteBuffer[]{b1, b2, b3});
} catch (IOException e) {
e.printStackTrace();
}
}
}
黏包 & 半包
原始数据
Hello,world\n
I'm zhangsan\n
How are you?\n
黏包:将多条数据整合为一条,Hello,world\nI'm zhangsan\nHo
半包:整合为一条后,分开读取,变成不完整的数据。w are you?\n
解决方式
package com.zhao.io;
import io.netty.buffer.ByteBuf;
import java.nio.ByteBuffer;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 13:53
* @Description: 黏包 & 半包
*/
public class TestByteBufferExam {
public static void main(String[] args) {
// 创建缓冲区
ByteBuffer source = ByteBuffer.allocate(32);
// 模拟数据
source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
// 调用方法
split(source);
source.put("w are you?\n".getBytes());
split(source);
}
// 进行字符串的切割
public static void split(ByteBuffer source){
// 切换至读模式
source.flip();
// 遍历
for (int i = 0; i < source.limit(); i++) {
// 如果到换行符,就停止
if (source.get(i) == '\n'){
int len = i + 1 - source.position();
// 创建写入 Buffer
ByteBuffer target = ByteBuffer.allocate(len);
// 由 source 读,向 target 写入
for (int j = 0; j < len; j++) {
// 切换至写入模式,移动指针,向source 写入
target.put(source.get());
}
// 打印结果
ByteBufferUtil.debugAll(target);
}
}
// 不进行元素覆盖,写到 Ho 时,进行写 w are you? 时,不会对 Ho 进行覆盖
source.compact();
}
}
FileChannel 的复制
package com.zhao.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 15:13
* @Description: FileChannel 的复制
*/
public class TestFileChannelTransferTo {
public static void main(String[] args) {
try (
FileChannel from = new FileInputStream("data.txt").getChannel();
FileChannel to = new FileOutputStream("to.txt").getChannel()
) {
// 效率高,底层利用操作系统的零拷贝进行优化
from.transferTo(0, from.size(), to);
} catch (IOException e) {
e.printStackTrace();
}
}
}
改进版
package com.zhao.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 15:13
* @Description: FileChannel 的复制
*/
public class TestFileChannelTransferTo {
public static void main(String[] args) {
try (
FileChannel from = new FileInputStream("data.txt").getChannel();
FileChannel to = new FileOutputStream("to.txt").getChannel()
) {
// 效率高,底层利用操作系统的零拷贝进行优化
// 遗憾的是,transferTo 最多只能传输 2G 大小的内容
// 弥补措施,可以进行多次传输
long size = from.size(); // 总数量
// left 变量代表还剩多少字节
for (long left = size; left > 0; ) {
left -= from.transferTo(size - left, left, to);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
遍历文件夹文件的数目
package com.zhao.io;
import java.io.File;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 16:14
* @Description: 递归得到文件夹和文件的数量
*/
public class TestFilesWalkFileTree1 {
private static int fileCount = 0;
private static int directoryCount = 0;
public static void main(String[] args) {
File srcFile = new File("/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk");
getAllFilePath(srcFile);
System.out.println("文件的数量:"+fileCount+",文件夹的数量:"+directoryCount); // 文件的数量:923,文件夹的数量:72
}
public static void getAllFilePath(File srcFile) {
File[] files = srcFile.listFiles();
// 遍历
if (files != null) {
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
directoryCount++;
getAllFilePath(files[i]);
System.out.println(files[i].getName());
} else if (files[i].isFile()) {
fileCount++;
getAllFilePath(files[i]);
System.out.println(files[i].getName());
} else {
//不是:获取绝对路径输出在控制台
System.out.println(files[i].getAbsolutePath());
}
}
}
}
}
package com.zhao.io;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 16:27
* @Description:
*/
public class TestFilesWalkFileTree2 {
public static void main(String[] args) throws IOException {
m1();
m2();
m3();
}
/**
* 删除含有文件的文件夹
*/
public static void m3() throws IOException {
AtomicInteger fileCount = new AtomicInteger();
AtomicInteger directoryCount = new AtomicInteger();
Files.walkFileTree(Paths.get("/Volumes/Transcend/jdk1.8.0_261.jdk/Contents/Home/bin"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
fileCount.incrementAndGet();
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.delete(dir);
directoryCount.incrementAndGet();
return super.postVisitDirectory(dir, exc);
}
});
System.out.println("删除含有文件的文件夹:" + directoryCount + " 个,删除含有文件的文件:" + fileCount + " 个");
}
/**
* 打印文件夹和文件的数量
*/
public static void m2() throws IOException {
AtomicInteger fileCount = new AtomicInteger();
Files.walkFileTree(Paths.get("/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".jar")) {
fileCount.incrementAndGet();
}
return super.visitFile(file, attrs);
}
});
System.out.println("以 .jar结尾的文件的数量:" + fileCount);
}
/**
* 打印文件夹和文件的数量
*/
public static void m1() throws IOException {
AtomicInteger fileCount = new AtomicInteger();
AtomicInteger directoryCount = new AtomicInteger();
Files.walkFileTree(Paths.get("/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("===== > " + dir);
directoryCount.incrementAndGet();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("===== > " + file);
fileCount.incrementAndGet();
return super.visitFile(file, attrs);
}
});
System.out.println("文件的数量:" + fileCount + ",文件夹的数量:" + directoryCount);
}
}
多级文件的拷贝
package com.zhao.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 17:58
* @Description: 拷贝多级文件
*/
public class TestFilesCopy {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
String source = "/Users/apple/Desktop/jdk1.8.0_261.jdk";
String target = "/Users/apple/Desktop/jdk8.jdk";
Files.walk(Paths.get(source)).forEach(path -> {
try {
String targetName = path.toString().replace(source, target);
// 是目录
if (Files.isDirectory(path)) {
Files.createDirectory(Paths.get(targetName));
}
// 是普通文件
else if (Files.isRegularFile(path)) {
Files.copy(path, Paths.get(targetName));
}
} catch (IOException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
我是一名食客,大家都叫我食神,叫的我挺不好意思。
今天我来到一家餐馆就餐,这家餐馆叫 ‘阻塞饭店’。
食神拿了菜单看得入了神,迟迟没有说话。
这时,憨憨赵来了,“小二,一碗凉水”。憨憨赵看见没人应他,抬起头看向小二。
小二此时,一句话也不敢说,目不转睛的看着食神,弯着腰,这个动作似乎保持了很久,态度谦卑极了。
食神开口了,“一壶酒,两斤牛肉,三斤烤鸭,四斤米饭!今天胃口不太好,等不够了再叫你。”
小二如负重托,“好嘞,客官,您稍等!”,对憨憨赵投了一个歉意的眼神,去后厨报菜去了。
憨憨赵: 。。。。。。
没过多久小二回来了,手里端着一些饭菜,端向食神。接着迈着哈吧步跑到憨憨赵面前,“客官需要点什么?”
憨憨赵陷入了沉思。
此时,门外传来了二货马 的声音,“五斤麻花,六斤酒,七斤米饭,八两肉”
小二目不转睛的看着我,弯着腰,这个动作似乎保持了很久,态度谦卑极了!
阻塞餐馆
package com.zhao.c1;
import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 20:10
* @Description: 阻塞餐馆
*/
public class Server {
private static Logger LOGGER = LoggerFactory.getLogger(Server.class);
public static void main(String[] args) throws IOException {
// 使用nio来理解阻塞模式
// 0. ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 1. 创建服务器(饭店开门)
ServerSocketChannel ssc = ServerSocketChannel.open();
// 2. 绑定监听端口(饭店地址)
ssc.bind(new InetSocketAddress(8080));
// 3. 连接集合(吃饭又岂是食神一人,集合装的都是来吃饭的)
List<SocketChannel> channes = new ArrayList<>();
while (true) {
LOGGER.debug("路人迈着八爷步,来到饭店,找了个位置,坐下");
// 4. accept 建立与客户端连接,SocketChannel 用来与客户端之间通信(菜单)
SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行
LOGGER.debug("路人拿起菜单,深思了许久,{}",sc);
channes.add(sc);
// 5. 遍历连接(依次处理请求)
for (SocketChannel channe : channes) {
// 6. 接受客户端发送的数据(路人缓缓开口了)
LOGGER.debug("小二弯着腰,等待路人点餐。这个动作似乎保持了很久,态度谦卑极了!{}",channe);
channe.read(buffer); // 阻塞方法,线程停止运行
// 7. 切换至读模式,把每位顾客的订单信息报给后厨
buffer.flip();
// 8. 后厨处理订单信息
ByteBufferUtil.debugAll(buffer);
// 9. 清零小黑板信息
buffer.clear();
LOGGER.debug("这位顾客的菜已经做完了,小黑板要掉信息了。。。{}",channe);
}
}
}
}
食神
package com.zhao.c1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 20:32
* @Description: 食神
*/
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8080));
System.out.println("食神正在思考吃什么");
}
}
顾客在未点完餐前,小二只为他一人服务,别人来了只能干等!
非阻塞模式
第二天来了一家非阻塞饭店。
憨憨赵前脚刚踏进饭店,
店小二:“呦,客官来了,快请快请!”
食神也紧随其后。
后面传来了:“呦,客官来了,快请快请!”
憨憨赵:“点酒。一碗凉水。”
店小二:“好嘞!”
食神:“点菜,一盘猪头肉”
店小二:“好嘞!”,“呦,客官来了,快请快请!”,不用看,二货马来了
二货马:“点菜,两碗凉水”
店小二:“好嘞!”
憨憨赵发现不管谁来,店小二都会及时发现,并且喊上自己的口头禅。店小二现在的路线像是一个环,前宾,服务员,后厨,他一个人全干了。速度奇快,在我的感知里,从这个环的开始到结束也就不到 1 秒,而且从未停歇过。但是看到小二的满头大汗,我知道这样下去肯定要把他累趴下的。
package com.zhao.c1;
import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 20:10
* @Description: 阻塞餐馆
*/
public class Server {
private static Logger LOGGER = LoggerFactory.getLogger(Server.class);
public static void main(String[] args) throws IOException {
// 使用nio来理解阻塞模式
// 0. ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 1. 创建服务器(饭店开门)
ServerSocketChannel ssc = ServerSocketChannel.open();
// 非阻塞模式,线程还会继续运行,如果没有连接建立,那 ssc 是 null
ssc.configureBlocking(false); // 放开的是 ssc.accept(); 方法,不会在该方法上面停留
// 2. 绑定监听端口(饭店地址)
ssc.bind(new InetSocketAddress(8080));
// 3. 连接集合(吃饭又岂是食神一人,集合装的都是来吃饭的)
List<SocketChannel> channes = new ArrayList<>();
while (true) {
// 4. accept 建立与客户端连接,SocketChannel 用来与客户端之间通信(菜单)
SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行
if (sc != null) {
sc.configureBlocking(false); // 非阻塞模式,放开的是 channe.read(buffer); 方法,不会在该方法上停留
LOGGER.debug("呦,客官来了,快请快请!{}", sc);
channes.add(sc);
}
// 5. 遍历连接(依次处理请求)
for (SocketChannel channe : channes) {
// 6. 接受客户端发送的数据(路人缓缓开口了)
int read = channe.read(buffer);// 非阻塞模式,线程仍会继续运行,如果没有读到数据,read 返回 0
if (read > 0) {
// 7. 切换至读模式,把每位顾客的订单信息报给后厨
buffer.flip();
// 8. 后厨处理订单信息
ByteBufferUtil.debugAll(buffer);
// 9. 清零小黑板信息
buffer.clear();
LOGGER.debug("这位顾客的菜已经做完了,小黑板要掉信息了。。。{}", channe);
}
}
}
}
}
Selector模式
第三天来了一家选择器饭店
憨憨赵前脚刚踏进饭店,
这次的店小二就比较懒了,但是憨憨赵看到了门外有一个硕大无比的摄像头。
此时,正在葛优瘫的店小二通过摄像头看到了憨憨赵,连忙起身
店小二:“呦,客官来了,快请快请!”
憨憨赵走进饭店既没点水,也没点饭。走到一旁坐了下去,憨憨赵注意到桌子上面有个按铃。
店小二又去葛优瘫了。
没过多久,食神也来了。店小二再次通过摄像头看到了,连忙起身
店小二:“呦,客官来了,快请快请!”
食神走进饭店既没点水,也没点饭。也是走到一旁坐了下去,食神也注意到桌子上面有个按铃。
店小二?又去葛优瘫了。
没过一会,憨憨赵按了响铃,把写好的菜单交给了店小二
店小二接过单子,上面赫然写着“一壶水”
随后,店小二让服务员按照单子去做了。
“客官,你的水”,随后在这张菜单后面打了一个 ✅,表示已经处理过了
食神也点好菜了,把菜单递给店小二,“一壶酒,两斤牛肉,三斤烤鸭,四斤米饭”
店小二连忙接过单子,但是并没有把单子交给服务员,似乎是忘记了。
这样没过一会,食神就询问后厨菜好了没有,着急呢!
但是不管怎么催后厨都没有用,单子在小二手里呢,催后厨,他也只能干瞪眼!
后厨无奈,因为他始终没见到单子,只好单向的取消了订单,表示让食神重新点菜。
经过以上教训,店长决定要分工明确一些,比如店小二的工作就是看大门的,服务员就是接单子的,后厨就是按照单子做菜的。每当发生什么事件,要向老板汇报,例如,来客人了,小二向老板发送来的是谁。客人下单了,服务员就要向老板汇报点了什么菜。后厨要做菜就要向老板申请菜单。
第二天
店小二负责接待客人,葛优瘫的店小二通过摄像头看到有人来了,起身迎接,并记录了这一事件,发送给老板。服务员把客人的单子发送给老板,后厨向老板申请单子(只是申请做菜的单子,饭店来了什么人,后厨并不关心)
一切看似正常,但是大家都忽略了一个致命的问题,就是做完菜并没有做标记,表示这道菜已经做完了。于是,出现了以下的情况
后厨做完了一道菜后,向老板申请了一个新单子,发现还是这道菜,当做好后,找不到对应的客人了。(上个客人吃完后,已经走了)
解决办法很简单,做完一道菜后,老板把单子删掉,再次申请时就不会重复了。(摄像头会监控这一切,不用担心客人会赖账的问题)
好景不长,食神因为今天吃了太多的东西,又在餐馆点了十斤肉,终于撑到了。老板急了,立马打 120 送食神去医院,老板,店小二和服务员也一同去了,饭店此时就剩后厨一人,因为老板没有把订单取消,于是后厨在不断的炒菜,累的头上直冒汗。
等到老板回来,后厨说了这件事,老板表示,如果下次还有这种情况,那就把订单取消。
package com.zhao.c1;
import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 20:10
* @Description: 阻塞餐馆
*/
public class Server {
private static Logger LOGGER = LoggerFactory.getLogger(Server.class);
public static void main(String[] args) throws IOException {
// 创建 Selector,管理多个 Channel
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
// 建立 Selector 和 Channel 的联系(注册)
// SelectionKey 将来事件发生后,通过它可以知道时哪个 Channel 发生的事件
/**
* 事件的类型
* accept:会在有连接请求时触发
* connect:是客户端,连接建立后触发
* read:可读事件
* write:可写事件
*/
SelectionKey sscKey = ssc.register(selector, 0, null);
// 关注的是哪种事件,现在 sscKey 只关注 ACCEPT 事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
LOGGER.debug("register key:{}", sscKey);
ssc.bind(new InetSocketAddress(8080));
while (true) {
// 判断是否有事件发生,如果四种事件其中一种发生了,会向下运行。没有发生,则阻塞
// select 在事件未处理时,它不会阻塞,事件发生后要么处理,要么取消,不能置之不理
selector.select();
// 处理事件,selectedKeys 集合内部包含了所有发生的事件(set集合)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理 key 时,要从 selectedKeys 集合中删除,否则下次处理就会有问题
iterator.remove();
LOGGER.debug("register key:{}", key);
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();
LOGGER.debug("{}", sc);
sc.configureBlocking(false);
SelectionKey scKey = sc.register(selector, 0, null);
scKey.interestOps(SelectionKey.OP_READ);
LOGGER.debug("{}", sc);
LOGGER.debug("scKey:{}", scKey);
} else if (key.isReadable()) { // 如果是 read
try {
SocketChannel channel = (SocketChannel) key.channel(); // 拿到触发事件的 channel
ByteBuffer buffer = ByteBuffer.allocate(16);
int read = channel.read(buffer);// 如果是正常断开,read 方法的返回值就是 -1
if (read == -1) {
key.cancel();
} else {
buffer.flip(); // 切换至读模式
ByteBufferUtil.debugAll(buffer);
}
} catch (IOException e) {
e.printStackTrace();
key.cancel(); // 因为客户端断开了,因此需要将 key 取消 (从 selector 的 keys 集合中真正删除 key )
}
}
// key.cancel(); // 取消订单
}
}
}
}
事情到此就结束了吗?不会的,强如微软,也在 windows 问世后也弥补了很多漏洞。何况一个毫无经验的饭店呢?
有一天来了一位名叫,嚯,他的名字好长,名叫:超级无敌噼里啪啦英俊潇洒风流倜傥人见人爱花见花开车见车爆胎...万‘花’丛中过片叶不沾身神奇小子·尼古拉斯二货马,就他一个名字,整整写了三张纸。
因为纸张的大小是有限制的,一次没写完只能把余下的写到另一张纸上面。
解决办法也很简单,客人来了,我们先记录如果一页不够写的话,就换大一点的纸张,并把之前记录的拷贝到新的纸张上面。但新纸张需要设置多大呢?两倍,第二次要是还不够,就再扩容两倍。例如:第一张纸容量是 10,第二张纸就是 20,第三张纸就是 40,第四张纸就是 80,以此类推。
餐厅:
package com.zhao.c1;
import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 20:10
* @Description: 阻塞餐馆
*/
public class Server {
private static Logger LOGGER = LoggerFactory.getLogger(Server.class);
// 进行字符串的切割
public static void split(ByteBuffer source){
// 切换至读模式
source.flip();
// 遍历
for (int i = 0; i < source.limit(); i++) {
// 如果到换行符,就停止
if (source.get(i) == '\n'){
int len = i + 1 - source.position();
// 创建写入 Buffer
ByteBuffer target = ByteBuffer.allocate(len);
// 由 source 读,向 target 写入
for (int j = 0; j < len; j++) {
// 切换至写入模式,移动指针,向source 写入
target.put(source.get());
}
// 打印结果
ByteBufferUtil.debugAll(target);
}
}
// 不进行元素覆盖,写到 Ho 时,进行写 w are you? 时,不会对 Ho 进行覆盖
source.compact();
}
public static void main(String[] args) throws IOException {
// 创建 Selector,管理多个 Channel
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
// 建立 Selector 和 Channel 的联系(注册)
// SelectionKey 将来事件发生后,通过它可以知道时哪个 Channel 发生的事件
/**
* 事件的类型
* accept:会在有连接请求时触发
* connect:是客户端,连接建立后触发
* read:可读事件
* write:可写事件
*/
SelectionKey sscKey = ssc.register(selector, 0, null);
// 关注的是哪种事件,现在 sscKey 只关注 ACCEPT 事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
LOGGER.debug("register key:{}", sscKey);
ssc.bind(new InetSocketAddress(8080));
while (true) {
// 判断是否有事件发生,如果四种事件其中一种发生了,会向下运行。没有发生,则阻塞
// select 在事件未处理时,它不会阻塞,事件发生后要么处理,要么取消,不能置之不理
selector.select();
// 处理事件,selectedKeys 集合内部包含了所有发生的事件(set集合)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理 key 时,要从 selectedKeys 集合中删除,否则下次处理就会有问题
iterator.remove();
LOGGER.debug("register key:{}", key);
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();
LOGGER.debug("{}", sc);
sc.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(16);
// 将一个 ByteBuffer 作为附件关联到 SelectionKey 上
SelectionKey scKey = sc.register(selector, 0, buffer);
scKey.interestOps(SelectionKey.OP_READ);
LOGGER.debug("{}", sc);
LOGGER.debug("scKey:{}", scKey);
} else if (key.isReadable()) { // 如果是 read
try {
SocketChannel channel = (SocketChannel) key.channel(); // 拿到触发事件的 channel
// 获取 SelectionKey 上关联的 Buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
int read = channel.read(buffer);// 如果是正常断开,read 方法的返回值就是 -1
if (read == -1) {
key.cancel();
} else {
split(buffer);
if (buffer.position() == buffer.limit()){
ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
// 切换至读模式
buffer.flip();
newBuffer.put(buffer); // 把旧集合中的元素写入至新集合
key.attach(newBuffer); // 第二次读取或多次读取时追加到原有的旧集合中
}
}
} catch (IOException e) {
e.printStackTrace();
key.cancel(); // 因为客户端断开了,因此需要将 key 取消 (从 selector 的 keys 集合中真正删除 key )
}
}
// key.cancel(); // 取消订单
}
}
}
}
客人
package com.zhao.c1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* @Auther: HackerZhao
* @Date: 2021/10/28 20:32
* @Description: 食神
*/
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8080));
SocketAddress address = sc.getLocalAddress();
sc.write(Charset.defaultCharset().encode("1234567\n890abcd3333\n"));
sc.write(Charset.defaultCharset().encode("中国\n"));
System.in.read();
}
}
后续:后厨想给客人写一封信。信很长,如下
服务器端
package com.zhao.c1;
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.nio.charset.Charset;
import java.util.Iterator;
/**
* @Auther: HackerZhao
* @Date: 2021/11/2 01:48
* @Description: 服务器端
*/
public class WriteServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8080));
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(true){
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()){
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
StringBuilder sb = new StringBuilder();
// 1. 向客户端发送大量数据
for (int i = 0; i < 3000000; i++) {
sb.append("a");
}
ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
// 判断buffer中是否还有字节
while (buffer.hasRemaining()) {
int write = sc.write(buffer);// 不能保证把所有数据都写入客户端,返回值代表实际写入的字节数
System.out.println(write);
}
}
}
}
}
}
客户端
package com.zhao.c1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @Auther: HackerZhao
* @Date: 2021/11/2 01:59
* @Description: 客户端
*/
public class WriteClient {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8080));
// 接收数据
int count = 0;
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
count += sc.read(buffer);
System.out.println(count);
buffer.clear();
}
}
}
后厨写了很长的信,期间来订单了他也不管不问,只顾着自己写信,老板很生气,让他不忙了写信,忙了你去炒菜。
UpdateServerCode
package com.zhao.c1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
/**
* @Auther: HackerZhao
* @Date: 2021/11/2 01:48
* @Description:
*/
public class WriteServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8080));
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
SelectionKey scKey = sc.register(selector, 0, null);
scKey.interestOps(SelectionKey.OP_READ);
StringBuilder sb = new StringBuilder();
// 1. 向客户端发送大量数据
for (int i = 0; i < 3000000; i++) {
sb.append("a");
}
ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
// 2. 返回值代表实际写入的字节数
int write = sc.write(buffer);// 不能保证把所有数据都写入客户端,
System.out.println(write);
// 3. 判断buffer中是否有剩余内容
if (buffer.hasRemaining()) {
// 4. 关注可写事件
scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);
// 5. 未写完的数据挂到 scKey 上
scKey.attach(buffer);
}
} else if (key.isWritable()) {
// 当可写的时候,取出挂载的 buffer,继续写入
ByteBuffer buffer = (ByteBuffer) key.attachment();
SocketChannel sc = (SocketChannel) key.channel();
int write = sc.write(buffer); // 从 buffer 读,向channel 写
System.out.println(write);
// 6. 清理操作
if (!buffer.hasRemaining()) {
key.attach(null); // 数据读完后,清除 buffer 与 key 之间的关系
key.interestOps(key.interestOps() - SelectionKey.OP_WRITE); // 不需要关注可写事件
}
}
}
}
}
}