主頁 > 後端開發 > 檔案隨機或順序讀寫原理深入淺出

檔案隨機或順序讀寫原理深入淺出

2021-10-26 06:14:58 後端開發

一、檔案讀寫的用戶程式、作業系統、磁盤互動原理

  最近為了徹底搞懂檔案讀寫原理,我特意查詢了很多資料,包括Java讀寫檔案的API代碼、作業系統處理檔案以及磁盤硬體知識等,由于網上現存技術文章,幾乎沒有找到一篇能夠徹底綜合講明白這個原理的文章,心中還是有很多疑問,且有不少文章包括書籍所闡述的隨機/順序讀寫原理講述的都是錯誤或誤導性的,所以我綜合了一下我能查閱到的所有資料,深入細節知識,給大家徹底講明白這事,原創文章,轉發請保留第一作者著作權,謝謝!   如下圖所示,我們撰寫的用戶程式讀寫檔案時必須經過的OS和硬體互動的記憶體模型,

1、讀檔案

  用戶程式通過編程語言提供的讀取檔案api發起對某個檔案讀取,此時程式切換到內核態,用戶程式處于阻塞狀態,由于讀取的內容還不在內核緩沖區中,導致觸發OS缺頁中斷例外,然后由OS負責發起對磁盤檔案的資料讀取,讀取到資料后,先存放在OS內核的主存空間,叫PageCache,然后OS再將資料拷貝一份至用戶行程空間的主存ByteBuffer中,此時程式由內核態切換至用戶態繼續運行程式,程式將ByteBuffer中的內容讀取到本地變數中,即完成檔案資料讀取作業,

2、寫檔案

  用戶程式通過編程語言提供的寫入檔案api發起對某個檔案寫入磁盤,此時程式切換到內核態用戶程式處于阻塞狀態,由OS負責發起對磁盤檔案的資料寫入,用戶寫入資料后,并不是直接寫到磁盤的,而是先寫到ByteBuffer中,然后再提交到PageCache中,最后由作業系統決定何時寫入磁盤,資料寫入PageCache中后,此時程式由內核態切換至用戶態繼續運行, 用戶程式將資料寫入內核的PageCache緩沖區后,即認為寫入成功了,程式由內核態切換回用于態,可以繼續后續的作業了,PageCache中的資料最終寫入磁盤是由作業系統異步提交至磁盤的,一般是定時或PageCache滿了的時候寫入,如果用戶程式通過呼叫flush方法強制寫入,則作業系統也會服從這個命令,立即將資料寫入磁盤然后由內核態切換回用戶態繼續運行程式,但是這樣做會損失性能,但可以確切的知道資料是否已經寫入磁盤了,

一、檔案讀寫詳細程序

1、讀檔案

  如下所示為一典型Java讀取某檔案內容的用戶編程代碼,接下來我們詳細解說讀取檔案程序,
            // 一次讀多個位元組
            byte[] tempbytes = new byte[100];
            int byteread = 0;
            in = new FileInputStream(fileName);//
            ReadFromFile.showAvailableBytes(in);
            // 讀入多個位元組到位元組陣列中,byteread為一次讀入的位元組數
            while ((byteread = in.read(tempbytes)) != -1) { //
                System.out.write(tempbytes, 0, byteread);
            }
View Code   首先通過位置①的代碼發起一個open的系統呼叫,程式由用戶態切換到內核態,作業系統通過檔案全路徑名在檔案目錄中找到目標檔案名對應的檔案iNode標識ID,然后用這個iNode標識ID在iNode索引檔案找到目標檔案iNode節點資料并加載到內核空間中,這個iNode節點包含了檔案的各種屬性(創建時間,大小以及磁盤塊空間占用資訊等等),然后再由內核態切換回用戶態,這樣程式就獲得了操作這個檔案的檔案描述,接下來就可以正式開始讀取檔案內容了,   然后再通位置②,回圈數次獲取固定大小的資料,通過發起read系統呼叫,作業系統通過檔案iNode檔案屬性中的磁盤塊空間占用資訊得到檔案起始位的磁盤物理地址,再從磁盤中將要取得資料拷貝到PageCache內核緩沖區,然后將資料拷貝至用戶行程空間,程式由內核態切換回用戶態,從而可以讀取到資料,并放入上面代碼中的臨時變數tempbytes中,   整個程序如下圖所示,   至于上面說到的作業系統通過iNode節點中的磁盤塊占用資訊去定位磁盤檔案資料,其細節程序如下圖所示,

