主頁 > 後端開發 > 從理論到實踐,刨根問底探索Java物件記憶體布局

從理論到實踐,刨根問底探索Java物件記憶體布局

2022-04-19 06:44:43 後端開發

從理論到實踐,刨根問底探索Java物件記憶體布局

所謂物件的記憶體布局,就是物件在分配到記憶體中后的存盤格式,

物件在記憶體中的布局一共包含三部分:

  1. 物件頭(Header)
  2. 實體資料(Instance Data)
  3. 對齊填充(Padding)

img

第一部分:物件頭

首先來看一下物件頭的結構

Java物件頭分為兩部分:

  1. Mark Word:物件自身運行時資料,
  2. Klass Pointer:型別指標,即物件指向它的類元資料的指標,

1、Mark Word

為啥叫Mark Word呢?我理解因為這部分是用來標記物件運行時的資料和狀態,比如物件的HashCode、GC分代年齡、鎖狀態標志、執行緒持有的鎖、偏向執行緒ID等,而Word呢,是因為這段資訊是用一個Word(字)長度來保存的,在32位系統中,一個字是32bit,也就是4位元組,64位系統中,一個字是64bit,也就是8位元組,

對于這部分的描述,從markOop.hpp原始碼的注釋中即可得知,

下面就以32位的虛擬機為例,來探尋一下物件頭的Mark Word部分是什么樣的資料結構,

先劃重點,鎖狀態很重要

這里要注意兩點:

  1. 物件頭的資料格式和物件的鎖狀態緊密相關,在不同的鎖狀態下,物件頭的結構都不一樣,其目的是為了盡量在極小的空間記憶體儲盡量多的資訊,
  2. 鎖狀態的標志位是固定的,無論是32位還是64位的虛擬機,物件頭中最后兩位就是鎖的狀態標志,

既然鎖的狀態很重要,那么就先看一下下鎖標志對應的狀態含義:

lock 狀態
01 無鎖
00 輕量級鎖(locked)
10 重量級鎖(monitor,inflated lock)
11 GC標記(marked)

01-無鎖狀態下的Mark Word結構

無鎖狀態下,涉及到兩種情況:

  1. 稀松平常的無鎖狀態
  2. 偏向鎖

是否存在偏向鎖,我們也是用1位長的標識來判斷,

當不存在偏向鎖時,1-25位是物件的HashCode,之后的4位是物件GC的分代年齡,之后的1位是偏向鎖的標志,此時該標志為0,

當存在偏向鎖時,1-23為是持有偏向鎖的執行緒的ID,之后的2位是偏向時間戳,然后4位依舊是物件GC的分代年齡,再之后的1位是偏向鎖的標志,此時該標志為1,

00-輕量級鎖狀態下的Mark Word結構

輕量級鎖狀態時,物件頭的前30位保存指向持有鎖的執行緒的堆疊幀中鎖記錄的指標,

此時,獲取了該物件偏向鎖的執行緒,會在執行緒的堆疊幀上建立鎖記錄的空間,并通過CAS的方式將物件頭的資訊復制到鎖記錄的位置,并將物件頭替換成指向鎖記錄的指標,

10-重量級鎖狀態下的Mark Word結構

當有兩個及以上的執行緒競爭同一個鎖,則輕量級鎖就會升級成重量級鎖,此時物件頭的前30位保存的是指向重量級鎖Monitor的指標,

關于Monitor,這里可以做個簡單的理解:Java的重量級鎖,是通過一個Monitor物件來實作的,JVM通過Monitor物件中的_owner、_EntryList來維護是哪個執行緒持有這個物件的鎖,以及后續的阻塞執行緒,原始碼在objectWaiter.hpp中可以深入了解,

11-GC標記

當最后兩位為11時,代表被GC標記了,則物件頭前面的30位資訊為空,

Mark Word 小結

在這里根據上面的描述,畫個圖來展示一下Mark Word這部分資料在32位系統和62位系統里的布局,更直觀清晰一些,

img

img

2、Klass Pointer(型別指標)

緊跟著Mark Word,是物件頭的另一部分——型別指標,型別指標也是用一個字的長度(32位系統是4byte,64位系統是8byte)來保存的,這個指標會指向該物件對應的類元資料,說人話就是,JVM通過這個指標知道這個物件是哪個類的實體,

3、陣列長度

如果這是一個普通的Java物件,則物件頭中只有Mark Word和Klass Pointer兩部分,當它是一個陣列物件時,物件頭中還需要一部分空間來保存陣列的長度,有了陣列長度,JVM才能夠知道一個陣列物件的大小,陣列長度這部分也是用一個字的長度(32位系統是4byte,64位系統是8byte)來保存,

如果64位的JVM開啟了+UseCompressedOops選項,則型別指標和陣列長度這兩個區域都會被壓縮成32位,

