Go語言Slice切片底層

Go語言(Golang)中切片(slice)的相關知識、包括切片與數組的關系、底層結構、擴容機制、以及切片在函數傳遞、截取、增刪元素、拷貝等操作中的特性。并給出了相關代碼示例和一道面試題。關鍵要點包括:

  1. 數組特性:Go語言中數組是一個值、數組變量表示整個數組、不同于C語言中指向第一個元素的指針。傳遞數組到函數或拷貝數組時、會有不同的內存地址和數據獨立性表現。

  2. 切片定義:切片是建立在Go數組之上的抽象類型、其底層結構包含指向底層數組的指針、長度和容量。

  3. 切片擴容

  • 新切片長度大于舊切片容量兩倍時、新容量為新長度

  • 舊容量小于256時、新容量為舊容量兩倍

  • 否則按1.25倍增速擴容、還會進行內存對齊。

  1. 函數傳遞:切片通過函數傳遞時、傳的是切片結構、在函數內改變切片可能影響函數外的切片、取決于底層數組是否變化。

  2. 切片操作

  • 通過?“:”?作截取切片、新切片與原切片共享底層數組

  • 刪除元素可通過拼接切片實現

  • 新增元素使用append操作

  • 深度拷貝可使用copy函數。

1.切片是什么

在Go語言中 切片(slice)是建立在數組之上的一種抽象類型。切片提供了一種更靈活的方式來處理數組、它允許動態地改變數組的大小、并且可以方便地進行切片操作。理解切片之前、我們需要先了解數組。

Go的數組 在Go語言中、數組的長度是類型的一部分、這意味著數組的長度是固定的、不能改變。

數組的傳遞和拷貝行為與C語言不同、Go語言中的數組是值類型、傳遞數組時會進行值拷貝。

1.1 示例一:

將數組傳遞到函數中 數組的地址不一樣

package mainimport "fmt"func main() {array := [3]int{1, 2, 3}// 數組傳遞到函數中test(array)fmt.Printf("array 外: %p\n", &array)
}func test(array [3]int) {fmt.Printf("array 內: %p\n", &array)}
  • 由于數組是值類型、傳遞數組時會進行值拷貝、因此在test函數中打印的地址與main函數中打印的地址不同。

1.2 值拷貝

值拷貝意味著拷貝的是變量的內容、而不是內存地址。因此拷貝出來的變量有自己的獨立副本、內容相同、但它們存儲在不同的內存地址中。

Go 語言中的切片(slice)是動態擴容的。當你向切片中添加元素時、Go 會自動管理切片的大小、并在需要時進行擴容。

具體行為:

  • 初始容量:當你創建一個切片時、Go 會為切片分配一個初始容量。如果你添加的元素超過了切片當前的容量Go 會自動擴容。

  • 擴容規則:Go 會根據當前切片的容量自動擴展切片的大小、通常是原來容量的2倍。擴容后、切片的長度和容量都會增加。

  • 內部機制:當切片擴容時、Go 會為新切片分配新的底層數組、并將原數組的元素拷貝到新數組中。這是一個代價比較高的操作、尤其是在需要多次擴容的情況下

2.底層結構

type slice struct {// 底層數組指針(或者說是指向一塊連續內存空間的起點)array unsafe.Pointer// 長度len  int// 容量cap  int
}

在這個結構中:

  • array:指向底層數組的指針、或者說是指向一塊連續內存空間的起點。

  • len:切片的長度、即切片中實際包含的元素數量。

  • cap:切片的容量、即切片可以包含的元素的最大數量,不包括可能的擴展空間。

切片擴容

  1. 計算目標容量

    1. case1: 如果新切片的長度大于舊切片容量的兩倍、則新切片容量就為新切片的長度。

    2. case2:

      • 如果舊切片的容量小于256、那么新切片的容量就是舊切片的容量的兩倍。

      • 反之需要用舊切片容量按照1.25倍的增速、直到大于新切片長度。

    3. 為了更平滑的過渡、每次擴大1.25倍、還會加上3/4 * 256

    4. 進行內存對齊、需要按照Go內存管理的級別去對齊內存、最終容量以這個為準。

