Java IO編程全解(四)——NIO編程

来源:http://www.cnblogs.com/Joanna-Yan/archive/2017/11/07/7793964.html
-Advertisement-
Play Games

轉載請註明出處:http://www.cnblogs.com/Joanna-Yan/p/7793964.html 前面講到:Java IO編程全解(三)——偽非同步IO編程 NIO,即New I/O,這是官方叫法,因為它相對於之前的I/O類庫是新增的。但是,由於之前老的I/O類庫是阻塞I/O,New ...


  轉載請註明出處:http://www.cnblogs.com/Joanna-Yan/p/7793964.html 

  前面講到:Java IO編程全解(三)——偽非同步IO編程

  NIO,即New I/O,這是官方叫法,因為它相對於之前的I/O類庫是新增的。但是,由於之前老的I/O類庫是阻塞I/O,New I/O類庫的目標就是要讓Java支持非阻塞I/O,所以,更多的人喜歡稱之為非阻塞I/O(Non-block I/O),由於非阻塞I/O更能夠體現NIO的特點,所以這裡使用的NIO都是指非阻塞I/O。

  與Socket類和ServerSocket類相對應,NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道實現。這兩種新增的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用非常簡單,但是性能和可靠性都不好,非阻塞則正好相反。開發人員一般可以根據自己的需要來選擇合適的模式,一般來說,低負載、低併發的應用程式可以選擇同步阻塞I/O以降低編程複雜度,但是對於高負載、高併發的網路應用,需要使用NIO的非阻塞模式進行開發。

1.NIO類庫簡介

  新的輸入/輸出(NIO)庫是在JDK1.4中引入的。NIO彌補了原來同步阻塞I/O的不足,它在標準Java代碼中提供了高速的、面向塊的I/O。通過定義包含數據的類,以及通過以塊的形式處理這些數據,NIO不使用本機代碼就可以利用低級優化,這是原來的I/O包所無法做到的。下麵對NIO的一些概念和功能做下簡單介紹,以便大家能夠快速地瞭解NIO類庫和相關概念。

  1.緩衝區Buffer

  Buffer是一個對象,它包含一些要寫入或者要讀出的數據。在NIO類庫中加入Buffer對象,體現了新庫與原I/O的一個重要區別。在面向流的I/O中,可以將數據直接寫入或者將數據直接讀到Stream對象中。

  在NIO庫中,所有數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的;在寫入數據時,寫入到緩衝區中。任何時候訪問NIO中的數據,都是通過緩衝區進行操作。

  緩衝區實質上是一個數組。通常它是一個位元組數組(ByteBuffer),也可以使用其他種類的數組。但是緩衝區不僅僅是一個數組,緩衝區提供了對數據的結構化訪問以及維護讀寫位置(limit)等信息。

  最常用的緩衝區是ByteBuffer,一個ByteBuffer提供了一組功能用於操作byte數組。除了ByteBuffer,還有其他的一些緩衝區,事實上,每一種Java基本類型(除了Boolean類型)都對應有一種緩衝區,具體如下:

  • ByteBuffer:位元組緩衝區
  • CharBuffer:字元緩衝區
  • ShortBuffer:短整型緩衝區
  • IntBuffer:整型緩衝區
  • LongBuffer:長整型緩衝區
  • FloatBuffer:浮點型緩衝區
  • DoubleBuffer:雙精度浮點型緩衝區

   每一個Buffer類都是Buffer介面的一個子實例。除了ByteBuffer,每一個Buffer類都有完全一樣的操作,只是它們所處理的數據類型不一樣。因為大多數標準I/O操作都是使用ByteBuffer,所以它除了具有一般緩衝區的操作之外還提供一些特有的操作,方便網路讀寫。

  2.通道Channel

  Channel是一個通道,可以通過它讀取和寫入數據,它就像自來水管一樣,網路數據通過Channel讀取和寫入。通道與流的不同之處在於通道是雙向的,流只是在一個方向上移動(一個流必須是InputStream或者OutputStream的子類),而且通道可以用於讀、寫或者同時讀寫。因為Channel是全雙工的,所以它可以比流更好地映射底層操作系統的API。

  3.多路復用器Selector

  多路復用器Selector是Java NIO編程的基礎,熟練地掌握Selector對於掌握NIO編程至關重要。多路復用器提供選擇已經就緒的任務的能力。簡單來講,Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連接接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作。

  一個多路復用器Selector可以同時輪詢多個Channel,由於JDK使用了epoll()代替傳統的select實現,所以它並沒有最大連接句柄1024/2048的限制。這也就意味著只需要一個線程負責Selector的輪詢,就可以接入成千上萬的客戶端,這確實是個非常巨大的進步。

