關於.net core高性能編程中的Span<T>和Memory<T>網上資料很多,這里就不說了。今天一直在看ReadOnlySequenceSegment<T>和SequenceReader<T>,看得腦殼痛,本篇着重說說對ReadOnlySequenceSegment<T>的理解。
如果對Span<T>和Memory<T>不了解,可以暫時理解為byte[],最好先去搜下相關資料。緩沖區相關知識可以參考官方文檔:https://docs.microsoft.com/zh-cn/dotnet/standard/io/buffers
內存片段ReadOnlySequenceSegment<T>
假設你已經了解了Memory<T>,它表示一段連續的內存,有時候我們讀取一條數據,它可能並不是存在連續的內存中。
這個我理解得不是很准確,但總體來說就是我們一個完整的數據分成了多個內存片段,每個內存片段用Memory<byte>(你也可以暫時理解為byte[])表示,那么可以以鏈表的形式,從邏輯上來表示這段完整的數據。比如Memory1上有個next屬性指向Memory2,同理Memory2上的next屬性指向Memory3,這樣的鏈表就能表示這段完整的數據了。
ReadOnlySequenceSegment<T>就是這樣一個鏈表,3個核心屬性定義如下:
1 public ReadOnlyMemory<T> Memory { get; protected set; } 2 public ReadOnlySequenceSegment<T>? Next { get; protected set; } 3 public long RunningIndex { get; protected set; }
- Memory:表示這個鏈表節點下的內存數據,也就是上面的Memory1、2、3
- Next:就是指向的下一個節點
- RunningIndex:指當前節點之前的節點的數據之和,比如Memory1里有1個字節、Memeory2里有2個字節,那么Memory3對應節點的RunningIndex就是3
這玩意是個抽象類,不過暫時可以不關心,因為我們通常開發時都可以從某個方法的參數獲得ReadOnlySequenceSegment<T>(下面馬上會說),而它里面就保存着這個鏈表的收尾兩個節點。
這里重點記住:
- ReadOnlySequenceSegment里面存儲的ReadOnlyMemory<T>(理解上約等於byte[])
- 多個ReadOnlySequenceSegment可以組成一個鏈表,從邏輯上表示一個完整的數據,ReadOnlySequenceSegment只是其中一個節點
內存片段容器ReadOnlySequence<T>
上面說的這個內存片段鏈表其實已經可以從邏輯上表示一段完整的數據了,但是ReadOnlySequenceSegment<T>只是這個鏈表中的一個節點,它能提供的屬性、方法等api只能針對自己這個節點,所以需要一個容器來容納整個鏈表,以提供對此連續內存片段操作的api
這里說的容器不是很准確,因為ReadOnlySequence只是存儲了整個鏈表的首位節點,但是由於是鏈表,其實只要知道首節點,就可以通過Next遞歸獲得整個鏈表的所有節點,因此我這里把它稱為容器
下面引用官方文檔的一張圖
綠色框中有3段藍色塊,我們可以理解為是鏈表中的一個節點(ReadOnlySequenceSegment),由於這個節點內部重要的就是保存着具體的數據Memory<T>,所以我們可以簡單的看成是3個Memory<T>,這里便於理解,也可以看成是3個byte[]。
根據綠色部分的3個不連續的內存片段,可以生成一個表示邏輯上連續的內存片段集合ReadOnlySequence,這個ReadOnlySequence包含3個Memory<T>,其中首位的片段只取原始片段的一部分。下面我根據理解再來一張圖
注:上面簡寫的16進制,A=0x0A
連續內存片段中的索引SequencePosition
只要知道一個數據在哪個片段中,並且知道它在這個片段中的哪個位置,就能表示一個具體的索引了。
但特別注意這個索引是針對原始鏈表來說的,也就是上面綠色快的部分,比如圖片中的“4”在第1段的索引3的位置;“A”,在第2段的索引2處。這種情況沒有辦法用單個數字來表示索引,因此單獨定義了SequencePosition來表示索引。
ReadOnlySequence的api
- 構造函數ReadOnlySequence(ReadOnlySequenceSegment<T> startSegment, int startIndex, ReadOnlySequenceSegment<T> endSegment, int endIndex)
- startSegment:鏈表的首個節點
- startIndex:首個節點不一定完全加入到ReadOnlySequence,此參數表示從第幾個值開始
- endSegment:鏈表的尾節點
- endIndex:尾節點也不一定完全加入ReadOnlySequence,此參數表示要加入的索引+1
- 按上圖所示,代碼應該這樣:new ReadOnlySequence(片段1,3,片段3,1); 注意最后一個參數是1,可以簡單理解為在尾節點取前幾個值加入到ReadOnlySequence
- End:就是最后一個片段的最后一個數據的索引對象,就是圖片中的片段3索引1
- Start:第一個片段的索引,片段1,索引2
- Length:ReadOnlySequence包含的值的長度,按圖中就是4 5 6 ....D F 2 長度為10
- GetPosition(int index):獲取第幾個值的索引對象,比如GetPosition(0),那就是黃色塊的0為4,它所處於綠色塊的索引為:片段1,索引2;GetPosition(4),那就是黃色塊的2,所處綠色快的片段2,索引1
- PositionOf(T value):查早某個值在這個序列中所處的索引,比如PositionOf(4),那就是在黃色塊的片段1的索引0處,最終結果就是綠色塊片段1的索引3處
- Slice():從這個連續內存片段集合中指定索引處開始,取一段數據,返回的是一個新的ReadOnlySequence。有幾個重載,比較容易猜到它的意義
-
bool TryGet(ref SequencePosition position, out ReadOnlyMemory<T> memory, bool advance = true)
嘗試從指定索引處開始讀取,所指定的索引處所在片段還有剩余數據,則本次讀取這些剩余數據,否則讀取下一個片段的數據。最終若讀取成功,則返回true,且將讀取到的數據賦值給memory參數。advance為true時,position將被直接賦值為下一個片段的索引0處。理解這個再看官方文檔那個循環就容易了。
主要api就這幾個。
SequenceReader<T>
.net core 3.x提供了SequenceReader來幫我們更容易的讀取ReadOnlySequence的數據。我們只要理解一點就能很容易的理解此對象。先看看下圖
這里我取名叫“已讀索引”,就是表示已經讀取過了。SequenceReader.Advance(2)就是將這個索引往后移2位,SequenceReader.Rewind(1)則表示將這個索引前移1位。
以圖中為例,若此時調用TryRead方法,則將獲取第3個位的數據,並且已讀索引向后移1位
理解這個再看官方文檔就簡單了,舉幾個例子
- Consumed:表示已經讀取了多數個數據,也就是“已讀索引”之前有幾個數據
- Remaining:表示整個序列還剩幾個數據,也就是“已讀索引”之后有幾個數據
- Advance(Int64):將“已讀索引”移動到指定位置
- AdvancePast(T):將"已讀索引"移動到指定值所在的位置
- TryRead(T):嘗試從“已讀索引”開始讀取1個值,並將“已讀索引”向后移動1位
- TryPeek(T):嘗試從“已讀索引”開始讀取1個值,但不移動“已讀索引”
- TryReadBigEndian():嘗試從“已讀索引”開始讀取4個值,並將其轉換位int類型的值,然后將“已讀索引”向后移動4位
其它的就不說了。
收~