主頁 >  其他 > Go Map 簡介 和 實作決議

Go Map 簡介 和 實作決議

2020-11-14 22:35:15 其他

序言

map 是go 中一種很重要的 映射查找的資料結構,通過 key 的hash 運算來找到 值,這在各個語言中都不少見,這篇我們主要講go map 的使用和其內部實作,

正文

map的使用

關于 map的使用問題, 如下

map的宣告為

/* 宣告變數,默認 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函式 */
map_variable := make(map[key_data_type]value_data_type)

沒有初始化的map不能進行插入操作,需要用make 函式進行初始化后才能進行操作,

map 的簡單函式操作,curd為

   var emptyMap map[string]int
	var initMap = make(map[string]int)
	//emptyMap["ok"]=1
	initMap["init"] = 2
	initMap["ok"] = 4
	delete(initMap, "init")
	for k, v := range initMap {
		fmt.Printf("%v  ,%v\n", k, v)
	}
	_,texist := initMap["fine"]
	fmt.Printf("exists find key is %v\n",texist)
	fmt.Printf("%v ,%v,not init map is nil %v \n", emptyMap, initMap, emptyMap == nil)

輸出情況為

[1 ok [2 3 4]]
ok  ,4
exists find key is false
map[] ,map[ok:4],not init map is nil true 

map的操作都很簡單,在這里不講了,

值得注意的是,這里的map 是非執行緒安全的,如果需要執行緒安全的map ,需要使用 sync.Map 或者自己用 sync.RWMute 自己來做鎖保護,

map 的實作結構

通過搜索 hmap 的結構定義,我們在 reflect 中找到 hmap的定義,這個也就是 go 中map的實際記憶體結構定義, 在 runtime\map.go 中能找到相關定義

// A header for a Go map.

const ( // 關鍵的變數
    bucketCntBits = 3
	bucketCnt     = 1 << bucketCntBits  // 一個桶最多存盤8個key-value對
	loadFactorNum = 13 // 擴散因子:loadFactorNum / loadFactorDen = 6.5,
	loadFactorDen = 2  // 即元素數量 >= (hash桶數量(2^hmp.B) * 6.5 / 8) 時,觸發擴容
)


type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	count     int // # live cells == size of map.  Must be first (used by len() builtin)
	flags     uint8  // 記錄幾個特殊的位標記,如當前是否有別的執行緒正在寫map、當前是否為相同大小的增長(擴容/縮容?)
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // 指向2^B個桶組成的陣列的指標,資料存在這里
	oldbuckets unsafe.Pointer // 指向擴容前的舊buckets陣列,只在map增長時有效
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // 保存溢位桶的鏈表和未使用的溢位桶陣列的首地址
}
// mapextra holds fields that are not present on all maps.
type mapextra struct {
	// If both key and elem do not contain pointers and are inline, then we mark bucket
	// type as containing no pointers. This avoids scanning such maps.
	// However, bmap.overflow is a pointer. In order to keep overflow buckets
	// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
	// overflow and oldoverflow are only used if key and elem do not contain pointers.
	// overflow contains overflow buckets for hmap.buckets.
	// oldoverflow contains overflow buckets for hmap.oldbuckets.
	// The indirection allows to store a pointer to the slice in hiter.
	overflow    *[]*bmap   
	oldoverflow *[]*bmap

	// nextOverflow holds a pointer to a free overflow bucket.
	nextOverflow *bmap  // 為溢位的桶保留鏈表
}

// A bucket for a Go map.
type bmap struct {
	// tophash存盤桶內每個key的hash值的高位元組
	// tophash[0] < minTopHash表示桶的疏散狀態
	// 當前版本bucketCnt的值是8,一個桶最多存盤8個key-value對
	tophash [bucketCnt]uint8
	// 特別注意:
	// 實際分配記憶體時會申請一個更大的記憶體空間A,A的前8位元組為bmap
	// 后面依次跟8個key、8個value、1個溢位指標
	// map的桶結構實際指的是記憶體空間A
}

可以看到 map的定義相比 slice 實在是復雜不少,我們盯著其中一點來寫,map的實作結構無非就是桶機制,我們看看它的桶是怎么實作的,給一張圖

具體點 bmap 容器的記憶體結構應該是這樣