2.NIO服務端序列圖

  NIO服務端通信序列圖如下圖所示:

  下麵,我們對NIO服務端的主要創建過程進行講解和說明,作為NIO的基礎入門,我們將忽略掉一些在生產環境中部署所需要的一些特性和功能。

  步驟一:打開ServerSocketChannel,用於監聽客戶端的連接,它是所有客戶端連接的父管道。

ServerSocketChannel acceptorSvr=ServerSocketChannel.open();

  步驟二:綁定監聽埠,設置連接為非阻塞模式。

acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"),port));
acceptorSvr.configureBlocking(false);

  步驟三:創建Reactor線程,創建多路復用器並啟動線程。

Selector selector=Selector.open();
New Thread(new ReactorTask()).start();

  步驟四:將ServerSocketChannel註冊到Reactor線程的多路復用器Selector上,監聽ACCEPT事件。

SelectionKey key=acceptorSvr.register(selector,SelectionKey.OP_ACCEPT,ioHandler);

  步驟五:多路復用器線上程run方法的無線迴圈體內輪詢準備就緒的Key。

int num=selector.select();
Set selectedKeys=selector.selectedKeys();
Iterator it=selectedKeys.iterator();
while(it.hasNext()){
  SelectionKey key=(SelectionKey )it.next();
  //...deal with I/O event...
}

  步驟六:多路復用器監聽到有新的客戶端接入,處理新的接入請求,完成TCP三次握手,建立物理鏈路。

SocketChannel channel=svrChannel.accpet();

  步驟七:設置客戶端鏈路為非阻塞模式。

channel.configureBlocking(false);
channel.socket().setReuseAddress(true);
......

  步驟八:將新接入的客戶端連接註冊到Reactor線程的多路復用器,監聽讀操作,用來讀取客戶端發送的網路消息。

SelectionKey key=socketChannel.register(selector,SelectionKey.OP_READ,ioHandler);

  步驟九:非同步讀取客戶端請求消息到緩衝區。

int readNumber=channel.read(receivedBuffer);

  步驟十:對ByteBuffer進行編解碼,如果有半包消息指針reset,繼續讀取後續的報文,將解碼成功的消息封裝成Task,投遞到業務線程池中,進行業務邏輯編排。

Object message=null;
while(buffer.hasRemain()){
  byteBuffer.mark();
  Object message=decode(byteBuffer);
  if(message==null){
    byteBuffer.reset();
    break;
  }
  messageList.add(message);
}
if(!byteBuffer.hasRemain()){
  byteBuffer.clear();
}else{
  byteBuffer.compact();
}
if(messageList!=null& !messageList.isEmpty()){
  for(Object messageE: messageList){
    handlerTask(messageE);
  }
}

  步驟十一:將POJO對象encode成ByteBuffer,調用SocketChannel的非同步write介面,將消息非同步發送給客戶端。

socketChannel.write(buffer);

  註意:如果發送區TCP緩衝區滿,會導致寫半包,此時,需要註冊監聽寫操作位,迴圈寫,直到整包消息寫入TCP緩衝區。

  當我們瞭解創建NIO服務端的基本步驟之後,下麵我們將前面的時間伺服器程式通過NIO重寫一遍,讓大家能夠學習到完整版的NIO服務端創建。

3.NIO創建的TimeServer源碼分析

package joanna.yan.nio;

public class TimeServer {