第二部分:實體資料

這部分就是物件真正存盤的有效資訊,也就是類里定義的各種型別欄位的內容,

第三部分:對齊填充

這部分沒有實際的含義,僅僅是起到占位符的作用,因為JVM要求物件起始地址必須是8位元組的整數倍,也就是一個物件的大小必須是8位元組的整數倍,所以如果一個物件的實體資料不滿足8位元組的整數倍,則需要做一個對齊填充的操作,保證物件的大小是8位元組的整數倍,

實踐一下

接下來我們來跑幾個demo看看真實的物件頭布局,借助JOL(Java Object Layout),我們可以分析JVM中物件的布局,

環境:64位系統,JDK8,

1、引入JOL依賴

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

2、一個簡單的類

public class OneObject {
    private int id;
    private String name;
    private double score;
}

3、列印物件記憶體布局

這里選擇了五種情況:

  1. 剛剛new出來的新鮮物件
  2. 經歷過gc后的物件
  3. 加鎖時的物件(偏向鎖)
  4. 多執行緒競爭鎖時的物件(重量級鎖)
  5. 算一下物件的hashCode

代碼如下:

@Test
public void showObjectData() throws InterruptedException {
    OneObject object = new OneObject();
    log.info("初始化后的物件布局:{}", ClassLayout.parseInstance(object).toPrintable());

    System.gc();
    log.info("gc一次之后的物件布局:{}", ClassLayout.parseInstance(object).toPrintable());

    synchronized (object) {
        log.info("加鎖時的物件布局:{}", ClassLayout.parseInstance(object).toPrintable());
    }

    for (int i = 0; i < 2; i++) {
        Thread thread = new Thread(()->{
            synchronized (object) {
                log.info("競爭鎖時的物件布局:{}", ClassLayout.parseInstance(object).toPrintable());
            }
        });
        thread.start();
    }

    Thread.sleep(500);
    object.hashCode();
    log.info("計算完hashCode的物件布局:{}", ClassLayout.parseInstance(object).toPrintable());
}

4、輸出結果

執行以上的代碼,輸出結果如下:

00:06:03.877 [main] INFO com.esparks.pandora.learning.vm.LearnObjectData - 初始化后的物件布局:com.esparks.pandora.learning.vm.OneObject object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           38 a1 08 00 (00111000 10100001 00001000 00000000) (565560)
     12     4                int OneObject.id                              0
     16     8             double OneObject.score                           0.0
     24     4   java.lang.String OneObject.name                            null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00:06:03.890 [main] INFO com.esparks.pandora.learning.vm.LearnObjectData - gc一次之后的物件布局:com.esparks.pandora.learning.vm.OneObject object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           38 a1 08 00 (00111000 10100001 00001000 00000000) (565560)
     12     4                int OneObject.id                              0
     16     8             double OneObject.score                           0.0
     24     4   java.lang.String OneObject.name                            null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00:06:03.891 [main] INFO com.esparks.pandora.learning.vm.LearnObjectData - 加鎖時的物件布局:com.esparks.pandora.learning.vm.OneObject object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           90 99 0b 6d (10010000 10011001 00001011 01101101) (1829476752)
      4     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      8     4                    (object header)                           38 a1 08 00 (00111000 10100001 00001000 00000000) (565560)
     12     4                int OneObject.id                              0
     16     8             double OneObject.score                           0.0
     24     4   java.lang.String OneObject.name                            null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00:06:03.892 [Thread-1] INFO com.esparks.pandora.learning.vm.LearnObjectData - 競爭鎖時的物件布局:com.esparks.pandora.learning.vm.OneObject object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           62 d9 00 20 (01100010 11011001 00000000 00100000) (536926562)
      4     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      8     4                    (object header)                           38 a1 08 00 (00111000 10100001 00001000 00000000) (565560)
     12     4                int OneObject.id                              0
     16     8             double OneObject.score                           0.0
     24     4   java.lang.String OneObject.name                            null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00:06:03.893 [Thread-2] INFO com.esparks.pandora.learning.vm.LearnObjectData - 競爭鎖時的物件布局:com.esparks.pandora.learning.vm.OneObject object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           62 d9 00 20 (01100010 11011001 00000000 00100000) (536926562)
      4     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      8     4                    (object header)                           38 a1 08 00 (00111000 10100001 00001000 00000000) (565560)
     12     4                int OneObject.id                              0
     16     8             double OneObject.score                           0.0
     24     4   java.lang.String OneObject.name                            null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00:06:04.398 [main] INFO com.esparks.pandora.learning.vm.LearnObjectData - 計算完hashCode的物件布局:com.esparks.pandora.learning.vm.OneObject object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           09 63 c1 aa (00001001 01100011 11000001 10101010) (-1430166775)
      4     4                    (object header)                           56 00 00 00 (01010110 00000000 00000000 00000000) (86)
      8     4                    (object header)                           38 a1 08 00 (00111000 10100001 00001000 00000000) (565560)
     12     4                int OneObject.id                              0
     16     8             double OneObject.score                           0.0
     24     4   java.lang.String OneObject.name                            null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

