热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java的nio_JAVA中的NIO(NewIO)

JAVA中的NIO标准的IO是基于字节流和字符流进行操作的,而JAVA中的NIO是基于Channel和Buffer进行操作的。传统IOgraphTB;字节流--In

JAVA中的NIO

标准的IO是基于字节流和字符流进行操作的,而JAVA中的NIO是基于Channel和Buffer进行操作的。

传统IO

graph TB;

字节流 --> InputStream;

字节流 --> OutputStream;

字符流 --> Reader;

字符流 --> Writer;

NIO

graph TB;

A[Channel] --> B[Buffer..];

C[Channel] --> D[Buffer..];

E[Channel] --> F[Buffer..];

核心模块

NIO主要有三个核心部分:Selector、Channel、Buffer

数据总是从Channel读取到Buffer或者从Buffer写入到Channel中。

Selector可以监听多个Channel的多个事件。

graph TB;

Selector --> A[Channel];

Selector --> B[Channel];

Selector --> C[Channel];

A --> E1[Event...];

B --> E2[Event...];

C --> E3[Event...];

传统的IO与Channel的区别

1.传统的IO是BIO的,而Channel是NIO的。

*当流调用了read()、write()方法后会一直阻塞线程直到数据被读取或写入完毕。

2.传统IO流是单向的,而Channel是双向的。

Channel

FileChannel:从文件中进行读取

DatagramChannel:可以通过UDP协议在网络中进行数据的传输

SocketChannel:可以通过TCP协议在网络中进行数据的传输

ServerSocketChannel:可以作为一个服务器监听连接

Channel通用API:

read(buffer):将数据从Channel读取到Buffer中,读取完毕返回-1。

read(buffer []):将数据从Channel读取到多个Buffer中,仅当第一个Buffer被写满后往第二个Buffer中进行写入。

write(buffer):将Buffer中的数据写入到Channel中。

write(buffer[]):将多个Buffer中的数据写入到Channel中,仅当第一个Buffer中的数据被读取完毕后再从第二个Buffer中进行读取。

register(selector,interest):将Channel注册到Selector中,同时需要向Selector传递要监听此Channel的事件类型(注册到Selector中的Channel一定要非阻塞的)

configureBlocking(boolean):设置Channel是否为阻塞。

transferFrom(position,count,channel):将其他Channel中的数据传输到当前Channel中。

transferTo(position,count,channel):将当前Channel中的数据传输到其他Channel中。

SocketChannel API

open()静态方法:创建SocketChannel。

connect(new InetSocketAddress(port))方法:连接服务器。

finishConnect()方法:判断是否已经与服务器建立连接。

ServerSocketChannel API

open()静态方法:创建ServerSocketChannel。

accept()方法:该方法会一直阻塞线程直到有新连接到达。

阻塞式与非阻塞式Channel

正常情况下Channel都是阻塞的,只有当调用了configureBlocking(false)方法时Channel才为非阻塞。

阻塞式Channel的connect()、accept()、read()、write()方法都会阻塞线程,直到处理完毕。

非阻塞式Channel的connect()、accept()、read()、write()方法都是异步的。

*当调用了非阻塞式Channel的connect()方法后,需要使用finishConnect()方法判断是否已经与服务器建立连接。

*当调用了非阻塞式Channel的accept()方法后,需要根据方法的返回值是否为NULL判断是否接收到新的连接。

*当调用了非阻塞式Channel的read()方法后,需要根据方法的返回值是否大于0判断是否有读取到数据。

*在使用非阻塞式Channel的write()方法时,需要借助while循环与hasRemaining()方法保证buffer中的内容被全部写入。

*FileChannel一定是阻塞的。

示例

public void testFileChannel() throws IOException {

RandomAccessFile randomAccessFile = new RandomAccessFile(new File("F:\\笔记\\nginx.txt"), "rw");

FileChannel fileChannel = randomAccessFile.getChannel();

ByteBuffer byteBuffer = ByteBuffer.allocate(64);

int count = fileChannel.read(byteBuffer);

while (count != -1) {

byteBuffer.flip();

System.out.println(new String(Arrays.copyOfRange(byteBuffer.array(),0,byteBuffer.limit()),Charset.forName("UTF-8")));

byteBuffer.clear();

count = fileChannel.read(byteBuffer);

}

}

Buffer

Buffer是一块可以进行读写操作的内存(顺序存储结构)

ByteBuffer:基于Byte类型进行存储

CharBuffer:基于Char类型进行存储

DoubleBuffer:基于Double类型进行存储

FloatBuffer:基于Float类型进行存储

