自己动手,实现一种类似List<T>的数据结构(一)

前言

上一篇文章《Unity3D中常用的数据结构总结与分析》简单总结了一下小匹夫工作中经常遇到的一些数据结构。不过小匹夫一直有种观点,就是光说的热闹实际啥也不做真的没啥意思。光说不练假把式,那么这篇文章不如记录一下小匹夫自己动手实现一个有类似功能的数据结构的过程吧。

模仿List<T>

寻思半天,写代码是为了啥?不是为了写以致用嘛?那么小匹夫工作中用的最多的数据结构是啥?思来想去还就是List<T>了,而且平时使用的时候的确也觉得有自己定制的空间。作为一个类,重要的无非是它的名字,构造函数,属性和各种方法,因为小匹夫喜欢吃鸡蛋,再加上一个好朋友的喵叫蛋壳,所以咱们的新数据结构就叫做EggArray<T>好了,既然是要模仿List<T>,那么定好类名之后我们自然需要去参考一下List<T>的构造函数,属性和方法(列出的都是公有的)从而进一步来确定我们自己的类成员咯。不过呢,首先我们要先明确我们自己的EggArray<T>到底需要怎么实现,以及实现哪些功能,List<T>只是我们模仿的对象,如果实现的还都是List<T>自己的那一套,我们也就没有什么必要做现在的这些事情了。

EggArray<T>是什么

上一篇文章分析过,List<T>的内部其实也是一个Array,且是强类型的,所以我们的EggArray<T>也秉承这个特点,内部通过一个Array来实现,且需要声明类型。但是同时我们也看到List<T>继承和实现了很多接口,比如能实现foreach方法的IEnumerable接口等,而且值类型和引用类型通吃。这里为了EggArray<T>实现起来轻装简行,我们不继承List<T>继承的各种接口,同时我们的EggArray只服务于引用类型。(也是从方便和使用的角度考虑,毕竟值类型不能赋值null,引用类型可以赋值null这一点,作为一个博客的内容就没有必要去考虑服务值类型了)。那么小伙伴可能想问了,不继承那些接口,像最基本的foreach这种需求是不是匹夫混蛋你就不想实现了?NO,NO,俗话说得好,"车到山前必有路,听说委托也不错"。。。咳咳扯远了,其实也很简单,小匹夫上上篇文章《Unity3D中使用委托和事件(一)》介绍过的委托代理其实就可以用来实现EggArray<T>的foreach功能,甚至还有好多小匹夫自己定制的功能,比如Map,Filter,Without之类的。下面具体实现的时候小匹夫还会再扯。

EggArray<T>的成员

那么明确了大的方向,再经过小匹夫自己的定制,对List<T>的成员进行增减之后,我们的EggArray<T>类和它的成员(变量&&属性,构造函数,私有方法,公有方法,小匹夫定制方法(在下一篇中说))如下:

EggArray类

//EggArray类
public class EggArray<T> where T : class
{
}

属性&变量(暂定,下一篇还会根据情况扩充):


属性

说明
Capacity EggArray的容量
Count EggArray中的元素个数
items T[],一个Array,因为上一篇文章说过List<T>的内部其实还是Array,所以内部我们也使用Array
foreachHandler 一个delegate,用来实现foreach的功能
//EggArray<T>的属性&&变量
private int capacity;
private int count;
private T[] items;
public delegate void foreachHandler(T item);

public int Count
{
    get
    {
        return this.count;
    }
}

public int Capacity
{
    get
    {
        return this.capacity;
    }
}

构造函数:

构造函数 说明
EggArray() 初始化 EggArray<T> 类的新实例,该实例为空并且具有默认初始容量。
EggArray(int32) 初始化 EggArray<T> 类的新实例,该实例为空并且具有指定的初始容量。
//EggArray的构造函数,默认容量为8
public EggArray() : this(8)
{
}

public EggArray(int capacity)
{
    this.capacity = capacity;
    this.items = new T[capacity];
}

下面就是EggArray的各种方法了,上文小匹夫已经说过了,咱们这里只是参考List<T>列出来的一些公共方法,有一些List<T>烂大街的方法比如Add,Rmove这些公共方法肯定都是要实现的,可是一些私有方法咱们平时接触不到呀,甚至在List<T>也没有查到。那么小匹夫觉得很重要,也很能体现咱们EggArray<T>长度十分灵活特点的一个私有方法,应该就非那个能灵活改变数组长度的方法莫属了吧?我们称之为Resize()好了。

