電腦程式的思維邏輯 (57) - 二進位文件和位元組流

来源:http://www.cnblogs.com/swiftma/archive/2016/12/12/6165599.html
-Advertisement-
Play Games

本節我們介紹如何在Java中以二進位位元組的方式來處理文件,介紹主要的流,包括它們的功能、用法、原理和使用場景,最後,我們總結一些簡單的實用方法。 ...


本節我們介紹在Java中如何以二進位位元組的方式來處理文件,上節我們提到Java中有流的概念,以二進位方式讀寫的主要流有:

  • InputStream/OutputStream: 這是基類,它們是抽象類。
  • FileInputStream/FileOutputStream: 輸入源和輸出目標是文件的流。
  • ByteArrayInputStream/ByteArrayOutputStream: 輸入源和輸出目標是位元組數組的流。
  • DataInputStream/DataOutputStream: 裝飾類,按基本類型和字元串而非只是位元組讀寫流。
  • BufferedInputStream/BufferedOutputStream: 裝飾類,對輸入輸出流提供緩衝功能。

下麵,我們就來介紹這些類的功能、用法、原理和使用場景,最後,我們總結一些簡單的實用方法。

InputStream/OutputStream

InputStream的基本方法

InputStream是抽象類,主要方法是:

public abstract int read() throws IOException;

read從流中讀取下一個位元組,返回類型為int,但取值在0到255之間,當讀到流結尾的時候,返回值為-1,如果流中沒有數據,read方法會阻塞直到數據到來、流關閉、或異常出現,異常出現時,read方法拋出異常,類型為IOException,這是一個受檢異常,調用者必須進行處理。read是一個抽象方法,具體子類必須實現,FileInputStream會調用本地方法,所謂本地方法,一般不是用Java寫的,大多使用C語言實現,具體實現往往與虛擬機和操作系統有關。

InputStream還有如下方法,可以一次讀取多個位元組:

public int read(byte b[]) throws IOException

讀入的位元組放入參數數組b中,第一個位元組存入b[0],第二個存入b[1],以此類推,一次最多讀入的位元組個數為數組b的長度,但實際讀入的個數可能小於數組長度,返回值為實際讀入的位元組個數。如果剛開始讀取時已到流結尾,則返回-1,否則,只要數組長度大於0,該方法都會儘力至少讀取一個位元組,如果流中一個位元組都沒有,它會阻塞,異常出現時也是拋出IOException。該方法不是抽象方法,InputStream有一個預設實現,主要就是迴圈調用讀一個位元組的read方法,但子類如FileInputStream往往會提供更為高效的實現。

批量讀取還有一個更為通用的重載方法:

public int read(byte b[], int off, int len) throws IOException

讀入的第一個位元組放入b[off],最多讀取len個位元組,read(byte b[])就是調用了該方法:

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

流讀取結束後,應該關閉,以釋放相關資源,關閉方法為:

public void close() throws IOException

不管read方法是否拋出了異常,都應該調用close方法,所以close通常應該放在finally語句內。close自己可能也會拋出IOException,但通常可以捕獲並忽略。

InputStream的高級方法

InputStream還定義瞭如下方法:

public long skip(long n) throws IOException
public int available() throws IOException
public synchronized void mark(int readlimit)
public boolean markSupported()
public synchronized void reset() throws IOException

skip跳過輸入流中n個位元組,因為輸入流中剩餘的位元組個數可能不到n,所以返回值為實際略過的位元組個數。InputStream的預設實現就是儘力讀取n個位元組並扔掉,子類往往會提供更為高效的實現,FileInputStream會調用本地方法。在處理數據時,對於不感興趣的部分,skip往往比讀取然後扔掉的效率要高。

available返回下一次不需要阻塞就能讀取到的大概位元組個數。InputStream的預設實現是返回0,子類會根據具體情況返回適當的值,FileInputStream會調用本地方法。在文件讀寫中,這個方法一般沒什麼用,但在從網路讀取數據時,可以根據該方法的返回值在網路有足夠數據時才讀,以避免阻塞。