IntBuffer:基于Int类型进行存储

LongBuffer:基于Long类型进行存储

ShortBuffer:基于Short类型进行存储

Buffer的内部结构

1.capacity:表示buffer的容量

2.position:表示当前的位置(从0开始,最大值为capacity-1)

3.limit:在写模式中表示可以写入的个数(与capacity一样),在读模式中表示可以读取的个数。

254f7fcc206018c09a4ccb091025c555.png

从写模式转换成读模式

limit设置为position+1,position设置为0。

从读模式转换成写模式

limit设置为capacity,position设置为0。

往Buffer中写数据

1.将数据从Channel读取到Buffer中。

2.使用Buffer的put()方法。

从Buffer中读数据

1.将Buffer中的数据写入到Channel中。

2.使用Buffer的get()方法

Buffer通用API:

allocate(size)静态静态:初始化一个Buffer。

flip():将buffer从写模式转换成读模式。

array():将Buffer中的内容转换成数组(不受limit控制)

get():获取Buffer中的内容。

hasRemaining():判断Buffer中是否还有未读的元素(limit - (postion+1) )

rewind():将positon设置为0。

clear():将limit设置为capacity,position设置为0。

compact():将所有未读的元素移动到Buffer的起始处,position指向最后一个未读的元素的下一位,limit设置为capacity。

*clear()和compact()方法都可以理解成将Buffer从读模式转换成写模式,区别在于compact()方法会保留未读取的元素。

mark():在当前position处打一个标记。

reset():将position恢复到标记处。

Selector

Selector用于监听多个Channel的多个事件(单线程)

graph TB;

Selector --> A[Channel];

Selector --> B[Channel];

Selector --> C[Channel];

A --> E1[connect];

B --> E2[accept];

C --> E3[connect];

C --> E4[read];

Channel的事件类型

1.连接就绪:当SocketChannel、DatagramChannel成功与服务器建立连接时将会触发连接就绪事件。

2.接收就绪:当有连接到达服务器时将会触发接收就绪事件。

3.读就绪:当SocketChannel、DatagramChannel有数据可读时将会触发读就绪事件。

4.写就绪:当SocketChannel、DatagramChannel可以进行数据写入时将会触发写就绪事件。

SelectionKey

SelectionKey用于存储Selector与Channel之间的相关信息。

SelectionKey中提供了四个常量分别代表Channel的事件类型。

SelectionKey.OP_CONNECT

SelectionKey.OP_ACCEPT

SelectionKey.OP_READ

SelectionKey.OP_WRITE

SelectableChannel提供的register(selector,interest)方法用于将Channel注册到Selector中,同时需要向Selector传递要监听此Channel的事件类型,当要监听的事件类型不止一个时可以使用或运算,当将Channel注册到Selector后会返回SelectionKey实例,用于存储Selector与此Channel之间的相关信息。

SelectionKey API:

interestOps()方法:返回Selector监听此Channel的事件类型。

readyOps()方法:返回此Channel目前就绪的事件。

isAcceptable():判断Channel是否接收就绪。

isConnectable():判断Channel是否连接就绪。

isReadable():判断Channel是否读就绪。

isWriteable():判断Channel是否写就绪。

channel():返回具体的Channel实例。

selector():返回Selector实例。

attach():往SelectionKey中添加一个附加对象。

attachment():返回保存在SelectionKey中的附加对象。

Selector API:

open()静态方法:创建一个Selector。

select()方法:该方法会一直阻塞线程直到所监听的Channel有事件就绪,返回就绪的Channel个数(只会返回新就绪的Channel个数)

selectedKeys()方法:返回就绪的Channel对应的SelectionKey。

*当Channel就绪的事件处理完毕后,需要手动删除SelectionKey集合中该Channel对应的SelectionKey,当该Channel再次有事件就绪时会自动加入到Selectionkey集合中。

非阻塞式Channel与Selector

非阻塞式Channel一般与Selector配合使用

当Selector监听到ServerSocketChannel接收就绪时,那么此时可以立即调用ServerSocketChannel的accept()方法获取新连接。

当Selector监听到SocketChannel读就绪时,那么此时可以立即调用SocketChannel的read()方法进行数据的读取。

非阻塞式服务器

/**

* @Author: Zhuang HaoTang

* @Date: 2019/10/26 16:35

* @Description:

*/

