PoolThreadCache 類的結構和源碼實現

PoolThreadCache?在 Netty 的內存池中扮演著線程本地緩存的角色。它的主要目的是減少線程在分配內存時對全局?PoolArena?的競爭,通過緩存一部分最近釋放的內存塊,使得同一線程后續申請相同規格的內存時能夠快速獲取,從而提高分配效率。

下面我們詳細分析其源碼:

主要成員變量

// ... existing code ...
final class PoolThreadCache {private static final InternalLogger logger = InternalLoggerFactory.getInstance(PoolThreadCache.class);private static final int INTEGER_SIZE_MINUS_ONE = Integer.SIZE - 1;final PoolArena<byte[]> heapArena; // 關聯的堆內存Arenafinal PoolArena<ByteBuffer> directArena; // 關聯的直接內存Arena// 針對不同大小規格 (Small/Normal) 和類型 (Heap/Direct) 的內存區域緩存// Small類型的內存通常來自PoolSubpageprivate final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;// Normal類型的內存通常直接來自PoolChunk的Pageprivate final MemoryRegionCache<byte[]>[] normalHeapCaches;private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;// 分配次數閾值,達到此閾值時觸發trim操作,清理緩存private final int freeSweepAllocationThreshold;// 標記緩存是否已被釋放,防止重復釋放private final AtomicBoolean freed = new AtomicBoolean();@SuppressWarnings("unused") // Field is only here for the finalizer.// 用于在對象被GC回收前,通過finalizer機制嘗試釋放緩存中的資源private final FreeOnFinalize freeOnFinalize;// 當前線程緩存的分配次數,用于配合freeSweepAllocationThresholdprivate int allocations;// TODO: Test if adding padding helps under contention//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;// ... existing code ...
}
  • heapArena?和?directArena: 分別指向該線程緩存關聯的堆內存池和直接內存池。線程在緩存未命中時,會向這兩個 Arena 申請內存。
  • smallSubPageHeapCaches,?smallSubPageDirectCaches,?normalHeapCaches,?normalDirectCaches: 這四個數組是核心的緩存存儲結構。它們是?MemoryRegionCache?類型的數組,MemoryRegionCache?內部維護了一個隊列,用于存儲緩存的內存塊信息。
    • smallSubPage...Caches: 用于緩存 "Small" 類型的內存塊。這類內存塊通常小于一個 Page,由?PoolSubpage?管理。數組的索引對應不同的?elemSize
    • normal...Caches: 用于緩存 "Normal" 類型的內存塊。這類內存塊通常大于等于一個 Page,直接從?PoolChunk?中分配。數組的索引對應不同的規格大小。
  • freeSweepAllocationThreshold: 這是一個重要的參數。當?PoolThreadCache?的?allocations?計數達到這個閾值時,會觸發?trim()?方法,嘗試回收一部分緩存的內存,以避免緩存過多導致內存浪費。
  • freed: 一個原子布爾值,確保?free()?方法只被執行一次,防止資源被多次釋放。
  • freeOnFinalize: 一個內部類實例,如果啟用了?useFinalizer,當?PoolThreadCache?對象被垃圾回收時,其?finalize?方法會被調用,進而調用?PoolThreadCache.free(true)?來釋放緩存的資源。這是一種兜底機制。
  • allocations: 記錄從該線程緩存成功分配出去的次數。

構造函數?PoolThreadCache(...)

PoolThreadCache.java

// ... existing code ...PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,int smallCacheSize, int normalCacheSize, int maxCachedBufferCapacity,int freeSweepAllocationThreshold, boolean useFinalizer) {checkPositiveOrZero(maxCachedBufferCapacity, "maxCachedBufferCapacity");this.freeSweepAllocationThreshold = freeSweepAllocationThreshold;this.heapArena = heapArena;this.directArena = directArena;if (directArena != null) {// 創建直接內存的Small和Normal類型的緩存數組smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.sizeClass.nSubpages);normalDirectCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, directArena);// 增加Arena中線程緩存的計數directArena.numThreadCaches.getAndIncrement();} else {// No directArea is configured so just null out all cachessmallSubPageDirectCaches = null;normalDirectCaches = null;}if (heapArena != null) {// 創建堆內存的Small和Normal類型的緩存數組smallSubPageHeapCaches = createSubPageCaches(smallCacheSize, heapArena.sizeClass.nSubpages);normalHeapCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, heapArena);// 增加Arena中線程緩存的計數heapArena.numThreadCaches.getAndIncrement();} else {// No heapArea is configured so just null out all cachessmallSubPageHeapCaches = null;normalHeapCaches = null;}// Only check if there are caches in use.// 如果配置了任何緩存,則freeSweepAllocationThreshold必須大于0if ((smallSubPageDirectCaches != null || normalDirectCaches != null|| smallSubPageHeapCaches != null || normalHeapCaches != null)&& freeSweepAllocationThreshold < 1) {throw new IllegalArgumentException("freeSweepAllocationThreshold: "+ freeSweepAllocationThreshold + " (expected: > 0)");}// 根據useFinalizer參數決定是否創建FreeOnFinalize實例freeOnFinalize = useFinalizer ? new FreeOnFinalize(this) : null;}// ... existing code ...