小匹夫还说过要自己定制一些平时会用到,但List<T>并没有现成方法的方法了。比如把EggArray<T>中的每个值映射到一个新的数组中的Map方法,遍历List中的每个值,返回包含所有通过predicate真值检测的元素值的Filter方法,或者是遍历List,以List中的元素的某个成员进行排序的indexBy方法,还有返回一个除去所有null值的Compact方法等等。下面就按照这3类不同的方法列出来我们的EggArray<T>中的方法。

私有方法

私有方法 说明
Resize 当数组元素个数大于或等于数组的容量时,调用该方法进行扩容,会创建一个新的Array存放数据,“增长因子”为2
//当数组元素个数不小于数组容量时,需要扩容,增长因子growthFactor为2
private void Resize()
{
    int capacity = this.capacity * growthFactor;
    if (this.count > capacity)
    {
        this.count = capacity;
    }
    T[] destinationArray = new T[capacity];
    Array.Copy(this.items, destinationArray, this.count);
    this.items = destinationArray;
    this.capacity = capacity;
}

公共方法(List<T>也有的)

公共方法 说明
Add 将对象添加到 EggArray<T> 的结尾处。
AddRange 将指定集合的元素添加到 EggArray<T> 的末尾。
Insert 将元素插入 EggArray<T> 的指定索引处。
Contains 确定某元素是否在 EggArray<T> 中。
Clear 从 EggArray<T> 中移除所有元素。
ToArray 将 EggArray<T> 的元素复制到新数组中。
Sort 使用默认比较器对整个 EggArray<T> 中的元素进行排序。
Foreach 对 EggArray<T> 的每个元素执行指定操作。
Remove 从 EggArray<T> 中移除特定对象的第一个匹配项。
RemoveAt 移除 EggArray<T> 的指定索引处的元素。
Find 搜索与指定谓词所定义的条件相匹配的元素,并返回整个 EggArray<T> 中的第一个匹配元素。
IndexOf 搜索指定的对象,并返回整个 EggArray<T> 中第一个匹配项的从零开始的索引。
///List<T>已有的功能
/// <summary>
/// Add the specified item.
/// </summary>
/// <param name="item">Item.</param>
public void Add(T item)
{
    if (this.count >= this.capacity)
    {
        this.Resize();
    }
    this.items[this.count++] = item;
}
/// <summary>
/// Adds the range.
/// </summary>
/// <param name="collection">Collection.</param>
public void AddRange(IEnumerable<T> collection)
{
    if (collection != null)
    {
        foreach (T current in collection)
        {
            this.Add(current);
        }
    }
}
/// <summary>
/// Insert the specified index and item.
/// </summary>
/// <param name="index">Index.</param>
/// <param name="item">Item.</param>
public void Insert(int index, T item)
{
    if (this.count >= this.capacity)
    {
        this.Resize();
    }
    this.count++;
    for (int i = this.count - 1; i > index; i--)
    {
        this.items[i] = this.items[i - 1];
    }
    this.items[index] = item;
}
/// <summary>
/// Contains the specified arg.
/// </summary>
/// <param name="arg">Argument.</param>
public bool Contains(T arg)
{
    for (int i = 0; i < this.count; i++)
    {
        if (this.items[i].Equals(arg))
        {
            return true;
        }
    }
    return false;
}
/// <summary>
/// Clear this instance.
/// </summary>
public void Clear()
{
    if (this.count > 0)
    {
        for (int i = 0; i < this.count; i++)
        {
            this.items[i] = null;
        }
        this.count = 0;
    }
}
/// <summary>
/// Tos the array.
/// </summary>
/// <param name="array">Array.</param>
public void ToArray(T[] array)
{
    if (array != null)
    {
        for (int i = 0; i < this.count; i++)
        {
            array[i] = this.items[i];
        }
    }
}
/// <summary>
/// Sort the specified comparer.
/// </summary>
/// <param name="comparer">Comparer.</param>
public void Sort(IComparer<T> comparer)
{
    Array.Sort<T>(this.items, 0, this.count, comparer);
}
/// <summary>
/// Foreach the specified handler.
/// </summary>
/// <param name="handler">Handler.</param>
public void Foreach(EggArray<T>.IterationHandler handler)
{
    for (int i = 0; i < this.count; i++)
    {
        handler(this.items[i]);
    }
}
/// <summary>
/// Remove the specified arg.
/// </summary>
/// <param name="arg">Argument.</param>
public bool Remove(T arg)
{
    for (int i = 0; i < this.count; i++)
    {
        if (this.items[i].Equals(arg))
        {
            this.items[i] = null;
            this.Compact();
            return true;
        }
    }
    return false;
}
/// <summary>
/// Removes at index.
/// </summary>
/// <param name="index">Index.</param>
public void RemoveAt(int index)
{
    if (index < this.count)
    {
        this.items[index] = null;
        this.Compact();
    }
}