public class Server {

public void start() throws IOException {

Selector selector = Selector.open();

ServerSocketChannel serverSocketChannel = createNIOServerSocketChannel();

System.out.println("start nio server and bind port 8888");

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

int ready = selector.select();

while (ready > 0) {

System.out.println("ready channel count " + ready);

Set selectionKeySet = selector.selectedKeys();

for (Iterator iterator = selectionKeySet.iterator(); iterator.hasNext(); ) {

SelectionKey selectionKey = iterator.next();

if (selectionKey.isAcceptable()) {

System.out.println("acceptable");

acceptHandler(selectionKey);

} else if (selectionKey.isReadable()) {

System.out.println("readable");

readHandler(selectionKey);

}

iterator.remove();

}

ready = selector.select();

}

}

private ServerSocketChannel createNIOServerSocketChannel() throws IOException {

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

serverSocketChannel.configureBlocking(false);

return serverSocketChannel;

}

private void acceptHandler(SelectionKey selectionKey) throws IOException {

Selector selector = selectionKey.selector();

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

SocketChannel socketChannel = serverSocketChannel.accept();

socketChannel.configureBlocking(false);

socketChannel.register(selector, SelectionKey.OP_READ);

System.out.println("accept client connection " + socketChannel.getLocalAddress());

}

private void readHandler(SelectionKey selectionKey) throws IOException {

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(100);

int num = socketChannel.read(byteBuffer);

if(num == -1){ // 连接已断开

System.out.println("client "+socketChannel.getLocalAddress() + " disconnection");

socketChannel.close();

return;

}

byteBuffer.flip();

while (byteBuffer.hasRemaining()) {

byte b = byteBuffer.get();

System.out.println((char) b);

}

}

public static void main(String[] args) throws IOException {

Server server = new Server();

server.start();

}

}

*一个Channel不会同时有多个事件就绪,以事件为单位。

*当客户端断开连接,那么将会触发读就绪,并且channel的read()方法返回-1,表示连接已断开,服务器应该要做出处理,关闭这个连接。

客户端

/**

* @Auther: Zhuang HaoTang

* @Date: 2019/10/26 16:36

* @Description:

*/

public class Client {

public static void main(String[] args) throws IOException, InterruptedException {

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(),8888));

String message = "today is sunday";

ByteBuffer byteBuffer = ByteBuffer.allocate(message.getBytes().length);

byteBuffer.put(message.getBytes());

byteBuffer.flip();

socketChannel.write(byteBuffer);

Thread.sleep(5000);

}

}

运行结果

ef95d61e139980d1ce12cd8f09b3f034.png

Reactor模式

Reactor有三种模式

1.Reactor单线程模式

2.Reactor多线程模式

3.主从Reactor多线程模式

*Reactor模式是在NIO下实现的。

Reactor单线程模式

3683a74d296dca95cf18a72b70d20238.png

1.单线程的事件分化器,同时这个线程需要处理接收、读、写就绪事件。

/**

* @Author: Zhuang HaoTang

* @Date: 2019/10/26 16:35

* @Description:

*/

public class ReactorSingleThreadServer {

private void start() throws IOException {

Selector selector = Selector.open();

ServerSocketChannel serverSocketChannel = createNIOServerSocketChannel();

System.out.println("start nio server and bind port 8888");

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

int ready = selector.select();

while (ready > 0) {

System.out.println("ready channel count " + ready);

Set selectionKeySet = selector.selectedKeys();

for (Iterator iterator = selectionKeySet.iterator(); iterator.hasNext(); ) {

SelectionKey selectionKey = iterator.next();

if (selectionKey.isAcceptable()) {

System.out.println("acceptable");

acceptHandler(selectionKey);

} else if (selectionKey.isReadable()) {

System.out.println("readable");

readHandler(selectionKey);

}

iterator.remove();

}

ready = selector.select();

}

}

private ServerSocketChannel createNIOServerSocketChannel() throws IOException {

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

serverSocketChannel.configureBlocking(false);

return serverSocketChannel;

}

private void acceptHandler(SelectionKey selectionKey) throws IOException {

Selector selector = selectionKey.selector();

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

SocketChannel socketChannel = serverSocketChannel.accept();

socketChannel.configureBlocking(false);

socketChannel.register(selector, SelectionKey.OP_READ);

System.out.println("accept client connection " + socketChannel.getLocalAddress());

}

private void readHandler(SelectionKey selectionKey) throws IOException {

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(100);

int num = socketChannel.read(byteBuffer);

if (num == -1) {

System.out.println("client " + socketChannel.getLocalAddress() + " disconnection");

socketChannel.close();

return;

}

byteBuffer.flip();

while (byteBuffer.hasRemaining()) {

byte b = byteBuffer.get();

System.out.println((char) b);

}

}

public static void main(String[] args) throws IOException {

ReactorSingleThreadServer server = new ReactorSingleThreadServer();

server.start();

}

}