3.切片問題

3.1 切片通過函數傳的是什么

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5, 10)PrintSliceStruct(&s)test(s)
}func test(s []int) {PrintSliceStruct(&s)
}func PrintSliceStruct(s *[]int) {// 代碼 將slice 轉換成 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 查看slice的結構fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

控制臺輸出:

slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
  • 切片的定義:你創建了一個切片 s、通過 make([]int, 5, 10) 創建了一個長度為 5、容量為 10 的切片。

  • 也就是說它初始化了一個包含 5 個元素且最大容量為 10 的底層數組

總結:

  • 切片傳遞:當切片通過參數傳遞到函數時、傳遞的是切片的值、但切片內部的底層數組地址(指針)并沒有被復制。

  • PrintSliceStruct 打印的結構:無論是在 main 函數還是 test 函數中、切片的底層數組地址、長度和容量都是相同的、因為底層數組是共享的。

為什么輸出相同:

輸出顯示的 Data 地址、Len 和 Cap 是一致的、因為 test(s) 傳遞的是切片的值(即切片的結構),但切片中的指針指向相同的底層數組。所以無論是傳遞給 PrintSliceStruct 函數的 s、還是 test 函數中的 s、它們指向的是同一個底層數組、并且它們的長度和容量保持一致

3.2 在函數里面改變切片 函數外的切片會被影響嗎

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5) // 創建一個長度為 5 的切片case1(s)             // 調用 case1 函數case2(s)             // 調用 case2 函數PrintSliceStruct(&s) // 打印切片結構
}// 底層數組不變
func case1(s []int) {s[1] = 1 // 修改切片中的元素PrintSliceStruct(&s) // 打印切片結構
}// 底層數組變化
func case2(s []int) {s = append(s, 0) // 擴容切片s[1] = 1         // 修改切片中的元素PrintSliceStruct(&s) // 打印切片結構
}func PrintSliceStruct(s *[]int) {// 將切片轉換成 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底層結構fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

關鍵點:

  • case1 函數:

  • case1 中、你傳入一個長度為 5 的切片 s、并修改切片中的元素。

  • 切片在函數內的操作是對原切片的修改、因此底層數組沒有發生變化、切片的容量、長度仍然相同。

  • 打印的 slice struct 的 Data、LenCap 字段顯示的是切片的原始底層數據結構。

  • case2 函數:

  • case2 中、你向切片添加一個元素(通過 append 操作)、這將可能導致切片的底層數組擴容。

  • 因為 append 操作在超出當前容量時會觸發擴容、所以 s 的底層數組會發生變化、容量也可能增加。

  • case2 中、s 被賦值為 append(s, 0)、這將導致原有切片 s 的底層數組被擴展、并且一個新的數組被分配給 s(s 指向的是新的底層數組)

  • 打印時會看到 slice struct 中的 Data 指向一個新的地址、表示底層數組已經發生了變化。

  • append(s, 0) 函數會檢查切片 s 是否有足夠的容量來存儲新的元素。如果切片的容量不足、append 函數會分配一個新的更大的數組、并復制舊數組的內容到新數組中、然后將新元素添加到新數組的末尾、并更新切片的指針以指向包含新元素的新底層數組。

3.3 截取切片

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5)  // 創建一個長度為 5 的切片,默認初始化為 [0 0 0 0 0]case1(s)  // 調用 case1,修改切片內容case2(s)  // 調用 case2,修改切片并改變底層數組case3(s)  // 調用 case3,截取切片并改變其長度case4(s)  // 調用 case4,截取切片的部分元素PrintSliceStruct(&s)  // 最后打印切片的底層結構
}// case1:修改切片元素、底層數組不變
func case1(s []int) {s[1] = 1  // 修改切片中的第二個元素,s[1] = 1PrintSliceStruct(&s)  // 打印修改后的切片底層結構
}// case2:重新賦值為新的切片
func case2(s []int) {s = s[:]  // 這里實際上并沒有改變切片的內容、它只是重新賦值為原切片的一個新引用。PrintSliceStruct(&s)  // 打印新的切片底層結構
}// case3:截取切片、底層數組不變
func case3(s []int) {s = s[:len(s)-1]  // 截取切片、去掉最后一個元素、新的切片長度為 4PrintSliceStruct(&s)  // 打印截取后的切片底層結構
}// case4:截取切片的部分元素、底層數組不變
func case4(s []int) {sl := s[1:2]  // 截取 s[1:2],即取出切片中索引為 1 的元素PrintSliceStruct(&sl)  // 打印截取后的新切片底層結構
}// PrintSliceStruct 打印切片的底層結構
func PrintSliceStruct(s *[]int) {// 將切片的指針轉換為 reflect.SliceHeader 結構體,通過 unsafe.Pointer 獲取底層數據ss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底層數據結構、包括:指向底層數組的內存地址、切片的長度和容量fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

總結:

  • 切片操作的影響

  • 修改切片元素不會改變底層數組的地址。

  • 重新賦值切片并沒有改變底層數組、除非涉及擴容(例如 append)。

  • 截取切片時、底層數組不變、切片的長度和容量可能會變化。

3.4 刪除元素

package mainimport ("fmt""reflect""unsafe"
)func main() {// 創建一個包含5個整數的切片s := []int{0, 1, 2, 3, 4}// 打印切片的底層結構PrintSliceStruct(&s)// 刪除切片中的最后一個元素,正確的做法是通過切片截取_ = s[4]                      // 訪問并丟棄切片中的最后一個元素s1 := append(s[:1], s[2:]...) // 刪除元素 s[1],//s[:1](即切片 [0])和 s[2:](即切片 [2, 3, 4])拼接在一起。// 打印修改后的切片fmt.Println(s)  // [0 2 3 4 4]fmt.Println(s1) // [0, 2, 3, 4]// 打印切片底層結構PrintSliceStruct(&s)PrintSliceStruct(&s1)// 訪問切片的元素s = s[:4] // 截取切片、刪除最后一個元素_ = s[3]  // 訪問切片中的最后一個元素(索引為3的元素)
}// 打印切片的底層結構
func PrintSliceStruct(s *[]int) {// 將切片轉換為 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底層結構fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}
  • 在 Go 中、切片操作需要特別注意切片的索引和截取。訪問切片中的元素時要小心類型不匹配(例如不能將一個切片元素賦值給切片)。

控制臺輸出:

slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 1 2 3 4]
[0 2 3 4 4]
[0 2 3 4]
slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 2 3 4 4]
slice struct: &{Data:1374390755328 Len:4 Cap:5}, slice is [0 2 3 4]