我們簡單看下, 通過 key 獲取值的流程

附上原始碼

func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
	if raceenabled && h != nil {
		callerpc := getcallerpc()
		pc := funcPC(mapaccess1)
		racereadpc(unsafe.Pointer(h), callerpc, pc)
		raceReadObjectPC(t.key, key, callerpc, pc)
	}
	if msanenabled && h != nil {
		msanread(key, t.key.size)
	}
	if h == nil || h.count == 0 {
		if t.hashMightPanic() {
			t.hasher(key, 0) // see issue 23734
		}
		return unsafe.Pointer(&zeroVal[0])
	}
	if h.flags&hashWriting != 0 {
		throw("concurrent map read and map write")
	}
	hash := t.hasher(key, uintptr(h.hash0))
	m := bucketMask(h.B)
	b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
	if c := h.oldbuckets; c != nil {
		if !h.sameSizeGrow() {
			// There used to be half as many buckets; mask down one more power of two.
			m >>= 1
		}
		oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
		if !evacuated(oldb) {
			b = oldb
		}
	}
	top := tophash(hash)
bucketloop:
	for ; b != nil; b = b.overflow(t) {
		for i := uintptr(0); i < bucketCnt; i++ {
			if b.tophash[i] != top {
				if b.tophash[i] == emptyRest {
					break bucketloop
				}
				continue
			}
			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
			if t.indirectkey() {
				k = *((*unsafe.Pointer)(k))
			}
			if t.key.equal(key, k) {
				e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
				if t.indirectelem() {
					e = *((*unsafe.Pointer)(e))
				}
				return e
			}
		}
	}
	return unsafe.Pointer(&zeroVal[0])
}

核心在那個for 回圈取值那,簡單而言就是

  1. 取得 hash 運算的 值 x, 通過 x 的低8位找到對應的bucket 桶號,
  2. 然后獲得 x的高8位 ,依次和 bucket 里面的8個 高位key 做對比, 如果沒找到匹配,然后和這個bucket 的溢位桶做對比,同樣的流程,
  3. 如果找到高8位相同,通過 指標運算直接找到 bucket那個key記憶體位置的值, 對比 bucket 中的key 和 輸入的key值,如果相等那就算找到,如果不等,還得重復這個流程繼續找,

emptyResult 表示 高8位沒有賦值,也就是這個 bucket之后 里面的某些key 和 value還沒有塞入值,

map 的初始化

使用make(map[k]v, hint)創建map時會呼叫makemap()函式,代碼邏輯比較簡單,
值得注意的是,makemap()創建的hash陣列,陣列的前面是hash表的空間,當hint >= 4時后面會追加2^(hint-4)個桶,之后再記憶體頁幀對齊又追加了若干個桶(參見2.2章節結構圖的hash陣列部分)
所以創建map時一次記憶體分配既分配了用戶預期大小的hash陣列,又追加了一定量的預留的溢位桶,還做了記憶體對齊,一舉多得,

// make(map[k]v, hint), hint即預分配大小
// 不傳hint時,如用new創建個預設容量為0的map時,makemap只初始化hmap結構,不分配hash陣列
func makemap(t *maptype, hint int, h *hmap) *hmap {
	// 省略部分代碼
	// 隨機hash種子
	h.hash0 = fastrand()

	// 2^h.B 為大于hint*6.5(擴容因子)的最小的2的冪
	B := uint8(0)
	// overLoadFactor(hint, B)只有一行代碼:return hint > bucketCnt && uintptr(hint) > loadFactorNum*(bucketShift(B)/loadFactorDen)
	// 即B的大小應滿足 hint <= (2^B) * 6.5
	// 一個桶能存8對key-value,所以這就表示B的初始值是保證這個map不需要擴容即可存下hint個元素對的最小的B值
	for overLoadFactor(hint, B) {
		B++
	}
	h.B = B

	// 這里分配hash陣列
	if h.B != 0 {
		var nextOverflow *bmap
		h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
		// makeBucketArray()會在hash陣列后面預分配一些溢位桶,
		// h.extra.nextOverflow用來保存上述溢位桶的首地址
		if nextOverflow != nil {
			h.extra = new(mapextra)
			h.extra.nextOverflow = nextOverflow
		}
	}

	return h
}