一般的流讀取都是一次性的,且只能往前讀,不能往後讀,但有時可能希望能夠先看一下後面的內容,根據情況,再重新讀取。比如,處理一個未知的二進位文件,我們不確定它的類型,但可能可以通過流的前幾十個位元組判斷出來,判讀出來後,再重置到流開頭,交給相應類型的代碼進行處理。

InputStream定義了三個方法,mark/reset/markSupported,用於支持從讀過的流中重覆讀取。怎麼重覆讀取呢?先使用mark方法將當前位置標記下來,在讀取了一些位元組,希望重新從標記位置讀時,調用reset方法。

能夠重覆讀取不代表能夠回到任意的標記位置,mark方法有一個參數readLimit,表示在設置了標記後,能夠繼續往後讀的最多位元組數,如果超過了,標記會無效。為什麼會這樣呢?因為之所以能夠重讀,是因為流能夠將從標記位置開始的位元組保存起來,而保存消耗的記憶體不能無限大,流只保證不會小於readLimit。

不是所有流都支持mark/reset的,是否支持可以通過markSupported的返回值進行判斷。InpuStream的預設實現是不支持,FileInputStream也不直接支持,但BufferedInputStream和ByteArrayInputStream可以。

OutputStream

OutputStream的基本方法是:

public abstract void write(int b) throws IOException;

向流中寫入一個位元組,參數類型雖然是int,但其實只會用到最低的8位。這個方法是抽象方法,具體子類必須實現,FileInputStream會調用本地方法。

OutputStream還有兩個批量寫入的方法:

public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException

在第二個方法中,第一個寫入的位元組是b[off],寫入個數為len,最後一個是b[off+len-1],第一個方法等同於調用:write(b, 0, b.length);。OutputStream的預設實現是迴圈調用單位元組的write方法,子類往往有更為高效的實現,FileOutpuStream會調用對應的批量寫本地方法。

OutputStream還有兩個方法:

public void flush() throws IOException
public void close() throws IOException

flush將緩衝而未實際寫的數據進行實際寫入,比如,在BufferedOutputStream中,調用flush會將其緩衝區的內容寫到其裝飾的流中,並調用該流的flush方法。基類OutputStream沒有緩衝,flush代碼為空。

需要說明的是文件輸出流FileOutputStream,你可能會認為,調用flush會強制確保數據保存到硬碟上,但實際上不是這樣,FileOutputStream沒有緩衝,沒有重寫flush,調用flush沒有任何效果,數據只是傳遞給了操作系統,但操作系統什麼時候保存到硬碟上,這是不一定的。要確保數據保存到了硬碟上,可以調用FileOutputStream中的特有方法。

close一般會首先調用flush,然後再釋放流占用的系統資源。同InputStream一樣,close一般應該放在finally語句內。

FileInputStream/FileOutputStream

FileOutputStream

FileOutputStream的主要構造方法有:

public FileOutputStream(File file) throws FileNotFoundException
public FileOutputStream(File file, boolean append) throws FileNotFoundException
public FileOutputStream(String name) throws FileNotFoundException
public FileOutputStream(String name, boolean append) throws FileNotFoundException

有兩類參數,一類是文件路徑,可以是File對象file,也可以是文件路徑name,路徑可以是絕對路徑,也可以是相對路徑,如果文件已存在,append參數指定是追加還是覆蓋,true表示追加,沒傳append參數表示覆蓋。new一個FileOutputStream對象會實際打開文件,操作系統會分配相關資源。如果當前用戶沒有寫許可權,會拋出異常SecurityException,它是一種RuntimeException。如果指定的文件是一個已存在的目錄,或者由於其他原因不能打開文件,會拋出異常FileNotFoundException,它是IOException的一個子類。

我們看一段簡單的代碼,將字元串"hello, 123, 老馬"寫到文件hello.txt中:

