主頁 > 後端開發 > 整理了將近三萬字,超硬核BIO,NIO,AIO,Netty 超越 99% IO文章(面試題+綜合案例+練習題)

整理了將近三萬字,超硬核BIO,NIO,AIO,Netty 超越 99% IO文章(面試題+綜合案例+練習題)

2021-07-23 07:47:04 後端開發

Java面試題IO篇

  • 所有題目均來自互聯網,整合不易希望大家沉下心來,認真學習,一鍵三連加關注就是對博主最大的支持!
  • 菜鳥教程地址:https://www.runoob.com/java/java-files-io.html

在這里插入圖片描述

文章目錄

  • Java面試題IO篇
  • 前言
  • BIO、NIO、AIO、Netty
    • 一. 什么是IO
    • 二. 在了解不同的IO之前先了解:同步與異步,阻塞與非阻塞的區別
    • 三. 什么是BIO
    • 四. 什么是NIO
    • 五. 什么是AIO
    • 六. 什么是Netty
    • 七. BIO和NIO、AIO的區別
    • 八. IO流的分類
    • 九. 什么是內核空間
    • 十. 五種IO模型
      • 1. 阻塞BIO(blocking I/O)
      • 2. 非阻塞NIO(noblocking I/O)
      • 3. 異步AIO(asynchronous I/O)
      • 4. 信號驅動IO(signal blocking I/O)
      • 5. IO多路轉接(I/O multiplexing)
    • 十一. 什么是位元(Bit),什么是位元組(Byte),什么是字符(Char),它們長度是多少,各有什么區別?
    • 十二. 什么叫物件序列化,什么是反序列化,實作物件序列化需要做哪些作業
    • 十三. 在實作序列化介面是時候一般要生成一個serialVersionUID欄位,它叫做什么,一般有什么用 ?
    • 十四. 怎么生成SerialversionUID
    • 十五. BufferedReader屬于哪種流,它主要是用來做什么的,它里面有那些經典的方法 ?
    • 十六. Java中流類的超類主要有那些?
    • 十七. 為什么圖片、視頻、音樂、檔案等 都是要位元組流來讀取
  • IO的常用類和方法以,及如何使用
    • 一. IO基本操作講解
      • 按字符流讀取檔案
        • 1. 按字符流的節點流方式讀取
        • 2. 按字符流的處理流方式讀取
      • 按字符流寫出檔案
        • 1. 按字符流的節點流方式寫出
        • 2. 按字符流的處理流方式寫出
      • 按位元組流寫入寫出檔案
        • 1. 按位元組流的節點流寫入寫出檔案
        • 2. 按位元組流的處理流寫入寫出檔案
    • 二. 網路操作IO講解
      • 網路操作IO編程演變歷史
        • 1. BIO編程會出現什么問題?
        • 2. 多執行緒解決BIO編程會出現的問題
        • 3. 執行緒池解決多執行緒BIO編程會出現的問題
        • 4 使用NIO實作網路通信
        • 5.使用Netty實作網路通信
  • IO練習題

前言

在這里插入圖片描述

送給大家一句話平凡的腳步也可以走完偉大的行程 !


BIO、NIO、AIO、Netty

在這里插入圖片描述

一. 什么是IO

  • Java中I/O是以流為基礎進行資料的輸入輸出的,所有資料被串行化(所謂串行化就是資料要按順序進行輸入輸出)寫入輸出流,簡單來說就是java通過io流方式和外部設備進行互動,

  • 在Java類別庫中,IO部分的內容是很龐大的,因為它涉及的領域很廣泛:標準輸入輸出,檔案的操作,網路上的資料傳輸流,字串流,物件流等等等,
    在這里插入圖片描述

  • 比如程式從服務器上下載圖片,就是通過流的方式從網路上以流的方式到程式中,在到硬碟中,

二. 在了解不同的IO之前先了解:同步與異步,阻塞與非阻塞的區別

  1. 同步:一個任務的完成之前不能做其他操作,必須等待(等于在打電話)
  2. 異步:一個任務的完成之前,可以進行其他操作(等于在聊QQ)
  3. 阻塞:是相對于CPU來說的, 掛起當前執行緒,不能做其他操作只能等待
  4. 非阻塞:無須掛起當前執行緒,可以去執行其他操作

三. 什么是BIO

  • BIO:同步并阻塞,服務器實作一個連接一個執行緒,即客戶端有連接請求時服務器端就需要啟動一個執行緒進行處理,沒處理完之前此執行緒不能做其他操作(如果是單執行緒的情況下,我傳輸的檔案很大呢?),當然可以通過執行緒池機制改善,BIO方式適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,JDK1.4以前的唯一選擇,但程式直觀簡單易理解,

四. 什么是NIO

  • NIO: 同步非阻塞,服務器實作一個連接一個執行緒,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個執行緒進行處理,NIO方式適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,并發局限于應用中,編程比較復雜,JDK1.4之后開始支持,

五. 什么是AIO

  • AIO: 異步非阻塞,服務器實作模式為一個有效請求一個執行緒,客戶端的I/O請求都是由作業系統先完成了再通知服務器應用去啟動執行緒進行處理,AIO方式使用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分呼叫作業系統參與并發操作,編程比較復雜,JDK1.7之后開始支持,

AIO屬于NIO包中的類實作,其實IO主要分為BIO和NIO,AIO只是附加品,解決IO不能異步的實作在以前很少有Linux系統支持AIO,Windows的IOCP就是該AIO模型,但是現在的服務器一般都是支持AIO操作,