// 分配hash陣列
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
	base := bucketShift(b) // base代表用戶預期的桶的數量,即hash陣列的真實大小
	nbuckets := base // nbuckets表示實際分配的桶的數量,>= base,這就可能會追加一些溢位桶作為溢位的預留
	if b >= 4 {
		// 這里追加一定數量的桶,并做記憶體對齊
		nbuckets += bucketShift(b - 4)
		sz := t.bucket.size * nbuckets
		up := roundupsize(sz)
		if up != sz {
			nbuckets = up / t.bucket.size
		}
	}

	// 后面的代碼就是申請記憶體空間了,此處省略
	// 這里大家可以思考下這個陣列空間要怎么分配,其實就是n*sizeof(桶),所以:
		// 每個桶前面是8位元組的tophash陣列,然后是8個key,再是8個value,最后放一個溢位指標
		// sizeof(桶) = 8 + 8*sizeof(key) + 8*sizeof(value) + 8
	
	return buckets, nextOverflow
}

寫入值和擴容

插入

  1. 引數合法性檢測,計算hash值
unc mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    // 不熟悉指標操作的同學,用指標傳參往往會踩空指標的坑
    // 這里大家可以思考下,為什么h要非空判斷?
    // 如果一定要在這里支持空map并檢測到map為空時自動初始化,應該怎么寫?
    // 提示:指標的指標
	if h == nil {
		panic(plainError("assignment to entry in nil map"))
	}
	// 在這里做并發判斷,檢測到并發寫時,拋例外
	// 注意:go map的并發檢測是偽檢測,并不保證所有的并發都會被檢測出來,而且這玩意是在運行期檢測,
	// 所以對map有并發要求時,應使用sync.map來代替普通map,通過加鎖來阻斷并發沖突
	if h.flags&hashWriting != 0 {
		throw("concurrent map writes")
	}
	hash := alg.hash(key, uintptr(h.hash0)) // 這里得到uint32的hash值
	h.flags ^= hashWriting // 置Writing標志,key寫入buckets后才會清除標志
	if h.buckets == nil { // map不能為空,但hash陣列可以初始是空的,這里會初始化
		h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
	}
	
	...

2. 定位key在hash表中的位置

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    ...
    
again:
	bucket := hash & bucketMask(h.B) // 這里用hash值的低階位定位hash陣列的下標偏移量
	if h.growing() {
		growWork(t, h, bucket) // 這里是map的擴容縮容操作,我們在第4章單獨講
	}
	// 通過下標bucket,偏移定位到具體的桶
	b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
	top := tophash(hash) // 這里取高8位用于在桶內定位鍵值對
	
	...
}

3. 進一步定位key可以插入的桶及桶中的位置

  • 兩輪回圈,外層回圈遍歷hash桶及其指向的溢位鏈表,內層回圈則在桶內遍歷(一個桶最多8個key-value對)
  • 有可能正好鏈表上的桶都滿了,這時inserti為nil,第4步會鏈接一個新的溢位桶進來

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    ...

    var inserti *uint8          // tophash插入位置
	var insertk unsafe.Pointer  // key插入位置
	var val unsafe.Pointer      // value插入位置
bucketloop:
	for {
		for i := uintptr(0); i < bucketCnt; i++ {
			if b.tophash[i] != top {
				if isEmpty(b.tophash[i]) && inserti == nil {
				    // 找到個空位,先記錄下tophash、key、value的插入位置
				    // 但要遍歷完才能確定要不要插入到這個位置,因為后面有可能有重復的元素
					inserti = &b.tophash[i]
					insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
					val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
				}
				if b.tophash[i] == emptyRest {
					break bucketloop // 遍歷完整個溢位鏈表,退出回圈
				}
				continue
			}
			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
			if t.indirectkey() {
				k = *((*unsafe.Pointer)(k))
			}
			if !alg.equal(key, k) {
				continue
			}
			// 走到這里說明map里找到一個重復的key,更新key-value,跳到第5步
			if t.needkeyupdate() {
				typedmemmove(t.key, k, key)
			}
			val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
			goto done // 更新Key后跳到第5步
		}
		ovf := b.overflow(t)
		if ovf == nil {
			break // 遍歷完整個溢位鏈表,沒找到能插入的空位,結束回圈,下一步再追加一個溢位桶進來
		}
		b = ovf // 繼續遍歷下一個溢位桶
	}

	...
}

