重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
第一章 数据结构与算法 (P1—P38)
网站建设哪家好,找创新互联公司!专注于网页设计、网站建设、微信开发、小程序定制开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了象山免费建站欢迎大家使用!
1.1 算法
1.1.1 算法的基本概念 (P1—P4)
所谓算法是指解题方案的准确完整的描述。
1. 算法的基本特征
(1)可行性(2)确定性(3)有穷性(4)拥有够的情报
2. 算法的基本要素
一个算法通常由两种基本要素组成:一是对数据对象的运算和操作,二是算法的控制结构。
(1) 算法中对数据的运算和操作 (插入、删除)
(2) 算法的控制结构
一个算法一般都可以用顺序、选择、循环三种基本控制结构组合而成。
1.1.2 算法复杂度(P4—P6)
算法的复杂度主要包括时间复杂度和空间复杂度。
1. 算法的时间复杂度
所谓算法的时间复杂度,是指执行算法所需要的计算工作量。
可以用算法在执行过程中所需基本运算的执行次数来度量算法的工作量。
3. 算法的空间复杂度
一个算法的空间复杂度,一般是指执行这个算法所需要的内存空间。
1.2数据结构的基本概念
数据结构,主要研究和讨论以下三个方面的问题:
① 数据的逻辑结构;
② 数据的存储结构;
③ 对各种数据结构进行的运算。(插入、删除)
主要目的是为了提高数据处理的效率。所谓提高数据处理的效率,主要包括两个方面:一是提高数据处理的速度,(时间复杂度)二是尽量节省在数据处理过程中所占用的计算机存储空间。(空间复杂度)
1.2.1什么是数据结构 (P6—P11)
1. 数据的逻辑结构
所谓数据的逻辑结构,是指反映数据元素之间逻辑关系的数据结构。
2. 数据的存储结构
数据的逻辑结构在计算机存储空间中的存放形式称为数据的存储结构(也称为数据的物理结构)
一种数据的逻辑结构根据需要可以表示成多种存储结构,常用的存储结构有顺序、链接、索引等存储结构。而采用不同的存储结构,其数据处理的效率是不同的。
1.2.3线性结构与非线性结构 (P12)
一般将数据分为两大类型:线性结构与非线性结构。
线性结构又称线性表
如果一个数据结构不是线性结构,则称之为非线性结构。
1.3线性表及其顺序存储结构
1.3.1线性表的基本概念 (P12—P13)
线性表是由n (n≥0)个数据元素a1,a2,…,an组成的一个有限序列,表中的每一个数据元素,除了第一个外,有且只有一个前件,除了最后一个外,有且只有一个后件。即线性表或是一个空表,或可以表示为。
(a1,a2,…,ai,…,an)
非空线性表有如下一些结构特征:
① 有且只有一个根结点a1,它无前件;
② 有且只有一个终结点an,它无后件;
③ 除根结点与终端结点外,其他所有结点有且只有一个前件,也有且只有一个后件。
1.3.2线性表的顺序存储结构 (P13—P14)
在计算机中存放线性表,一种最简单的方法是顺序存储,也称为顺序分配。
线性表的顺序存储结构具有以下两个基本特点:
① 线性表中所有元素据所占的存储空间是连续的;
② 线性表中各数据元素在存储空间中是按逻辑顺序依次存放的。
假设线性表中的第一个数据元素的存储地址为ADR(a1),每一个数据元素占K个字节,则线性表中第i 个元素ai在计算机存储空间中的存储地址为
ADR(a1)=ADR(a1)+(i-1)K
1.3.3顺序表的插入运算 (P14—P15)
在平均情况下,要在线性表中插入一个新元素,需要移动表中一半的元素。因此,在线性表顺序存储的情况下,要插入一个新元素,其效率是很低的。
1.3.4顺序表的删除运算 (P15—P16)
在平均情况下,要在线性表中删除一个元素,需要移动表中表中一半的元素。因此,在线性表顺序存储的情况下,要删除一个元素,其效率也是很低的。
由线性表在存储结构下的插入与删除运算可以看出,线性表的顺序存储结构对于小线性表或者其中元素不常变动的线性表来说是合适的,因为顺序存储的结构比较简单。但这种顺序存储的方式对于元素经常需要变动的大线性表就不太合适了,因为插入删除的效率比较低。
1.4栈和队列
1.4.1栈及其基本运算 (P16—P18)
1.什么是栈
栈是限定在一端进行插入与删除的另一端称为栈底。即栈是按照“先进后出”(FILO)或“后进先出”(LIFO)的原则组织数据的,因此,栈也被称为“先进后出”表或“后进先出”表。由此可以看出,栈具有记忆作用。
2.栈的顺序存储及其运算(采用顺序存储结构的栈称为顺序栈)
栈的基本运算有三种:入栈、退栈与读栈顶元素。
(1) 入栈运算(2)退栈运算(3)读栈顶元素
1.4.2队列及其基本运算 (P18—P20)
1.什么是队列
队列(queue)是指允许在一端进行插入、而在另一端进行删除的线性表。允许插入的一端称为队尾,通常用一个称为尾指针(rear)的指针指向队尾元素,一端称为排头(也称为队头)通常也用一个排头指针(front)指向排头元素的前一个位置。
队列双称为“先进先出”或“后进后出”的线性表。
3. 循环队列及其运算
在实际应用中,队列的顺序存储结构一般采用循环队列的形式。
所谓循环队列,就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。
(1) 入队运算
(2) 退队运算
1.5线性链表
1.5.1线性链表的基本概念 (P20—P23)
由于线性表的顺序存储结构存在以上这些缺点,对于大的线性表,特别是元素变动频繁的大线性表不宜采用顺序存储结构,而是采用下面要介绍的链式存储结构。
在链式存储方式中,要求每个结点由两部分组成:一部分用于存放数据元素值,称为数据域;另一部分用于存放指针,称为指针域。
在链式存储结构中,存储数据结构的存储空间可以下连续,各数据结点的存储顺序与数据元素之间的逻辑关系可以不一致,而数据元素之间的逻辑关系是由指针域来确定的。
链式存储方式既可用于表示线性结构,也可以用于表示非线性结构。
1. 线性链表
线性表的链式存储结构称为线性链表。
2. 带链的栈
栈也是线性表,也可以采用链式存储结构。
3. 带链的队列
与栈类似,队列也是线性表,也可以采用链式存储结构。
1.5.2线性链表的基本运算 (P23—P25)
线性链表在插入过程中不发生数据元素移动的现象,只需改变有关结点的指针即可,从而提高了插入的效率。
从线性链表的删除过程可以看出,在线性链表中删除一个元素后,不需要移动表的数据元素,只需改变被删除元素所在结点的前一个结点的指针域即可。
1.5.3循环链表及其基本运算 (P25—P26)
循环链表具有以下两个特点:
(1) 在循环链表中增加了一个表头结点,指针域指向线性表的第一个元素的结点。循环链表的头指针指向表头结点。
(2) 循环链表中最后一个结点的指针域不是空,而是指向表头结点。即在循环链表中,所有结点的指针构成了一个环状链。
1. 6树与二叉树
1.6.1树的基本概念 (P26—P28)
在树结构中,每一个结点只有一个前件,称为父结点,没有前件的结点只有一个,称为树的根结点,简称为树的根。
在树结构中,每一个结点可以有多个后件,它们都称为该结点的子结点。没有后件的结点称为叶子结点。
在树结构中,一个结点所拥有的后件个数称为该结点的度
在树中,所有结点中的最大的度称为树的度。
根结点在第1层。
树的最大层次称为树的深度。
1.6.2二叉树及其基本性质 (P28—P31)
1. 什么是二叉树
二叉树具有以下两个特点:
① 非空二叉树只有一个根结点;
② 每一个结点最多有两棵子树,且分别称为该结点的左子树与右子树。
2. 二叉树的基本性质
性质1在二叉树的第K层上,最多有2K-1(K≥1)个结点。
性质2深度为m的二叉树最多有2m-1个结点。
性质3在任意一棵二叉树中,度为0的结点(即叶子结点)总是比度为2的结点多一个。
3. 满二叉树与完全二叉树
(1)满二叉树
所谓满二叉树是指这样的一种二叉树:除最后一层外,每一层上的所有结点都有两个子结点,这就是说,在满二叉树中,每一层上的结点数都达到最大值,即在满二叉树的第K层上有2K-1个结点,且深度为m的满二叉树有2m-1个结点。
(2)完全二叉树
所谓完全二叉树是指这样的二叉树:除最后一层外,每一层上的结点数均达到最大值;在最后一层上只缺少右边若干结点。
满二叉树也是完全二叉树,而完全二叉树一般不是满二叉树。
性质6设完全二叉树共有n个结点。从根结点开始,按层序用自然数1,2,…,n给结点进行编号,则对于编号为k(k=1,2,…,n)的结点有以下结论:
① 若k=1,则该结点为根结点,它没有父结点;若k1,则该结点的父结点编号为INT(k/2)。
② 若2k≤n,则编号为k的结点的左子结点编号为2k;否则该结点无左子结点。
③ 若2k+1≤n,则编号为k的结点的右子结点编号为2k+1;否则该结点无右子结点。
1.6.3二叉树的存储结构 (P31—P32)
在计算机中,二叉树通常采用链式存储结构。
1.6.4二叉树的遍历 (P32—P33)
二叉树的遍历可以分为三种:前序遍历、中序遍历、后序遍历。
1. 前序遍历(DLR)
2. 中序遍历(LDR)
3. 后序遍历(LRD)
1.7查找技术
1.7.1顺序查找 (P33)
顺序查找又称顺序搜索。
对于大的线性表来说,顺序查找的效率是很低的。虽然顺序查找的效率不高,但在下列两种情况下也只能采用顺序查找:
(1) 线性表无序表,则不管是顺序存储结构还是链式存储结构,都只能用顺序查找。
(2) 即使是有序线性表,如果采用链式存储结构,也只能用顺序查找。
1.7.2二分法查找 (P33—P34)
二分法查找只适用于顺序存储的有序表。
显然,当有序线性表为顺序存储时都能采用二分查找,并且,二分查找的效率要比顺序查找高得多。可以证明,对于长度为n的有序线性表,在最坏情况下,二分查找只需要比较log2n次,而顺序查找需要比较n次。
1.8排充技术
1.8.1交换类排序法 (P34—P35)
1. 冒泡排序法
冒泡排序法是一种最简单的交换类排序方法。
假设线性表的长度为n,则在最坏情况下,冒泡排序需要的比较次数为n(n-1)/2。
2. 快速排序法
快速排序法也是一种互换类的排序方法,但由于它比冒泡排序法的速度快,因此称之为快速排序法。
1.8.2插入类排序法 (P35—P37)
1. 简单插入排序法
自以为插入排序,是指将无序序列中的各元素依次插入到已经有序的线性表中。
在简单插入排序法中,这种排序方法的效率与冒泡排序法相同。在最坏情况下,证券交易插入排序需要n(n-1)/2次比较。
2. 希尔排序法
希尔排序法属于插入类排序,但它对简单插入排序做了较大的改进。
1.8.3选择类排序法 (P37—P38)
1. 简单选择排序法
从中选出最小的元素,将它交换到表的最前面。
简单选择排序法在最坏情况下需要比较n(n-2)/2次。
2. 堆排序法
堆排序法属于选择类的排序方法。
堆排序的方法对于规模较小的线性表并不合适,但对于较大规模的来说是很有效的。
分享到搜狐微博
第2章 程序设计基础 (P40—P45)
2.1程序设计方法与风格
程序设计的风格总体而言应该强调简单和清晰,程序必须是可以理解的。可以认为,著名的“清晰第一,效率第二”的论点已成为当今主导的程序设计风格。
源程序文档化应考虑如下几点:
(1) 符号名的命名:符号名的命名应具有一定的实际含义,以便于对程序功能的理解。
(2) 程序注释:正确的注释能够帮助读者理解程序。注释一般分为序言性注释和功能性注释。
(3) 视觉组织:为使程序的结构一目了然,可以在程序中利用空格、空行、缩进等技巧使程序层次清晰。
2.2结构化程序设计
2.2.1结构化程序设计的原则 (P41—P42)
结构化程序设计方法的主要原则可以概括为自顶向下,逐步求精,模块化,限制使用goto语句。
2.2.2结构化程序的基本结构与特点 (P42—P43)
1. 顺序结构
2. 选择结构:选择结构又称为分支结构。
3. 重复结构:重复结构又称为循环结构。
2.3面向对象的程序设计
今天面向对象方法已经发展成为主流的软件开发方法。
一些著名的面向对象语言(如C++、Java)
2.3.2面向对象方法的基本概念 (P45—P48)
1. 对象
对象是面向对象方法中最基本的概念。对象可以用来表示客观世界中的任何实体。
面向对象的程序设计方法中涉及的对象由一组表示其静态特征的属性和它可执行的一组操作组成。
(4) 封装性。
2. 类(Class)和实例(Instance)
将属性、操作相似的对象归为类,也就是说,类是具有共同属性、共同方法方法的对象的集合。所以,类是对象的抽象,而一个对象则是其对应类的一个实例。
3. 消息
对象间的这种相互合作需要一个机制协助进行,这样的机制称为“消息”。消息是一个实例与另一个实例之间传递的信息。
4. 继承
继承是面向对象的方法的一个主要特征。
第3 章 软件工程基础
3.1软件工程基本概念
3.1.1软件定义与软件特点 (P50)
计算机软件是包括程序、数据及相关文档的完整集合。
可见软件由两部分组成:一是机器可执行和程序和数据;二是机器不可执行的,与软件开发、运行、维护、使用等有关的文档。
软件的特点:
① 软件是一种逻辑实体,而不是物理实体,具有抽象性。
② 软件的生产与硬件不同,它没有明显的制作过程。
③ 软件在运行、使用期间不存在磨损、老化问题。
④ 软件的开发、运行对计算机系统具有依赖性,受计算机系统的限制,这导致了软件移植的问题。
⑤ 软件复杂性高,成本昂贵。
⑥ 软件开发涉及诸多的社会因素。
3.1.2软件危机与软件工程 (P51—P52)
软件工程概念的出现源自软件危机。
20世纪60年代末以后,“软件危机”。所谓软件危机是泛指在计算机软件的开发和维护过程中所遇到的一系列严重问题。
1968年在北大西洋公约组织会议(NATO会议)上,讨论摆脱软件危机的办法,软件工程作为一个概念首次被提出。
软件工程包括个要素,即方法、工具和过程。
3.1.3软件工程过程与软件生命周期 (P52—P53)
2.软件生命周期
通常,将软件产品从提出、实现、使用维护到停止使用退役的过程称为软件生命周期。
3.1.4软件工程的目标与原则(P53—P54)
1. 软件工程的目标
软件工程内容主要包括:软件开发技术和软件工程管理。
3.1.5软件开发工具与软件开发环境 (P54)
1. 软件开发工具 (VB、VC++、VFP)
2. 软件开发环境
软件开发环境或称软件工程环境是全面支持软件开发全过程的软件工具集合。
计算机辅助软件工程(CASE)
3.2结构化分析方法
3.2.1需求分析与需求分析方法 (P53—P59)
1. 需求分析
(1) 需求分析阶段的工作
需求分析阶段的工作,可以概括为四个方面:
① 需求获取
② 需求分析
③ 编写需求规格说明书
④ 需求评审
2. 需求分析方法
常见的需求分析方法有:
① 结构化分析方法。主要包括:面向数据流的结构化分析方法(SA)面向数据结构的Jackson方法(JSD)面向数据结构的结构化数据系统开发方法(DSSD)
② 面向对象的分析方法(OOA)
3.2.2结构化分析方法 (P55—P59)
2.结构化分析的常用工具
(1) 数据流图(DFD)
(2) 数据字典(DD)
数据字典是结构化分析方法的核心。
(3) 判定树
(4) 判定表
3.2.3软件需求规格说明书 (P59—P60)
软件规格说明书(SRS)是需求分析阶段的最后成果,是软件开发中的重要文档。
软件需求规格说明书的作用是:
① 便于用户、开发人员进行理解和交流。
② 反映出用户问题的结构,可以作为软件开发工作的基础和依据
③ 作为确认测试和验收的依据。
3.3结构化设计方法
3.3.1软件设计基本概念 (P60—P62)
1.软件设计的基础
软件设计分两步完成:概要设计和详细设计。
2.软件设计的基本原理
(1) 抽象
(2) 模块化
(3) 信息隐蔽
(4) 模块独立性
模块独立程度是评价设计好坏的重要度量标准。衡量软件的模块独立软件的模块独立性使用耦合性和内聚性两个定性的度量标准。
① 内聚性:内聚性是一个模块内部各个元素间彼此结合的紧密程度的度量。
② 耦合性:耦合性是模块间互相连接的紧密程度的度量。
耦合性与内聚性是模块独立性的两个定性标准,耦合与内聚是相互关联的。在程序结构中,各模块的内聚性越强,则耦合性越弱。一般较优秀的软件设计,应尽量做到高内聚,低耦合。
3.3.3详细设计 (P67—P71)
几种主要的工具:
1. 程序流程图(PFD)
2. N-S (盒图)
3. PAD图 PAD图是问题分析图(Problem Analysis Diagram)的英文缩写。
4. PDL
过程设计语言(PDL)也称为结构化的英语和伪码。
3.4软件测试
软件测试的投入,通常其工作量、成本占软件开发总工作量、总成本的40%以上。
软件测试是保证软件质量的重要手段,其主要过程涵盖了整个软件生命期的过程。
3.4.1软件测试的目的 (P71)
关于软件测试的目的,软件测试是为了发现错误而执行程序的过程。
3.4.3软件测试技术与方法综述(P71—P77)
可以分为静态测试和动态测试方法。若按照功能划分可以分为白盒测试和黑盒测试方法。
1. 静态测试与动态测试
(1) 静态测试
静态测试可以由人工进行,充分发挥人的逻辑思维优势。
(2) 动态测试
静态测试不实际运行软件,主要通过人工进行。动态测试是基于计算机的测试,是为了发现错误而执行程序的过程。
2. 白盒测试
白盒测试方法也称结构测试或逻辑驱动测试。
3. 黑盒测试方法
黑盒测试方法也称功能测试或数据驱动测试。黑盒测试是对软件已经实现的功能是否满足需求进行测试和验证。黑盒测试完全不考虑程序内部和逻辑结构和内部特性。
3.4.4软件测试的实施(P77—P80)
软件测试是保证软件质量的重要手段。
软件测试过程一般按4个步骤进行,
1. 单元测试
单元测试是对软件设计的最小单位——模块(程序单元)进行正确性检验的测试。
2. 集成测试
集成测试是测试和组装软件的过程。
3. 确认测试
4. 系统测试
3.5程序的调试
3.5.1基本概念 (P80—P81)
程序调试的任务是诊断和改正程序中的错误。它与软件测试不同,软件测试是尽可能多地发现软件中的错误。
软件测试贯穿整个软件生命期,调试主要在开发阶段。
3.5.2软件调试方法 (P81—P82)
1. 强行排错法
2. 回溯法
3. 原因排除法
第4章 数据库设计基础 (P84—P111)
4.1数据库系统的基本概念
4.1.1数据、数据库、数据库管理系统 (P84—P87)
1. 数据
数据(Data)实际上就是描述事物的符号记录。
2. 数据库
数据库(简称DB)是数据的集合。
3. 数据库管理系统
数据库管理系统(简称DBMS)它是一种软件。
数据库管理系统是数据库系统的核心。
目前流行的DBMS均为关系数据库系统,如微软的Visual FoxPro和Access等。
4. 数据库管理员(简称DBA)
5. 数据库系统
数据库系统(简称DBS)由如下几部分组成:数据库(数据)、数据库管理系统(软件)、数据库管理员(人员)、系统平台之一____硬件平台(硬件)、系统平台之二——软件平台(软件)这五个部分构成了一个以数据库为核心的完整的运行实体,称为数据库系统。
4.1.2数据库系统的发展 (P87—P88)
数据管理发展至今已经历了三个阶段:人工管理阶段、文件系统阶段和数据库系统阶段。
1. 关系数据库系统阶段
数据管理三个阶段的比较
人工管理 文件系统 数据库系统
特点 数据共享程度 无共享
冗余度大 共享性差
冗余度大 共享性大
冗余度小
数据独立性 不独立,完全依赖于程序 独立性差 具有高度的物理独立性和一定的逻辑独立性
4.1.3数据库系统的基本特点 (P88—P890
数据库系统具有以下特点:
1. 数据的集成性
2. 数据的高共享性与低冗余性
3. 数据独立性
数据独立性是数据与程序间的互不依赖性,数据独立性一般分为物理独立性与逻辑独立性两级。
(1) 物理独立性:物理独立性即是数据的物理结构的改变,从而不致引起应用程序的变化。
(2) 逻辑独立性:数据库总体逻辑结构的改变,不需要相应修改应用程序,这就是数据 的逻辑独立性。
4. 数据统一管理与控制
4.1.4数据库系统的内部结构体系 (P89—P91)
1. 数据库系统的三级模式
(1) 概念模式。概念模式是数据库系统中全局数据逻辑结构的描述,是全体用户(应用)公共数据视图。
(2) 外模式。外模式也称子模式或用户模式。它是用户的数据视图。
(3) 内模式。内模式又称物理模式,它给出了数据库物理存储结构与物理存取方法。
2. 数据库系统的两级映射
(1) 概念模式到内模式的映射。
(2) 外模式到概念模式的映射。
4.2数据模型
4.2.1数据模型的基本概念 (P91)
数据模型按不同的应用层次分成三种类型,它们是概念数据模型、逻辑模型、物理数据模型,
概念模型有E-R模型、逻辑数据模型又称数据模型,
层次模型、网状模型、关系模型,
物理数据模型又称物理模型。
1.2.2 E-R模型 (P91—P95)
概念模型是E-R模型(或实体联系模型)
1.E-R模型的基本概念
(1)实体
现实世界中的事物可以抽象成为实体
(2)属性
现实世界均有一些特性,这些特性可以用属性来表示。属性刻画了实体的特征。
(3)联系
一对一的联系,简记为1:1。
一对多或多对一联系,简记为1:M(1:m)或M:1(m:1)。
多对多联系,简高为M:N或m:n。
3.E-R模型的图示法
在E-R图中用椭圆形表示属性。
在E-R图中用菱形表示联系。
4.2.3层次模型的基本结构是树形结构 (P95)
4.2.4网状模型 (P95—P96)
网状模型是一个不加任何条件限制的无向图。
4.2.5关系模型 (P96—P98)
1.关系的数据结构
关系模型采用二维表来表示。
4.3关系代数
(4)查询
① 投影运算
② 选择运算
③ 笛卡尔积运算
则关系R与S经笛卡尔积记为R×S。
3.关系代数中的扩充运算
(1)交运算 (还有并和差)
关系R与S经交运算后所得到的关系是由那些既在R内又在S内的有序组成,记为R∩S。
(2)除运算
如果将笛卡尔积运算看作乘运算的话,那么除运算就是它的运算。
T÷R=S或R/R=S
4.4数据库设计与管理
数据库设计是数据库应用核心。
4.4.1数据库设计概述 (P104)
整个数据库应用系统的开发成目标独立的若干阶段。它们是:需求分析阶段、概念设计阶段、逻辑设计阶段、物理设计阶段。
4.4.2数据库设计的需求分析 (P104—P105)
4.4.3数据库概念设计 (画E-R图) (P105—P108)
4.4.4数据库的逻辑设计 (P108—P109)
1. 从E-R图向关系模式转换。
4.4.5数据库的物理设计 (P110)
.Net开发工具包
整体下载:
1. Snippet Compiler:
2. Source Analysis:
3. GhostDoc:
4. SandCastle:
5. NUnit:
6. MyGeneration:
7. Reflector:
8. Regex Tester:
9. LINQPad:
10. NAnt:
Snippet Compiler
Snippet Compiler是一个基于 Windows 的小型应用程序,你可以通过它来编写、编译和运行代码。如果你具有较小的代码段,并且你不想创建完整的 Visual Studio .NET 项目(以及该项目附带的所有文件),则该工具会很有用。现在Snippet Compiler已经支持.NET Framework 3.5,最新版本为Snippet Compiler Live 2008 Ultimate Edition for Developers (Alpha).
官方主页:
Microsoft Source Analysis for C#
Microsoft Source Analysis for C#是一款C#(不支持VB.NET)代码规范检查工具,前身是微软内部代码规范检查和代码格式强制工具StyleCop,目的是帮助项目团队执行一系列常用的源代码格式规范,它会根据预定义的C#代码格式的最佳实践进行检查,与FxCop不同的是它直接对源代码进行检查,且并不提供灵活的规则设置,强制开发者使用相同的习惯进行C#代码的编写。
官方主页:
GhostDoc
GhostDoc是Visual Studio的一个免费插件,可以帮助开发者生成比较完整规范的XML格式代码注释,如果你的代码遵循微软类库开发人员设计规范 ,由它自动产生的注释就已经完全可以很好地表达开发者创建的方法或者属性的意图,无需手工再进行修改。有了这些标准的XML注释,我们可以使用微软的文档工具Sandcastle生成专业级别的帮助文档。如我们有这样一段代码:
public bool Add(string item)
{
//......
}
public void AppendHtmlText(IHtmlProvider htmlProvider)
{
//......
}
使用GhostDoc生成的注释如下:
/// summary
/// Adds the specified item.
/// /summary
/// param name="item"The item./param
/// returns/returns
public bool Add(string item)
{
//......
}
/// summary
/// Appends the HTML text.
/// /summary
/// param name="htmlProvider"The HTML provider./param
public void AppendHtmlText(IHtmlProvider htmlProvider)
{
//......
}
官方主页:
Sandcastle
Sandcastle是微软发布的一个帮助文档生成工具,它通过反射程序集中的源代码和添加代码到中的XML注释来创建专业级别的帮助文档。Sandcastle于2006年推出,它的面世也使得曾经列入.NET开发必备十大工具之一的文档生成工具NDoc的作者Kevin Downs在2006年7月宣告不再投入NDoc Open Source Project的开发。
官方主页:
Nunit
NUnit 是为 .NET 框架生成的开放源代码单元测试框架。NUnit 使你可以用你喜欢的语言编写测试,从而测试应用程序的特定功能。当你首次编写代码时,单元测试是一种测试代码功能的很好方法,它还提供了一种对应用程序进行回归测试的方法。NUnit 应用程序提供了一个用于编写单元测试的框架,以及一个运行这些测试和查看结果的图形界面。
官方主页:
MyGeneration
作为.NET开发人员,手边有一款代码生成工具必不可少。旧版.NET开发必备十大工具中,作者曾经推荐了非常著名的CodeSmith,不幸的是现在CodeSmith已经商业化,需要花钱购买;幸运的是我们又有一款免费并开源的代码生成工具选择MyGeneration,它的功能丝毫不亚于CodeSmith,完全基于模板引擎进行代码的生成.
官方主页:
Reflector for .NET
相信大名鼎鼎的Reflector for .NET大家都已经用过了,几年前它已经位于.NET开发必备十大工具榜,现在自然也不能例外。它是一个类浏览器和反编译器,可以分析程序集并向你展示它的所有秘密。使用Reflector for .NET可以浏览程序集的类和方法,可以分析由这些类和方法生成的 Microsoft 中间语言 (MSIL),并且可以反编译这些类和方法并查看 C# 或 Visual Basic.NET 中的等价类和方法。经过多年的发展,Reflector for .NET已经发展到了5.1版本,并且提供了相当丰富的插件,利用这些插件我们可以浏览Silverlight程序结构、浏览WPF资源文件、与TestDriven.net集成等。
The Regulator
The Regulator能够使生成和测试正则表达式变得很容易,它允许你输入一个正则表达式以及一些针对其运行该表达式的输入。这样,在应用程序中实现该正则表达式之前,你便可以了解它将产生什么效果以及它将返回哪些种类的匹配项。另外它还提供了正则表达式库管理功能,在线更新正则表达式库,可以在RegexLib.com上搜索需要的正则表达式.
官方主页:
Regex Tester:
LINQPad
随着在.NET Framework 3.5中对于LINQ的支持,越来越多的开发者在开发中使用了LINQ to SQL,但是编写LINQ to SQL查询似乎又成了一件很麻烦的事情,好在我们还有LINQPad这个工具,用来编写LINQ查询,不仅仅是LINQ to SQL,同时它也支持LINQ to XML、LINQ to Objects,另外LINQPad是完全免费的且无需安装,只要下载它的可执行文件就可以了。官方主页:
NAnt
NAnt 是一个基于 .NET 的生成工具,与当前版本的 Visual Studio .NET 不同,它使得为你的项目创建生成过程变得非常容易。当你拥有大量从事单个项目的开发人员时,你不能依赖于从单个用户的座位进行生成。你也不希望必须定期手动生成该项目。你更愿意创建每天晚上运行的自动生成过程。NAnt 使你可以生成解决方案、复制文件、运行 NUnit 测试、发送电子邮件,等等。遗憾的是,NAnt 缺少漂亮的图形界面,但它的确具有可以指定应该在生成过程中完成哪些任务的控制台应用程序和 XML 文件。目前NAnt已经支持.NET Framework 3.5,它的最新版本是0.86 Beta 1。官方主页:
[CodedUITest]
public class CodedUITest1
{
public CodedUITest1()
{
Type t = typeof(CodedUITest1);
//关键是GetCustomAttributes
//根据输出的字符串,你就可以用一些办法来判断了
foreach (object o in t.GetCustomAttributes(true))
Debug.WriteLine(o);
MethodInfo mi = t.GetMethod("CodedUITestMethod1");
foreach (object o in mi.GetCustomAttributes(true))
Debug.WriteLine(o);
}
[TestMethod]
public void CodedUITestMethod1()
{
}
}
尽管很多人相信在.net应用中谈及内存及资源泄露是件很轻松的事情。但GC(垃圾回收器)并不是魔法师,并不能把你完全从小心翼翼处理内存与资源损耗中解放出来。
本文中我将解释缘何内存泄露依然存在以及如何避免其出现。别担心,本文不涉及GC内部工作机制及其它.net的资源及内存管理等高级特性中。
理解泄露本身及如何避免其出现很重要,尤其因为它无法轻松地自动检测到。单元测试在此方面无能为力。一旦产品中你的程序崩溃了,你需要马上找出解决方案。所以在一切都还不是太晚前,花些时间来学习一下本文吧。
Table of Content
· 介绍
· 泄露?资源?指什么?
· 如何检测泄露并找到泄露的资源
· 常见内存泄露原因
· 常见内存泄露原因演示
· 如何避免泄露
· 相关工具
· 结论
· 资源
介绍
近期,我参与了一个大的.net项目(暂叫它项目X吧),我在项目中负责追踪内存与资源泄露。大部分时间我都花在与GUI关联的泄露上,更准确地说是一个基于Composite UI Application Block (CAB).的windows窗体应用。接下来我要说的直接应用到winform上的内容,多数见解同样可以适用到其它.net应用中(像WPF,Silverlight,ASP.NET,Windows service,console application 等等)。
我不是个处理泄露方面的专家,所以我不得不深入钻研了一下应用程序,做一些清理工作。本文的目标是与你们分享在我解决问题过程中的所得所悟。希望能够帮助那些需要检测与解决内存、资源泄露问题的朋友。下面的概述部分首先会介绍什么是泄露,之后会看看如何检测到泄露和被泄露资源,以及如何解决与避免类似泄露,最后我会列出一个对此过程有帮助的工具列表及相关资源。
泄露?资源?指什么?
内存泄露
在进一步深入前,让我们先来定义下我所谓的“内存泄露”。简单引用在Wikipedia上找到的定义吧。该定义与我打算通过本文所帮助解决的问题完美的一致:
在计算机科学领域中,内存泄露是指一种特定的内存损耗,该损耗是由一个计算机程序未成功释放不需要的内存引起的。通常是程序中的BUG阻碍了不需要内存的释放。
仍然来自Wikipedia:”以下语言提供了自动的内存管理,但并不能避免内存泄露。像 Java,C#,VB.NET或是LISP等。”
GC只回收那些不再使用的内存。而使用中的内存无法释放。在.net中,只要有一个引用指向的对象均不会被GC所释放。
句柄与资源
内存可不是唯一被视为资源的。当你的.net应用程序在Windows上运行时,消耗着一个完整的系统资源集。微软定义了系统三类对象:用户(user),图形设备接口(GUI),以及系统内核(kernel)。我不会在此给出完整的分类对象列表,只是指出一些重要的:
· 系统通过使用用户对象(User objects) 来支持windows管理。相关对象包括:提速缓冲表(Accelerator tables),Carets(补字号?),指针(Cursors),钩子(Hooks),图标(Icons),菜单(Menus)和窗体(Windows)。
· GDI对象 支持图形绘制:位图(bitmaps),笔刷(Brushes),设备上下文(DC),字体(Fonts),内存设置上下文(Memory DCs),元文件(Metafiles),画笔(Pens),区域(Regions)等。
· 内核对象 支持内存管理,进程执行和进程间通讯(IPC):文件,进程,线程,信号(Semaphores),定时器(Timer),访问记号(Access tokens),套接字(Sockets)等。
所有系统对象的详细情况都可以在MSDN中找到。
系统对象之外,你还会碰到句柄(handles).据MSDN的陈述,应用程序不能直接访问对象数据或是对象所代表的系统资源。取而代之,应用程序一定都会获得一个对象句柄(Handle),可以使用它检查或是修改系统资源。在.net中无论如何,多数情况下系统资源的使用都是透明的,因为系统对象与句柄都由.net类直接或间接代表了。
非托管资源
像系统对象(System objects)这样的资源自身都不是个问题,但本文仍涵盖了它们,因为像Windows这样的操作系统对可同时打开的 套接字、文件等的数量都有限制。所以关注应用程序所使用系统对象的数量非常重要。
在特定时间段内一个进程所能使用的User与GDI对象数目也是有配额的。缺省值是10000个GDI对象和10000个User对象。如果想知道本机的相关设置值,可以使用如下的注册表键:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows: GDIProcessHandleQuota 和 USERProcessHandleQuota.
猜到了什么?确实没有这么简单,还有一些你会很快达到的其它限制。比如参照:我的一篇有关桌面堆的博客 所述。
假设这些值是可以自定义的,你也许认为一个解决方案就是打破默认值的限制—调高这些配额。但我认为这可不是个好主意,有如下原因:
1. 配额存在的原因:系统中不是只有你独自一个应用程序,所有运行在计算机中的其它进程与你的应用应该分享系统资源。
2. 如果你修改配额,使它不同于其它系统了。你不得不确认所有你的应用程序需要运行的机器都完成了这样的修改,而且这样的修改从系统管理员的角度来说是否会有问题也需要确认。
3. 大部分都采用了默认配额值。如果你发现配置值对你应用程序来说不够,那你可能确实有些清理工作要做了。
如何检测泄露及找到泄露的资源
泄露带来的实际问题在MSDN上的一篇文章中有着很好的描述:
哪怕在小的泄露只要它反复出现也会拖垮系统。
这与水的泄露异曲同工。一滴水的落下不是什么大问题。但是一滴一滴如此反复的泄露也会变为一个大问题。
像我稍后解释的,一个无意义的对象可以在内存中维持一整图的重量级对象。
仍然是同一篇文章,你会了解到:
通常三步根除泄露:
1.发现泄露
2.找到被泄露的资源
3.决定在源码中何时何处释放该资源
最直接“发现”泄露的方式是遭受泄露引发的问题
你或许没有见过内存不足。“内存不足”提示信息极少出现。因为操作系统运行中实际内存(RAM)不足时,它会使用硬盘空间来扩展内存。(称为虚拟内存)。
在你的图形应用程序中可能更多出现的是“句柄不足”的异常。准确的异常不是System.ComponentModel.Win32Exception 就是 System.OutOfMemoryException 均包含如下信息:”创建窗体句柄错误”。这两个异常多发于两个资源被同时使用的情况下,通常都因为该释放的对象没有被释放所致。
另外一种你会经常碰到的情况是你的应用程序或是整个系统变更得越来越慢。这种情况的发生是因为你的系统资源即将耗尽。
我来做个生硬的推断:大多数应用程序的泄露在多数时间里都不是个问题,因为由泄露导致出现的问题只在你的应用程序集中使用很长时间的情况下才会出现。
如果你怀疑有些对象在应该被释放后仍逗留在内存中,那需要做的第一件事就是找出这些对象都是什么。
这看起来很明显,但是找起来却不是这样。
建议通过内存工具找到非预期逗留在内存中的高级别对象或是根容器。在项目x中,这些对象可能是类似LayoutView实例一样的对象们(我们使用了MVP(Model View Presentation )模式)。在你的实际项目中,它可能依赖于你的根对象是什么。
下一步就是找出它们该消失却还在的原因。这才是调试器与工具能真正帮忙的。它们可以显示出这些对象是如何链接在一起的。通过查看那些指向“僵尸对象”(the zombie object)的引用你就可以找到引起问题的根本原因了。
你可以选择 ninja方式(译者:间谍方式?)(参照 工具介绍章节中有关 SOS.dll 和 WinDbg 的部分)。
我在项目X中用了JetBrains的dotTrace,本文中我将继续使用它来介绍。在后面的工具相关章节中我会向你更多的介绍该工具。
你的目标是找到最终引起问题的那个引用。不要停留在你找到的第一个目标上,但是也要问问自己为什么这个家伙还在内存中。
常见内存泄露的原因
上面提到的泄露情况在.net中较常见。好消息是造成这些泄露的原因并不多。这意味着当你尝试解决一个泄露问题时,不需要在大量可能的原因间搜寻。
我们来回顾一下这些常见的罪魁祸首,我把它们区别开来:
· 静态引用
· 未注销的事件绑定
· 未注销的静态事件绑定
· 未调用Dispose方法
· Dispose方法未正常完成
除了上列典型的原因外,还有些其它情况也可能引发泄露:
· Windows Forms:绑定源滥用
· CAB:未移除对工作项的调用
我只列出了可能在你应用程序中出现的一些原因,但应该清楚你的应用程序依赖的其它.net代码、库实际使用中也可能引发泄露。
我们来举个例子。在项目x中,使用了一套第三方控件来构造界面。其中一个用来显示所有工具栏的控件,它管理着一个工具栏列表。这种方式没什么,但有一点,即使被管理的工具栏自身实现了IDisposable接口,管理类却永远也不会去调用它的Dispose方法。这是一个bug.幸运的是这发生在一个很容易发现的工作区:只能我们自身来调用所有工具样的Dispose方法了。不幸的是这还不够,工具栏类自身问题也不少:它并没有释放自身承载的控件(按钮,标签等等)。所以在解决方案中还要添加对每个工具栏中控件的释放,但是这次可就没那么简单了,因为工具栏中的每个子控件都不同。不管怎么样这只是一个特殊的例子,我要表达的观点是你应用程序中使用的任何第三方库、组件都可能引发泄漏。
最后,还有一种由.net framework造成的泄露,由一些不好的使用习惯引起。即使.net framework自身可能引发泄露,但这是你极少会遭遇到的情况。把责任推到.net身上很容易,但在我们把问题推到别人头上前,还是应该先从自身写的代码出发,看看里面有没有问题。
常见泄露演示
我已经列举出了泄露主要的来源,但我还不想仅限于此。如果每个泄露我都能举个鲜活的例子的话,我想本文会更实用些。好,我们先启动Vs 和 dotTrace , 然后看些示例代码。我会同时演示如何解决或是避免每个泄露情况。
项目X中使用了CAB和MVP模式,这意味着界面由工作空间、视图和呈现者组成。简单起见,我决定使用包含一组窗口的Winform应用。其中使用了与Jossef Goldberg的一篇关于“Wpf应用程序内存泄露”文章中相同的方法。甚至我会直接把相同的例子和事件处理函数应用到我的Winform App中。