六. 什么是Netty

  • Netty:Netty是由JBOSS提供的一個Java開源框架,Netty提供異步的、事件驅動的網路應用程式框架和工具,用以快速開發高性能、高可靠性的網路服務器和客戶端程式,
  • Netty 是一個基于NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網路應用,例如實作了某種協議的客戶,服務端應用,Netty相當簡化和流線化了網路應用的編程開發程序,例如,TCP和UDP的socket服務開發,
    在這里插入圖片描述

Netty是由NIO演進而來,使用過NIO編程的用戶就知道NIO編程非常繁重,Netty是能夠能跟好的使用NIO


七. BIO和NIO、AIO的區別

  1. BIO是阻塞的,NIO是非阻塞的.
  2. BIO是面向流的,只能單向讀寫,NIO是面向緩沖的, 可以雙向讀寫
  3. 使用BIO做Socket連接時,由于單向讀寫,當沒有資料時,會掛起當前執行緒,阻塞等待,為防止影響其它連接,,需要為每個連接新建執行緒處理.,然而系統資源是有限的,,不能過多的新建執行緒,執行緒過多帶來執行緒背景關系的切換,從來帶來更大的性能損耗,因此需要使用NIO進行BIO多路復用,使用一個執行緒來監聽所有Socket連接,使用本執行緒或者其他執行緒處理連接,
  4. AIO是非阻塞 以異步方式發起 I/O 操作,當 I/O 操作進行時可以去做其他操作,由作業系統內核空
    間提醒IO操作已完成(不懂的可以往下看)

八. IO流的分類

在這里插入圖片描述
按照讀寫的單位大小來分:

  • 字符流 :以字符為單位,每次次讀入或讀出是16位資料,其只能讀取字符型別資料, (Java代碼接收資料為一般為 char陣列,也可以是別的 )
  • 位元組流:以位元組為單位,每次次讀入或讀出是8位資料,可以讀任何型別資料,圖片、檔案、音樂 視頻等, (Java代碼接收資料只能為 byte陣列 )

按照實際IO操作來分:

  • 輸出流:從記憶體讀出到檔案,只能進行寫操作,
  • 輸入流:從檔案讀入到記憶體,只能進行讀操作,

注意:輸出流可以幫助我們創建檔案,而輸入流不會,

按照讀寫時是否直接與硬碟,記憶體等節點連接分:

  • 節點流:直接與資料源相連,讀入或讀出,
  • 處理流:也叫包裝流,是對一個對于已存在的流的連接進行封裝,通過所封裝的流的功能呼叫實作資料讀寫,如添加個Buffering緩沖區,(意思就是有個快取區,等于軟體和mysql中的redis)

注意:為什么要有處理流?主要作用是在讀入或寫出時,對資料進行快取,以減少I/O的次數,以便下次更好更快的讀寫檔案,才有了處理流,

九. 什么是內核空間

  • 我們的應用程式是不能直接訪問硬碟的,我們程式沒有權限直接訪問,但是作業系統(Windows、Linux…)會給我們一部分權限較高的記憶體空間,他叫內核空間,和我們的實際硬碟空間是有區別的,

在這里插入圖片描述

十. 五種IO模型

  • 注意:我這里的用戶空間就是應用程式空間

1. 阻塞BIO(blocking I/O)

  • A拿著一支魚竿在河邊釣魚,并且一直在魚竿前等,在等的時候不做其他的事情,十分專心,只有魚上鉤的時,才結束掉等的動作,把魚釣上來,
  • 在內核將資料準備好之前,系統呼叫會一直等待所有的套接字,默認的是阻塞方式,

在這里插入圖片描述

2. 非阻塞NIO(noblocking I/O)

  • B也在河邊釣魚,但是B不想將自己的所有時間都花費在釣魚上,在等魚上鉤這個時間段中,B也在做其他的事情(一會看看書,一會讀讀報紙,一會又去看其他人的釣魚等),但B在做這些事情的時候,每隔一個固定的時間檢查魚是否上鉤,一旦檢查到有魚上鉤,就停下手中的事情,把魚釣上來, B在檢查魚竿是否有魚,是一個輪詢的程序

在這里插入圖片描述

3. 異步AIO(asynchronous I/O)

  • C也想釣魚,但C有事情,于是他雇來了D、E、F,讓他們幫他等待魚上鉤,一旦有魚上鉤,就打電話給C,C就會將魚釣上去,

在這里插入圖片描述
當應用程式請求資料時,內核一方面去取資料報內容回傳,另一方面將程式控制權還給應用行程,應用行程繼續處理其他事情,是一種非阻塞的狀態,

4. 信號驅動IO(signal blocking I/O)

  • G也在河邊釣魚,但與A、B、C不同的是,G比較聰明,他給魚竿上掛一個鈴鐺,當有魚上鉤的時候,這個鈴鐺就會被碰響,G就會將魚釣上來,

在這里插入圖片描述
信號驅動IO模型,應用行程告訴內核:當資料報準備好的時候,給我發送一個信號,對SIGIO信號進行捕捉,并且呼叫我的信號處理函式來獲取資料報,

5. IO多路轉接(I/O multiplexing)

  • H同樣也在河邊釣魚,但是H生活水平比較好,H拿了很多的魚竿,一次性有很多魚竿在等,H不斷的查看每個魚竿是否有魚上鉤,增加了效率,減少了等待的時間,

在這里插入圖片描述
IO多路轉接是多了一個select函式,select函式有一個引數是檔案描述符集合,對這些檔案描述符進行回圈 監聽,當某個檔案描述符就緒時,就對這個檔案描述符進行處理

  • IO多路轉接是屬于阻塞IO,但可以對多個檔案描述符進行阻塞監聽,所以效率較阻塞IO的高,