打印原切片 s 時、它仍然指向原底層數組(長度為 5、容量為 5)、而且由于 s[4] 在內存中并沒有被移除、原底層數組中的最后一個元素 4 被保留、因此 s 顯示為 [0 2 3 4 4]。

簡而言之s 顯示為 [0 2 3 4, 4] 是因為原始切片的底層數組并沒有被修改、而 append 操作生成了一個新的切片(s1)并分配了新的底層數組。所以s 中仍然包含原數組中的所有元素、最后一個 4 仍然存在。

為什么 s變成了 [0, 2, 3, 4, 4]

  • append 會根據切片的容量決定是否會使用原來的底層數組。如果原切片的容量足夠大、append 就會直接修改原切片。

  • 在這段代碼中、由于原始切片 s 的容量足夠大(原始切片 s 的容量為 5)、append 仍然修改了原始切片 s 的內容。切片的 s 和 s1 都指向相同的底層數組。

重點:

  • 原切片 s 的容量沒有改變:s 底層的數組仍然包含原來 s 的所有元素。

  • append 沒有重新分配新的底層數組:由于原切片的容量足夠、所以 append 在修改原底層數組時、并沒有創建新的底層數組。因此原始切片 s 中的 4 仍然存在。

  • 修改后 s 中的元素為 [0, 2, 3, 4, 4]:雖然你刪除了 s[1] 這個元素、但 append 使得 s 的底層數組沒有發生變化,因此原始的 4 元素仍然保留在切片中。

