解剖SQLSERVER 第五篇 OrcaMDF里讀取Bits類型數據(譯)
http://improve.dk/reading-bits-in-orcamdf/
Bits類型的存儲跟SQLSERVER其他定長數據類型的存儲很不一樣。通常,所有定長列都會顯示出來,一個條記錄里定長數據部分的字段數據總是一個挨著一個
我們可以寫入磁盤的最小數據單位是一個字節,存儲位類型數據的天真的方法就是使用一整個(字節@)來存儲每一個位,使用常用的格式去解釋位類型數據是很簡單的
,不過這會浪費一些空間 ,就像null位圖,如果一個表只有3列,那么用一個字節來存儲null位圖會比較浪費,因為其他的5個位都沒有用到
@:文章里是用位 ,這里應該是用字節吧
在記錄的內部位類型是如何存儲的?
一些位類型列的值是存儲在一個字節中的,最大可以到8個位,通常,我們會有如下表定義
CREATE TABLE BitTest ( A bit B bit C bit D int )
記錄的定長部分數據需要占用5個字節,4個字節存儲int 列 ,而另一個字節存儲A 、B、C這三列位類型的數據,只用了字節里面的3個位
我們再添加一些列
CREATE TABLE BitTest ( A bit B bit C bit D int E bit F bit G bit H smallint I bit J bit K bit )
E到G列按道理來說應該存儲在D列的后面,但是他們會繼續使用第一個 bit byte,直到第一個 bit byte使用完所有的位空間為止
下面的圖顯示了H列(smallint )直接存儲在D列的后面,而在D列后面是存儲K列的新bit byte,因為第一個bit byte已經滿了
當讀取行記錄里的位類型時我們需要知道的狀態
很明顯,我們一次不能只讀取一個字段的值,我們讀取固定長度數據類型的時候還需要讀取定長數據偏移指針
我們需要一些能在讀取的時候指示我們當前讀取到字節中哪一個位屬于哪一個字段的狀態,然后我們讀取一個新的bit byte
我來介紹一下RecordReadState類
public class RecordReadState { // We start out having consumed all bits as none have been read private int currentBitIndex = 8; private byte bits; public void LoadBitByte(byte bits) { this.bits = bits; currentBitIndex = 0; } public bool AllBitsConsumed { get { return currentBitIndex == 8; } } public bool GetNextBit() { return (bits & (1 << currentBitIndex++)) != 0; } }
RecordReadState 類當前只需要處理bits,但是將來我可能還要創建一個BitReadState 類用來保存讀取狀態
RecordReadState 類保存了一個字節用來當作指針指出下一個可用的位在字節的哪個地方,如果字節已經用完了存儲滿了所有的位數據
(currentBixIndex = 8 (0-7 being the available bits)),方法AllBitsConsumed 就會返回true,指示我們需要讀取一個新的 bit byte
GetNextBit方法只是簡單的從 bit byte中讀取當前的bit ,然后將currentBitIndex(bit index)的值加1
demo
using NUnit.Framework; using OrcaMDF.Core.Engine.Records; namespace OrcaMDF.Core.Tests.Engine.Records { [TestFixture] public class RecordReadStateTests { [Test] public void General() { var state = new RecordReadState(); // No bits available Assert.IsTrue(state.AllBitsConsumed); state.LoadBitByte(0xD2); // 11010010 // Bits available Assert.IsFalse(state.AllBitsConsumed); // Reading bit values Assert.IsFalse(state.GetNextBit()); Assert.IsTrue(state.GetNextBit()); Assert.IsFalse(state.GetNextBit()); Assert.IsFalse(state.GetNextBit()); Assert.IsTrue(state.GetNextBit()); Assert.IsFalse(state.GetNextBit()); Assert.IsTrue(state.GetNextBit()); // One bit left Assert.IsFalse(state.AllBitsConsumed); Assert.IsTrue(state.GetNextBit()); // Bits exhausted, ready for next byte Assert.IsTrue(state.AllBitsConsumed); } } }
SqlBit實現
一旦我們實現了狀態的讀取,我們就可以實現SqlBit 類型
public class SqlBit : ISqlType { private readonly RecordReadState readState; public SqlBit(RecordReadState readState) { this.readState = readState; } public bool IsVariableLength { get { return false; } } public short? FixedLength { get { if (readState.AllBitsConsumed) return 1; return 0; } } public object GetValue(byte[] value) { if(readState.AllBitsConsumed && value.Length != 1) throw new ArgumentException("All bits consumed, invalid value length: " + value.Length); if (value.Length == 1) readState.LoadBitByte(value[0]); return readState.GetNextBit(); } }
SqlBit 在構造函數里傳入一個read state,read state指示當前記錄讀取操作的范圍。需要注意的是固定長度需要依據read state里的當前AllBitsConsumed值
如果字節里面所有位都被占用,那么意味著需要讀取整個字節,如果if (readState.AllBitsConsumed)返回0表示不需要讀取整個字節,但是GetValue方法依然會被調用
GetValue方法會驗證一種情況:readState.AllBitsConsumed 返回真,證明 bit byte是有數據存儲在里面,但是value.Length返回的長度是0,那證明有問題了
如果我們讀到一個值,我們會請求 read state 去裝載一個新的bit byte ,之后,我們可以調用GetNextBit 方法返回 read state的當前bit
相關測試
using NUnit.Framework; using OrcaMDF.Core.Engine.Records; using OrcaMDF.Core.Engine.SqlTypes; namespace OrcaMDF.Core.Tests.Engine.SqlTypes { [TestFixture] public class SqlBitTests { [Test] public void GetValue() { var readState = new RecordReadState(); var type = new SqlBit(readState); // No bytes read - length is one Assert.AreEqual(1, type.FixedLength); // Load byte and check length is 0 readState.LoadBitByte(0xD2); Assert.AreEqual(0, type.FixedLength); Assert.IsFalse((bool)type.GetValue(new byte[0])); Assert.IsTrue((bool)type.GetValue(new byte[0])); Assert.IsFalse((bool)type.GetValue(new byte[0])); Assert.IsFalse((bool)type.GetValue(new byte[0])); Assert.IsTrue((bool)type.GetValue(new byte[0])); Assert.IsFalse((bool)type.GetValue(new byte[0])); Assert.IsTrue((bool)type.GetValue(new byte[0])); // One bit left - length should still be 0 Assert.AreEqual(0, type.FixedLength); Assert.IsTrue((bool)type.GetValue(new byte[0])); // All bits consumed - length should be 1 Assert.AreEqual(1, type.FixedLength); } } }
第五篇完
文章列表