Reactor多线程模式

bd10bd2b4052e7c59c2222bede1766dc.png

1.单线程的事件分发器。

2.具体事件类型的Handler线程池(针对读写就绪事件)

3.业务线程池。

/**

* @Author: Zhuang HaoTang

* @Date: 2019-10-28 17:00

* @Description:

*/

public class ReactorMultiThreadServer {

private ThreadPoolExecutor eventHandlerPool = new ThreadPoolExecutor(10, 50, 2, TimeUnit.MINUTES, new ArrayBlockingQueue(200), new ThreadPoolExecutor.CallerRunsPolicy());

private void start() throws IOException {

Selector selector = Selector.open();

ServerSocketChannel serverSocketChannel = createNIOServerSocketChannel();

System.out.println("start nio server and bind port 8888");

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

selector.select();

for (;;) {

Set selectionKeySet = selector.selectedKeys();

for (Iterator iterator = selectionKeySet.iterator(); iterator.hasNext(); ) {

final SelectionKey selectionKey = iterator.next();

if (selectionKey.isAcceptable()) {

System.out.println("acceptable");

acceptHandler(selectionKey); // 单线程同步处理接收就绪

} else if (selectionKey.isReadable()) {

System.out.println("readable");

eventHandlerPool.submit(new Runnable() {

@Override

public void run() {

readHandler(selectionKey);

}

});

}

iterator.remove();

}

selector.select();

}

}

private ServerSocketChannel createNIOServerSocketChannel() throws IOException {

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

serverSocketChannel.configureBlocking(false);

return serverSocketChannel;

}

private void acceptHandler(SelectionKey selectionKey) throws IOException {

Selector selector = selectionKey.selector();

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

SocketChannel socketChannel = serverSocketChannel.accept();

if (socketChannel != null) {

socketChannel.configureBlocking(false);

socketChannel.register(selector, SelectionKey.OP_READ);

System.out.println("accept client connection " + socketChannel.getLocalAddress());

}

}

private void readHandler(SelectionKey selectionKey) {

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(100);

try {

int num = socketChannel.read(byteBuffer);

if (num == -1) {

System.out.println("client " + socketChannel.getLocalAddress() + " disconnection");

socketChannel.close(); // 底层有些逻辑

return;

}

byteBuffer.flip();

while (byteBuffer.hasRemaining()) {

byte b = byteBuffer.get();

System.out.println((char) b);

}

} catch (Exception e) {

System.out.println("由于连接关闭导致并发线程读取异常");

}

}

public static void main(String[] args) throws IOException {

ReactorMultiThreadServer reactorServer = new ReactorMultiThreadServer();

reactorServer.start();

}

}

主从Reactor多线程模式

91a36032f068a9a09c352ace7eabc411.png

1.使用两个单线程的事件分发器。

第一个事件分发器只负责监听ServerSocketChannel的接收就绪事件,同时ServerSocketChannel接收到的连接要注册到第二个事件分发器中。

第二个事件分发器只负责监听SocketChannel的读、写就绪事件。

2.具体事件类型的Handler线程池(针对读写就绪事件)

3.业务线程池。

/**

* @Author: Zhuang HaoTang

* @Date: 2019-10-28 17:00

* @Description:

*/