構造函數的主要工作是初始化各個成員變量,特別是根據傳入的參數創建不同類型的?MemoryRegionCache?數組。

  • smallCacheSize,?normalCacheSize: 分別定義了 Small 類型和 Normal 類型緩存區域中?MemoryRegionCache?隊列的大小。
  • maxCachedBufferCapacity: 定義了可以被緩存的 Buffer 的最大容量。超過這個容量的 Buffer 不會被緩存。
  • directArena.sizeClass.nSubpages: 這個值決定了?smallSubPageDirectCaches?數組的大小,即支持多少種不同規格的 Small 類型直接內存緩存。
  • directArena.numThreadCaches.getAndIncrement(): 每當一個?PoolThreadCache?關聯到一個?PoolArena?時,會增加?PoolArena?內部的線程緩存計數器。

createSubPageCaches

PoolThreadCache.java

// ... existing code ...
private static <T> MemoryRegionCache<T>[] createSubPageCaches(int cacheSize, int numCaches) {if (cacheSize > 0 && numCaches > 0) {@SuppressWarnings("unchecked")MemoryRegionCache<T>[] cache = new MemoryRegionCache[numCaches];for (int i = 0; i < cache.length; i++) {// TODO: maybe use cacheSize / cache.length// 顯式類型實參 T 可被替換為 <>cache[i] = new SubPageMemoryRegionCache<>(cacheSize);}return cache;} else {return null;}
}

這個方法用于創建?SubPageMemoryRegionCache?數組。

numCaches?通常是?arena.sizeClass.nSubpages,表示支持的 Small 類型規格數量。每個?SubPageMemoryRegionCache?實例的內部隊列大小由?cacheSize?決定。

createNormalCaches

// ... existing code ...
@SuppressWarnings("unchecked")
private static <T> MemoryRegionCache<T>[] createNormalCaches(int cacheSize, int maxCachedBufferCapacity, PoolArena<T> area) {if (cacheSize > 0 && maxCachedBufferCapacity > 0) {int max = Math.min(area.sizeClass.chunkSize, maxCachedBufferCapacity);// Create as many normal caches as we support based on how many sizeIdx we have and what the upper// bound is that we want to cache in general.List<MemoryRegionCache<T>> cache = new ArrayList<MemoryRegionCache<T>>() ;// 從nSubpages開始,因為之前的sizeIdx是為Small類型保留的// area.sizeClass.sizeIdx2size(idx) <= max 確保只為不超過maxCachedBufferCapacity的規格創建緩存for (int idx = area.sizeClass.nSubpages; idx < area.sizeClass.nSizes &&area.sizeClass.sizeIdx2size(idx) <= max; idx++) {// 顯式類型實參 T 可被替換為 <>cache.add(new NormalMemoryRegionCache<>(cacheSize));}return cache.toArray(new MemoryRegionCache[0]);} else {return null;}
}

這個方法用于創建?NormalMemoryRegionCache?數組。

它會遍歷?PoolArena?的?SizeClasses?中定義的 Normal 類型的規格,但只為那些大小不超過?maxCachedBufferCapacity?(且不超過?chunkSize) 的規格創建緩存。

內存分配方法

  • allocateSmall(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int sizeIdx)

  • allocateNormal(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int sizeIdx)?這兩個方法分別用于分配 Small 和 Normal 類型的內存。它們首先通過?cacheForSmall?或?cacheForNormal?找到對應的?MemoryRegionCache,然后調用通用的?allocate?方法。

  • allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity)

    PoolThreadCache.java

    // ... existing code ...
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {if (cache == null) {// no cache found so just return false herereturn false;}// 嘗試從指定的MemoryRegionCache分配boolean allocated = cache.allocate(buf, reqCapacity, this);// 如果分配成功,并且總分配次數達到閾值if (++ allocations >= freeSweepAllocationThreshold) {allocations = 0; // 重置計數器trim(); // 執行trim操作,清理緩存}return allocated;
    }
    

這是實際執行從緩存分配的邏輯:

1. 如果找不到對應的?MemoryRegionCache?(例如,該規格的緩存未啟用或請求的?sizeIdx?超出范圍),則返回?false

2. 調用?cache.allocate(buf, reqCapacity, this)?嘗試從該?MemoryRegionCache?的隊列中取出一個緩存的?Entry?并用它初始化?buf

3. 如果分配成功 (allocated?為?true),則?allocations?計數器加1。

4. 檢查?allocations?是否達到?freeSweepAllocationThreshold。如果是,則將?allocations?重置為0,并調用?trim()?方法來清理所有緩存區域中不活躍的條目。

添加到緩存方法?add(...)

PoolThreadCache.java

// ... existing code ...@SuppressWarnings({ "unchecked", "rawtypes" })boolean add(PoolArena<?> area, PoolChunk chunk, ByteBuffer nioBuffer,long handle, int normCapacity, SizeClass sizeClass) {// 根據normCapacity計算sizeIdxint sizeIdx = area.sizeClass.size2SizeIdx(normCapacity);// 根據area類型(Heap/Direct)、sizeIdx和sizeClass(Small/Normal)獲取對應的MemoryRegionCacheMemoryRegionCache<?> cache = cache(area, sizeIdx, sizeClass);if (cache == null) {return false; // 找不到合適的緩存區域}if (freed.get()) { // 如果緩存已被標記為釋放,則不再添加return false;}// 調用MemoryRegionCache的add方法return cache.add(chunk, nioBuffer, handle, normCapacity);}// ... existing code ...

當一個?PooledByteBuf?被釋放時,如果滿足一定條件(例如,它的大小適合緩存,且其來源的?PoolArena?允許緩存),PoolArena?會嘗試調用此方法將其對應的內存塊信息(chunk,?handle,?normCapacity?等)添加到當前線程的?PoolThreadCache?中。

  1. 計算?sizeIdx
  2. 通過?cache(area, sizeIdx, sizeClass)?方法定位到具體的?MemoryRegionCache
  3. 如果緩存已被釋放 (freed.get()?為?true),則不添加。
  4. 調用?MemoryRegionCache.add(...)?將內存塊信息封裝成?Entry?對象并嘗試放入其內部隊列。

緩存檢索方法

  • cache(PoolArena<?> area, int sizeIdx, SizeClass sizeClass): 根據?sizeClass?(Normal 或 Small) 調用?cacheForNormal?或?cacheForSmall
  • cacheForSmall(PoolArena<?> area, int sizeIdx): 判斷?area?是堆內存還是直接內存,然后從?smallSubPageHeapCaches?或?smallSubPageDirectCaches?中獲取緩存。
  • cacheForNormal(PoolArena<?> area, int sizeIdx): 類似?cacheForSmall,但操作的是?normalHeapCaches?和?normalDirectCaches。注意這里?idx = sizeIdx - area.sizeClass.nSubpages,因為?sizeIdx?是全局的,而 Normal 類型的緩存在數組中的索引需要減去 Small 類型的規格數量。
  • cache(MemoryRegionCache<T>[] cache, int sizeIdx): 簡單的數組訪問,并進行邊界檢查。

這些方法共同構成了從緩存數組中定位特定?MemoryRegionCache?的邏輯。

釋放資源方法?free(boolean finalizer)

PoolThreadCache.java

// ... existing code ...void free(boolean finalizer) {// As free() may be called either by the finalizer or by FastThreadLocal.onRemoval(...) we need to ensure// we only call this one time.// 使用AtomicBoolean確保free操作只執行一次if (freed.compareAndSet(false, true)) {if (freeOnFinalize != null) {// Help GC: this can race with a finalizer thread, but will be null out regardlessfreeOnFinalize.cache = null; // 解除FreeOnFinalize對PoolThreadCache的引用}// 依次釋放所有類型的緩存區域int numFreed = free(smallSubPageDirectCaches, finalizer) +free(normalDirectCaches, finalizer) +free(smallSubPageHeapCaches, finalizer) +free(normalHeapCaches, finalizer);if (numFreed > 0 && logger.isDebugEnabled()) {logger.debug("Freed {} thread-local buffer(s) from thread: {}", numFreed,Thread.currentThread().getName());}// 遞減Arena中的線程緩存計數if (directArena != null) {directArena.numThreadCaches.getAndDecrement();}if (heapArena != null) {heapArena.numThreadCaches.getAndDecrement();}}}private static int free(MemoryRegionCache<?>[] caches, boolean finalizer) {if (caches == null) {return 0;}int numFreed = 0;for (MemoryRegionCache<?> c: caches) {numFreed += free(c, finalizer); // 遍歷釋放數組中的每個MemoryRegionCache}return numFreed;}private static int free(MemoryRegionCache<?> cache, boolean finalizer) {if (cache == null) {return 0;}return cache.free(finalizer); // 調用MemoryRegionCache的free方法}// ... existing code ...

當線程結束或者?PooledByteBufAllocator?關閉時,會調用此方法來釋放?PoolThreadCache?中緩存的所有內存塊。

  • freed.compareAndSet(false, true)?保證了此方法體內的邏輯只執行一次。
  • finalizer?參數指示這次釋放是否由 finalizer 機制觸發。如果是,MemoryRegionCache.freeEntry?的行為會有所不同(主要是為了避免在 finalizer 線程中執行可能導致死鎖或復雜狀態的操作)。
  • 它會遍歷所有四種緩存數組,并調用每個?MemoryRegionCache?實例的?free(finalizer)?方法,該方法會清空其內部隊列并將所有緩存的?Entry?代表的內存塊歸還給?PoolArena
  • 最后,遞減關聯?PoolArena?中的?numThreadCaches?計數。

整理緩存方法?trim()

PoolThreadCache.java

// ... existing code ...void trim() {trim(smallSubPageDirectCaches);trim(normalDirectCaches);trim(smallSubPageHeapCaches);trim(normalHeapCaches);}private static void trim(MemoryRegionCache<?>[] caches) {if (caches == null) {return;}for (MemoryRegionCache<?> c: caches) {trim(c);}}private static void trim(MemoryRegionCache<?> cache) {if (cache == null) {return;}cache.trim(); // 調用MemoryRegionCache的trim方法}// ... existing code ...

當?PoolThreadCache?的?allocations?達到?freeSweepAllocationThreshold?時被調用。它會遍歷所有緩存數組,并調用每個?MemoryRegionCache?實例的?trim()?方法。MemoryRegionCache.trim()?會根據其自身的分配情況和隊列大小,決定是否釋放一部分緩存的?Entry

SubPageMemoryRegionCache<T>?和?NormalMemoryRegionCache<T>?

這兩個類都繼承自抽象的?MemoryRegionCache<T>。它們的主要區別在于構造時傳入的?SizeClass?(Small 或 Normal) 以及它們如何實現?initBuf?方法:

  • SubPageMemoryRegionCache.initBuf(...)?調用?chunk.initBufWithSubpage(...),用于從?PoolSubpage?初始化?PooledByteBuf
  • NormalMemoryRegionCache.initBuf(...)?調用?chunk.initBuf(...),用于從?PoolChunk?的 Page 初始化?PooledByteBuf

MemoryRegionCache<T>?

abstract static class

這是線程緩存的核心數據結構之一,代表特定規格內存的緩存區域。

  • size: 緩存隊列的容量,是2的冪次方。
  • queue:?PlatformDependent.newFixedMpscUnpaddedQueue(this.size)?創建的一個多生產者單消費者隊列 (MPSC),用于存儲緩存的?Entry<T>?對象。由于?PoolThreadCache?是線程本地的,這里的“多生產者”實際上是指其他線程釋放內存并嘗試將內存塊添加到這個線程的緩存中(雖然 Netty 的設計主要是當前線程釋放的內存回到當前線程的緩存),而“單消費者”就是當前線程自己從緩存中分配內存。
  • sizeClass: 標記這個緩存區域是用于?Small?還是?Normal?類型的內存。
  • allocations: 記錄從這個特定?MemoryRegionCache?分配出去的次數,用于其自身的?trim()?邏輯。
  • add(PoolChunk<T> chunk, ...): 創建一個新的?Entry?對象(通過?RECYCLER?獲取),設置好?chunkhandle?等信息,然后嘗試將其加入?queue。如果隊列已滿 (offer?返回?false),則立即回收這個?Entry?對象。
  • allocate(PooledByteBuf<T> buf, ...): 從?queue?中取出一個?Entry?(poll)。如果隊列為空,返回?false。否則,調用抽象方法?initBuf?用取出的?Entry?中的信息來初始化?buf,然后回收?Entry?對象,并增加?allocations?計數。
  • free(int max, boolean finalizer): 從隊列中移除最多?max?個?Entry,并對每個?Entry?調用?freeEntry
  • trim(): 計算當前隊列中可以釋放的?Entry?數量(基于?size - allocations),然后調用?free(free, false)?來釋放它們。allocations?在這里代表了近期從該緩存區域成功分配的次數,如果這個數字遠小于隊列的容量?size,說明緩存利用率不高,可以進行清理。
  • freeEntry(Entry entry, boolean finalizer): 這是將緩存的內存塊真正歸還給?PoolArena?的地方。它獲取?Entry?中的?chunk,?handle,?nioBuffer,?normCapacity。如果不是由 finalizer 觸發,它會先回收?Entry?對象本身,然后調用?chunk.arena.free(chunk, entry.nioBuffer, handle, normCapacity, this)?將內存塊歸還給 Arena。如果是 finalizer 觸發,則只歸還內存塊,不立即回收?Entry?(避免在 finalizer 中操作 Recycler 可能引發的問題)。

Entry<T>?

一個簡單的 POJO,用于封裝緩存的內存塊信息 (PoolChunk,?ByteBuffer nioBuffer,?long handle,?int normCapacity)。它通過 Netty 的?Recycler?進行對象池管理,以減少?Entry?對象自身的創建和銷毀開銷。

// ... existing code ...private static final ObjectPool<Entry<?>> RECYCLER = ObjectPool.newPool(new ObjectCreator<Entry<?>>() {@SuppressWarnings("unchecked")@Overridepublic Entry<?> newObject(Handle<Entry<?>> handle) {return new Entry(handle);}});static final class Entry<T> {final EnhancedHandle<Entry<?>> recyclerHandle; // Recycler的句柄PoolChunk<T> chunk;ByteBuffer nioBuffer; // 緩存的NIO ByteBuffer,主要用于Direct Bufferlong handle = -1;     // 內存塊在PoolChunk中的句柄int normCapacity;     // 規格化容量Entry(Handle<Entry<?>> recyclerHandle) {this.recyclerHandle = (EnhancedHandle<Entry<?>>) recyclerHandle;}void recycle() {chunk = null;nioBuffer = null;handle = -1;recyclerHandle.recycle(this);}// "Unguarded" version of recycle() that must only be used when we are sure that the Entry is not double-recycled.// This is the case when we obtained the Entry from the queue and add it to the cache again.void unguardedRecycle() {chunk = null;nioBuffer = null;handle = -1;recyclerHandle.unguardedRecycle(this);}}@SuppressWarnings("rawtypes")private static Entry newEntry(PoolChunk<?> chunk, ByteBuffer nioBuffer, long handle, int normCapacity) {Entry entry = RECYCLER.get(); // 從Recycler獲取Entry對象entry.chunk = chunk;entry.nioBuffer = nioBuffer;entry.handle = handle;entry.normCapacity = normCapacity;return entry;}
// ... existing code ...

FreeOnFinalize?

一個簡單的包裝類,其?finalize()?方法會調用?PoolThreadCache.free(true)。這是為了在?PoolThreadCache?對象本身被 GC 回收時,能夠嘗試釋放其占用的緩存資源,作為一種安全保障。

完全移動到 Java9+ 后, 會使用? java.lang.ref.Cleaner

// ... existing code ...
// Used to free the cache via a finalizer. This is just a best effort and should only be used if the
// ThreadLocal is not removed via FastThreadLocal.onRemoval(...) as this is the preferred way to free the cache.
private static final class FreeOnFinalize {private PoolThreadCache cache;FreeOnFinalize(PoolThreadCache cache) {this.cache = cache;}@Overrideprotected void finalize() throws Throwable {try {super.finalize();} finally {PoolThreadCache cache = this.cache;// this can race with a non-finalizer thread calling free: regardless who wins, the cache will be// null outthis.cache = null;if (cache != null) {// We must only call free if the cache was not null before, which means it was not freed before// by an explicit call to PoolThreadCache.free().//// We must use true as parameter which indicates that we were called from a finalizer.cache.free(true);}}}
}
}

總結

PoolThreadCache?通過精心設計的緩存結構和回收策略,有效地提升了 Netty 內存分配的性能。它利用線程本地性避免了鎖競爭,并通過?MemoryRegionCache?對不同規格的內存進行細粒度管理。

freeSweepAllocationThreshold?和?trim?機制確保了緩存在提供性能優勢的同時,不會無限制地消耗內存。內部類如?MemoryRegionCache?和?Entry?的設計,以及?Recycler?的使用,都體現了 Netty 對性能和資源管理的極致追求。

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

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

相關文章

Linux中的阻塞信號與信號原理

在Linux操作系統中&#xff0c;信號&#xff08;Signal&#xff09;是進程間通信和進程控制的核心機制之一。信號是一種異步通知機制&#xff0c;可以向進程發送異步事件通知&#xff0c;以便進程能夠處理系統級別的事件。本文將詳細探討Linux中的信號原理&#xff0c;重點講解…

QT學習教程(三十五)

事件處理&#xff08;- Event Processingn&#xff09; 事件是視窗系統或者Qt 本身在各種不同的情況下產生的。當用戶點擊或者釋放鼠標&#xff0c;鍵盤時&#xff0c;一個鼠標事件或者鍵盤事件就產生了。當窗口第一次顯示時&#xff0c;一個繪制事件會產生告訴新可見的窗口繪…

【Dify 案例】【MCP實戰】【三】【超級美食家】

接上次的超級助理,我們這一期給出一個超級美食家 首先:我的MCP要申請一個key ` 我們來看看這個MCP服務怎么使用呢。`https://modelscope.cn/mcp/servers/@worryzyy/howtocook-mcp插件里面需要配置 {"mcpServers":{"amap-amap-sse":{"url":&qu…

4.文件管理(文本、日志、Excel表)

目錄 1.文本 2.日志 3.Excel表 1.文本 using System.Text;namespace (自己創建的一個類) {/// <summary>/// 配置文件*.ini讀寫器。/// </summary>public class IniFile{[System.Runtime.InteropServices.DllImport("kernel32")]private static ex…

Java 包裝類詳解

什么是包裝類 Java包裝類&#xff08;Wrapper Classes&#xff09;是將8種基本數據類型封裝成對象的類&#xff0c;位于java.lang包中。每個基本數據類型都有對應的包裝類&#xff1a; byte → Byteshort → Shortint → Integerlong → Longfloat → Floatdouble → Doublec…

阿里云ACP認證-數據倉庫

數據倉庫 Kappa架構&#xff1a;將實時和離線代碼統一&#xff08;優化lambda架構&#xff09;&#xff0c;但是不好修正數據&#xff0c;開發周期長&#xff0c;成本浪費&#xff0c;對于歷史數據的高吞吐量力不從心 原一代數據倉庫&#xff1a; 離線&#xff1a;hivemaxcom…

WebRTC(五):TURN協議

TURN&#xff08;Traversal Using Relays around NAT&#xff09;協議是一個網絡協議&#xff0c;旨在解決 NAT&#xff08;網絡地址轉換&#xff09;和防火墻 環境下的 UDP/TCP通信問題。它通常與 STUN 和 ICE 協議一起使用&#xff0c;廣泛應用于 WebRTC、SIP 和視頻會議等實…

Python 的內置函數 hasattr

Python 內建函數列表 > Python 的內置函數 hasattr Python 的內置函數 hasattr() 用于檢查一個對象是否具有指定的屬性或方法。該函數的語法為&#xff1a; hasattr(object, name)參數說明&#xff1a; object&#xff1a;要檢查的對象&#xff0c;可以是任何 Python 對象…

docker使用技巧之把擴展卷命名變成有意義

背景 之前使用別人的鏡像之后&#xff0c;啟動docker后發出現了一堆看不懂名稱的擴展卷 eg&#xff1a;集群查看 擴展卷查看 這個時候如果有很多集群需要清理擴展卷就很麻煩&#xff0c;不知道是哪個集群的 操作步驟 可以實現的分析&#xff1a;這個擴展卷的信息應該是和…

《博物通書》《博物新編》與滿清歷史篡改

《博物新編》作為近代西方科技輸入中國的首部著作&#xff0c;其問世猶如一顆投入平靜湖面的巨石&#xff0c;在 19 世紀中期的中國激起層層漣漪&#xff0c;對中國近代科學發展產生了多維度、深層次的影響。它不僅是知識傳播的載體&#xff0c;更是推動中國科學從傳統走向近代…

【入門】【例18.1】 睡眠

| 時間限制&#xff1a;C/C 1000MS&#xff0c;其他語言 2000MS 內存限制&#xff1a;C/C 64MB&#xff0c;其他語言 128MB 難度&#xff1a;中等 分數&#xff1a;100 OI排行榜得分&#xff1a;12(0.1分數2難度) 出題人&#xff1a;root | 描述 一個人只有每天睡眠時間到達 8…

DAY 38 Dataset和Dataloader類

知識點回顧&#xff1a; Dataset類的__getitem__和__len__方法&#xff08;本質是python的特殊方法&#xff09;Dataloader類minist手寫數據集的了解 作業&#xff1a;了解下cifar數據集&#xff0c;嘗試獲取其中一張圖片 import torch import torch.nn as nn import torch.o…

【Kubernetes】以LOL的視角打開K8s

前言 對于大部分后端程序員乃至于非后端程序員來說&#xff0c;在當前的云原生時代&#xff0c;Kubernetes&#xff08;后稱K8s&#xff09;都是繞不開的一項技術&#xff1b;同時&#xff0c;對于這個時代的程序員來說&#xff0c;“英雄聯盟”&#xff08;后稱LOL&#xff0…

UE5 游戲模板 —— FirstShootGame

UE5 游戲模板 —— FirstShootGame 前言一、GameMode二、組件1.ShooterPickUpComponent單播多播 2.ShooterWeaponComponent附著武器開火 3.小結4.ShooterProjectile初始化碰撞受擊檢測 三、Character初始化輸入移動 總結 前言 有了前兩個俯視角游戲的基礎讓我們來看看相對復雜…

國家級與省級(不含港澳臺)標準地圖服務網站匯總

在先前的文章中&#xff0c;介紹了部分省級的標準地圖服務網站可以下載各個區縣近幾年、不同要素的標準地圖&#xff08;鏈接&#xff1a;國家與省市縣 標準地圖服務網站 審圖號地圖下載&#xff09;&#xff0c;但是當時只匯總了部分省級的標準地圖服務網站。 這兩天看到了一個…

前端開發面試題總結-vue3框架篇(一)

文章目錄 Vue3高頻問答一、vue2/vue3中常用的構建工具和腳手架分別是什么? 有什么區別?二、請說一說vue2和vue3的區別&#xff1f;三、請說一說vue2和vue3響應式原理的區別&#xff1f;四、vue3 如何定義響應式數據?五、說一說你對vue3中的setup函數?六、說一說vue3中的路由…

【LLM06---相對位置編碼】

文章目錄 相對位置編碼經典式XLNET式T5式DeBERTa式 相對位置編碼 上一節我們介紹了絕對位置編碼&#xff0c;這一節我們來看相對位置編碼&#xff0c;也就是用相對位置信息來表示&#xff0c;之前每一個token的位置式通過一個絕對的位置向量來表示的&#xff0c;現在我們在計算…

純跟蹤算法本質解密:航向角偏差=預瞄角?數學證明與工程實踐

定義關鍵問題 在深入純跟蹤算法核心前&#xff0c;必須澄清一對容易被混淆但至關重要的概念&#xff1a; 概念坐標系物理意義計算方式航向角偏差(α_global)全局坐標系車輛航向與預瞄點方向的夾角預瞄點方位角 - 車輛航向角預瞄角(α_body)車身坐標系預瞄點相對于車輛縱軸的夾…

自動駕駛叉車在倉庫環境中是否安全?

隨著自動駕駛叉車的興起&#xff0c;倉庫運營持續演進。叉車自動化技術的引入使倉庫設施變得更快、更安全且更具成本效益。然而一個關鍵問題依然存在&#xff1a;它們在繁忙的倉庫環境中是否安全&#xff1f; 一 、什么是自動駕駛叉車&#xff1f; 自動駕駛叉車&#xff0c;也…

Neo4j操作指南:修改節點數據與新增節點屬性

Neo4j操作指南&#xff1a;修改節點數據與新增節點屬性 引言 Neo4j作為領先的圖數據庫&#xff0c;提供了靈活的數據操作方式。在實際應用中&#xff0c;我們經常需要修改已有節點的數據或為節點添加新屬性。本文將詳細介紹如何使用Cypher查詢語言在Neo4j中完成這些操作&…