轉 http://blog.csdn.net/kakashi8841/article/details/42025367數組
版權全部,轉載須註明出處!
緩存
在作網絡通訊的時候,常常須要用到:網絡
咱們須要設計一個類來實現:app
這個實現你能夠用一個List<byte>來實現(自己List就支持無限往裏面添加元素)。不過因爲這個類比較特殊。處於網絡最底層,使用比較頻繁。所以咱們仍是本身用byte[]來處理。
函數
這個簡單,在實現了上一步添加字節數組的基礎上,剩下的只須要把各類數據類型編碼成byte數組而已。好比一個int變成用4個byte表示。
oop
好比你寫入了10個byte,那麼確定須要能從裏面讀出這10個byte。否則寫入的數據就沒意義了。
ui
<span style="font-family:Microsoft YaHei;">using System; using System.Text; namespace com.duoyu001.net { namespace buffer { /* ============================================================================== * 功能描述:字節緩衝類 * 創 建 者:cjunhong * 主 頁:http://blog.csdn.net/kakashi8841 * 郵 箱:[url=mailto:john.cha@qq.com]john.cha@qq.com[/url] * 建立日期:2014/12/02 16:22:09 * ==============================================================================*/ public class ByteBuffer { //增長的容量 public const short CAPACITY_INCREASEMENT = 128; public const ushort USHORT_8 = (ushort) 8; public const short SHORT_8 = (short) 8; //字節數組 private byte[] buffers; //讀取索引 private int readerIndex; //寫的索引 private int writerIndex; //上次備份的reader索引 private int readerIndexBak; //字符數組 空字符串 public static byte[] NULL_STRING = new byte[] {(byte) 0, (byte) 0}; public ByteBuffer() : this(8) { } /// <summary> /// 帶參構造函數 初始化字節數組 /// </summary> /// <param name="initCapacity">初始容量</param> public ByteBuffer(int initCapacity) { buffers = new byte[initCapacity]; } /// <summary> /// 帶參構造函數 向字節數組中 寫字節 /// </summary> /// <param name="buffers">字節數組</param> public ByteBuffer(byte[] buffers) : this(buffers.Length) { writeBytes(buffers); } public void writeBytes(byte[] data, int dataOffset, int dataSize) { ensureWritable(dataSize); Array.Copy(data, dataOffset, buffers, writerIndex, dataSize); writerIndex += dataSize; } public void writeBytes(byte[] data) { writeBytes(data, 0, data.Length); } public void writeByte(byte data) { writeBytes(new byte[] {data}); } public void writeByte(int data) { writeBytes(new byte[] {(byte) data}); } public void writeShort(int data) { writeBytes(new byte[] {(byte) (data >> 8), (byte) data}); } public void writeInt(int data) { writeBytes(new byte[] { (byte) (data >> 24), (byte) (data >> 16), (byte) (data >> 8), (byte) data }); } public void writeString(string data) { writeString(data, Encoding.UTF8); } public void writeString(string data, Encoding encoding) { if (data == null) { writeBytes(NULL_STRING); } else { byte[] b = encoding.GetBytes(data); byte[] strBytes = new byte[b.Length + 2]; strBytes[0] = (byte) ((b.Length & 0xff00) >> 8); strBytes[1] = (byte) (b.Length & 0xff); b.CopyTo(strBytes, 2); writeBytes(strBytes); } } public byte readByte() { byte b = buffers[readerIndex]; readerIndex++; return b; } public ushort readUnsignShort() { ushort u = (ushort) (buffers[readerIndex] << USHORT_8 | buffers[readerIndex + 1]); readerIndex += 2; return u; } public short readShort() { short i = (short) (buffers[readerIndex] << SHORT_8 | buffers[readerIndex + 1]); readerIndex += 2; return i; } public int readInt() { int i = buffers[readerIndex] << 24 | buffers[readerIndex + 1] << 16 | buffers[readerIndex + 2] << 8 | buffers[readerIndex + 3]; readerIndex += 4; return i; } public uint readUnsignInt() { return (uint) readInt(); } public byte[] readBytes(int length) { byte[] b = new byte[length]; Array.Copy(buffers, readerIndex, b, 0, length); readerIndex += length; return b; } public string readString() { return readString(Encoding.UTF8); } public string readString(Encoding encoding) { ushort charLength = readUnsignShort(); byte[] strBytes = readBytes(charLength); return encoding.GetString(strBytes); } public void writeBuffer(ByteBuffer buff) { byte[] bytes = buff.readBytes(buff.readableBytes()); writeBytes(bytes); } public ByteBuffer readBuffer(int length) { byte[] bytes = readBytes(length); return new ByteBuffer(bytes); } public byte[] toArray() { return readBytes(readableBytes()); } public byte[] getBytes() { return buffers; } public int readableBytes() { return writerIndex - readerIndex; } public void saveReaderIndex() { readerIndexBak = readerIndex; } public void loadReaderIndex() { readerIndex = readerIndexBak; } private void ensureWritable(int dataSize) { int leftCapacity = buffers.Length - writerIndex; if (leftCapacity < dataSize) { int oldReaderIndex = readerIndex; int oldWriterIndex = writerIndex; writerIndex = readableBytes(); readerIndex = 0; if (buffers.Length - writerIndex >= dataSize) { Array.Copy(buffers, oldReaderIndex, buffers, 0, oldWriterIndex - oldReaderIndex); } else { byte[] newBuffers = new byte[buffers.Length + CAPACITY_INCREASEMENT]; Array.Copy(buffers, oldReaderIndex, newBuffers, 0, oldWriterIndex - oldReaderIndex); buffers = newBuffers; } } } public int getReaderIndex() { return readerIndex; } public int getWriterIndex() { return writerIndex; } public int getCapacity() { return buffers.Length; } public string remainBufferString() { string s = ""; for (int i = readerIndex; i < writerIndex; i++) { s += buffers; if (i < writerIndex - 1) { s += ", "; } } return s; } } } }</span>
好比你想寫入一個int。那麼只要調用writeInt方法。想寫入short、byte、string等 只要調用相應的writeShort、writeByte、writeString等方法便可。
this
好比你想讀出一個int。那麼只要調用readInt方法。相應讀取short、byte、string等 也只要調用readShort、readByte、readString等方法。
編碼
好比你想讀取一個數據,而後判斷數據是否符合指望值,若是不符合則返回到讀取前狀態。(這個在處理分包的時候常常須要用到,由於你須要確認本次想讀取的數據是否已經所有接受完,若是還沒接受完,那麼你就須要等到下次接受完整再來讀取)那麼只要這麼調用:
saveReaderIndex
readXXX
loadReaderIndex
url
能夠看到。writeInt、writeShort、writeByte、writeString等方法,都是調用writeBytes(byte[] data)。沒錯,正如上面說的,各類寫方法,只是把指定的數據類型編碼成byte數組,而後添加到裏面而已。
好比writeInt,其實只是簡單用移位獲取它每一個8位的的數據(一個int是由4個byte組成的嘛-_-)。而後就writeBytes(byte[] data)
public void writeBytes(byte[] data, int dataOffset, int dataSize) { ensureWritable(dataSize); Array.Copy(data, dataOffset, buffers, writerIndex, dataSize); writerIndex += dataSize; }
上面說了,咱們要支持能無限往裏面寫數據。所以,第一行代碼ensureWritable就是來肯定當前是否能寫入指定長度的數據。若是判斷不行,則會進行擴容(怎樣判斷具體的思路咱們在後面會說)。
第二行代碼則是把寫入的數據複製到咱們這個緩存對象上的指定位置。
第三行則是把寫指針writeIndex日後移。
細心的讀者應該會發現ensureWritable中有關於readerIndex、writerIndex這些參數的一些計算。接着看下面。
咱們使用byte[]數組來保存數據,那麼咱們怎麼知道若是追加數據的時候,應該把新數據加入到數組中的哪一個位置?怎麼知道當前有多少數據能夠讀?有多少空間能夠寫入數據?
咱們使用writerIndex來記錄當前數組中哪一個位置能夠開始寫入數據。最開始這個值爲0,每當寫入一個byte的時候,這個值加1。(請看上面1.2中writeBytes的第三行代碼)
緩衝區的數據是讀取以後就會丟棄的,可是若是每次讀取就要重建數組來實現丟棄,這樣的開銷就太大了。所以,咱們可使用readerIndex來記錄當前讀取到數組中哪一個位置,那麼下次讀取就會從這個位置開始讀取數據了。每當讀取一個byte,readerIndex加1。你能夠先看看下面這段readInt的代碼,讀取了一個int(4個byte),那麼readerIndex會增長4。
public int readInt() { int i = buffers[readerIndex] << 24 | buffers[readerIndex + 1] << 16 | buffers[readerIndex + 2] << 8 | buffers[readerIndex + 3]; readerIndex += 4; return i; }
怎樣判斷當前有多少數據能夠讀?根據上面對這兩個參數的解釋,咱們能夠輕易得出問題的答案是:writerIndex-readerIndex。這也正是readableBytes方法中的實現。
public int readableBytes() { return writerIndex - readerIndex; }
最簡單的方法是:判斷數據剩餘寫入空間是否大於要寫入的數據長度。剩餘寫入空間即:buffers.Length - writerIndex
當時若是上面判斷出的剩餘寫入空間比要寫入的數據長度小時,是否就要重建一個更大的數組呢?不必定,由於還能夠回收一些已經讀取過的空間來使用。具體代碼:
private void ensureWritable(int dataSize) { int leftCapacity = buffers.Length - writerIndex; if (leftCapacity < dataSize) { int oldReaderIndex = readerIndex; int oldWriterIndex = writerIndex; writerIndex = readableBytes(); readerIndex = 0; if (buffers.Length - writerIndex >= dataSize) { Array.Copy(buffers, oldReaderIndex, buffers, 0, oldWriterIndex - oldReaderIndex); } else { byte[] newBuffers = new byte[buffers.Length + CAPACITY_INCREASEMENT]; Array.Copy(buffers, oldReaderIndex, newBuffers, 0, oldWriterIndex - oldReaderIndex); buffers = newBuffers; } } }
能夠看到,當知足buffers.Length - writerIndex >= dataSize條件時,是沒有重建數組的。由於這時候說明你前面有一些讀取過的數據,所以你只須要把那部分讀取過的數據丟棄掉,就有更多的空間來容納要寫入的數據了。
前面說了,你有時候須要進行嘗試數據讀取,可是當發現沒到讀取時間的時候,想要恢復讀取狀態,就能夠經過在讀取前保存讀取指針,後面能夠恢復讀取指針。基本關於這個類在Socket通訊中使用已經足夠知足大部分應用場景的須要了。至於你說你用Protobuf或什麼之類的協議。和這個類是無關的。好比:讀取的時候只要這個對象中讀取byte字節,而後反序列化成Protobuf數據便可。反正這個類是通訊中最基礎的一個數據類。有須要的就拿去吧~