OutputStream output =  new FileOutputStream("hello.txt");
try{
    String data = "hello, 123, 老馬";
    byte[] bytes = data.getBytes(Charset.forName("UTF-8"));
    output.write(bytes);
}finally{
    output.close();
}

OutputStream只能以byte或byte數組寫文件,為了寫字元串,我們調用String的getBytes方法得到它的UTF-8編碼的位元組數組,再調用write方法,寫的過程放在try語句內,在finally語句中調用close方法。

FileOutputStream還有兩個額外的方法:

public FileChannel getChannel()
public final FileDescriptor getFD()

FileChannel定義在java.nio中,表示文件通道概念,我們不會深入介紹通道,但記憶體映射文件方法定義在FileChannel中,我們會在後續章節介紹。FileDescriptor表示文件描述符,它與操作系統的一些文件記憶體結構相連,在大部分情況下,我們不會用到它,不過它有一個方法sync:

public native void sync() throws SyncFailedException;

這是一個本地方法,它會確保將操作系統緩衝的數據寫到硬碟上。註意與OutputStream的flush方法相區別,flush只能將應用程式緩衝的數據寫到操作系統,sync則確保數據寫到硬碟,不過一般情況下,我們並不需要手工調用它,只要操作系統和硬體設備沒問題,數據遲早會寫入,但在一定特定情況下,一定需要確保數據寫入硬碟,則可以調用該方法。

FileInputStream

FileInputStream的主要構造方法有:

public FileInputStream(String name) throws FileNotFoundException
public FileInputStream(File file) throws FileNotFoundException

參數與FileOutputStream類似,可以是文件路徑或File對象,但必須是一個已存在的文件,不能是目錄。new一個FileInputStream對象也會實際打開文件,操作系統會分配相關資源,如果文件不存在,會拋出異常FileNotFoundException,如果當前用戶沒有讀的許可權,會拋出異常SecurityException。

我們看一段簡單的代碼,將上面寫入的文件"hello.txt"讀到記憶體並輸出:

InputStream input = new FileInputStream("hello.txt");
try{
    byte[] buf = new byte[1024];
    int bytesRead = input.read(buf);    
    String data = new String(buf, 0, bytesRead, "UTF-8");
    System.out.println(data);
}finally{
    input.close();
}

讀入到的是byte數組,我們使用String的帶編碼參數的構造方法將其轉換為了String。這段代碼假定一次read調用就讀到了所有內容,且假定位元組長度不超過1024。為了確保讀到所有內容,可以逐個位元組讀取直到文件結束:

int b = -1;
int bytesRead = 0;
while((b=input.read())!=-1){
    buf[bytesRead++] = (byte)b;
}

在沒有緩衝的情況下逐個位元組讀取性能很低,可以使用批量讀入且確保讀到文件結尾,如下所示:

byte[] buf = new byte[1024];
int off = 0;
int bytesRead = 0;
while((bytesRead=input.read(buf, off, 1024-off ))!=-1){
    off += bytesRead;
}    
String data = new String(buf, 0, off, "UTF-8");

不過,這還是假定文件內容長度不超過一個固定的大小1024。如果不確定文件內容的長度,不希望一次性分配過大的byte數組,又希望將文件內容全部讀入,怎麼做呢?可以藉助ByteArrayOutputStream。

ByteArrayInputStream/ByteArrayOutputStream

ByteArrayOutputStream

ByteArrayOutputStream的輸出目標是一個byte數組,這個數組的長度是根據數據內容動態擴展的。它有兩個構造方法:

public ByteArrayOutputStream()
public ByteArrayOutputStream(int size) 

第二個構造方法中的size指定的就是初始的數組大小,如果沒有指定,長度為32。在調用write方法的過程中,如果數組大小不夠,會進行擴展,擴展策略同樣是指數擴展,每次至少增加一倍。

ByteArrayOutputStream有如下方法,可以方便的將數據轉換為位元組數組或字元串:

public synchronized byte[] toByteArray()
public synchronized String toString()
public synchronized String toString(String charsetName)

