前言

上一篇文章《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()
{
} 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 - ; i > index; i--)
{
this.items[i] = this.items[i - ];
}
this.items[index] = item;
}
/// <summary>
/// Contains the specified arg.
/// </summary>
/// <param name="arg">Argument.</param>
public bool Contains(T arg)
{
for (int i = ; 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 > )
{
for (int i = ; i < this.count; i++)
{
this.items[i] = null;
}
this.count = ;
}
}
/// <summary>
/// Tos the array.
/// </summary>
/// <param name="array">Array.</param>
public void ToArray(T[] array)
{
if (array != null)
{
for (int i = ; 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, , this.count, comparer);
}
/// <summary>
/// Foreach the specified handler.
/// </summary>
/// <param name="handler">Handler.</param>
public void Foreach(EggArray<T>.IterationHandler handler)
{
for (int i = ; 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 = ; 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 = ; i < this.count; i++)
{
if (this.items[i].Equals(arg))
{
return i;
}
}
return -;
}

以上便是我们仿照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>的数据结构(二)》中,小匹夫将详细介绍下小匹夫觉得有用且有趣的方法。

装模作样的声明一下:本博文章若非特殊注明皆为原创,若需转载请保留原文链接及作者信息慕容小匹夫

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

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

    前言: 首先,小匹夫要祝各位看官圣诞快乐,新年愉快-.上一篇文章<自己动手,实现一种类似List<T>的数据结构(一)> 介绍了一下不依靠List<T>实现的各种接 ...

  2. jquery另外一种类似tab切换效果

    简要:最近做项目一些效果不能用淘宝kissy框架 所以代码得自己写啊 网上当然有很多组件 但是用他们的代码很多(有的是我不需要的代码) 且还要看API 还不如自己动手写个简单一个,是这么一种简单的效果 ...

  3. SQL中一种类似GUID值的函数实现

        开发中会需要用到多列值组合成一个ID值的情况.比如做数据清洗的时候,一张表A有五列,分别是医院.科室.医生.职称.电话.面有许多重复的数据需要和另一个表B(和A列相同)做对比.清洗需要做两件事 ...

  4. 一种类似Retrofit声明接口即可实现调用的WebApi客户端框架

    为.Net出力 java有okhttp,还在okhttp这上搞了一个retrofit,.net有HttpClient,但目前我没有发现有类似的retrofit框架.最近在搞mqtt的webApi封装, ...

  5. React中ref的三种用法 可以用来获取表单中的值 这一种类似document.getXXId的方式

    import React, { Component } from "react" export default class MyInput extends Component { ...

  6. Apache是目前应用最广的Web服务器,PHP3是一种类似ASP的脚本语言

    一.如何获得软件? 获得这3个软件包的方法很多,目前大多数Linux分发都捆绑了这3个软件包,如RedHat.本文介绍的安装方法是基于从这些软件的官方站点上下载获得的软件包进行的,针对RedHat L ...

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

    一.什么是队列,换句话说,队列主要特征是什么? 四个字:先进先出 六个字:屁股进,脑袋出 脑补个场景:日常排队买饭,新来的排在后面,前面打完饭的走人,这就是队列: OK,思考一个问题,我为什么写了两种 ...

  8. 快速排序的一种实现(Mark Allen 数据结构与算法 c语言版)

    之前关于快速排序一直比较模糊,网上有几种常见写法: 方法一: void quickSort(int s[], int l, int r) { if (l< r) { int i = l, j = ...

  9. Go-利用Map实现类似Python的Set数据结构

    该笔记参考<Go并发编程实战> 首先实现一个自定义的HashSet 利用interface{}作为键,布尔型作为值. package main import ( "bytes&q ...

随机推荐

  1. Hawk 4.6 并行化

    并行化 Hawk支持单机并行化,也就是使用多线程获取数据.它可以控制目前所有任务的数量,为了不给网站造成过大的压力,仅当任务池中的任务数量小于一定值后,才会插入新的任务. 你可以在数据清洗的 执行面板 ...

  2. Windows下Visual studio 2013 编译 Audacity

    编译的Audacity版本为2.1.2,由于实在windows下编译,其源代码可以从Github上取得 git clone https://github.com/audacity/audacity. ...

  3. C++标准库实现WAV文件读写

    在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库. WAV文件结构 ...

  4. linux centos中添加删除修改环境变量,设置java环境变量

    前言 安装完软件必要添加环境变量.指令很少,然而长时间不写就会不自信:我写的对吗?于是百度开始,于是发现又是各有千秋.好吧,好记星不如烂笔头.当然,最重要的是,百度出来的都他妈的是如何添加环境变量,只 ...

  5. Java进击C#——前言

    本章简言 记得三年前笔者来到现在的公司的时候,公司人口不出十个人.那个时候笔者刚从日本回来,想在福州.厦门.青岛找一个合适自己发展的机会.最后我的一个福州的朋友打电话希望我能过去帮他,跟他一起创业.这 ...

  6. javascript arguments(转)

    什么是arguments arguments 是是JavaScript里的一个内置对象,它很古怪,也经常被人所忽视,但实际上是很重要的.所有主要的js函数库都利用了arguments对象.所以agru ...

  7. Android之使用Bundle进行IPC

    一.Bundle进行IPC介绍 四大组件中的三大组件(Activity.Service.Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口 ...

  8. MongoDB基础

    1.概念及特点 说明:由于部分语句中$ 符号无法正常显示,使用¥代表 概念 MongoDB是一个基于文档的分布式的开源的NoSQL数据库,文档的结构为BSON形式,每一个文档都有一个唯一的Object ...

  9. Jexus Web Server 完全傻瓜化图文配置教程(基于Ubuntu 12.04.3 64位)[内含Hyper-v 2012虚拟机镜像下载地址]

    1. 前言 近日有感许多新朋友想尝试使用Jexus,不过绝大多数都困惑徘徊在Linux如何安装啊,如何编译Mono啊,如何配置Jexus啊...等等基础问题,于是昨日向宇内流云兄提议,不如搞几个配置好 ...

  10. 这可能是史上最全的CSS自适应布局总结教程

    标题严格遵守了新广告法,你再不爽,我也没犯法呀!话不多说,直入正题. 所谓布局,其实包含两个含义:尺寸与定位.也就是说,所有与尺寸和定位相关的属性,都可以用来布局. 大体上,布局中会用到的有:尺寸相关 ...