十一. 什么是位元(Bit),什么是位元組(Byte),什么是字符(Char),它們長度是多少,各有什么區別?

  • Bit最小的二進制單位 ,是計算機的操作部分取值0或者1
  • Byte是計算機中存盤資料的單元,是一個8位的二進制數,(計算機內部,一個位元組可表示一個英文字母,兩個位元組可表示一個漢字,) 取值(-128-127)
  • Char是用戶的可讀寫的最小單位,他只是抽象意義上的一個符號,如‘5’,‘中’,‘¥’ 等等等等,在java里面由16位bit組成Char 取值 (0-65535)
  • Bit 是最小單位 計算機他只能認識0或者1
  • Byte是8個位元組 是給計算機看的
  • 字符 是看到的東西 一個字符 = 二個位元組

十二. 什么叫物件序列化,什么是反序列化,實作物件序列化需要做哪些作業

  • 物件序列化,將物件以二進制的形式保存在硬碟上
  • 反序列化;將二進制的檔案轉化為物件讀取
  • 實作serializable介面,不想讓欄位放在硬碟上就加transient

十三. 在實作序列化介面是時候一般要生成一個serialVersionUID欄位,它叫做什么,一般有什么用 ?

  • 如果用戶沒有自己宣告一個serialVersionUID,介面會默認生成一個serialVersionUID
  • 但是強烈建議用戶自定義一個serialVersionUID,因為默認的serialVersinUID對于class的細節非常敏感,反序列化時可能會導致InvalidClassException這個例外,
  • (比如說先進行序列化,然后在反序列化之前修改了類,那么就會報錯,因為修改了類,對應的SerialversionUID也變化了,而序列化和反序列化就是通過對比其SerialversionUID來進行的,一旦SerialversionUID不匹配,反序列化就無法成功,

十四. 怎么生成SerialversionUID

  • 可序列化類可以通過宣告名為 “serialVersionUID” 的欄位(該欄位必須是靜態 (static)、最終(final) 的 long 型欄位)顯式宣告其自己的 serialVersionUID
  • 兩種顯示的生成方式(當你一個類實作了Serializable介面,如果沒有顯示的定義serialVersionUID,Eclipse會提供這個提示功能告訴你去定義 ,在Eclipse中點擊類中warning的圖示一下,Eclipse就會自動給定兩種生成的方式,

十五. BufferedReader屬于哪種流,它主要是用來做什么的,它里面有那些經典的方法 ?

  • 屬于處理流中的緩沖流,可以將讀取的內容存在記憶體里面,有readLine()方法

十六. Java中流類的超類主要有那些?

超類代表頂端的父類(都是抽象類)

  • java.io.InputStream
  • java.io.OutputStream
  • java.io.Reader
  • java.io.Writer

十七. 為什么圖片、視頻、音樂、檔案等 都是要位元組流來讀取

  • 這個很基礎,你看看你電腦檔案的屬性就好了,CPU規定了計算機存盤檔案都是按位元組算的

在這里插入圖片描述


IO的常用類和方法以,及如何使用

  • 前面講了那么多廢話,現在我們開始進入主題,后面很長,從開始的檔案操作到后面的網路IO操作都會有例子

在這里插入圖片描述

一. IO基本操作講解

  • 這里的基本操作就是普通的讀取操作,如果想要跟深入的了解不同的IO開發場景必須先了解IO的基本操作

按字符流讀取檔案

在這里插入圖片描述

1. 按字符流的節點流方式讀取

  • 如果我們要取的資料基本單位是字符,那么用(字符流)這種方法讀取檔案就比較適合,比如:讀取test.txt檔案

注釋:

  • 字符流 :以字符為單位,每次次讀入或讀出是16位資料,其只能讀取字符型別資料, (Java代碼接收資料為一般為 char陣列,也可以是別的 )
  • 位元組流:以位元組為單位,每次次讀入或讀出是8位資料,可以讀任何型別資料,圖片、檔案、音樂視頻等, (Java代碼接收資料只能為 byte陣列 )
  • FileReader 類:(字符輸入流) 注意:new FileReader(“D:\test.txt”);//檔案必須存在,
import java.io.FileReader;
import java.io.IOException;

public class TestFileReader {

    public static void main(String[] args) throws IOException {
        int num = 0;
        char[] buf = new char[1024];
        //字符流、節點流打開檔案類
        FileReader fr = new FileReader("C:\\Users\\mzc\\Desktop\\審管聯動\\test.txt");//檔案必須存在
        //FileReader.read():取出字符存到buf陣列中,如果讀取為-1代表為空即結束讀取,
        //FileReader.read():讀取的是一個字符,但是java虛擬機會自動將char型別資料轉換為int資料,
        //如果你讀取的是字符A,java虛擬機會自動將其轉換成97,如果你想看到字符可以在回傳的字符數前加(char)強制轉換如
        while ((num = fr.read(buf)) != -1) {
        }
        //檢測一下是否取到相應的資料
        for (int i = 0; i < buf.length; i++) {
            System.out.print(buf[i]);
        }
    }
}

在這里插入圖片描述在這里插入圖片描述

2. 按字符流的處理流方式讀取

  • 效果是一樣,但是給了我們有不同的選擇操作,進行了一個小封裝,加緩沖功能,避免頻繁讀寫硬碟,我這只是簡單演示,處理流其實還有很多操作
  • BufferedReader 類: 字符輸入流使用的類,加緩沖功能,避免頻繁讀寫硬碟
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * @author mengzhichao
 * @create 2021-07-20-9:12
 */
public class Main {
    public static void main(String[] args) throws IOException {
        int num = 0;
        //字符流接收使用的String陣列
        String[] bufstring = new String[1024];
        //字符流、節點流打開檔案類
        FileReader fr = new FileReader("C:\\Users\\mzc\\Desktop\\審管聯動\\test.txt");//檔案必須存在
        //字符流、處理流讀取檔案類
        BufferedReader br = new BufferedReader(fr);
        //臨時接收資料使用的變數
        String line = null;
        //BufferedReader.readLine():單行讀取,讀取為慷訓傳null
        while ((line = br.readLine()) != null) {
            bufstring[num] = line;
            num++;
        }
        br.close();//關閉檔案
        for (int i = 0; i < num; i++) {
            System.out.println(bufstring[i]);
        }
    }
}

在這里插入圖片描述
在這里插入圖片描述

按字符流寫出檔案

在這里插入圖片描述

1. 按字符流的節點流方式寫出

  • 寫出字符,使用(字符流)這種方法寫出檔案比較適合,比如:輸出內容添加到test.txt檔案
  • FileWriter類:(字符輸出流),如果寫出檔案不存在會自動創建一個相對應的檔案,使用FileWriter寫出檔案默認是覆寫原檔案,如果要想在源檔案添加內容不覆寫的話,需要構造引數添加true引數:看示例了解
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;


/**
 * @author mengzhichao
 * @create 2021-07-20-9:12
 */
public class Main {
    public static void main(String[] args) throws IOException {
        //File是操作檔案類
        File file = new File("C:\\Users\\mzc\\Desktop\\審管聯動\\test.txt");//檔案必須存在
        //字符流、節點流寫出檔案類
        //new FileWriter(file,true),這個true代表追加,不寫就代表覆寫檔案
        FileWriter out = new FileWriter(file, true);
        //寫入的位元組,\n代表換行
        String str = "\n哈嘍";
        //寫入
        out.write(str);
        out.close();
    }
}

在這里插入圖片描述

在這里插入圖片描述

2. 按字符流的處理流方式寫出

  • BufferedWriter : 增加緩沖功能,避免頻繁讀寫硬碟, 我這里: //new FileWriter(file),這里我只給了他檔案位置,我沒加true代表覆寫源檔案
import java.io.*;


/**
 * @author mengzhichao
 * @create 2021-07-20-9:12
 */
public class Main {
    public static void main(String[] args) throws IOException {
        //File是操作檔案類
        File file = new File("C:\\Users\\mzc\\Desktop\\審管聯動\\test.txt");//檔案必須存在
        //字符流、節點流寫出檔案類
        //new FileWriter(file),這個我沒加true代表覆寫檔案
        Writer writer = new FileWriter(file);
        字符流、處理流寫出檔案類
        BufferedWriter bw = new BufferedWriter(writer);
        bw.write("\n我直接覆寫");
        bw.close();
        writer.close();
    }
}

在這里插入圖片描述在這里插入圖片描述

按位元組流寫入寫出檔案

在這里插入圖片描述

1. 按位元組流的節點流寫入寫出檔案

  • 如果我們要取的資料 圖片、檔案、音樂視頻等型別,就必須使用位元組流進行讀取寫出
  • 字符流 :以字符為單位,每次次讀入或讀出是16位資料,其只能讀取字符型別資料, (Java代碼接收資料為一般為 char陣列,也可以是別的 )
  • 位元組流:以位元組為單位,每次次讀入或讀出是8位資料,可以讀任何型別資料,圖片、檔案、音樂視頻等, (Java代碼接收資料只能為 byte陣列 )
  • FileInputStream:(位元組輸入流)
  • FileOutputStream:(位元組輸出流)
import java.io.*;


/**
 * @author mengzhichao
 * @create 2021-07-20-9:12
 */
public class Main {
    public static void main(String[] args) throws IOException {
        //創建位元組輸入流、節點流方式讀取檔案
        FileInputStream fis = new FileInputStream("C:\\Users\\mzc\\Desktop\\審管聯動\\稻香.mp3");
        //創建位元組輸入流、節點流方式輸出檔案
        FileOutputStream fos = new FileOutputStream("C:\\Users\\mzc\\Desktop\\審管聯動\\copy.mp3");
        //根據檔案大小做一個位元組陣列
        byte[] arr = new byte[fis.available()];
        //將檔案上的所有位元組讀取到陣列中
        fis.read(arr);
        //將陣列中的所有位元組一次寫到了檔案上
        fos.write(arr);
        fis.close();
        fos.close();
    }
}

在這里插入圖片描述
在這里插入圖片描述

2. 按位元組流的處理流寫入寫出檔案

  • FileInputStream:(位元組輸入流)
  • FileOutputStream:(位元組輸出流)
  • BufferedInputStream:(帶緩沖區位元組輸入流)
  • BufferedOutputStream:(帶緩沖區位元組輸入流) 帶緩沖區的處理流,緩沖區的作用的主要目的是:避免每次和硬碟打交道,提高資料訪問的效率,
import java.io.*;


/**
 * @author mengzhichao
 * @create 2021-07-20-9:12
 */
public class Main {
    public static void main(String[] args) throws IOException {
        //創建檔案輸入流物件,關聯致青春.mp3
        FileInputStream fis = new FileInputStream("C:\\Users\\mzc\\Desktop\\審管聯動\\稻香.mp3");
        //創建緩沖區對fis裝飾
        BufferedInputStream bis = new BufferedInputStream(fis);
        //創建輸出流物件,關聯copy.mp3
        FileOutputStream fos = new FileOutputStream("C:\\Users\\mzc\\Desktop\\審管聯動\\copy2.mp3");
        //創建緩沖區對fos裝飾
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //回圈直接輸出
        int i;
        while ((i = bis.read()) != -1) {
            bos.write(i);
        }
        bis.close();
        bos.close();
    }
}

在這里插入圖片描述在這里插入圖片描述


二. 網路操作IO講解

  • 我這使用Socket簡單的來模擬網路編程IO會帶來的問題
  • Socket:就是基于TCP實作的網路通信,比http要快,很多實作網路通信的框架都是基于Socket來實作

網路操作IO編程演變歷史

在這里插入圖片描述

1. BIO編程會出現什么問題?

  • BIO是阻塞的
  • 例子: 阻塞IO(blocking I/O) A拿著一支魚竿在河邊釣魚,并且一直在魚竿前等,在等的時候不做其他的事情,十分專心,只有魚上鉤的時,才結束掉等的動作,把魚釣上來,

在這里插入圖片描述

  • 那不是要等待第一個人資源完成后后面的人才可以繼續?因為BIO是阻塞的所以讀取寫出操作都是非常浪費資源的

BIO代碼示例:( 后面有代碼,往后移動一點點,認真看,代碼學習量很足 )

  • 我這有三個類,我模擬啟動服務端,然后啟動客戶端,模擬客戶端操作未完成的時候啟動第二個客戶端

在這里插入圖片描述

  1. 啟動服務端(BIOServer)
    在這里插入圖片描述

  2. 啟動第一個客戶端(Client1),發現服務器顯示連接成功 先不要在控制臺輸入 ,模擬堵塞,(我的代碼輸入了就代表請求完成了)
    在這里插入圖片描述
    在這里插入圖片描述

  3. 啟動第二個客戶端(Client2), 發現服務端沒效果 ,而客戶端連接成功(在堵塞當中) 我這啟動了倆個Client,注意看,(這倆個代碼是一樣的)
    在這里插入圖片描述在這里插入圖片描述

  4. 第一個客戶控制臺輸入,輸入完后就會關閉第一個客戶端, 在看服務端發現第二個客戶端連接上來了
    在這里插入圖片描述在這里插入圖片描述

BIO通信代碼:

  • BIOServer(TCP協議Socket使用BIO進行通信:服務端)
package com.mzc.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author mengzhichao TCP協議Socket使用BIO進行通信:服務端
 * @create 2021-07-20-11:07
 */
public class BIOServer {
    // 在main執行緒中執行下面這些代碼
    public static void main(String[] args) {
        //使用Socket進行網路通信
        ServerSocket server = null;
        Socket socket = null;
        //基于位元組流
        InputStream in = null;
        OutputStream out = null;
        try {
            server = new ServerSocket(8000);
            System.out.println("服務端啟動成功,監聽埠為8000,等待客戶端連接...");
            while (true) {
                socket = server.accept(); //等待客戶端連接
                System.out.println("客戶連接成功,客戶資訊為:" + socket.getRemoteSocketAddress());
                in = socket.getInputStream();
                byte[] buffer = new byte[1024];
                int len = 0;
                //讀取客戶端的資料
                while ((len = in.read(buffer)) > 0) {
                    System.out.println(new String(buffer, 0, len));
                }
                //向客戶端寫資料
                out = socket.getOutputStream();
                out.write("hello!".getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • Client1(TCP協議Socket使用BIO進行通信:客戶端1)
package com.mzc.io;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author mengzhichao TCP協議Socket使用BIO進行通信:客戶端1
 * @create 2021-07-20-11:07
 */
public class Client1 {
    public static void main(String[] args) throws IOException {
        //創建套接字物件socket并封裝ip與port
        Socket socket = new Socket("127.0.0.1", 8000);
        //根據創建的socket物件獲得一個輸出流
        //基于位元組流
        OutputStream outputStream = socket.getOutputStream();
        //控制臺輸入以IO的形式發送到服務器
        System.out.println("TCP連接成功 \n請輸入:");
        String str = new Scanner(System.in).nextLine();
        byte[] car = str.getBytes();
        outputStream.write(car);
        System.out.println("TCP協議的Socket發送成功");
        //重繪緩沖區
        outputStream.flush();
        //關閉連接
        socket.close();
    }
}
  • Client2(TCP協議Socket使用BIO進行通信:客戶端2)
package com.mzc.io;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author mengzhichao  TCP協議Socket:客戶端2
 * @create 2021-07-20-11:07
 */
public class Client2 {
    public static void main(String[] args) throws IOException {
        //創建套接字物件socket并封裝ip與port
        Socket socket = new Socket("127.0.0.1", 8000);
        //根據創建的socket物件獲得一個輸出流
        //基于位元組流
        OutputStream outputStream = socket.getOutputStream();
        //控制臺輸入以IO的形式發送到服務器
        System.out.println("TCP連接成功 \n請輸入:");
        String str = new Scanner(System.in).nextLine();
        byte[] car = str.getBytes();
        outputStream.write(car);
        System.out.println("TCP協議的Socket發送成功");
        //重繪緩沖區
        outputStream.flush();
        //關閉連接
        socket.close();
    }
}

為了解決堵塞問題,可以使用多執行緒,請看下面


2. 多執行緒解決BIO編程會出現的問題

這時有人就會說,我多執行緒不就解決了嗎?

  • 使用多執行緒是可以解決堵塞等待時間很長的問題,因為他可以充分發揮CPU
  • 然而系統資源是有限的,不能過多的新建執行緒,執行緒過多帶來執行緒背景關系的切換,從來帶來更大的性能損耗

在這里插入圖片描述


多執行緒BIO代碼示例: ( 后面有代碼,往后移動一點點,認真看,代碼學習量很足 )

  • 四個客戶端,這次我多復制了倆個一樣客戶端類

在這里插入圖片描述

  1. 先啟動服務端,在啟動所有客戶端,測驗,發現連接成功
    在這里插入圖片描述

  2. 在所有客戶端輸入訊息(Client1、Client2這些是我在客戶端輸入的訊息)發現沒有問題
    在這里插入圖片描述

多執行緒BIO通信代碼:

  • BIOThreadService (TCP協議Socket使用多執行緒BIO進行通行:服務端)

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author mengzhichao TCP協議Socket使用多執行緒BIO進行通行:服務端
 * @create 2021-07-20-11:07
 */
public class BIOThreadServer {
    // 在main執行緒中執行下面這些代碼
    public static void main(String[] args) {
        try {
            ServerSocket server = new ServerSocket(8000);
            System.out.println("服務端啟動成功,監聽埠為8000,等待客戶端連接... ");
            while (true) {
                Socket socket = server.accept();//等待客戶連接
                System.out.println("客戶連接成功,客戶資訊為:" + socket.getRemoteSocketAddress());
                //針對每個連接創建一個執行緒, 去處理I0操作
                //創建多執行緒創建開始
                Thread thread =new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            InputStream in = socket.getInputStream();
                            byte[] buffer = new byte[1024];
                            int len = 0;
                            //讀取客戶端的資料
                            while ((len = in.read(buffer)) > 0) {
                                System.out.println(new String(buffer, 0, len));
                            }
                            //向客戶端寫資料
                            OutputStream out = socket.getOutputStream();
                            out.write("hello".getBytes());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                thread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  • 客戶端的代碼還是上面的代碼

為了解決執行緒太多,這時又來了,執行緒池 !


3. 執行緒池解決多執行緒BIO編程會出現的問題

  • 這時有人就會說,我TM用執行緒池?
    在這里插入圖片描述
  • 執行緒池固然可以解決這個問題,萬一需求量還不夠還要擴大執行緒池,當是這是我們自己靠著自己的思想完成的IO操作,Socket 上來了就去創建執行緒去搶奪CPU資源,MD,執行緒都TM做IO去了,CPU也不舒服呀
  • 這時呢:Jdk官方坐不住了,兄弟BIO的問題交給我,我來給你解決:NIO的誕生

執行緒池BIO代碼示例:(老樣子,代碼在后面)

在這里插入圖片描述

  1. 先啟動服務端,在啟動所有客戶端,測驗
    在這里插入圖片描述
  2. 在所有客戶端輸入訊息(Client1、Client2這些是我在客戶端輸入的訊息)發現沒有問題
    在這里插入圖片描述

執行緒池BIO通信代碼:

  • BIOThreadPoolService (TCP協議Socket使用執行緒池BIO進行通行:服務端)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//TCP協議Socket使用執行緒池BIO進行通行:服務端
public class BIOThreadPoolService {
    public static void main(String[] args) {
        //創建執行緒池
        ExecutorService executorService = Executors.newFixedThreadPool(30);
        try {
            ServerSocket server = new ServerSocket(8000);
            System.out.println("服務端啟動成功,監聽埠為8000,等待客戶端連接...");
            while (true) {
                Socket socket = server.accept();
                //等待客戶連接
                System.out.println("客戶連接成功,客戶資訊為:" + socket.getRemoteSocketAddress());
                //使用執行緒池中的執行緒去執行每個對應的任務
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            InputStream in = socket.getInputStream();
                            byte[] buffer = new byte[1024];
                            int len = 0;
                            //讀取客戶端的資料
                            while ((len = in.read(buffer)) > 0) {
                                System.out.println(new String(buffer, 0, len));
                            }
                            //向客戶端寫資料
                            OutputStream out = socket.getOutputStream();
                            out.write("hello".getBytes());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 客戶端的代碼還是上面的代碼

4 使用NIO實作網路通信

  • NIO是JDK1.4提供的操作,他的流還是流,沒有改變,服務器實作的還是一個連接一個執行緒,當是: 客戶端發送的連接請求都會注冊到多路復用器上 ,多路復用器輪詢到連接有I/O請求時才啟動一個執行緒進行處理,NIO方式適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,并發局限于應用中,編程比較復雜,JDK1.4之后開始支持,

在這里插入圖片描述
看不懂介紹可以認真看看代碼實體,其實不難

什么是通道(Channel)

  • Channel是一個物件,可以通過它讀取和寫入資料, 通常我們都是將資料寫入包含一個或者多個位元組的緩沖區,然后再將快取區的資料寫入到通道中,將資料從通道讀入緩沖區,再從緩沖區獲取資料,
  • Channel 類似于原I/O中的流(Stream),但有所區別:流是單向的,通道是雙向的,可讀可寫,流讀寫是阻塞的,通道可以異步讀寫,

什么是選擇器(Selector)

  • Selector可以稱他為通道的集合,每次客戶端來了之后我們會把Channel注冊到Selector中并且我們給他一個狀態,在用死回圈來環判斷( 判斷是否做完某個操作,完成某個操作后改變不一樣的狀態 )狀態是否發生變化,知道IO操作完成后在退出死回圈

什么是Buffer(緩沖區)

  • Buffer 是一個緩沖資料的物件, 它包含一些要寫入或者剛讀出的資料,
  • 在普通的面向流的 I/O 中,一般將資料直接寫入或直接讀到 Stream 物件中,當是有了Buffer(緩沖區)后,資料第一步到達的是Buffer(緩沖區)中
  • 緩沖區實質上是一個陣列( 底層完全是陣列實作的,感興趣可以去看一下 ),通常它是一個位元組陣列,內部維護幾個狀態變數,可以實作在同一塊緩沖區上反復讀寫(不用清空資料再寫),

代碼實體:

  • 目錄結構
    在這里插入圖片描述

  • 運行示例,先運行服務端,在運行所有客戶端控制臺輸入訊息就好了,
    在這里插入圖片描述

  • 服務端示例,先運行,想要搞定NIO請認真看代碼示例,真的很清楚

package com.mzc.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.Iterator;
import java.util.Set;

/**
 * @author mengzhichao
 * @create 2021-07-20-14:57
 */
public class NIOServer {
    public static void main(String[] args) throws IOException {

        //111111111
        //Service端的Channel,監聽埠的
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //設定為非阻塞
        serverChannel.configureBlocking(false);
        //nio的api規定這樣賦值埠
        serverChannel.bind(new InetSocketAddress(8000));
        //顯示Channel是否已經啟動成功,包括系結在哪個地址上
        System.out.println("服務端啟動成功,監聽埠為8000,等待客戶端連接..." + serverChannel.getLocalAddress());

        //22222222
        //宣告selector選擇器
        Selector selector = Selector.open();
        //這句話的含義,是把selector注冊到Channel上面,
        //每個客戶端來了之后,就把客戶端注冊到Selector選擇器上,默認狀態是Accepted
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        //33333333
        //創建buffer緩沖區,宣告大小是1024,底層使用陣列來實作的
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        RequestHandler requestHandler = new RequestHandler();

        //444444444
        //輪詢,服務端不斷輪詢,等待客戶端的連接
        //如果有客戶端輪詢上來就取出對應的Channel,沒有就一直輪詢
        while (true) {
            int select = selector.select();
            if (select == 0) {
                continue;
            }
            //有可能有很多,使用Set保存Channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                //使用SelectionKey來獲取連接了客戶端和服務端的Channel
                SelectionKey key = iterator.next();
                //判斷SelectionKey中的Channel狀態如何,如果是OP_ACCEPT就進入
                if (key.isAcceptable()) {
                    //從判斷SelectionKey中取出Channel
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    //拿到對應客戶端的Channel
                    SocketChannel clientChannel = channel.accept();
                    //把客戶端的Channel列印出來
                    System.out.println("客戶端通道資訊列印:" + clientChannel.getRemoteAddress());
                    //設定客戶端的Channel設定為非阻塞
                    clientChannel.configureBlocking(false);
                    //操作完了改變SelectionKey中的Channel的狀態OP_READ
                    clientChannel.register(selector, SelectionKey.OP_READ);
                }
                //到此輪訓到的時候,發現狀態是read,開始進行資料互動
                if (key.isReadable()) {
                    //以buffer作為資料橋梁
                    SocketChannel channel = (SocketChannel) key.channel();
                    //資料要想讀要先寫,必須先讀取到buffer里面進行操作
                    channel.read(buffer);
                    //進行讀取
                    String request = new String(buffer.array()).trim();
                    buffer.clear();
                    //進行列印buffer中的資料
                    System.out.println(String.format("客戶端發來的訊息: %s : %s", channel.getRemoteAddress(), request));
                    //要回傳資料的話也要先回傳buffer里面進行回傳
                    String response = requestHandler.handle(request);
                    //然后回傳出去
                    channel.write(ByteBuffer.wrap(response.getBytes()));
                }
                iterator.remove();
            }
        }
    }
}
  • 客戶端示例:( 我這用的不是之前的了,有修改 )運行起來客戶端控制臺輸入訊息就好了, 要模擬
    測驗,請復制粘貼改一下,修改客戶端的類名就行了,四個客戶端代碼一樣的 ,

在這里插入圖片描述

package com.mzc.nio;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author mengzhichao TCP協議Socket:客戶端
 * @create 2021-07-20-14:57
 */
public class Client1 {

    public static void main(String[] args) throws IOException {
        //創建套接字物件socket并封裝ip與port
        Socket socket = new Socket("127.0.0.1", 8000);
        //根據創建的socket物件獲得一個輸出流
        OutputStream outputStream = socket.getOutputStream();
        //控制臺輸入以IO的形式發送到服務器
        System.out.println("TCP連接成功 \n請輸入:");
        while (true) {
            byte[] car = new Scanner(System.in).nextLine().getBytes();
            outputStream.write(car);
            System.out.println("TCP協議的Socket發送成功");
            //重繪緩沖區
            outputStream.flush();
        }
    }
}

5.使用Netty實作網路通信

  • Netty是由JBOSS提供的一個Java開源框架,Netty提供異步的、事件驅動的網路應用程式框架和工具,用以快速開發高性能、高可靠性的網路服務器和客戶端程式,
  • Netty 是一個基于NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網路應用,例如實作了某種協議的客戶,服務端應用,Netty相當簡化和流線化了網路應用的編程開發程序,例如,TCP和UDP的Socket服務開發,

在這里插入圖片描述
Netty是由NIO演進而來,使用過NIO編程的用戶就知道NIO編程非常繁重,Netty是能夠能跟好的使用NIO

  • Netty的原里就是NIO,他是基于NIO的一個完美的封裝,并且優化了NIO,使用他非常方便,簡單快捷

我直接上代碼

  1. 先添加依賴:
<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>4.1.16.Final</version>
</dependency>
  1. NettyServer 模板,看起來代碼那么多, 其實只需要添加一行訊息就好了
  • 請認真看中間的代碼
package com.mzc.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;

import java.nio.channels.SocketChannel;

/**
 * @author mengzhichao
 * @create 2021-07-21-15:23
 */
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast("encoder", new ObjectEncoder());
                            pipeline.addLast(" decoder", new io.netty.handler.codec.serialization.ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            //重點,其他的都是復用的,這是真正的I0的業務代碼,把他封裝成一個個的個Hand1e類就行了,把他當成 SpringMVC的Controller
                            pipeline.addLast(new NettyServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture f = b.bind(8000).sync();
            System.out.println("服務端啟動成功,埠號為:" + 8000);
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}
  1. 需要做的IO操作,重點是繼承ChannelInboundHandlerAdapter類就好了
package com.mzc.netty;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author mengzhichao
 * @create 2021-07-21-15:38
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    RequestHandler requestHandler = new RequestHandler();

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println(String.format("客戶端資訊: %s",
                channel.remoteAddress()));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws
            Exception {
        Channel channel = ctx.channel();
        String request = (String) msg;
        System.out.println(String.format("客戶端發送的訊息 %s : %s",
                channel.remoteAddress(), request));
        String response = requestHandler.handle(request);
        ctx.write(response);
        ctx.flush();
    }
}
  1. 客戶的代碼還是之前NIO的代碼,我在復制下來一下吧
package com.mzc.netty;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author mengzhichao TCP協議Socket:客戶端
 * @create 2021-07-20-14:57
 */
public class Client1 {

    public static void main(String[] args) throws IOException {
        //創建套接字物件socket并封裝ip與port
        Socket socket = new Socket("127.0.0.1", 8000);
        //根據創建的socket物件獲得一個輸出流
        OutputStream outputStream = socket.getOutputStream();
        //控制臺輸入以IO的形式發送到服務器
        System.out.println("TCP連接成功 \n請輸入:");
        while (true) {
            byte[] car = new Scanner(System.in).nextLine().getBytes();
            outputStream.write(car);
            System.out.println("TCP協議的Socket發送成功");
            //重繪緩沖區
            outputStream.flush();
        }
    }
}
  • 運行測驗,還是之前那樣,啟動服務端,在啟動所有客戶端控制臺輸入就好了:

在這里插入圖片描述


IO練習題

在這里插入圖片描述

  • 練習一. 統計一個檔案calcCharNum.txt中字母Aa出現的總次數,
package com.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

//練習一:統計一個檔案calcCharNum.txt中字母‘A’和'a'出現的總次數,
/*
 * 讀取檔案:FileInputStream
 * 判斷單個字符出現的次數,一次只能讀一個,當讀到的內容相符時,相應數量加1
 */
public class TestOne {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //1.添加檔案路徑
        File file=new File("E:\\calcCharNum.txt");
        //2.創建流,讀取檔案
        FileInputStream fis=null;
        try {
            fis=new FileInputStream(file);
            int numA=0;
            int numa=0;
            int data=0;
            while((data=fis.read())!=-1) {
                if(new String((char)data+"").equals("a")) {
                    numa++;
                }
                if(new String((char)data+"").equals("A")) {
                    numA++;
                }
            }
            System.out.println("a的個數:"+numa);
            System.out.println("A的個數:"+numA);
            System.out.println("總數:"+(numa+numA));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
  • 練習二. 在電腦E盤下創建一個檔案為HelloWord.txt檔案,判斷它是檔案還是目錄,再創建一個目錄IOTest,之后將HelloWorld.txt移動到IOTest目錄下去,之后遍歷IOTest這個目錄下的檔案,
package com.test;

import java.io.File;
import java.io.IOException;

/*
 * 練習二:在電腦E盤下創建一個檔案為HelloWord.txt檔案,
    判斷它是檔案還是目錄,
    再創建一個目錄IOTest,
    之后將HelloWorld.txt移動到IOTest目錄下去,
    之后遍歷IOTest這個目錄下的檔案,
 */
public class TestTwo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //在E盤下創建檔案
        File file=new File("E:","HeloWorld.txt");
        //創建檔案
        boolean isCreate;
        try {
            isCreate=file.createNewFile();
            if(isCreate) {
                System.out.println("創建檔案成功");
            }else {
                System.out.println("創建檔案失敗");
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("創建檔案失敗");
        }
        //判斷是檔案還是目錄
        if(file.isFile()) {
            System.out.println("這是一個檔案");
        }else {
            System.out.println("這是一個目錄");
        }
        //創建目錄
        File file2=new File("E:/IOTest");
        file2.mkdirs();
        //移動檔案至目錄下
        if(file.renameTo(new File("E:/IOTest/HelloWorld.txt"))) {
            System.out.println("檔案移動成功");
        }else {
            System.out.println("檔案移動失敗");
        }
        //遍歷目錄
        String[] arrs=file2.list();
        for (String string : arrs) {
            System.out.println(string);
        }
    }
}

感謝瀏覽, 在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/289568.html

標籤:java

上一篇:java入門-----運算子

下一篇:java程式員面試國企后感嘆:技術太落后!還是只適合在互聯網公司里享受996的“福報”

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more