①根據檔案路徑從檔案目錄中找到iNode ID,

  用戶讀取一個檔案,首先需要呼叫OS中檔案系統的open方法,該方法會回傳一個檔案描述符給用戶程式,OS首先根據用戶傳過來的檔案全路徑名在目錄索引資料結構中找到檔案對應的iNode標識ID,目錄資料是存在于磁盤上的,在OS初始化時就會加載到記憶體中,由于目錄資料結構并不會很龐大,一次性加載駐留到記憶體也不是不可以或者部分加載,等需要的時候在從磁盤上調度進記憶體也可以,根據檔案路徑在目錄中查找效率應該是很高的,因為目錄本身就是一棵樹,應該也是類似資料庫的樹形索引結構,所以它的查找演算法時間復雜度就是O(logN),具體細節我暫時還沒弄清楚,這不是重點,   iNode就是檔案屬性索引資料了,磁盤格式化時OS就會把磁盤磁區成iNode區和資料區,iNode節點就包含了檔案的一些屬性資訊,比如檔案大小、創建修改時間、作者等等,其中最重要的是還存有整個檔案資料在磁盤上的分布情況(檔案占用了哪些磁盤塊),

②根據iNode ID從Inode索引中找到檔案屬性,

  得到iNode標識的ID后,就可以去iNode資料中查找到對應的檔案屬性了,并加載到記憶體,方便后續讀寫檔案時快速獲得磁盤定位,iNode資料結構應該類似哈希結構了,key就是iNode標識ID,value就是具體某個檔案的屬性資料物件了,所以它的演算法時間復雜度就是O(1),具體細節我暫時還沒弄清楚,這不是重點,   我們系統中的檔案它的檔案屬性(iNode)和它的資料正文是分開存盤的,檔案屬性中有檔案資料所在磁盤塊的位置資訊,

③根據檔案屬性中的磁盤空間塊資訊找到需要讀取的資料所在的磁盤塊的物理位置

  檔案屬性也就是iNode節點這個資料結構,里面包含了檔案正文資料在磁盤物理位置上的分布情況,磁盤讀寫都是以塊為單位的,所以這個位置資訊其實也就是一個指向磁盤塊的物理地址指標,   其結構圖如下,   檔案屬性里就包含了檔案正文資料占有磁盤所有資訊,但是由于檔案屬性大小有限制,而檔案大小沒有限制,這樣會導致磁盤塊占用資訊超出限制,所以最后一個磁盤資料項設計為特殊的作用,它是一個指向更多磁盤占用資訊資料的指標,這些更多資訊存放在普通的資料區,這樣當檔案iNode加載到記憶體后,可以把其他更多磁盤塊資訊一起加載進來,這樣就避免了iNode索引檔案太大的問題, 后續的檔案讀寫系統呼叫,由用戶態切換至內核態,作業系統就可以根據檔案資料的相對位置(偏移量)快速從iNode中的磁盤塊占用資料結構中找到其對應的磁盤物理位置在哪里了,很明顯這個資料結構類似哈希結構,其演算法復雜度就是O(1),   比如我們現在討論的讀取資料,每次用戶代碼的api呼叫read方法時,由于時從頭開始讀取,所以OS就從上圖中“磁盤塊0”資料項開始迭代,獲取對應的物理磁盤塊起始地址開始讀取資料并拷貝至PageCache緩沖區,再拷貝至用戶行程緩沖區,這樣用戶代碼就可以獲取這些資料了, 考慮到另外一種隨機讀的場景,我們并不是把整個檔案從頭開始讀一遍,而是需要直接定位到檔案的中間某個位置開始 讀取部分內容,如下所示,
  RandomAccessFile raf=new RandomAccessFile(new File("D:\\3\\test.txt"), "r");   
            //獲取RandomAccessFile物件檔案指標的位置,初始位置是0  
            System.out.println("RandomAccessFile檔案指標的初始位置:"+raf.getFilePointer());  
            raf.seek(pointe);//移動檔案指標位置  
            byte[]  buff=new byte[1024];  
            //用于保存實際讀取的位元組數  
            int hasRead=0;  
            //回圈讀取  
            while((hasRead=raf.read(buff))>0){  
                //列印讀取的內容,并將位元組轉為字串輸入  
                System.out.println(new String(buff,0,hasRead));       
            }  
