缘由:聊天系统中,请求历史记录,要把数据插在前面,新来的消息要插在后面,每一条又要按照索引取值。
经常看到 list.insert(0,x),这种代码,真的很烦。。因为每次insert都要去复制数组。就在想既然需要频繁在前面插入数据,为什么不用链表呢,然后发现还有需求是要按索引取值,链表的取值。。只能去不断的next(),也是烦的一笔。于是,OvonicList 应运而生!
思路就是 biList 在需要改变容量的时候,容量变成当前size的3倍,然后把原有的数据放到中间,再去操作。这样,首尾添加的时候,只有当需要扩容的时候(而不是list那样每次)才会复制数组,效率高一点
大概的示意图如上。
需要的字段有,头部,尾部,数量,数组,容量
基本接口,头部添加,尾部添加,按索引取值,按索引移除,迭代器 等等
private int _head;
private int _tail;
private int _size;
private T[] _items;
private int _capacity;
public void AddFirst(T t)
public void AddLast(T t)
public T this[int index]
public void RemoveAt(int index)
public void Clear()
public bool Remove(T t)
public IEnumerator<T> GetEnumerator()
先看看需要改变容量的场景
1.往前面添加的时候头部index到了0 _head == 0
2.往后面添加的时候尾部index到了数组最后一个 _tail == items.Length-1
3.移除项的时候,容量过大,暂定 _size < _items.Length / 4
然后码代码。做性能测试。
第一行耗时 是自己写的OvonicList ,第二行是LinkedList ,第三行是List
图一:三种扩容条件放在一个if里面做判断。 100w次测试,发现效率居然还比不上List.insert
纳尼!!!??一定是我写的有问题,于是把三种条件拆分了一下
图二:拆分后,比list稍微快那么一丢丢
还是不应该啊,继续找原因,原来是最开始写的 默认容量3,并且第一个元素插入index为1,并没有真正的插入到中间位置,于是优化了一下初始容量最小为11,并且插入位置 (_capacity - 1) / 2;
图三:这下性能完爆List了,只比链表慢一丢丢
还是不服,又对移除做了下优化,List中按index移除,直接就是从该index'开始从新复制后面的数组去,稍微做了下优化,我判断,如果是在前半部分,就数组往后顺序移动,如果在后半部分,就往前顺序移动,果然又快了一丢丢
图四:中间插入移除的时候,改变复制数组为数组顺序移动,性能再好一点
最后这个结果已经算是比较满意了,满足了快速的双端添加,按索引取值,非常适合聊天系统这种场景。接下来只需要添加一些 addrange、构造函数传入数组之类的了。不过这些,基本很少会用到。最后贴上全部代码
using System;
using System.Collections;
using System.Collections.Generic;
public class biList<T> : IReadOnlyList<T>
{
private const int Mincapacity = 0xF;
private int _head;
private int _tail;
private int _size;
private T[] _items;
private int _capacity;
public biList() : this(Mincapacity)
{
}
public biList(int capacity)
{
_capacity = capacity < Mincapacity ? Mincapacity : capacity;
_items = new T[_capacity];
_head = 0;
_tail = 0;
_size = 0;
}
public void AddFirst(T t)
{
if (_size == 0)
{
_head = _tail = (_capacity - 1) / 2;
_items[_head] = t;
_size++;
return;
}
if (_head == 0)
{
Prepare();
}
_items[--_head] = t;
_size++;
}
public void AddLast(T t)
{
if (_size == 0)
{
_head = _tail = (_capacity - 1) / 2;
_items[_head] = t;
_size++;
return;
}
if (_tail == _items.Length - 1)
{
Prepare();
}
_items[++_tail] = t;
_size++;
}
public void Clear()
{
Array.Clear(_items, 0, _items.Length);
_head = 0;
_tail = 0;
_size = 0;
}
public void RemoveAt(int index)
{
IndexCheck(index);
var realIndex = _head + index;
//经测试,移除的时候采用method2 效率更高
//method 1
// {
// _size--;
// Array.Copy(_items, realIndex + 1, _items, realIndex, _tail - realIndex + 1);
// }
//method 2
if (index <= (_size - 1) / 2)
{
for (int i = realIndex; i > _head; i--)
{
_items[i] = _items[i - 1];
}
_items[_head++] = default(T);
_size--;
}
else
{
for (int i = realIndex; i < _tail; i++)
{
_items[i] = _items[i + 1];
}
_items[_tail--] = default(T);
_size--;
}
if (_size < _items.Length / 4)
{
Prepare();
}
}
public bool Remove(T t)
{
for (int i = _head; i < _size; i++)
{
if (EqualityComparer<T>.Default.Equals(_items[i], t))
{
RemoveAt(i);
return true;
}
}
return false;
}
private void Prepare()
{
_capacity = _size * 3;
var temp = _items;
_items = new T[_capacity];
Array.Copy(temp, _head, _items, _size, _size);
_head = _size;
_tail = 2 * _size - 1;
}
private void IndexCheck(int index)
{
if (index < 0 || index > _size - 1)
{
throw new IndexOutOfRangeException();
}
}
public IEnumerator<T> GetEnumerator()
{
var index = _head - 1;
while (++index <= _tail)
{
yield return _items[index];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count
{
get { return _size; }
}
public T this[int index]
{
get
{
IndexCheck(index);
return _items[_head + index];
}
}
}