/// <summary>
/// Indexs the of.
/// </summary>
/// <returns>The of.</returns>
/// <param name="arg">Argument.</param>
public int IndexOf(T arg)
{
    for (int i = 0; i < this.count; i++)
    {
        if (this.items[i].Equals(arg))
        {
            return i;
        }
    }
    return -1;
}

以上便是我们仿照List<T>的公共方法所要实现的我们自己的公共方法,但是看说明我们很快就能发现一个问题。啥嘞?对嘞,就是很多方法都有方向性。比如Add方法,是将新的对象添加到EggArray<T>的末尾,可是我想要加到最开始怎么办。又或者Find方法,返回第一个满足条件的元素,但是要是我想要找最后一个匹配的呢?类似的问题还存在于IndexOf,Remove等等。所以这就是我们定制我们自己方法的定制思路之一:为了拓展已有方法的适用范围。(关于两端操作,大家想到了什么吗?没错,就是LinkedList,但是LinkedList本质上是链表,而我们的内部实现其实是Array,所以只是借鉴一下LinkedList的功能而非实现方法。其实这里对insert方法的实现就能看出和EggArray内部同为Array的List<T>在处理中间插入新的元素是多蛋疼的一件事情)

但是我们回到List<T>的MSDN页面,看看罗列出来的公有方法,总觉得少了点什么。哎?最直观的,貌似没有Slice呀。或者是我想做一些有限的过滤功能以得到符合我们简单需求的新数组,哎?貌似也没有Filter之类的功能?其实我们还有好多需求。。。那么我们第二条定制思路就有了:为了实现List<T>没有实现而我们日常需要用到的功能。在继续下面的内容之前,还是要简单说明一下几个需要注意的点。

  1. Insert方法,上面已经说过了,处理元素插入时,数组是不如链表的。
  2. Contains、IndexOf等方法,这里需要说明一下,在这些方法中我使用了.equles来判断作为参数传入的元素是否与数组内的元素值相同。作为一个处理引用类型的数据结构,我还是要说明一下equles和==的区别,即equals是比较他们的值,而==相当于比较它们在堆中的位置!即==判断的是是否是同一个对象。为了严谨,下面还将引入用==进行比较确定元素身份的方法。
  3. Foreach的实现手段,如上文所述,我们并没有继承和实现那么多接口,所以List<T>实现Foreach的手段我们就无法使用了。但是想想Foreach的目的无法就是遍历的过程中进行一些自己需要的操作,所以这里我使用了delegate来实现这一点。同样,Find这样的功能也可以通过delegate来实现,关于Find的实现放在下面的代码中了。

好啦,上面就是这篇文章的内容了,因为断断续续写了一周所以内容有点多,如果都盛放在一篇里面,可能连小匹夫都要有点密集恐惧症了。那么在下一篇文章《自己动手,实现一种类似List<T>的数据结构(二)》中,小匹夫将详细介绍下小匹夫觉得有用且有趣的方法。

时间: 2024-11-05 23:34:25

自己动手,实现一种类似List<T>的数据结构(一)的相关文章

自己动手,实现一种类似List&lt;T&gt;的数据结构(二)之Underscore.js的前缘

前言: 首先,小匹夫要祝各位看官圣诞快乐,新年愉快-.上一篇文章<自己动手,实现一种类似List<T>的数据结构(一)> 介绍了一下不依靠List<T>实现的各种接口,仿造一个轻量级数据结构的过程.可能有的看官会有一些疑问,例如一些功能可以通过Linq提供的拓展来实现呀.此言不虚但也不全对,为了我们在工作中能方便的操作集合而提供的这些拓展方法(包括我们自己也可以构建的拓展方法),例如 Where,Any,Max,All...balalbala等等这些方法都是针对IEnu