    public static void main(String[] args) {
        int port=9090;
        if(args!=null&&args.length>0){
            try {
                port=Integer.valueOf(args[0]);
            } catch (Exception e) {
                // 採用預設值
            }
        }
        
        MultiplexerTimeServer timeServer=new MultiplexerTimeServer(port);
        new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
    }
}
package joanna.yan.nio;

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.Date;
import java.util.Iterator;
import java.util.Set;
/**
 * 多路復用類
 * 它是一個獨立的線程,負責輪詢多路復器Selector,可以處理多個客戶端的併發接入。
 * @author Joanna.Yan
 * @date 2017年11月6日下午3:51:41
 */
public class MultiplexerTimeServer implements Runnable{

        private Selector selector;//多路復用器
        private ServerSocketChannel servChannel;
        private volatile boolean stop;
        
        /**
         * 初始化多路復用器、綁定監聽埠
         * @param port
         */
        public MultiplexerTimeServer(int port){
            try {
                selector=Selector.open();
                servChannel=ServerSocketChannel.open();
                servChannel.configureBlocking(false);
                servChannel.socket().bind(new InetSocketAddress(port), 1024);
                servChannel.register(selector, SelectionKey.OP_ACCEPT);
                System.out.println("The time server is start in port: "+port);
                
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    public void stop(){
            this.stop=true;
      }
    @Override
public void run() { while(!stop){ try { //設置selector的休眠時間為1s,無論是否有讀寫等事件發生,selector每隔1s都被喚醒一次。 selector.select(1000); //當有處於就緒狀態的Channel時,selector就返回就緒狀態的Channel的SelectionKey集合。 Set<SelectionKey> selectedKeys=selector.selectedKeys(); Iterator<SelectionKey> it=selectedKeys.iterator(); SelectionKey key=null; //通過對就緒狀態的Channel集合進行迭代,可以進行網路的非同步讀寫操作。 while(it.hasNext()){ key=it.next(); it.remove(); try { handleInput(key); } catch (Exception e) { if(key!=null){ key.cancel(); if(key.channel()!=null){ key.channel().close(); } } } } } catch (IOException e) { e.printStackTrace(); } }        /* * 多路復用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動去註冊並關閉,所以不需要重覆釋放資源。 */ if(selector!=null){ try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } }     private void handleInput(SelectionKey key) throws IOException{ if(key.isValid()){ //處理新接入的請求消息 //通過SelectionKey的操作位進行判斷即可獲知網路事件類型 if(key.isAcceptable()){ //Accept the new connection ServerSocketChannel ssc=(ServerSocketChannel) key.channel(); SocketChannel sc=ssc.accept(); //-----以上操作相當於完成了TCP的三次握手,TCP物理鏈路正式建立------ //將新創建的SocketChannel設置為非同步非阻塞,同時也可以對其TCP參數進行設置,例如TCP接收和發送緩衝區的大小等。 sc.configureBlocking(false); //Add the new connection to the selector sc.register(selector, SelectionKey.OP_READ); }          if(key.isReadable()){ //Read the data SocketChannel sc=(SocketChannel) key.channel(); //由於實現我們得知客戶端發送的碼流大小,作為常式,我們開闢一個1K的緩衝區 ByteBuffer readBuffer=ByteBuffer.allocate(1024); //由於已經設置SocketChannel為非同步非阻塞模式,因此它的read是非阻塞的。 int readBytes=sc.read(readBuffer); /* * readBytes>0 讀到了位元組,對位元組進行編解碼; * readBytes=0 沒有讀取到位元組,屬於正常場景,忽略; * readByte=-1 鏈路已經關閉,需要關閉SocketChannel,釋放資源 */             if(readBytes>0){ //將緩衝區當前的limit設置為position,position設置為0,用於後續對緩衝區的讀取操作。 readBuffer.flip(); //根據緩衝區可讀的位元組個數創建位元組數組 byte[] bytes=new byte[readBuffer.remaining()]; //調用ByteBuffer的get操作將緩衝區可讀的位元組數組複製到新創建愛你的位元組數組中 readBuffer.get(bytes); String body=new String(bytes, "UTF-8"); System.out.println("The time server receive order: "+body); //如果請求指令是"QUERY TIME ORDER"則把伺服器的當前時間編碼後返回給客戶端 String currentTime="QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date( System.currentTimeMillis()).toString() : "BAD ORDER"; doWrite(sc,currentTime); }else if(readBytes<0){              //對端鏈路關閉 key.cancel(); sc.close(); }else{ //讀到0位元組,忽略 } } } }     private void doWrite(SocketChannel channel,String response) throws IOException{ if(response!=null&& response.trim().length()>0){ byte[] bytes=response.getBytes(); ByteBuffer writeBuffer=ByteBuffer.allocate(bytes.length); //調用ByteBuffer的put操作將位元組數組複製到緩衝區 writeBuffer.put(bytes); writeBuffer.flip(); channel.write(writeBuffer); /* * 需要指出的是,由於SocketChannel是非同步非阻塞的,它並不保證一次性能夠把需要發送的位元組數組發送完, * 此時會出現“寫半包”問題,我們需要註冊寫操作,不斷輪詢Selector,將沒有發送完畢的ByteBuffer發送完畢, * 可以通過ByteBuffer的hasRemaining()方法判斷消息是否發送完成。 * 此處僅僅是各簡單的入門級常式,沒有演示如何處理“寫半包”場景,後面會說到。 */ } } }

4.NIO客戶端序列圖

  NIO客戶端創建序列圖如圖所示。

  步驟一:打開SocketChannel,綁定客戶端本地地址(可選,預設系統會隨機分配一個可用的本地地址)

SocketChannel clientChannel=SocketChannel.open();

  步驟二:設置SocketChannel為非阻塞模式,同時設置客戶端連接的TCP參數。

clientChannel.configureBlocking(false);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE);

  步驟三:非同步連接服務端。

boolean connected=clientChannel.connect(new InetSocketAddress("ip",port));

  步驟四:判斷是否連接成功,如果連接成功,則直接註冊讀狀態位到多路復用器中,如果當前沒有連接成功(非同步連接,返回false,說明客戶端已經發送sync包,服務端沒有返回ack包,物理鏈路還沒有建立)。

if(connected){
  clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);
}else{
  clientChannel.register(selector,SelectionKey.OP_CONNECT,ioHandler);
}

  步驟五:向Reactor線程的多路復用器註冊OP_CONNECT狀態位,監聽服務端的TCP ACK應答。

clientChannel.register(selector,SelectionKay.OP_CONNECT,ioHandler);

  步驟六:創建Reactor線程,創建多路復用器並啟動線程。

Selector selector=Selector.open();
new Thread(new ReactorTask()).start();

  步驟七:多路復用器線上程run方法的無限迴圈體內輪詢準備就緒的key。

int num=selector.select();
Set selectedKeys=selector.selectedKeys();
Iterator it=selectedKeys.iterator();
while(it.hasNext()){
  SelectionKey key=(SelectionKey)it.next();
  //...deal with I/O event...
}

  步驟八:接收connect事件進行處理。

if(key.isConnectable()){
  //handlerConnect();
}

  步驟九:判斷連接結果,如果連接成功,註冊讀事件到多路復用器。

if(channel.finishConnect()){
  registerRead();
}

  步驟十:註冊讀事件到多路復用器。

clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);

  步驟十一:非同步讀客戶端請求消息到緩衝區。

int readNumber=channel.read(receivedBuffer);

  步驟十二:對ByteBuffer進行編解碼,如果有半包消息接收緩衝區Reset,繼續讀取後續的報文,將解碼成功的消息封裝成Task,投遞到業務線程池中,進行業務邏輯編排。

Object message=null;

while(buffer.hasRemain()){
  byteBuffer.mark();
  Object message=decode(byteBuffer);
  if(message==null){
    byteBuffer.reset();
    break;
  }
  messageList.add(message);
}

if(!byteBuffer.hasRemain()){
  byteBuffer.clear();
}else{
  byteBuffer.compact();
}

if(messageList!=null & !messageList.isEmpty()){
  for(Object messageE:messageList){
    handlerTask(messageE);
  }
}

  步驟十三:將POJO對象encode成ByteBuffer,調用SocketChannel的非同步write介面,將消息非同步發送給客戶端。

socketChannel.wirte(buffer);

5.NIO創建的TimeClient源碼分析

package joanna.yan.nio;

public class TimeClient {
    public static void main(String[] args) {
        int port=9090;
        if(args!=null&&args.length>0){
            try {
                port=Integer.valueOf(args[0]);
            } catch (Exception e) {
                // 採用預設值
            }
        }
        
        new Thread(new TimeClientHandle("127.0.0.1", port),"TimClient-001").start();
    }
}
package joanna.yan.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
 * 處理非同步連接和讀寫操作
 * @author Joanna.Yan
 * @date 2017年11月6日下午4:33:14
 */
public class TimeClientHandle implements Runnable{
    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean stop;
    
    /**
     * 初始化NIO的多路復用器和SocketChannel對象
     * @param host
     * @param port
     */
        public TimeClientHandle(String host,int port){
        this.host=host==null ? "127.0.0.1" : host;
        this.port=port;
        try {
            selector=Selector.open();
            socketChannel=SocketChannel.open();
            //設置為非同步非阻塞模式,同時還可以設置SocketChannel的TCP參數。例如接收和發送的TCP緩衝區大小
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
@Override
public void run() { try { doConnect(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } while(!stop){ try { selector.select(1000); Set<SelectionKey> selectedKeys=selector.selectedKeys(); Iterator<SelectionKey> it=selectedKeys.iterator(); SelectionKey key=null; while(it.hasNext()){//輪詢多路復用器Selector,當有就緒的Channel時 key=it.next(); it.remove(); try { handleInput(key); } catch (Exception e) { if(key!=null){ key.cancel(); if(key.channel()!=null){ key.channel().close(); } } } } } catch (IOException e) { e.printStackTrace(); System.exit(1); } } //多路復用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動註冊並關閉,所以不需要重覆釋放資源。 /* * 由於多路復用器上可能註冊成千上萬的Channel或者pipe,如果一一對這些資源進行釋放顯然不合適。 * 因此,JDK底層會自動釋放所有跟此多路復用器關聯的資源。 */ if(selector!=null){ try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } //多路復用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動註冊並關閉,所以不需要重覆釋放資源。 /* * 由於多路復用器上可能註冊成千上萬的Channel或者pipe,如果一一對這些資源進行釋放顯然不合適。 * 因此,JDK底層會自動釋放所有跟此多路復用器關聯的資源。 */ if(selector!=null){ try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } private void handleInput(SelectionKey key) throws ClosedChannelException, IOException { if(key.isValid()){ //判斷是否連接成功 SocketChannel sc=(SocketChannel) key.channel(); if(key.isConnectable()){//處於連接狀態,說明伺服器已經返回ACK應答消息 if(sc.finishConnect()){//對連接結果進行判斷 /* * 將SocketChannel註冊到多路復用器上,註冊SelectionKey.OP_READ操作位, * 監聽網路讀操作,然後發送請求消息給服務端。 */ sc.register(selector, SelectionKey.OP_READ); doWrite(sc); }else{ System.exit(1);//連接失敗,進程退出 } } if(key.isReadable()){ //開闢緩衝區 ByteBuffer readBuffer=ByteBuffer.allocate(1024); //非同步讀取 int readBytes=sc.read(readBuffer); if(readBytes>0){ readBuffer.flip(); byte[] bytes=new byte[readBuffer.remaining()]; readBuffer.get(bytes); String body=new String(bytes, "UTF-8"); System.out.println("Now is: "+body); this.stop=true; }else if(readBytes<0){ //對端鏈路關閉 key.cancel(); sc.close(); }else{ //讀到0位元組,忽略 } } } } private void doConnect() throws IOException { //如果直接連接成功,則將SocketChannel註冊到多路復用器Selector上,發送請求消息,讀應答 if(socketChannel.connect(new InetSocketAddress(host, port))){ socketChannel.register(selector, SelectionKey.OP_READ); doWrite(socketChannel); }else{ /* * 如果沒有直接連接成功,則說明服務端沒有返回TCP握手應答信息,但這並不代表連接失敗, * 我們需要將SocketChannel註冊到多路復用器Selector上,註冊SelectionKey.OP_CONNECT, * 當服務端返回TCP syn-ack消息後,Selector就能輪詢到整個SocketChannel處於連接就緒狀態。 */ socketChannel.register(selector, SelectionKey.OP_CONNECT); } } private void doWrite(SocketChannel sc) throws IOException { byte[] req="QUERY TIME ORDER".getBytes(); ByteBuffer writeBuffer=ByteBuffer.allocate(req.length); //寫入到發送緩衝區中 writeBuffer.put(req); writeBuffer.flip(); //由於發送是非同步的,所以會存在"半包寫"問題,此處不贅述 sc.write(writeBuffer); if(!writeBuffer.hasRemaining()){//如果緩衝區中的消息全部發送完成 System.out.println("Send order 2 server succeed."); } } }

  通過源碼對比分析發現,NIO編程難度確實比同步阻塞BIO大很多,此處我們的NIO常式並沒有考慮“半包讀”和“半包寫”,如果加上這些,代碼會更加複雜。NIO代碼既然這麼複雜,為什麼它的應用卻越來越廣泛呢,使用NIO編程的優點總結如下:

  1. 客戶端發起的連接操作是非同步的,可以通過多路復用器註冊OP_CONNECT等待後續結果,不需要像之前的客戶端那樣被同步阻塞。
  2. SocketChannel的讀寫操作都是非同步的,如果沒有可讀寫的數據它不會同步等待,直接返回,這樣I/O通信線程就可以處理其他的鏈路,不需要同步等待這個鏈路可用。
  3. 線程模型的優化:由於JDK的Selector在Linux等主流操作系統上通過epoll實現,它沒有連接句柄數的限制(只受限於操作系統的最大句柄數或者對單個進程的句柄限制),這意味著一個Selector線程可以同時處理成千上萬個客戶端連接,而且性能不會隨著客戶端的增加而線性下降,因此,它非常適合做高性能、高負載的網路伺服器。

  JDK1.7升級了NIO類庫,升級後的NIO類庫被稱為NIO 2.0。引入註目的是,Java正式提供了非同步文件I/O操作,同時提供了與UNIX網路編程事件驅動I/O對應的AIO。

Java IO編程全解(五)——AIO編程

如果此文對您有幫助,微信打賞我一下吧~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 簡介 Quarzt是一個項目中定時執行任務的開源項目,Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,它可以與J2EE與J2SE應用程式相結合也可以單獨使用,這裡我們介紹和 整合的例子 因為Spring已經整合Quarzt,所以我們只需要配置一下即可。 ...
  • std::ios::sync_with_stdio(false); std::cin.tie(nullptr); 第一句話是解除ios與stdio之間的同步關係。第二句話是解除cin與cout之間的綁定。 在開始讀入數據前,插入這兩句話就可以加快cin、cout的輸入輸出速度。cin、cout運作速 ...
  • 一個方法可以執行不同個數參數,前提是聲明時賦值 ...
  • 什麼是函數模板? 就是不寫具體的數據類型,而用一個虛擬類型來代表,這樣可以提高效率。 ...
  • 什麼是圖|ω・`) 圖G是一個有序二元組(V,E),其中V稱為頂集(Vertices Set),E稱為邊集(Edges set),E與V不相交。它們亦可寫成V(G)和E(G)。 E的元素都是二元組,用(x,y)表示,其中x,y∈V。 (摘自百度百科) 簡單來說,圖就是由點和邊組成的東西。也可以理解為 ...
  • 什麼是內置函數?也成內聯函數 嵌入到主函數中的函數稱為內置函數,也就是雖然函數寫在main()的外邊,但是我們通過一個關鍵字inline進行標識,這樣就可以把寫在外邊的函數當成寫在了主函數main()的裡邊。 一個函數寫在主函數外邊與寫在主函數裡邊有什麼區別?都可以正常運行,但程式效率不同。寫在主函 ...
  • 以上代碼是獲取博客文章的列表 ...
  • import urllib import time ##讀取指定的網址 url = [] page = 1 while page <= 11: url_con = urllib.urlopen('http://blog.sina.com.cn/s/articlelist_1193111400_0_' ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...