物件布局分析

這里我們先以第一種情況的物件布局來簡單說明輸出的格式,直接在圖上標明啦,

img

這里要插一句,JOL在輸出物件頭的時候,是按照四個位元組的長度從記憶體中獲取物件的物件頭資料,所以你會看到64位的Mark Word會拆成兩行(兩個4位元組)列印出來,

幾個問題

看到這里的時候,腦海里不禁冒出幾個問題,

Q1:為什么Klass pointer只有四個位元組呢?

因為JVM默認開啟了+UseCompressedOops選項,所以Klass Pointer被壓縮成了32位,如果在啟動時配置了-UseCompressedOops選項,那么Klass Pointer就也是64位啦,

Q2:說好了Mark Word的最后兩位是鎖狀態,這剛創建的物件,最后兩位怎么就是00了呢?

這個就要和位元組存盤的大小端模式有關了,

舉個例子,一個16進制的整數0x12345678,對應的二進制整數為:00010010 00110100 01010110 01111000(12 34 56 78),一共占用四個位元組,那么在記憶體中應該如何存盤這長度為4byte的位元組序列的資料呢?

有兩種方式:

  1. 按照記憶體地址的順序,依次保存12 34 56 78這四個位元組的資料,這種將位元組序列的高序位元組存盤在記憶體的起始地址上的方式,叫大端模式,
  2. 按照記憶體地址的順序,依次保存78 56 34 12這四個位元組的資料,這種將位元組序列的低序位元組存盤在記憶體的起始地址上的方式,叫小端模式,

img

而一般我們用的x86或者ARM的CPU,采用的都是小端模式來保存記憶體中的位元組序列,所以和我們常見的順序是反著的,因此,你看到的輸出的前兩行的物件頭,實際上的值是這樣的:

img

所以物件初始化后,就是無鎖狀態啦,

分析不同狀態時的物件頭

剛才已經就剛初始化的物件分析過一次記憶體布局了,而在鎖狀態不同的情況下,變化也只限于物件頭中Mark Word的值變動,所以這里就快速的分析一下其余的四種狀態時的Mark Word了,這里我也自動將輸出的小端格式轉換成正常的順序來分析,也是通過實際情況來回顧驗證一下剛才的理論知識啦,對照前面的圖片中不同的顏色比對就可以了,

1.gc一次之后的Mark Word

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001001

紅色的01代表無鎖狀態,藍色的0代表無偏向鎖,黃色的0001是GC分代年齡,因為GC過一次,該物件沒有被回收,年齡加1,

2.加鎖時的Mark Word

00000000 00000000 00000000 00000001 01101101 00001011 10011001 10010000

紅色的00代表輕量級鎖狀態,綠色的一串是指向持有鎖的執行緒的堆疊幀中鎖記錄的指標,

3.競爭鎖時的Mark Word

00000000 00000000 00000000 00000001 00100000 00000000 11011001 01100010

紅色的10代表重量級鎖狀態,綠色的一串是指向重量級鎖Monitor的指標,

4.計算完hashCode的Mark Word

00000000 00000000 00000000 01010110 10101010 11000001 01100011 00001001

紅色的01代表此時回歸到無鎖狀態,,藍色的0代表無偏向鎖,黃色的0001是GC分代年齡為1,紫色這一串是剛才計算的hashCode,保存在了這里,所以只有在呼叫了hashCode()方法時,JVM才會把物件的hashCode保存到物件頭中,

總結

好啦,總結一下,

本篇文章先是介紹了Java物件的記憶體布局(由物件頭、實體資料、對齊填充三部分組成);之后詳細的介紹了物件頭的資料結構(Mark Word、Klass Pointer、陣列長度),以及不同鎖狀態下(01無鎖、00輕量級鎖、10重量級鎖、11GC標記),Mark Word中的資料格式以及代表的含義;最后通過JOL列印出物件的記憶體布局,進一步驗證了前半部分枯燥的理論知識,

希望看到這里,能讓你徹底的理解Java物件在記憶體中的完整樣貌啦~

參考資料

  1. markOop.hpp原始碼,主要在注釋中
  2. objectWaiter.hpp原始碼
  3. 《深入理解Java虛擬機》

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

標籤:Java

上一篇:java httpClient訪問https 跳過驗證

下一篇:作業 3 年的同事不知道如何回滾代碼,我真是醉了。。

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