Ionic 设置全局变量,三种方法设置图片一种是直接增加,一种是replace,第三种是管道和第二种类似

原文地址:https://www.cnblogs.com/sugartang/p/11432422.html

几种C#框架提供的数据结构对单值查找的效率比较

做分词组件时,有网友提出采用Hashtable 数据结构查找字符串效率较低,建议改为Dictionary,其理由是采用Hashtable 时Key值是object 会触发装箱和拆箱动作,一直对这种说法表示怀疑,因为我理解只有值类型和引用类型通过object 互转时才会发生装箱和查询,引用类型之间强制转换不应发生装箱和拆箱,而Dictionary 泛型实际上底层还是调用的Hashtable,所以效率怎么会比Hashtable 要高呢?今天决定对比较常用的4种数据结构做一个测试,以便以后做系统性能优

使用java语言实现一个队列(两种实现比较)(数据结构)

一.什么是队列,换句话说,队列主要特征是什么? 四个字:先进先出 六个字:屁股进,脑袋出 脑补个场景:日常排队买饭,新来的排在后面,前面打完饭的走人,这就是队列: OK,思考一个问题,我为什么写了两种实现,它们的区别是什么,哪个性能更好一些? 我觉得学习一定要带着问题来学习: 二.队列的两种实现 1.数组队列 数组队列比较简单,基于之前写的动态数组所实现的,基本方法都是根据队列的特性从而选择性的调用动态数组的方法来实现的. public class ArrayQueue<E> implemen

unity 随笔

转载 慕容小匹夫 从游戏脚本语言说起,剖析Mono所搭建的脚本基础 深入浅出聊优化:从Draw Calls到GC 谁偷了我的热更新?Mono,JIT,IOS JS or C#?不存在的脚本之争 Mono为何能跨平台?聊聊CIL(MSIL) Unity3D移动平台动态读取外部文件全解析 自己动手,实现一种类似List<T>的数据结构(二) 自己动手,实现一种类似List<T>的数据结构(一) 自己动手之使用反射和泛型,动态读取XML创建类实例并赋值 Unity3D中常用的数据结构总结

深入浅出 Java Concurrency (3): 原子操作 part 2[转]

在这一部分开始讨论数组原子操作和一些其他的原子操作. AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray的API类似,选择有代表性的AtomicIntegerArray来描述这些问题. int get(int i) 获取位置 i 的当前值.很显然,由于这个是数组操作,就有索引越界的问题(IndexOutOfBoundsException异常). 对于下面的API起始和AtomicInteger是类似的,这种通过方法.参数的名称就能够得到

Android Bundle传递简单数据、对象数据

Android开发过程中进程遇到组件之间.进程之间等数据的传递,数据传递有很多种,其中使用Bundle传递非常方便. Bundle可以传递多种数据,是一种类似map的key-value数据结构 简单的调用如下所示 Bundle bundle=new Bundle(); bundle.put***(key,value) 但是有时候需要我们传递一个对象,做法就是先把该对象使用serializable序列化 public class Book implements Serializable{ } 然后

每秒高达千万分发,如何应对直播互动平台中海量消息挑战?

由于直播平台的特点,对系统功能设计的可靠性要求更高,同时,如何在直播火热的当下快速实现直播平台的构建,成为很多企业的现实需求.本文主要分享融云直播互动系统的设计与实践,详细介绍礼物.红包等以消息为基础的互动方案设计思路和实践方案并阐述如何结合融云自身技术优势,助力直播企业快速布局市场. 直播互动平台的特点 融云于 2014 年成立时,国内直播平台还未大批兴起,当时我们只提供聊天室服务.2015   年,源于大量客户对直播互动的需求,融云预见直播平台未来会有很好的前景,于是开始与客户积极交流来挖掘

Atomic

CAS原语 CAS(compare and swap)是一组原语指令,用来实现多线程下的变量同步. public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } 在 x86 下的指令CMPXCHG实现了CAS,前置LOCK既可以达到原子性操作.截止2013,大部分多核处理器均支持CAS. CAS原语