toString()方法使用系統預設編碼。

ByteArrayOutputStream中的數據也可以方便的寫到另一個OutputStream:

public synchronized void writeTo(OutputStream out) throws IOException

ByteArrayOutputStream還有如下額外方法:

public synchronized int size()
public synchronized void reset()

size返回當前寫入的位元組個數。reset重置位元組個數為0,reset後,可以重用已分配的數組。

使用ByteArrayOutputStream,我們可以改進上面的讀文件代碼,確保將所有文件內容讀入:

InputStream input = new FileInputStream("hello.txt");
try{
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    byte[] buf = new byte[1024];
    int bytesRead = 0;
    while((bytesRead=input.read(buf))!=-1){
        output.write(buf, 0, bytesRead);
    }    
    String data = output.toString("UTF-8");
    System.out.println(data);
}finally{
    input.close();
}

讀入的數據先寫入ByteArrayOutputStream中,讀完後,再調用其toString方法獲取完整數據。

ByteArrayInputStream

ByteArrayInputStream將byte數組包裝為一個輸入流,是一種適配器模式,它的構造方法有:

public ByteArrayInputStream(byte buf[])
public ByteArrayInputStream(byte buf[], int offset, int length)

第二個構造方法以buf中offset開始length個位元組為背後的數據。ByteArrayInputStream的所有數據都在記憶體,支持mark/reset重覆讀取。

為什麼要將byte數組轉換為InputStream呢?這與容器類中要將數組、單個元素轉換為容器介面的原因是類似的,有很多代碼是以InputStream/OutputSteam為參數構建的,它們構成了一個協作體系,將byte數組轉換為InputStream可以方便的參與這種體系,復用代碼。

DataInputStream/DataOutputStream

上面介紹的類都只能以位元組為單位讀寫,如何以其他類型讀寫呢?比如int, double。可以使用DataInputStream/DataOutputStream,它們都是裝飾類。

DataOutputStream

DataOutputStream是裝飾類基類FilterOutputStream的子類,FilterOutputStream是OutputStream的子類,它的構造方法是:

public FilterOutputStream(OutputStream out)

它接受一個已有的OutputStream,基本上將所有操作都代理給了它。

DataOutputStream實現了DataOutput介面,可以以各種基本類型和字元串寫入數據,部分方法如下:

void writeBoolean(boolean v) throws IOException;
void writeInt(int v) throws IOException;
void writeDouble(double v) throws IOException;
void writeUTF(String s) throws IOException;

在寫入時,DataOutputStream會將這些類型的數據轉換為其對應的二進位位元組,比如:

  • writeBoolean: 寫入一個位元組,如果值為true,則寫入1,否則0
  • writeInt: 寫入四個位元組,最高位位元組先寫入,最低位最後寫入
  • writeUTF: 將字元串的UTF-8編碼位元組寫入,這個編碼格式與標準的UTF-8編碼略有不同,不過,我們不用關心這個細節。 

與FilterOutputStream一樣,DataOutputStream的構造方法也是接受一個已有的OutputStream:

public DataOutputStream(OutputStream out)

我們來看一個例子,保存一個學生列表到文件中,學生類的定義為:

class Student {
    String name;
    int age;
    double score;
    
    public Student(String name, int age, double score) {
         ...
    }
    ...
}    

我們省略了構造方法和getter/setter方法,學生列表內容為:

List<Student> students = Arrays.asList(new Student[]{
        new Student("張三", 18, 80.9d),
        new Student("李四", 17, 67.5d)
});

將該列表內容寫到文件students.dat中的代碼可以為:

public static void writeStudents(List<Student> students) throws IOException{
    DataOutputStream output = new DataOutputStream(
            new FileOutputStream("students.dat"));
    try{
        output.writeInt(students.size());
        for(Student s : students){
            output.writeUTF(s.getName());
            output.writeInt(s.getAge());
            output.writeDouble(s.getScore());
        }
    }finally{
        output.close();
    }
}

