.net GC内存管理 前置篇 (数据类型)

内存栈和内存Stack vs Heap

栈Stack :
1)是一个内存数组,类似子弹弹夹一样先进后出的结构体,意味着只能从顶部添加或从顶部删除;
2)内存分配是静态的,变量无法调整大小;
3)访问速度相对快;
3)同时只能由一个执行线程使用;
5)一旦脱离作用域,立刻被清除;

堆Heap :
1)是一块内存,在其中分配了块以存储某些类型的数据对象;
2)内存分配是动态的,变量可以调整大小;
3)访问速度相对慢;
4)同时能被多个线程使用;
5)托管堆由GC来清除,非托管堆由个人自行清除;


值类型

值类型被创建后,会在栈Stack上分配一个固定的空间,存储创建的实际数据
当值类型超出它所存在的作用域时,会在栈上被立刻清除;

引用类型

引用类型被创建后,会在堆Heap上分配一个非固定的空间,存储创建的实际数据;同时在栈Stack上分配一个固定的空间,存储的数据为一个地址,而这个地址代表了刚刚在堆Heap上创建的实际数据的位置;
当引用类型的实例,超出它所存在的作用域时,会在栈上被立刻清除;而在堆上的数据会等待下一次GC回收来清除(如果它可被回收,具体见上一篇博客);

C# 数据类型(值类型,基类为System.ValueType)

  • bool :布尔
  • char :字符
  • decimal :高精度浮点数
  • double :双精度浮点数
  • float :单精度浮点数
  • int :整形
  • long :长整形
  • enum :枚举
  • struct :结构体
    没有 == 操作符,需要自己重写
  • keyvaluepair :键值对
    Dictionary的子元素,通常foreach Dictionary时候的item,因为它继承自结构体struct,所以没有 == 操作符,需要自己重写,虽然Dictionary是引用类型,但是keyvaluepair却是值类型

 ps: 这里只列了常用部分的数据类型,其他相近类似的也是属于值类型;

C# 数据类型(引用类型,基类为System.object)

  • class :类
  • interface : 接口
  • delegate : 委托
  • object :基类
  • string :字符串
    被当做值类型来使用的引用类型,string的几乎所有操作符和方法都是通过堆中实际值来计算,而不是栈中的地址;
  • string[] :
    字符串数据,需在初始化的时候就定义好长度;
  • ArrayList :
    类型集合,长度可以伸缩,可以添加任何类型的元素,所以没有类型安全,实际是以object来实现,这就意味着添加元素的时候,会把元素放进object中俗称装箱,拿出时再拆箱成具体类型;
  • List<T>:
    ArrayList的泛型版本,节省了装箱拆箱的性能,对类型有约束,所以是类型安全;
  • HashTable :
    key value集合,长度可以伸缩,可以添加任何类型的元素,所以没有类型安全,实际是以object来实现,同样需要装箱拆箱(也可以说是ArrayList的key value版本)(据说有提供线程安全的方法Synchronized,但还是建议用专门提供线程安全的Concurrent集合)
  • Dictionary :
    HashTable 的泛型版本,节省了装箱拆箱的性能,对类型有约束,所以是类型安全;(也可以说是List<T>的key value版本)
  • ConcurrentBag :
    线程安全版本的List<T>
  • ConcurrentDictionary :
    线程安全版本的Dictionary <T>

 ps: 这里只列了常用部分的数据类型,其他相近类似的也是属于引用类型;

Q&A

  • Q:引用类型是分配在堆里的吗?
    A:引用类型是在堆里存储实际数据,然后在栈中存储指向堆里实际数据的地址;引用类型的Operator(==, ! =, =等)和 方法(add,remove等)均是操作的地址,重写过该方法的除外;
  • Q:Null是什么数据类型?Nullable是什么数据类型
  • A:Null没有类型,对于引用类型来说,赋值null指的是空引用,对于值类型来说,一般是不能赋值null的,除非是Nullable比如int?;Nullable实际是Nullable<T>的结构体struct,一种实现可赋值空的把戏;
  • Q: 所有的类型都继承自System.object,难道System.ValueType不是吗?如果是的话,继承自System.ValueType的类型为什么是值类型而不是引用类型?
    A:System.ValueType是继承自System.object,但是CLR对继承自System.ValueType的子类做了特殊处理,使其拥有值类型的特性。
  • Q: == vs equals
    A:对于引用类型来说,==比较是存在栈中的引用地址,equals则比较的是存在堆中的实际对象值;
    对于值类型来说,==和equals都是比较的是存在栈中的实际值,但是 == 会尝试把左右两边的比较值做类型转换再比较,而equals不会;
    但是有一些例外,比如string是引用类型,==是被重写过的,实际用的是equals,所以string永远比较是实际值,而class使用equals时,即使属性值都一样也是false,因为class并没有重写equals,所有调的是父类的equals方法,          但是父类并不知道class中有哪些属性,所以也是false。
    鉴于==和equals都是可以被覆盖,如果只是单纯想比较引用地址是否一样,可以使用ReferenceEquals,该方法不会被覆盖;
    string s1 = "test";
    string s2 = "test";
    string s3 = "test1".Substring(0, 4);
    object s4 = s3;
    Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));
    Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s3), s1 == s3, s1.Equals(s3));
    Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s4), s1 == s4, s1.Equals(s4));
    

    The output is:

    True True True
    False True True
    False False True
  • Q:值类型与引用类型在代码上最大的区别?
    A:当引用类型实例都是同一地址时,改变被引用对象的值时,所有实例都会改变,除非是通过深拷贝的方式来改变;当引用类型和值类型被当做参数传入另一个方法返回时,引用类型实例会自动改变,而值类型要通过ref、out的方式来返回才可以,这是因为引用类型传递是地址,而值类型传递的是值的复制;

 

posted @ 2020-08-18 15:16  willardzmh  阅读(170)  评论(0编辑  收藏  举报