4. 插入key

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    ...
    // 擴容相關
    if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
		hashGrow(t, h)
		goto again // Growing the table invalidates everything, so try again
	}

	if inserti == nil { // inserti == nil說明上1步沒找到空位,整個鏈表是滿的,這里添加一個新的溢位桶上去
		newb := h.newoverflow(t, b) // 分配新溢位桶,優先用3.1章節預留的溢位桶,用完了則分配一個新桶記憶體
		inserti = &newb.tophash[0]
		insertk = add(unsafe.Pointer(newb), dataOffset)
		val = add(insertk, bucketCnt*uintptr(t.keysize))
	}

	// 當key或value的型別大小超過一定值時,桶只存盤key或value的指標,這里分配空間并取指標
	if t.indirectkey() {
		kmem := newobject(t.key)
		*(*unsafe.Pointer)(insertk) = kmem
		insertk = kmem
	}
	if t.indirectvalue() {
		vmem := newobject(t.elem)
		*(*unsafe.Pointer)(val) = vmem
	}
	typedmemmove(t.key, insertk, key) // 在桶中對應位置插入key
	*inserti = top // 插入tophash,hash值高8位
	h.count++ // 插入了新的鍵值對,h.count數量+1

	...
}

5. 結束插入

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    ...
    
 done:
	if h.flags&hashWriting == 0 {
		throw("concurrent map writes")
	}
	h.flags &^= hashWriting // 釋放hashWriting標志位
	if t.indirectvalue() {
		val = *((*unsafe.Pointer)(val))
	}
	return val // 回傳value可插入位置的指標,注意,value還沒插入
}

mapassign()只插入tophash和key,并回傳val指標,編譯器會在呼叫mapassign()后用匯編往val插入value

擴容

關于擴容相關代碼在 mapassign 那里

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    ...
    
    if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
		hashGrow(t, h)
		goto again // Growing the table invalidates everything, so try again
	}
	
	...
}

// overLoadFactor()回傳true則觸發擴容,即map的count大于hash桶數量(2^B)*6.5
func overLoadFactor(count int, B uint8) bool {
	return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

// tooManyOverflowBuckets(),顧名思義,溢位桶太多了觸發縮容
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
	if B > 15 {
		B = 15
	}
	return noverflow >= uint16(1)<<(B&15)
}

解釋:

map只在插入元素即mapassign()函式中對是否擴容縮容進行觸發,條件即是上面這段代碼:

  • 條件1:當前不處在growing狀態
  • 條件2-1:觸發擴容:map的資料量count大于hash桶數量(2B)*6.5,注意這里的(2B)只是hash陣列大小,不包括溢位的桶
  • 條件2-2:觸發縮容:溢位的桶數量noverflow>=32768(1<<15)或者>=hash陣列大小,

map 縮容是偽縮容

現在可以解釋為什么我把map的縮容叫做偽縮容了:因為縮容僅僅針對溢位桶太多的情況,觸發縮容時hash陣列的大小不變,即hash陣列所占用的空間只增不減,

也就是說,如果我們把一個已經增長到很大的map的元素挨個全部洗掉掉,hash表所占用的記憶體空間也不會被釋放,

所以如果要實作“真縮容”,需自己實作縮容搬遷,即創建一個較小的map,將需要縮容的map的元素挨個搬遷過來:

// go map縮容代碼示例
myMap := make(map[int]int, 1000000)

// 假設這里我們對bigMap做了很多次插入,之后又做了很多次洗掉,此時bigMap的元素數量遠小于hash表大小
// 接下來我們開始縮容
smallMap := make(map[int]int, len(myMap))
for k, v := range myMap {
    smallMap[k] = v
}
myMap = smallMap // 縮容完成,原來的map被我們丟棄,交給gc去清理

洗掉元素操作

代碼為