我們先寫了列表的長度,然後針對每個學生、每個欄位,根據其類型調用了相應的write方法。

DataInputStream

DataInputStream是裝飾類基類FilterInputStream的子類,FilterInputStream是InputStream的子類。

DataInputStream實現了DataInput介面,可以以各種基本類型和字元串讀取數據,部分方法如下:

boolean readBoolean() throws IOException;
int readInt() throws IOException;
double readDouble() throws IOException;
String readUTF() throws IOException;

在讀取時,DataInputStream會先按位元組讀進來,然後轉換為對應的類型。

DataInputStream的構造方法接受一個InputStream:

public DataInputStream(InputStream in)

還是以上面的學生列表為例,我們來看怎麼從文件中讀進來:

public static List<Student> readStudents() throws IOException{
    DataInputStream input = new DataInputStream(
            new FileInputStream("students.dat"));
    try{
        int size = input.readInt();
        List<Student> students = new ArrayList<Student>(size);
        for(int i=0; i<size; i++){
            Student s = new Student();
            s.setName(input.readUTF());
            s.setAge(input.readInt());
            s.setScore(input.readDouble());
            students.add(s);
        }
        return students;
    }finally{
        input.close();
    }
}

基本是寫的逆過程,代碼比較簡單,就不贅述了。

使用DataInputStream/DataOutputStream讀寫對象,非常靈活,但比較麻煩,所以Java提供了序列化機制,我們在後續章節介紹。

BufferedInputStream/BufferedOutputStream

FileInputStream/FileOutputStream是沒有緩衝的,按單個位元組讀寫時性能比較低,雖然可以按位元組數組讀取以提高性能,但有時必須要按位元組讀寫,比如上面的DataInputStream/DataOutputStream,它們包裝了文件流,內部會調用文件流的單位元組讀寫方法。怎麼解決這個問題呢?方法是將文件流包裝到緩衝流中。

BufferedInputStream內部有個位元組數組作為緩衝區,讀取時,先從這個緩衝區讀,緩衝區讀完了再調用包裝的流讀,它的構造方法有兩個:

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size)

size表示緩衝區大小,如果沒有,預設值為8192。

除了提高性能,BufferedInputStream也支持mark/reset,可以重覆讀取。

與BufferedInputStream類似,BufferedOutputStream的構造方法也有兩個,預設的緩衝區大小也是8192,它的flush方法會將緩衝區的內容寫到包裝的流中。

在使用FileInputStream/FileOutputStream時,應該幾乎總是在它的外麵包上對應的緩衝類,如下所示:

InputStream input = new BufferedInputStream(new FileInputStream("hello.txt"));
OutputStream output =  new BufferedOutputStream(new FileOutputStream("hello.txt"));

再比如:

DataOutputStream output = new DataOutputStream(
        new BufferedOutputStream(new FileOutputStream("students.dat")));
DataInputStream input = new DataInputStream(
        new BufferedInputStream(new FileInputStream("students.dat")));    

實用方法

可以看出,即使只是按二進位位元組讀寫流,Java也包括了很多的類,雖然很靈活,但對於一些簡單的需求,卻需要寫很多代碼,實際開發中,經常需要將一些常用功能進行封裝,提供更為簡單的介面。下麵我們提供一些實用方法,以供參考。

拷貝

拷貝輸入流的內容到輸出流,代碼為:

public static void copy(InputStream input,
        OutputStream output) throws IOException{
    byte[] buf = new byte[4096];
    int bytesRead = 0;
    while((bytesRead = input.read(buf))!=-1){
        output.write(buf, 0, bytesRead);
    }
}    

將文件讀入位元組數組

代碼為:

public static byte[] readFileToByteArray(String fileName) throws IOException{
    InputStream input = new FileInputStream(fileName);
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    try{
        copy(input, output);
        return output.toByteArray();
    }finally{
        input.close();
    }
}

這個方法調用了上面的拷貝方法。

將位元組數組寫到文件