public class MainSubReactorMultiThreadServer {

private ThreadPoolExecutor eventHandlerPool = new ThreadPoolExecutor(10, 50, 2, TimeUnit.MINUTES, new ArrayBlockingQueue(200), new ThreadPoolExecutor.CallerRunsPolicy());

private void start() throws IOException {

final Selector mainSelector = Selector.open();

final Selector subSelector = Selector.open();

new Thread(new Runnable() {

@Override

public void run() {

try {

startMainSelector(mainSelector, subSelector);

} catch (IOException e) {

e.printStackTrace();

}

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

try {

startSubSelector(subSelector);

} catch (IOException e) {

e.printStackTrace();

}

}

}).start();

}

/**

* 第一个事件分发器,用于监听ServerSocketChannel的接收就绪事件

*/

private void startMainSelector(Selector mainSelector, final Selector subSelector) throws IOException {

ServerSocketChannel serverSocketChannel = createNIOServerSocketChannel();

System.out.println("start nio server and bind port 8888");

serverSocketChannel.register(mainSelector, SelectionKey.OP_ACCEPT);

mainSelector.select();

for (; ; ) {

Set selectionKeySet = mainSelector.selectedKeys();

SelectionKey selectionKey = Iterables.getOnlyElement(selectionKeySet);

if (selectionKey.isAcceptable()) {

System.out.println("acceptable");

acceptHandler(selectionKey, subSelector); // 单线程同步处理接收就绪

selectionKeySet.clear();

}

mainSelector.select();

}

}

/**

* 第二个事件分发器,用于监听SockChannel的读写就绪事件

*/

private void startSubSelector(Selector subSelector) throws IOException {

subSelector.select();

for (; ; ) {

Set selectionKeySet = subSelector.selectedKeys();

for (Iterator iterator = selectionKeySet.iterator(); iterator.hasNext(); ) {

final SelectionKey selectionKey = iterator.next();

if (selectionKey.isReadable()) {

System.out.println("readable");

eventHandlerPool.submit(new Runnable() {

@Override

public void run() {

readHandler(selectionKey);

}

});

iterator.remove();

}

}

subSelector.select();

}

}

private ServerSocketChannel createNIOServerSocketChannel() throws IOException {

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

serverSocketChannel.configureBlocking(false);

return serverSocketChannel;

}

private void acceptHandler(SelectionKey selectionKey, Selector subSelector) throws IOException {

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

SocketChannel socketChannel = serverSocketChannel.accept();

if (socketChannel != null) {

socketChannel.configureBlocking(false);

subSelector.wakeup(); // 往Selector注册Channel时,Selector要处于非阻塞状态

socketChannel.register(subSelector, SelectionKey.OP_READ);

System.out.println("accept client connection " + socketChannel.getLocalAddress() + " and register to subSelector");

}

}

private void readHandler(SelectionKey selectionKey) {

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(100);

try {

int num = socketChannel.read(byteBuffer);

if (num == -1) {

System.out.println("client " + socketChannel.getLocalAddress() + " disconnection");

socketChannel.close(); // 底层有些逻辑

return;

}

byteBuffer.flip();

while (byteBuffer.hasRemaining()) {

byte b = byteBuffer.get();

System.out.println((char) b);

}

} catch (Exception e) {

System.out.println("由于连接关闭导致并发线程读取异常");

}

}

public static void main(String[] args) throws IOException {

MainSubReactorMultiThreadServer reactorServer = new MainSubReactorMultiThreadServer();

reactorServer.start();

}

}

通用客户端

/**

* @Author: Zhuang HaoTang

* @Date: 2019/10/26 16:36

* @Description:

*/

public class Client {

public static void main(String[] args) throws IOException, InterruptedException {

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

String message = "today is sunday";

ByteBuffer byteBuffer = ByteBuffer.allocate(message.getBytes().length);

byteBuffer.put(message.getBytes());

byteBuffer.flip();

socketChannel.write(byteBuffer);

Thread.sleep(5000);

ByteBuffer byteBuffer1 = ByteBuffer.allocate("wo".getBytes().length).put("wo".getBytes());

byteBuffer1.flip();

socketChannel.write(byteBuffer1);

ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);

while (true) {

socketChannel.read(receiveBuffer);

receiveBuffer.flip();

while (receiveBuffer.hasRemaining()) {

System.out.println((char)receiveBuffer.get());

}

receiveBuffer.clear();

}

}

}

*主线程不需要等待具体事件类型的Handler处理完毕,直接异步返回,那么将会导致事件重复就绪,程序做出相应的控制即可。

*当channel有数据可读时,将会触发读就绪,那么主线程将会不停的向线程池提交任务,直到某个线程读取完毕,此时将会停止读就绪,其他线程读取到的个数为0。

*当客户端断开连接时,将会触发读就绪,那么主线程将会不停的向线程池提交任务,直到某个线程关闭连接,此时将会停止读就绪

一般不会直接去使用JAVA NIO,只是通过JAVA NIO学习他的设计思想,如果要想搭建NIO服务器那么应该使用Netty等NIO框架。

关于BIO和NIO的选择

BIO即同步并阻塞,线程会进入阻塞状态,如果并发连接数只有几百,那么创建几百个线程去处理是没有任何问题的,这种方式更加简单高效。

但是如果并发连接数达到几万,那么显然创建几万个线程去处理是不可行的,系统承受不了这个负荷,此时应该使用NIO,即同步非阻塞,利用更少的线程去做更多的事情。

JAVA NIO就是使用NIO(同步非阻塞),使用IO多路复用的Select模型。

eb368d3a8b12c9fe105292422904efb3.png

*不管客户端有多少个并发连接和请求,服务端总是可以利用更少的线程去处理(单线程事件分发器 和 具体事件类型的Handler线程池)



推荐阅读
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
author-avatar
日蕾文散Lotus
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有