func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
    ...

            b.tophash[i] = emptyOne // 先標記洗掉
			// 如果b.tophash[i]不是最后一個元素,則暫時先占著坑,emptyOne標記的位置暫時不能被插入新元素(見3.2章節插入函式)
			if i == bucketCnt-1 {
				if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
					goto notLast
				}
			} else {
				if b.tophash[i+1] != emptyRest {
					goto notLast
				}
			}
			for { // 如果b.tophash[i]是最后一個元素,則把末尾的emptyOne全部清除置為emptyRest
				b.tophash[i] = emptyRest
				if i == 0 {
					if b == bOrig {
						break // beginning of initial bucket, we're done.
					}
					// Find previous bucket, continue at its last entry.
					c := b
					for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
					}
					i = bucketCnt - 1
				} else {
					i--
				}
				if b.tophash[i] != emptyOne {
					break
				}
			}

    ...
}

洗掉與插入類似,前面的步驟都是引數和狀態判斷、定位key-value位置,然后clear對應的記憶體,不展開說,以下是幾個關鍵點:

  • 洗掉程序中也會置hashWriting標志
  • 當key/value過大時,hash表里存盤的是指標,這時候用軟洗掉,置指標為nil,資料交給gc去刪,當然,這是map的內部處理,外層是無感知的,拿到的都是值拷貝
  • 無論Key/value是值型別還是指標型別,洗掉操作都只影響hash表,外層已經拿到的資料不受影響,尤其是指標型別,外層的指標還能繼續使用

由于定位key位置的方式是查找tophash,所以洗掉操作對tophash的處理是關鍵:

  • map首先將對應位置的tophash[i]置為emptyOne,表示該位置已被洗掉
  • 如果tophash[i]不是整個鏈表的最后一個,則只置emptyOne標志,該位置被洗掉但未釋放,后續插入操作不能使用此位置
  • 如果tophash[i]是鏈表最后一個有效節點了,則把鏈表最后面的所有標志為emptyOne的位置,都置為emptyRest,置為emptyRest的位置可以在后續的插入操作中被使用,
  • 這種洗掉方式,以少量空間來避免桶鏈表和桶內的資料移動,事實上,go 資料一旦被插入到桶的確切位置,map是不會再移動該資料在桶中的位置了,

總結

Go 語言使用拉鏈法來解決哈希碰撞的問題實作了哈希表,它的訪問、寫入和洗掉等操作都在編譯期間轉換成了運行時的函式或者方法,

map 底層是hash實作,資料結構為hash陣列 + 桶 + 溢位的桶鏈表,每個桶存盤最多8個key-value對

查找和插入的原理:key的hash值(低階位)與桶數量相與,得到key所在的hash桶,再用key的高8位與桶中的tophash[i]對比,相同則進一步對比key值,key值相等則找到

go map不支持并發,插入、洗掉、搬遷等操作會置writing標志,檢測到并發直接panic

每次擴容hash表增大1倍,hash表只增不減

支持有限縮容,delete操作只置洗掉標志位,釋放溢位桶的空間依靠觸發縮容來實作,

map在使用前必須初始化,否則panic:已初始化的map是make(map[key]value)或make(map[key]value, hint)這兩種形式,而new或var xxx map[key]value這兩種形式是未初始化的,直接使用會panic,

go采用的hash演算法應是很成熟的演算法,極端情況暫不考慮,所以綜合情況下go map的時間復雜度應為O(1)

空間復雜度分析:
首先我們不考慮因洗掉大量元素導致的空間浪費情況(這種情況現在go是留給程式員自己解決),只考慮一個持續增長狀態的map的一個空間使用率:
由于溢位桶數量超過hash桶數量時會觸發縮容,所以最壞的情況是資料被集中在一條鏈上,hash表基本是空的,這時空間浪費O(n),
最好的情況下,資料均勻散列在hash表上,沒有元素溢位,這時最好的空間復雜度就是擴散因子決定了,當前go的擴散因子由全域變數決定,即loadFactorNum/loadFactorDen = 6.5,即平均每個hash桶被分配到6.5個元素以上時,開始擴容,所以最小的空間浪費是(8-6.5)/8 = 0.1875,即O(0.1875n)

結論:go map的空間復雜度(指除去正常存盤元素所需空間之外的空間浪費)是O(0.1875n) ~ O(n)之間,

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

標籤:AI

上一篇:區塊鏈作業3生成通過兩個線性方程組的解贖回的交易

下一篇:智能金融專案的N種死法1:離交易過近

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

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more