View Code   程式代碼呼叫seek方法直接定位到某個檔案相對位置開始讀取內容,實際上就是呼叫了OS管理檔案的系統呼叫seek函式,這個系統呼叫需要傳遞一個檔案相對位置也就是偏移量,不是指磁盤的物理位置,檔案的相對位置偏移量是從0開始的,結束位置和檔案的大小位元組數相等,作業系統拿到這個偏移量后,就可以計算出檔案所屬的邏輯塊編號,因為每個塊是固定大小的,所以能計算出來,通過檔案屬性的邏輯磁盤塊資訊就能得到磁盤塊的物理位置,從而可以快速直接定位到磁盤物理塊讀取到需要的資料,這里說的邏輯塊和物理塊的概念是有區別的,邏輯塊屬于當前的檔案從0開始編號,物理塊才是磁盤真正的存放資料的區域,屬于全域的,編號自然不是從0開始的,

2、寫檔案

  寫檔案的程序和前面闡述的差不多,相關的知識點也在讀檔案中已經順帶描述了,就不在贅述了,這里就說些特別需要注意的點就行,

  ③根據空閑塊索引找到可以寫入的物理位置并寫入

  如上圖所示,OS寫檔案內容時首先要訪問磁盤空閑塊索引表,這是個什么東西呢?由于磁盤很大,不可能每次寫資料時,都讓磁頭從頭到尾遍歷一次才能找到空閑位置,這樣效率可想而知的差勁,所以OS會把磁盤上的空閑塊索引起來存放在磁盤某個位置上,后續磁盤存盤和洗掉檔案內容時都通過這個空閑塊索引表快速定位,同時洗掉資料也會更新索引表增加空閑塊, 空閑塊記錄索引的實作常用有兩種,一種是我們熟悉的鏈表結構,還有一種是位圖結構,這里就不詳細討論了,

  ④寫入資料后更新iNode里的磁盤占用塊索引

  資料寫入后,那么這個空閑塊就被占用了,自然也就需要更新下iNode檔案屬性里的磁盤占用塊索引資料了, 我們前面說的寫檔案都是只講了尾部追加這種方式,但是實際上我們可以通過RandomAccessFile類實作檔案隨機位置寫功能,但是我們同時也有一些困惑,為啥不能直接在中間某個位置插入我們要寫的內容,而是要先把插入位置后面的內容截取放入臨時檔案中,插入新內容后,再把臨時檔案內容尾部追加到原來的檔案中來實作檔案修改?代碼如下所示,