結論:

append 操作有時會創建新的底層數組(如果容量不足)、但如果原切片的容量足夠、append 直接修改原切片的底層數組。在這種情況下原切片 s 會保持原來的容量和數據、導致 s 顯示為 [0, 2, 3, 4, 4],即最后一個 4 保留下來了。

3.5 新增元素

package mainimport ("fmt""reflect""unsafe"
)func main() {case1()case2()case3()
}// case1 函數展示了使用 append 在切片末尾添加元素的行為
func case1() {// 創建一個長度為 3,容量為 3 的切片s1 := make([]int, 3, 3)// 向切片添加一個元素 1,append 返回一個新的切片s1 = append(s1, 1)// 打印切片的底層結構PrintSliceStruct(&s1) //1
}// case2 函數展示了在原切片上使用 append 并打印切片結構的變化
func case2() {// 創建一個長度為 3,容量為 4 的切片s1 := make([]int, 3, 4)// 向切片添加一個元素 1,append 會擴展切片的長度s2 := append(s1, 1)// 打印原切片 s1 和新切片 s2 的底層結構PrintSliceStruct(&s1)//2PrintSliceStruct(&s2)//3
}// case3 函數與 case2 類似,展示了切片長度、容量變化的行為
func case3() {// 創建一個長度為 3,容量為 3 的切片s1 := make([]int, 3, 3)// 向切片添加一個元素 1,append 返回一個新的切片s2 := append(s1, 1)// 打印原切片 s1 和新切片 s2 的底層結構PrintSliceStruct(&s1)//4PrintSliceStruct(&s2)//5
}// PrintSliceStruct 打印切片的底層結構
func PrintSliceStruct(s *[]int) {// 使用 reflect 和 unsafe 包將切片轉換成 reflect.SliceHeader 結構體ss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底層結構fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

控制臺輸出

slice struct: &{Data:1374390755328 Len:4 Cap:6}, slice is [0 0 0 1]
slice struct: &{Data:1374390779936 Len:3 Cap:4}, slice is [0 0 0]
slice struct: &{Data:1374390779936 Len:4 Cap:4}, slice is [0 0 0 1]
slice struct: &{Data:1374390673552 Len:3 Cap:3}, slice is [0 0 0]
slice struct: &{Data:1374390755376 Len:4 Cap:6}, slice is [0 0 0 1]

case1:

  • 使用 make([]int, 3, 3) 創建了一個長度為 3,容量為 3 的切片 s1,初始內容為 [0, 0, 0]。

  • 然后append(s1, 1) 會將元素 1 添加到切片的末尾、生成一個新的切片并返回。由于容量是 3、append 會自動擴容新的切片長度是 4

  • 最后調用 PrintSliceStruct 打印 s1 切片的底層結構。

case2:

  • make([]int, 3, 4) 創建了一個長度為 3、容量為 4 的切片 s1

  • 使用 append(s1, 1) 向切片添加元素 1、生成一個新切片 s2。由于 s1 的容量已足夠、不會觸發擴容。

  • 通過 PrintSliceStruct 打印切片 s1 和 s2 的底層結構。

s3 和 s4 參考上面

3.6 操作原來切片會影響新的切片嗎

在 Go 中、切片是引用類型,這意味著當你創建一個新切片時,它實際上可能會指向同一個底層數組。因此,如果你修改了原切片(比如通過 append 或其他操作),它可能會影響到新切片,特別是在底層數組沒有被重新分配的情況下。

切片和底層數組

  • 切片(slice)是一個非常輕量級的抽象,它包含了三個部分:指向底層數組的指針、切片的長度和切片的容量。

  • 當你對切片進行操作時(例如使用 append、copy 或直接修改),這些操作通常會影響到底層數組。

  • 如果多個切片引用同一個底層數組,改變其中一個切片的內容可能會影響到其他切片,尤其是在沒有擴容時。

append操作

  • 當使用 append 函數時、如果切片的容量足夠、append 會直接在原底層數組上操作、不會創建新的底層數組。在這種情況下、修改原切片的內容會影響到新切片、因為它們指向相同的底層數組。

  • 如果容量不足、append 會創建一個新的底層數組、并將原切片的數據復制到新數組中、這時原切片和新切片就指向不同的底層數組了、它們互不影響。

例子:

沒有擴容:

s1 := []int{1, 2, 3}
s2 := s1      // s2 指向與 s1 相同的底層數組
s1[0] = 100   // 修改 s1 中的第一個元素
fmt.Println(s1) // 輸出 [100, 2, 3]
fmt.Println(s2) // 輸出 [100, 2, 3]

這里s1 和 s2 指向相同的底層數組,因此修改 s1 會影響到 s2。

擴容時:

s1 := []int{1, 2, 3}
s2 := append(s1, 4)  // s2 創建了新的底層數組
s1[0] = 100          // 修改 s1 中的第一個元素
fmt.Println(s1) // 輸出 [100, 2, 3]
fmt.Println(s2) // 輸出 [1、2、3、 4]

這里s2 創建了一個新的底層數組,因此修改 s1 不會影響 s2。

結論:

  • 修改原切片會影響新切片:如果新切片是通過引用原切片的底層數組創建的(沒有觸發擴容)、修改原切片的內容會影響到新切片。

  • 擴容時不影響:如果 append 或其他操作導致了擴容、原切片和新切片就會指向不同的底層數組、互不影響。

4.字節面試題

下面這道題的輸出是什么

package mainimport "fmt"func main() {// 定義一個匿名函數 doAppend,用來執行 append 操作并打印切片的長度和容量doAppend := func(s []int) {s = append(s, 1)         // 向切片中添加元素 1printLengthAndCapacity(s) // 打印切片的長度和容量}// 創建一個長度為 8,容量為 8 的切片 ss := make([]int, 8, 8)// 傳遞 s 的前 4 個元素(即 s[:4])到 doAppenddoAppend(s[:4])  // 只傳遞前4個元素的切片// 打印原始切片 s 的長度和容量printLengthAndCapacity(s)// 傳遞整個切片 s 到 doAppenddoAppend(s)// 打印原始切片 s 的長度和容量printLengthAndCapacity(s)
}func printLengthAndCapacity(s []int) {fmt.Println() fmt.Printf("len=%d cap=%d \n", len(s), cap(s))  // 打印切片的長度和容量
}
  • len(s) 是切片的長度。

  • cap(s) 是切片的容量、表示切片底層數組的大小。

len=5 cap=8 len=8 cap=8 len=9 cap=16 len=8 cap=8

調用 doAppend(s[:4]):

  • s[:4] 是 s 切片的前 4 個元素、創建一個新的切片 [0, 0, 0, 0],長度為 4、容量為 8(因為它引用的是原切片的底層數組)。

  • 在 doAppend 中、執行 append(s, 1)、這會向切片添加一個元素 1、導致切片的長度變為 5、容量保持為 8(因為它沒有觸發擴容)。

  • 打印結果為:len=5 cap=8。

調用 doAppend(s)

  • 這次傳遞整個 s 切片、長度為 8、容量為 8。

  • 執行 append(s 1)、這會向切片 s 添加一個元素 1。因為 s 的容量是 8、不能再容納更多元素、因此會觸發擴容、新的底層數組的容量將是原來的 2 倍、即 16、長度變為 9。

  • 打印結果為:len=9 cap=16。

  • 注意:

  • append 創建了一個新的底層數組、并返回了一個新的切片。如果你不把返回的新切片賦值回 s、原始切片 s 不會改變、仍然指向舊的底層數組。

  • 由于 append(s 1) 返回的是一個新的切片、但并沒有將它賦值回 s、所以原始切片 s 的長度和容量沒有變化、仍然是 len=8 和 cap=8

如果改成將新切片賦值回s

package mainimport "fmt"func main() {// 定義一個匿名函數 doAppend 用于向切片添加元素doAppend := func(s []int) {s = append(s, 1)          // 向切片中添加元素 1printLengthAndCapacity(s) // 打印切片的長度和容量}// 定義一個匿名函數 doAppend 用于向切片添加元素doAppends := func(s []int) []int {s = append(s, 1) // 使用 append 向切片添加一個元素 1printLengthAndCapacity(s)return s}// 創建一個長度為 8,容量為 8 的切片 ss := make([]int, 8, 8)// 傳遞前 4 個元素的切片doAppend(s[:4]) // 只傳遞前4個元素的切片printLengthAndCapacity(s)// 傳遞整個切片 ss = doAppends(s) // 將返回的新切片賦值回 sprintLengthAndCapacity(s)
}func printLengthAndCapacity(s []int) {fmt.Println()fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}
len=5 cap=8 len=8 cap=8 len=9 cap=16 len=9 cap=16 

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75672.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75672.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75672.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vue3 ts 自定義指令 app.directive

在 Vue 3 中,app.directive 是一個全局 API,用于注冊或獲取全局自定義指令。以下是關于 app.directive 的詳細說明和使用方法 app.directive 用于定義全局指令,這些指令可以用于直接操作 DOM 元素。自定義指令在 Vue 3 中非常強大&#xff0…

基于python的機器學習(五)—— 聚類(二)

一、k-medoids聚類算法 k-medoids是一種聚類算法,它是基于k-means聚類算法的一種改進。k-medoids算法也是一種迭代算法,但是它將中心點限定為數據集中的實際樣本點,而不是任意的點。 具體來說,k-medoids算法從數據集中選擇k個初…

解釋:指數加權移動平均(EWMA)

指數加權移動平均(EWMA, Exponential Weighted Moving Average) 是一種常用于時間序列平滑、異常檢測、過程控制等領域的統計方法。相比普通移動平均,它對最近的數據賦予更高權重,對舊數據逐漸“淡化”。 ? 一、通俗理解 想象你…

Spring Boot 項目基于責任鏈模式實現復雜接口的解耦和動態編排!

全文目錄: 開篇語前言一、責任鏈模式概述責任鏈模式的組成部分: 二、責任鏈模式的核心優勢三、使用責任鏈模式解耦復雜接口1. 定義 Handler 接口2. 實現具體的 Handler3. 創建訂單對象4. 在 Spring Boot 中使用責任鏈模式5. 配置責任鏈6. 客戶端調用 四、…

COMSOL仿真遇到的兩個小問題

最近跑熱仿真的時候跑出了兩個問題,上網查發現也沒什么解決方式,最后自己誤打誤撞的摸索著解決了,在這里分享一下。 問題一 我當時一準備跑仿真就彈出了這個東西,但在此之前從未遇到 然后我試著在它說的路徑中建立recoveries文件…

如何在英文學術寫作中正確使用標點符號?

標點符號看似微不足道,但它們是書面語言的無名英雄。就像熟練的指揮家指揮管弦樂隊一樣,標點符號可以確保您的寫作流暢、傳達正確的含義并引起讀者的共鳴。正如放錯位置的音符會在音樂中造成不和諧一樣,放錯位置的逗號或缺少分號也會使您的寫…

【深度學習與大模型基礎】第10章-期望、方差和協方差

一、期望 ——————————————————————————————————————————— 1. 期望是什么? 期望(Expectation)可以理解為“長期的平均值”。比如: 擲骰子:一個6面骰子的點數是1~6&#x…

JAVA虛擬機(JVM)學習

入門 什么是JVM JVM:Java Virtual Machine,Java虛擬機。 JVM是JRE(Java Runtime Environment)的一部分,安裝了JRE就相當于安裝了JVM,就可以運行Java程序了。JVM的作用:加載并執行Java字節碼(.class&#…

【數據結構與算法】——堆(補充)

前言 上一篇文章講解了堆的概念和堆排序,本文是對堆的內容補充 主要包括:堆排序的時間復雜度、TOP 這里寫目錄標題 前言正文堆排序的時間復雜度TOP-K 正文 堆排序的時間復雜度 前文提到,利用堆的思想完成的堆排序的代碼如下(包…

什么是柜臺債

柜臺債(柜臺債券業務)是指通過銀行等金融機構的營業網點或電子渠道,為投資者提供債券買賣、托管、結算等服務的業務模式。它允許個人、企業及機構投資者直接參與銀行間債券市場的交易,打破了以往僅限機構參與的壁壘。以下是綜合多…

【Android讀書筆記】讀書筆記記錄

文章目錄 一. Android開發藝術探索1. Activity的生命周期和啟動模式1.1 生命周期全面分析 一. Android開發藝術探索 1. Activity的生命周期和啟動模式 1.1 生命周期全面分析 onPause和onStop onPause后會快速調用onStop,極端條件下直接調用onResume 當用戶打開新…

Java對象內存結構詳解

Java對象內存結構詳解 Java對象在JVM內存中的存儲結構可以分為三個部分:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。以下是64位JVM(開啟壓縮指針)下…

【TI MSPM0】Printf重定向學習

一、新建工程 通過XDS110與電腦進行通信。 選擇這兩個引腳 需要添加這兩個頭文件 在程序中添加這三個函數即可對printf進行重定向 二、封裝函數 另一種方法 封裝一個函數,定義一個數組

深度強化學習基礎 0:通用學習方法

過去自己學習深度強化學習的痛點: 只能看到各種術語、數學公式勉強看懂,沒有建立清晰且準確關聯 多變量交互關系浮于表面,有時候連環境、代理控制的變量都混淆 模型種類繁多,概念繁雜難整合、對比或復用,無框架分析所…

asm匯編源代碼之-字庫轉換程序

將標準的16x16點陣漢字庫(下載16x16漢字庫)轉換成適合VGA文本模式下顯示的點陣漢字庫 本程序需要調用file.asm中的子程序,所以連接時需要把file連接進來,如下 C:\> tlink chghzk file 調用參數描述如下 C:\> chghzk ; 無調用參數,轉換標準庫文件(SRC16.FNT)為適合VGA…

uniapp轉換markdown

效果 AI智能體 微信小程序 流式 1.安裝Node.js 參考:2024最新版Node.js下載安裝及環境配置教程(非常詳細)_node.js 安裝-CSDN博客 2.需要克隆項目到本地或直接到項目地址下載壓縮包。 參考:uniapp中解析markdown支持網頁和小程序_uniapp ma…

用java代碼如何存取數據庫的blob字段

一.業務 在業務中我們被要求將文件或圖片等轉成 byte[] 或 InputStream存到數據庫的Blob類型的字段中. 二.Blob類型介紹 在 MySQL 中,Blob 數據類型用于存儲二進制數據。MySQL 提供了四種不同的 Blob 類型: TINYBLOB: 最大存儲長度為 255 個字節。BL…

qemu(2) -- 定制開發板

1. 前言 qemu支持自定義開發板,本文就記錄一下折騰的過程。基于qemu-10.0.0-rc3添加x210vb3s開發板。 2. 添加板卡文件 網上參考了一些文章,有些文章使用的版本和我的不一樣,折騰起來費了點時間,最后發現還是直接參考qemu中已有…

Python在糖尿病分類問題上尋找具有最佳 ROC AUC 分數和 PR AUC 分數(決策樹、邏輯回歸、KNN、SVM)

Python在糖尿病分類問題上尋找具有最佳 ROC AUC 分數和 PR AUC 分數(決策樹、邏輯回歸、KNN、SVM) 問題模板解題思路1. 導入必要的庫2. 加載數據3. 劃分訓練集和測試集4. 數據預處理5. 定義算法及其參數6. 存儲算法和對應指標7. 訓練模型并計算指標8. 找…

CPU(中央處理器)

一、CPU的定義與核心作用 CPU 是計算機的核心部件,負責 解釋并執行指令、協調各硬件資源 以及 完成數據處理,其性能直接影響計算機的整體效率。 核心功能: 從內存中讀取指令并譯碼。執行算術邏輯運算。控制數據在寄存器、內存和I/O設備間的…