zoukankan      html  css  js  c++  java
  • C#的数据类型以及内存管理机制剖析(1)

    尽管C#(事实上是基于.Net Framework的所有语言)自动处理了内存的分配和释放的问题,并且引入了垃圾收集机制,有完善的数据类型管理能力。但是对于很多情况下,了解其深层的机制是非常有用的,能够大大提高程序的效率。如今Phone7的发布,在移动设备和一些特殊应用上。聪明而又有技巧地处理内存管理和各种数据类型显得非常有用,能够更好得维护和开发程序。

    1. Windows内存管理机制

    各位要深入了解C#的内存管理机制,首先必须要先了解下Windows的内存管理系统。俺记得大学的时候开过一门课叫《操作系统原理》,大家别砸我,这门课俺烤得不好……hh, 关键是提一下内存管理系统,大家还记得块映射和分页映射不?CPU在高速缓存里维护了一张表,放的是内存和磁盘上的数据块的映射关系。具体我就不说了(书上弄了两章来掰扯这个机制),反正就是把一个程序的内存空间分成很多块,会有个算法把最近使用的内存块写入内存(RAM),内存块还可以分,放进高速缓存(Cache),以便下次访问能够高速读取。当然还涉及到写的机制,有很多种方式,还有冲突处理,锁什么的,这些机制当年很令人抓狂哈,现在想想其实网站和程序的缓存机制不是就脱胎于这些算法吗?只不过简单了很多。在32位的系统下,一个程序的最大内存空间是4GB,这4GB就这么在底层的操作系统下管理着,分成了很多段,页和块。这些内存块里放了些什么东西呢?嗯,大家都知道的--数据和逻辑。逻辑就是你的代码中的方法,表示计算,运行,处理;那数据就是要加工的对象了。在C++里面分成简单数据类型和指针数据类型。这个数据,包括变量,常量,对象实体。通常数据占据了一个程序绝大部分的内存空间。也是我们需要进行内存管理的部分。

    2. C#数据类型

    其实C#中的数据类型也是和C++一样分两种,分别是值类型和引用类型。

    (1)值类型对应于C++的简单数据类型,比如int,double,bool,值类型是.Net Framework的预定义类型,总共有13个,详见下表:

    类型 说明 注意
    char 16位unicode字符  
    sbyte 8位有符号整型  
    short 16位有符号整型  
    int 32位有符号整型 数值默认变量类型
    long 64位有符号整型 前缀:L
    byte 8位无符号整型  
    ushort 16位无符号整型  
    unit 32位无符号整型 前缀:U
    ulong 64位无符号整型 前缀:UL
    float 32位单精浮点数 前缀:F
    double 64位双精浮点数 默认浮点数变量类型
    decimal 128位高精度十进位数 会造成性能损失,前缀:M
    bool bool值 true/false

    *16进制数加前缀0x,如0x12ba;

    还有一点必须指出,C#中的结构struct和C++不同,这是一个值类型,不能被继承。struct派生于System.ValueType。

    .Net的内置类型也叫CTS类型,值类型存储的位置是内存空间的堆栈区(stack),《数据结构》里的堆栈(先进后出)

    {
    int p1;
    int p2;
    //code
    {
    int q1;
    int q2;
    //code
    }
    }
    stack top 变量
    700012-700015 p1
    700008-700011 p2
    700004-700007 q1
    700000-700003 q2
    stack bottom  

    可看到上图所示,变量是外层括号先进栈,内层后进栈的顺序。释放时则反之。堆栈指针总是指向最后进栈的变量,同时保留指向下一个堆栈空位的指针。

    这样的结构会使得变量的读取非常高效,只要移动指针就能获得和释放变量。缺点是不够灵活,管理很大的数据结构效率不高。对于值类型,是不需要.net的垃圾回收机制的,本身就能很好地进行内存管理。

    (2)引用类型对应另外一种C++的数据类型,指针类型。这就是说我们如果创建一个引用类型,那么实际是在堆中分配了一块内存空间。然后这个变量实际上是指向这个内存块的指针,在C#中有两种数据是作为引用类型的:第一类是CTS类型中的引用类型,有两个:object和string; 第二类是C#中各种的类,必须从object继承(包括自定义类和C#类库)。广义上讲object类型加上string也对,因为所有C#(.Net Framwork)的类都是继承于System.object这个基类的。

    为什么我们要把Object和String类型分开呢?这个是有原因的,string类型是个很特殊的引用类型。

    using System;
    
    namespace StringTest
    {
        class StringExpress
        {
            public static void Main()
            {
                string s1 = "I am a string";
                string s2 = s1;
                Console.WriteLine(s1);
                Console.WriteLine(s2);
                s2 = "I am a new string";
                Console.WriteLine(s1);
                Console.WriteLine(s2);
            }
        }
    }

    既然是引用类型大家觉得通常情况下应该显示什么呢?因为变量只是个引用(指针),所以s1和s2应该是指向同一个内存区的,事实上,当运行

    string s2 = s1;

    s1和s2的确是指向一样的字符串变量的,前两行输出是:

    I am a string

    I am a string

    大家猜猜后两行会是什么呢?结果是:

    I am a string

    I am a new string

    这就是string类型的特殊之处了,当你给string赋一个新值时,将会创建新的对象而不会覆盖原来的值,这实际上是应用了运算符重载,重新分配了引用对象。

    Object类有很多应用和特点,会在以后的文章中讨论。装箱拆箱,还有一些对类基本方法也都是在Object中定义的。

    3. 垃圾收集

    垃圾回收是.Net中的一个非常重要的机制,事实上这个机制在很大程度上确保了.Net Framework的高性能。垃圾回收是一种对引用类型数据进行释放的自动机制。在C++中这个是不存在的,所有的对象实体都要代码明确得释放,否则会一直占据内存空间。

    我们知道引用类型,维护的是一个指针。这个指针本身实际上也是放在堆栈(Stack)中的,而指向的内存空间是在堆(heap)中。GC(垃圾收集)去释放堆中的内存是根据引用指针来判断地,如果堆中的一个内存块已经没有任何引用(引用变量已经超出范围),那么当一轮垃圾回收进行时,这块内存会被释放。事实上.Net的垃圾回收机制并不只是做了这些。通常堆中的数据,由于多次释放和分配,会变得支离破碎。成为一块块的内存块,这样为分配新的内存空间造成麻烦。因为分配新的空间时需要遍历整个堆空间,以确定一块足够创建对象实例的内存块,并建立引用变量,同时维护堆的存储记录。这样不但效率底下,而且会降低内存使用率。

    GC在垃圾回收时会做两个步骤,1.释放内存空间。2.重新调整并压缩堆区,使剩下的对象重新移动到堆的一个端部。尽管这样做会造成一些额外的资源开销,但是完成这个步骤后,实例化一个对象,以及查找访问实例都会快很多,事实上提高了性能。这个第二步机制也是托管堆和未托管堆的主要区别。

    垃圾收集的时机是由系统控制的,具体的算法微软并没有公布过。通常我们也可以自己在需要的时候调用垃圾回收机制,能够显式地执行System.GC.Collect()。但不推荐,因为这个过程事实上是比较消耗资源的,没有自动收集效率高;对整个程序的执行效果来说,正常情况下还是使用系统的自动收集机制更好些。

    下一篇文章将会详细分析垃圾回收机制和C#的对象使用。

  • 相关阅读:
    每天一道算法题(13)——使用递归颠倒栈
    每天一道算法题(12)——和为n的连续正数序列或者随机数
    函数模板
    答题总结(1)
    顶点间最短路径长度之探寻算法
    最小生成树
    new与delete,malloc与free
    C++的继承与接口
    笔记13 AOP中After和AfterReturning的区别
    笔记12 注入AspectJ切面
  • 原文地址:https://www.cnblogs.com/yangjian2006/p/2090007.html
Copyright © 2011-2022 走看看