public static void insert(String fileName, long pos, String insertContent) throws IOException{
        File file = File.createTempFile("tmp", null);
        file.deleteOnExit();
        RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        raf.seek(pos);
        byte[] buff = new byte[64];
        int hasRead = 0;
        while((hasRead = raf.read(buff)) > 0){
            fileOutputStream.write(buff);
        }
        raf.seek(pos);
        raf.write(insertContent.getBytes());
        //追加檔案插入點之后的內容
        while((hasRead = fileInputStream.read(buff)) > 0){
            raf.write(buff, 0, hasRead);
        }
        raf.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
View Code   按照我們上面的闡述,寫入的檔案內容完全可以存入磁盤上的一個新塊,然后更新下iNode屬性里的占用磁盤塊索引資料即可,也不需要真的去移動磁盤上的所有資料塊,看上去成本也很小,可為啥我們的編程api卻都不支持呢?   我想答案可能是這樣的,假如允許我們上面那種操作,如果一個很大的文本檔案,你現在只是編輯了文本中間某個位置的一個字,即插入了一個文本字符,那么此時這個新增的文本內容就得在磁盤上找到一個新的塊存盤下來,這樣是不是有點浪費空間呢?因為磁盤上的一個塊只能分配給一個檔案使用,塊大小如果是64kb的話,一個字符也就占用了2個位元組的空間,更要命的是這樣一搞,使得原本滿存狀態的塊,出現很多不連續的空洞,這樣就會使得讀取檔案時資料是不連續的,系統需要額外資訊記錄這些中間的存盤空洞,加大了讀取難度,這就是我猜測的原因,實際上作業系統層面也沒有這種操作插入的系統呼叫函式,故編程語言層面也就沒法支持了,   作業系統層面給上層應用程式提供了寫檔案的兩個系統呼叫,write和append,其中append是write的限制形式,即只能在檔案尾部追加,而write雖然提供了隨機位置寫,但是并不是將新內容插入其中,而是覆寫原有的資料,我們平時使用Word文本編輯軟體時,如果對一個很大的檔案進行編輯,然后點擊保存,你會發現很慢,同時你還能看到檔案所在的目錄下生成了一個新的處于隱藏狀態的臨時檔案,這些現象也能說明我們上面的觀點,即編輯檔案時,需要一個成本很高的程序,如下圖所示,  

三、常見認知誤區澄清

1、磁盤上檔案存盤資料結構是鏈表,每一塊檔案資料節點里有一個指標指向下一塊資料節點,理解錯誤!

  很多人都知道磁盤存盤一個檔案不可能是連續分配空間的,而是東一塊西一塊的存盤在磁盤上的,就誤以為這些分散的資料節點就像鏈表一樣通過其中一個指標指向下一塊資料節點,如下圖所示,   怎么說呢?這種方案以前也是有一些檔案系統實作過的方案,但是現在常見的磁盤檔案系統都不再使用這種落后的方案,而是我前面提到的iNode節點方案,也就是說磁盤上存盤的檔案資料塊就是純資料,沒有其他指標之類的額外資訊,之所以我們能順利定位這些資料塊,都全靠iNode節點屬性中磁盤塊資訊的指標,

2、append檔案尾部追加方法是順序寫,也就是磁盤會分配連續的空間給檔案存盤,理解錯誤!

  這種觀點,包括網上和某些技術書籍里的作者都有這種觀點,實際上是錯誤的,或許是他們根本沒有細究檔案存盤底層OS和磁盤硬體的作業原理導致,我這里就重新總結糾正一下這種誤導性觀點, 前面說過,append系統呼叫是write的限制形式,即總是在檔案末尾追加內容,看上去好像是說順序寫入檔案資料,因為是在尾部追加啊!所以這樣很容易誤導大家以為這就是順序寫,即磁盤存盤時分配連續的空間給到檔案,減少了磁盤尋道時間,   事實上,磁盤從來都不會連續分配空間給哪個檔案,這是我們現代檔案系統的設計方案,前面介紹iNode知識時也給大家詳細說明了,所以就不再贅述,我們用戶程式寫檔案內容時,提交給OS的緩沖區PageCache后就回傳了,實際這個內容存盤在磁盤哪個位置是由OS決定的,OS會根據磁盤未分配空間索引表隨機找一個空塊把內容存盤進去,然后更新檔案iNode里的磁盤占用塊索引資料,這樣就完成了檔案寫入操作,所以append操作不是在磁盤上接著檔案末尾內容所在塊位置連續分配空間的,最多只能說邏輯上是順序的,   那么邏輯上的隨機寫write是不是會慢呢?根據前面介紹的原理,應該是相同的效率,我們可以做如下測驗驗證,使用RandomAccessFile 實作,因為只有這個類支持隨機位置寫入,其他寫檔案類都只提供尾部追加方式,打開一個20M的文本檔案“test.txt”,分別采用如下兩個方法,隨機位置寫入和尾部追加寫入做100000次操作,多次測得大概的平均耗時資料,
// 檔案隨機位置寫入 耗時:1000ms
public static void randomWrite1(String path,String content) throws Exception {
RandomAccessFile raf=new RandomAccessFile(path,"rw");
Random random=new Random();
for(int i=0;i<100000;i++){
raf.seek(random.nextInt((int)raf.length())); // 在檔案隨機位置寫入覆寫
raf.write((i+content+System.lineSeparator()).getBytes());
}
raf.close();
}
// 檔案尾部位置寫入 耗時:800ms
public static void randomWrite2(String path,String content) throws Exception {
RandomAccessFile raf=new RandomAccessFile(path,"rw");
for(int i=0;i<100000;i++){
raf.seek(raf.length()); // 總是在檔案尾部追加
raf.write((i+content+System.lineSeparator()).getBytes());
}
raf.close();
}
View Code   看上去采用尾部追加性能略高,實際上也相差不大,多出的200ms只是生成亂數消耗的,因為如果說隨機位置寫需要某些人認為的磁盤磁頭來回反復移動,則性能不可能只差這么一丟丟,實際上我去掉亂數生成的代碼,改用固定中間位置寫入,這兩個方法耗時幾乎沒有區別了,這說明無論是尾部追加還是隨機位置寫入方式,性能都是一樣的,因為根據前面介紹的原理,OS通過iNode中的磁盤占用塊哈希表,可以快速定位到目標磁盤物理位置,其演算法時間復雜度是O(1),所以尾部追加也是一樣的定位效率, 可能也有些人想說怎么不試試BufferWriter、FileWriter等寫入類,效率高幾個數量級比RandomAccessFile ,我也的確測驗過,但是這些類都是采用尾部追加模式,無法和其隨機位置寫入做比較,所以沒法拿出來測驗說明,但是這些類寫入性能之所以遠高于直接用RandomAccessFile 尾部追加,我想是因為api方法做了用戶程式層面的優化,比如批量寫入,批量轉化成Byte之類的,而RandomAccessFile 可能就是最原始的直接對接OS系統呼叫層的API了,

3、mmap記憶體映射技術之所以快,是因為直接把磁盤檔案映射到用戶空間記憶體,不走內核態,理解錯誤!

  這也是一種常見的認知誤區,實際上這個技術是作業系統給用戶程式提供的一個系統呼叫函式,它把檔案映射到OS內核緩沖區空間,同時共享給用戶行程,也可以共享給多個用戶行程,映射程序中不會產生實際的資料從磁盤真正調取動作,只有用戶程式需要的時候才會調入部分資料,總之也是和普通檔案讀取一樣按需調取,那么mmap技術為什么在讀取資料時會比普通read操作快幾個數量級呢? 上面我們講述了普通讀寫操作的記憶體模型,用戶程式要讀取到磁盤上的資料,要經歷4次內核態切換以及2次資料拷貝操作,那么mmap技術由于是和用戶行程共享內核緩沖區,所以少了一次拷貝操作(資料從內核緩沖區到用戶行程緩沖區),從而大大提高了性能,如下圖所示,

4、mmap記憶體映射技術寫檔案快是因為順序寫磁盤,理解錯誤!

  上面的問題基本已經讓我們理解了mmap技術的記憶體模型,同樣的,我們寫檔案時,由于也少了一次資料從用戶緩沖區到內核緩沖區的拷貝操作,使得我們的寫效率非常的高,并不是很多人認為的資料直達磁盤,中間不經過內核態切換,并且連續在磁盤上分配空間寫入,這些理解都是錯誤的,

5、隨機讀寫檔案比順序讀寫檔案慢,是因為磁盤移動磁頭來回隨機移動導致,理解錯誤!

  這也是一種常見的誤區,我看過很多文章都是這樣認為的,其實所有的寫操作在硬體磁盤層面上都是隨機寫,這是由現代作業系統的檔案系統設計方案決定的,我們用戶程式寫入資料提交給OS緩沖區之后,就與我們沒關系了,作業系統決定何時寫入磁盤中的某個空閑塊,所有的檔案都不是連續分配的,都是以塊為單位分散存盤在磁盤上,原因也很簡單,系統運行一段時間后,我們對檔案的增刪改會導致磁盤上資料無法連續,非常的分散,   當然OS提交PageCache中的寫入資料時,也有一定的優化機制,它會讓本次需要提交給磁盤的資料規劃好磁頭調度的策略,讓寫入成本最小化,這就是磁盤調度演算法中的電梯演算法了,這里就不深入講解了,   至于讀檔案,順序讀也只是邏輯上的順序,也就是按照當前檔案的相對偏移量順序讀取,并非磁盤上連續空間讀取,即便是seek系統呼叫方法隨機定位讀,理論上效率也是差不多的,都是使用iNode的磁盤占用塊索引檔案快速定位物理塊,

 

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

標籤:Java

上一篇:同事亂用 Redis 卡爆,我真是醉了...

下一篇:這樣調優之后,單機也能扛下100W連接

標籤雲
其他(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