public static void writeByteArrayToFile(String fileName,
        byte[] data) throws IOException{
    OutputStream output = new FileOutputStream(fileName);
    try{
        output.write(data);
    }finally{
        output.close();    
    }
}

Apache有一個類庫Commons IO,裡面提供了很多簡單易用的方法,實際開發中,可以考慮使用。

小結

本節我們介紹瞭如何在Java中以二進位位元組的方式讀寫文件,介紹了主要的流。

  • InputStream/OutputStream:是抽象基類,有很多面向流的代碼,以它們為參數,比如本節介紹的copy方法。
  • FileInputStream/FileOutputStream:流的源和目的地是文件。
  • ByteArrayInputStream/ByteArrayOutputStream:源和目的地是位元組數組,作為輸入相當於是適配器,作為輸出封裝了動態數組,便於使用。
  • DataInputStream/DataOutputStream:裝飾類,按基本類型和字元串讀寫流。
  • BufferedInputStream/BufferedOutputStream:裝飾類,提供緩衝,FileInputStream/FileOutputStream一般總是應該用該類裝飾。

最後,我們提供了一些實用方法,以方便常見的操作,在實際開發中,可以考慮使用專門的類庫如Apache Commons IO。

本節介紹的流不適用於處理文本文件,比如,不能按行處理,沒有編碼的概念,下一節,就讓我們來看文本文件和字元流。

----------------

未完待續,查看最新文章,敬請關註微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及電腦技術的本質。用心原創,保留所有版權。


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

-Advertisement-
Play Games
更多相關文章
  • 這個系統是南方七星彩投註網站系統源碼,網站是採用php+MySQL的。基本實現功能如下:這個是普通的七星前四位的網投註平臺股東-總代理-代理-會員 這四個級別 網站大家只限於學習與交流,並且在合法的範圍使用,為了防止系統其他用戶,代碼有進行加密了,不便多多瞭解。 投註網站源碼附件: http://f ...
  • 對於一個有登錄限制(許可權限制)的網站,用戶輸入身份驗證信息以後,驗證成功後跳轉到登錄前的頁面是一項很人性化的功能。那麼獲取登錄前的頁面地址就很關鍵,今天在做一個yii2項目的登錄調試時發現了一些很有意思的問題,記錄下來。 1,場景描述 網站SiteA上的頁面Page2需要登錄後才能查看,Page2的 ...
  • ...
  • 列印一排*,很簡單,列印下圖 也很簡單,代碼如下: 可是昨天想了好久都沒想到怎樣做到下麵圖片的樣子,今天突然就有了靈感 代碼很簡單,就是昨天想破了腦袋都想不出來,好笨啊我 第一行列印一個*,第二行行列印兩個*,大三行列印三個*,這樣分析就找到規律了,定義一個a=1,外層迴圈實現列印幾行,定義一個i= ...
  • 上面的代碼是段合法的cpp代碼嗎? 答案當然是是的. 這些 問號 是個什麼鬼?它們就是cpp標準中定義的 "Trigraph(MS)" .之所以出現這些神奇的符號,主因主要是字元集的問題.簡單來說可以理解為某些老外的鍵盤沒有"{","|","\"這些符號.所以需要用這種類似"轉義字元"的東東來表達c ...
  • KMP演算法是字元串模式匹配當中最經典的演算法,原來大二學數據結構的有講,但是當時只是記住了原理,但不知道代碼實現,今天終於是完成了KMP的代碼實現。原理KMP的原理其實很簡單,給定一個字元串和一個模式串,然後找模式串在給定字元串中的位置。將兩個字元串轉換為字元數組,然後從兩個數組的開始位置"i","j ...
  • Error Handling with Exceptions ___ The ideal time to catch an error is at compile time, before you even try to run the program. The rest of the proble ...
  • 今天跑程式的時候莫名其妙的出現了下麵的一個異常: java.lang.NoSuchMethodException:com.ca.agent.model.mybatis.ApprovalInforCangra.setSubDate([Ljava.lang.String;) 這類異常信息在以前是處理過的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...