听一个朋友说冷不丁被问了一个问题:类复用的方式有哪些?
其实这个问题也算是比较简单,类复用的方式主要有两种:组合和继承。
所谓组合,就是把一个类的实例作为另一个类的属性保存。
所谓继承,就是一个类是另一个类的子类。
查看电路原理图
GPIO内部引脚结构图。
1)内部上下拉电阻:决定默认引脚状态,VDD3.3V Vss–0VGND
2)默认引脚状态:上拉、下拉、浮空:不舍设置上下拉电压值由引脚所接的“外部外设”决定。
3)具体设置什么应该引脚接的是什么。
4)整个引脚的结构分为上下两部,输入/输出。5)三种输入方式:一般数字输入,复用输入、模拟输入。
1)一般数字输入:TTL斯密特触发器作用,由于从引脚输入的高低电平信号不是很完美,所以需要经过斯密特触发器的修整,使信号稳定。
输入寄存器的作用:输入的数字信号(数据)将缓存在“输入数据寄存器”中,然后程序即可以从“输入数据寄存器”中读出输入的数据,拿到数据后,程序就可以根据需要来使用这个数据了。
2)复用功能:不经过“数据寄存器”,经TTL修整后,可以交给复用该引脚的其它“片内外设”的寄存器,之后接自己的要求使用。
使用复用输入,配置GPIO的寄存器外,还要配置USB、DMA、UART这些外设的寄存器,让这些片内外设工作后,才能接收“复用输入”的数据。
3)模拟输入:无须修整,输入到芯片内部后,交给处理模拟信号的“片内外设”,交给AD,AD就后进行模拟/数字的转换,将模拟信号变为数字信号,什么时候使用。
比如,温度传感器将温度转为模拟电信号后,需要通过模拟输入由芯片内部的AD,再将其转为数字信号。4)三种输出:一般数字输出、复用输出、模拟输出。 1)一般数字输出:先写入复位/置位寄存器,再将数据导入“输出数据寄存器”然后输出。 下面还有一种,直接将数据写入“数据输出寄存器”,再输出。 每组GPIO16个引脚,共用相同的“输入数据寄存器“,”输出数据寄存器“”复位/置位寄存器”等。不过每组GPIO寄存器都是独立的。
“输出控制用于选择输出类型,数字信号可以有两种输出模式,推挽式和开漏式。推挽式输出,两个MOS管都工作,开漏输出只有一个N-MOS管工作,腿玩输出有更加强劲的输出能力,特殊要求时,才会选开漏输出。
2)复用输出:例:使用LCD相关复用,此时程序会先将图像数据交给"LCD片内外设”,然后通过复用输出路径将输出从引脚输出,交给LCD夜晶以供显示。
3)模拟输出:例:芯片内部DA将数字信号转换为模拟信号。
Java 提供了哪些 IO 方式, NIO 如何实现多路复用?
Java IO 方式
Java IO 方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单区分。
同步阻塞 IO
首先,传统的 Java.io 包基于流模型实现,提供了我们最熟知的一些 IO 功能,比如 File 抽象,输入输出流等,交互方式是同步 、阻塞的方式,也就是说,在读取输入流或者写入输出流是,在读写动作完成之前,线程会一直阻塞在哪,他们之间的调用时可靠的先行顺序。
java.io 包的好处就是代码比较简单直观,缺点就是 IO 效率和扩展性存在的局限性,容易成为应用性能的瓶颈。
很多时候,人们也把 java.net下面提供的部分网络API,比如 Socket、 Serversocket、 HttpURLConnection也归类到同步阻塞IO类库,因为网络通信IO行为。
同步非阻塞IO
在Java1.4中引入了NIO框架(java.nio包),提供了 Channel、 Selector、 Buffer等新的抽象,可以构建多路复用的、同步非阻塞IO程序,同时提供了更接近操作系统底层的高性能数据操作方
异步非阻塞IO
第三,在Java7中,NIO有了进一步的改进,也就是NIO2,引入了异步非阻塞IO方式,也有很多人叫它AIO( Asynchronous IO)。异步IO操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
什么是同步异步?
区分同步或异步( synchronous/ asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系
什么是阻塞非阻塞?
区分阻塞与非阻塞( blocking/on- blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 Serversocket新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管IO操作是否结束,直接返回,相应操作在后台继续处理。
IO不仅仅是对文件的操作,网络编程中,比如 Socket 通信,都是典型的IO操作目标。
输入流、输出流( Inputstream/outputstream)是用于读取或写入字节的,例如操作图片文件。
Reader/ Writer则是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取, Reader/ Writer相当于构建了应用逻辑和原始数据之间的桥梁
BufferedOutputStream 等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高IO处理效率。这种设计利用了缓冲区,将批量数据进行一次操作,很多IO工具类都实现了Closeable接口,因为需要进行资源的释放。比如,打开 FileInputstream,它就会获取相应的文件描述符( FileDescriptor)
利用 try-with-resources、try-finally 等机制保证 FileInputstream被明确关闭,进而相应文件描述符也会失效,否则将导致资源无法被释放。之前提到的 Cleaner或finalizer 机制作为资源释放的最后保障,也是必要的。
https://mp.weixin.qq.com/s?__biz=MzU4NDEwMzU3Mg==&mid=2247484409&idx=1&sn=3493be8a596b09ce0303cde0c0bc9fed&chksm=fd9fa002cae82914d77792d2232a74a76cb41525a164abd3bb1ac012d78edf089e242d3f9d5d&token=2120176138&lang=zh_CN#rd
Charset.defaultCharset().encode("Hello world!")
Selector 同样是基于底层操作系统机制,不同模式,不同版本都存在区别,例如。在 linux 上依赖 epoll, windows 上 NIO2 依赖的是 iocp。
通过一个典型场景,为什么需要多路复用,如果需要实现一个服务器应用,只简单要求能同时服务多个客户端请求即可。
public class DemoServer extends Thread {
private ServerSocket serverSocket;
public int getPort() {
return serverSocket.getLocalPort();
}
public void run() {
try {
serverSocket = new ServerSocket(0);
while (true) {
// 非常占用内存资源,每个客户端启用一个线程是十分不合理
Socket socket = serverSocket.accept();
RequesHandler requesHandler = new RequesHandler(socket);
requesHandler.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
;
}
}
}
public static void main(String[] args) throws IOException {
DemoServer server = new DemoServer();
server.start();
try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) {
BufferedReader buferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
buferedReader.lines().forEach(s -> System.out.println(s));
}
}
}
// 简化实现,不做读取,直接发送字符串
class RequesHandler extends Thread {
private Socket socket;
RequesHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (PrintWriter out = new PrintWriter(socket.getOutputStream());) {
out.println("Hello world!");
out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
每次 new 一个线程或者销毁一个线程是有明显的开销的,每个线程都有单独的线程结构,非常占用内存资源,每个客户端启用一个线程是十分不合理的, 因此可以采用线程池的方式进行优化.
也是阻塞IO,采用线程池的方式处理请求,当来一个新的客户端连接时,将请求 Socket 封装成一个 task ,放到线程池中取执行。
serverSocket = new ServerSocket(0);
executor = Executors.newFixedThreadPool(8);
while (true) {
Socket socket = serverSocket.accept();
RequesHandler requesHandler = new RequesHandler(socket);
executor.execute(requesHandler);
}
通过一个固定大小的线程池,来负责管理工作线程,避免频繁创建,销毁线程的开销。
试想,如果连接数并不是特别多,只有几百个连接,这种模式可以很好的工作。但是如果连接数急剧上升,这种实现就无法很好的工作,因为线程上下文切换开销会在高并发时变得很明显。
如果连接数并不是非常多,只有最多几百个连接的普通应用,这种模式往往可以工作的很好。但是,如果连接数量急剧上升,这种实现方式就无法很好地工作了,因为线程上下文切换开销会在高并发时变得很明显,这是同步阻塞方式的低扩展性劣势。
NIO(非阻塞IO) 多路复用机制
public class NIOServer extends Thread {
public void run() {
try (Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建Selector和Channel
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
serverSocket.configureBlocking(false);
// 注册到Selector,并说明关注点
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();// 阻塞等待就绪的Channel,这是关键点之一
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 生产系统中一般会额外进行就绪状态检查
sayHelloWorld((ServerSocketChannel) key.channel());
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void sayHelloWorld(ServerSocketChannel server) throws IOException {
try (SocketChannel client = server.accept();) {
ByteBuffer readBuffer = ByteBuffer.allocate(32);
client.read(readBuffer);
System.out.println("Server received : " + new String(readBuffer.array()));
ByteBuffer writeBuffer = ByteBuffer.allocate(128);
writeBuffer.put("hello xiaoming".getBytes());
writeBuffer.flip();
client.write(writeBuffer);
//client.write(Charset.defaultCharset().encode("Hello world!"));
}
}
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.start();
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
ByteBuffer writeBuffer = ByteBuffer.allocate(32);
ByteBuffer readBuffer = ByteBuffer.allocate(32);
writeBuffer.put("hello".getBytes());
writeBuffer.flip();
while (true) {
writeBuffer.rewind();
socketChannel.write(writeBuffer);
// readBuffer.clear();
socketChannel.read(readBuffer);
System.out.println("Client received : " + new String(readBuffer.array()));
}
} catch (IOException e) {
}
}
/**
* @return
*/
private int getPort() {
return 8888;
}
这样做的好处:
在前面两个样例,阻塞IO和伪异步IO,一个是使用 new 线程的方式,一个是采用线程池管理的方式, IO都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的Channel,来决定做什么,仅仅select阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。
JDK 1.7 升级了NIO 类库,升级后的 NIO 也被称为 NIO 2.0 ,NIO 2.0 引入了异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
AsynchronousServerSocketChannel serverSock = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress("127.0.0.1", 8888));
serverSock.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
final ByteBuffer buffer = ByteBuffer.allocate(1024);
@Override
public void completed(final AsynchronousSocketChannel result, Object attachment) {
buffer.clear();
try {
// 把socket中的数据读取到buffer中
result.read(buffer).get();
buffer.flip();
System.out.println("Echo " + new String(buffer.array()).trim() + " to " + result);
// 把收到的直接返回给客户端
result.write(buffer);
buffer.flip();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
}
}
@Override
public void failed(Throwable throwable, Object attachment) {
}
});
听一个朋友说冷不丁被问了一个问题:类复用的方式有哪些?
其实这个问题也算是比较简单,类复用的方式主要有两种:组合和继承。
所谓组合,就是把一个类的实例作为另一个类的属性保存。
所谓继承,就是一个类是另一个类的子类。
转载于:https://my.oschina.net/xinguimeng/blog/796481
title : 每日深耕,勤练不缀之java提供了哪些IO方式?NIO如何实现多路复用?
可以构建高扩展性应用的能力
JAVA IO 有很多种,基于不同的IO抽象模型和交互方式
可以进行简单区分
1.传统的java.io包(BIO),基于流模型实现,提供了我们最熟知的一些IO性能,如:File抽象,输入输出流等
交互方式是同步,堵塞的方式,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序
java.io包的好处是代码比较简单,直观,缺点是IO效率和扩展性存在局限,成为性能瓶颈。
java.net下面提供的部分网络API,比如Socket、ServerSocket、HttpURLConnection也归类到同步阻塞IO类库。
2.java1.4引入了NIO框架(java.io包),提供了Channel,Selector,Buffer等新的抽象,可以构建多路复用的、同步非阻塞IO程序,,提供了更接近操作系统底层的高性能数据操作方式
3.java7中,NIO有了进一步的改进,也就是NIO2,引入了异步非阻塞IO方式,也有很多人叫它AIO(Asynchronous IO)。异步IO操作基于事件和回调机制,可以理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作在面试中,考察的点涉及方方面面
区分同步和异步?
同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步,而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系
区分阻塞和非阻塞?
在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当前条件就绪才能继续。比如:ServerSocket新连接建立完毕,或数据读取,写入操作完成,而 非阻塞 则是不管IO操作是否结束,直接返回,相应操作则在后台处理。
我们不能说同步和阻塞就是低效,分情况而定。
对于java.io的总结:
JAVA NIO
NIO的组成部分:
1.Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的Buffer实现
2.Channel,文件描述符,是NIO中被用来支持批量式IO操作的一种抽象。file 、Socket ,被认为是比较高层次的抽象,而 channel则是更加操作系统底层的一种抽象。我们可以通过Socket获取Channel。
4.Selector,是NIO实现多复用的基础,他提供了一种高效的机制,可以检测到注册在Selector的多个Channel中,是否有Channel处于就绪状态,进而实现单线程对多CHANNNEL的高效管理
NIO解决的问题
为什么需要NIO,为什么需要多路复用?
场景:我们需要实现一个服务器应用,简单要求能够同时服务多个客户端请求
package DemoClient;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class DemoServer extends Thread{
private ServerSocket serverSocket;
public int getPort(){
return serverSocket.getLocalPort();//相当于一个平常的成员变量
}
public void run(){
try{
serverSocket =new ServerSocket(0);//服务器端启动ServerSocket,端口0表示自动绑定一个空闲端口
while (true){
Socket socket =serverSocket.accept();//客户端的请求,阻塞等待客户端连接
RequestHandler requestHandler =new RequestHandler(socket) ;
requestHandler.start();
}
}catch (IOException e){
e.printStackTrace();
} finally {
if(serverSocket !=null){
try{
serverSocket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException{
DemoServer server =new DemoServer();
server.start();//服务器开启
/**
* 利用Scoket模拟一个简单的客户端,只进行连接、读取、打印
*/
try(Socket client =new Socket(InetAddress.getLocalHost(),server.getPort())){
BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(client.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println(s));
}
}
class RequestHandler extends Thread {
private Socket socket;
RequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {//重载
try (PrintWriter out = new PrintWriter(socket.getOutputStream());) {
out.println("hello world");
out.flush();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
线程实现比较重量级,启动和销毁一个线程有明显开销,每个线程都有单独的线程栈结构 ,占用非常明显的内存,所以,每一个Client启动一个线程都有点浪费
所以,我们可以修正问题,引入线程池机制来避免浪费
通过一个固定大小的线程池,来负责管理工作线程,避免频繁创建、销毁线程的开销,这是构建并发服务的典型方式
如果只有几百个连接的普通应用,这种模式往往可以工作很好。但是如果数量急剧上升,这种实现方式就无法很好工作,因为线程上下文切换开销会在高并发时变得很明显,这是同步阻塞方式的低扩展性劣势
NIO引用的多路复用机制,提供了另外一种思路
package DemoServerClient;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
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;
import java.util.Set;
public class NIOServer extends Thread{
/**NIO的组成1.Buffer 2.channel 多线程并发抽象概念 3.Selector筛选器
* 同步非阻塞IO框架
*/
public void run(){
try(Selector selector =Selector.open();//通过Selector.open()创建一个Selector,作为一个类似调度员的角色
/**通过创建一个ServerSocketChannnel,并且向Selector注册,通过指定SelectionKey.OP_ACCEPT,
* 告诉调度员,它关注的是新的连接请求
* 为什么我们要明确配置非阻塞模式呢?因为在阻塞模式下,注册操作是不允许的
*/
ServerSocketChannel serverSocket =ServerSocketChannel.open();){//创建channel selector
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(),8888));//创建连接端口,监听
serverSocket.configureBlocking(false);
//注册到Selector,并说明关注点
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while(true){
/**Selector阻塞在select操作,当有Channel发生接入请求后,就会被唤醒
*/
selector.select();//阻塞等待就绪的Channel,这是关键点之一
Set <SelectionKey> selectionKeys =selector.selectedKeys();//建一个selector的HashSet
Iterator <SelectionKey> iter =selectionKeys.iterator();//迭代器迭代 selectionKey的Set
while(iter.hasNext()){
SelectionKey key =iter.next();
//生产系统中一般会额外进行就绪状态检查
sayHelloWorld((ServerSocketChannel)key.channel());//强制类型转换,找出那个channel
}
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
*在此方法中,通过ScoketChannel和Buffer进行数据操作,发送一段字符串
* @param server
* @throws IOException
*/
private void sayHelloWorld(ServerSocketChannel server) throws IOException{
try(SocketChannel client =server.accept();){//客户端只管发送请求就ok
client.write(Charset.defaultCharset().encode("helloworld!"));
}
}
}
IO是同步阻塞模式,需要多线程实现多任务处理
NIO利用单线程轮询时间的机制,高效定位就绪的Channel,来决定做什么,仅仅select阶段是阻塞的,可以避免大量客户连接时,频繁切换线程带来的问题
异步非阻塞AIO
java7引入NIO2(AIO)时,又增添了一种额外的异步IO模式,利用事件和回调,处理Accept,Read等操作
AsychronousServerSocketChannel对应于上面例子的SeverSocketChannel;
AsychronousSocketChannel对应于SocketChannel
业务逻辑的关键点在于,通过指定的CompletionHandler回调接口,在accept/read/write等关键节点,通过事件机制调用,实现异步