C#中的引用类型以及特殊引用类型详解
作者:Darren?Ji 发布时间:2023-06-18 01:43:46
基本
哪些属于引用类型
类(object,string),接口、数组、委托
引用类型分配在哪里
引用类型变量位于线程栈。
引用类型实例分配在托管堆上。
当引用类型实例的大小小于85000bytes,被分配在GC堆上,当大于或等于85000bytes,被分配在LOH(Large Object Heap)上。
变量(Variable),对象(Object),实例(Instance)
变量:
变量分配在线程栈上。
变量可以是值类型,也可以是引用类型。
当变量是引用类型时,包含了对对象的引用(内存地址),也叫做"对象引用"。
对象:
对类、接口、委托和数组等的一个抽象描述。
实例:
在堆上创建的对象,称为对象实例。
引用类型没有new意味着什么?
Object a = null;
Console.WriteLine(a.ToString());
运行报错"未将对象引用设置到对象实例"。
意思是,在线程栈上创建的变量a没有指向到堆上的对象实例。
托管堆上的垃圾回收
GC会遍历所有托管堆上的对象,按照一定的递归遍历算法,对那些没有被引用的不可访问对象实施回收。
new的背后发生了什么
class Program
{
static void Main(string[] args)
{
Person p;
p = new Person(20);
}
}
public class Person
{
public int _age;
public Person(int age)
{
_age = age;
}
public Person()
{
}
}
另外,引用类型的值,比如这里的引用类型Person中的值_age也被分配在托管堆上。
线程栈上的2个变量引用同一个对象实例的内存地址
线程栈上的2个变量引用同一个对象实例的内存地址,改变其中一个变量的值会影响到另外一个变量。
class Program
{
static void Main(string[] args)
{
Person p1 = new Person(20);
Person p2 = p1;
Console.WriteLine("没有改变时p2的年龄是:" + p2._age + "岁");
p1._age = 18;
Console.WriteLine("改变p1的值,p2的年龄也被改变了,现在是:" + p2._age + "岁,真好,年轻了!");
Console.ReadKey();
}
}
public class Person
{
public int _age;
public Person(int age)
{
_age = age;
}
public Person()
{
}
}
string类型是特殊的引用类型
特殊性体现在
从应用角度体现了值类型语义,从内存角度实现为引用类型存储,位于托管堆。
什么是string
可以看作是char的集合char[]
string创建与实例化
string str = "Hello";
以下错误
String str = new String("Hello");
因为System.String没有提供此构造函数
以下可以
Char[] cs = {'a', 'b','c'};
String strArr = new String(cs);
但很少使用这种方式。
字符串的恒定性Immutability
是指字符串一经创建,就不可改变。
字符串一旦创建,在托管堆上分配一块连续的内存空间。
恒定性的好处:
对String对象的任意操作,不会改变原字符串。
操作字符串不会出现线程同步的问题。
成就了字符串驻留。
恒定性的不足:
因为恒定性,对字符串的任何操作,比如字符串比较,字符串链接,字符串格式化等都会创建新的字符串,这样造成内存与性能的双重损耗。如下:
public static void Main()
{
string str = "This is a test about immuntablility of string type.";
Console.WriteLine(str.Inseert(0,"Hi").Substring(19).ToUpper());
Console.WriteLine(str);
}
由于Insert,Substring,ToUpper这些方法,都会创建出新的临时字符串,而这些新的字符串不被其他代码引用的时候,就会被垃圾回收,造成性能上的损失。
恒定性的前提,String为密封类:
public sealed class String:IComparable, ICloneable,IConvertible,IComparable<string>,IEnumerable<char>,IEnumerable,IEquatable<string>
字符串驻留String Interning
MSDN对于字符驻留的定义:公共语言运行库通过维护一个哈希表(Hash Table)来存放字符串,该表成为拘留池,也叫驻留池。
字符串驻留弥补了恒定性的不足:
对于相同的字符串,CLR不会不会为其分配内存空间,而是共享同一内存。
CLR内部维护了一个哈希表HashTable来管理其创建的大部分String对象。key是string本身,value是string对应的内存地址。
驻留的2个静态方法:
public static string Intern(string str);
当str位于作为key位于CLR的驻留池时,返回对str的引用,否则将str字符串添加到hash table中,作为key,并返回引用。
public static string IsInterned(string str);
当str位于作为key位于CLR的驻留池时,返回对str的引用,否则返回null引用,也不添加到hash table中。
字符串驻留是进程级的
可以跨应用程序域AppDomain而存在,驻留池在CLR加载时创建,分配在System Domain中,被进程所有AppDomain所共享,其生命周期不受GC控制。
例子1:
static void Main(string[] args)
{
string strA = "ab";
string strB = "ab";
Console.WriteLine(ReferenceEquals(strA,strB));
string strC = "a";
string strD = strC + "b";
Console.WriteLine(ReferenceEquals(strA, strD));
strD = String.Intern(strD);
Console.WriteLine(ReferenceEquals(strA,strD));
Console.ReadKey();
}
返回:
true
false
true
分析:
strA与strB内容相同,在hash table中的key相同,对应了一样的引用地址,所以返回true。
strD的内容虽然与strA相同,但由于是动态生成的,不会把hash table中key为ab的引用地址赋值给strD,所以strA与strD引用地址不一样,返回false。
strD = String.Intern(strD);手动对strD实施驻留,并发现hash table中已经有了ab这个key,就把对应的引用地址赋值给了strD,这样,strA与strD引用地址相同,返回true。
例子2:
static void Main(string[] args)
{
string s1 = "abc";
string s2 = "ab";
string s3 = s2 + "c";
Console.WriteLine(string.IsInterned(s3) ?? "null");
Console.WriteLine(ReferenceEquals(s1,s3));
Console.ReadKey();
}
返回:
abc
false
分析:
string.IsInterned(s3)对s3进行手动驻留,发现hash table中abc这个key,于是,就返回abc的引用地址。但并没有把引用地址赋值给s3。
s1和s3的引用地址还是不一样,返回false。
例子3:
static void Main(string[] args)
{
string strA = "abcdef";
string strC = "abc";
string strD = strC + "def";
Console.WriteLine(ReferenceEquals(strA,strD));
string StrE = "abc" + "def";
Console.WriteLine(ReferenceEquals(strA,StrE));
Console.ReadKey();
}
返回:
False,
true
分析:
因为strD = strC + "def"中strD的内容虽然与strA想同,但因为是动态生成的,不会被添加到hash table中,所以引用地址不一样,返回false。
"abc"+"def在IL中呈现为abcdef,不是动态生成的,并且发现hash table中已经有了abcdef这个key,于是就把对应的引用地址赋值给了strE,这样strA和StrE就有了相同的引用地址,返回true。如图:
例子4:
static void Main(string[] args)
{
string s = "abc";
//IsInterned()获取字符串变量的引用
Console.WriteLine(string.IsInterned(s) ?? "null");
Console.ReadKey();
}
返回
"abc"。
分析:通过string s = "abc"使得hash table中有abc这个key,当进行string.IsInterned(s)手动驻留判断的时候,发现有abc这个key,就把对应的引用地址返回。
例子5:
class Program
{
static void Main(string[] args)
{
string s = "ab";
s += "c";
//IsInterned()获取字符串变量的引用
Console.WriteLine(string.IsInterned(s) ?? "null");
Console.ReadKey();
}
}
返回
null
分析:通过string s = "ab";使得hash table中有了ab这个key,s += "c"是动态拼接,不会把abc放到hashtable中,所以当通过string.IsInterned(s)手动驻留判断的时候,发现没有abc这个key,就返回null。
来源:https://www.cnblogs.com/darrenji/p/3599550.html


猜你喜欢
- Java Memory Model简称JMM, 是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见性、是否可以重排序等问题的无
- 这篇文章主要介绍了Java 比较接口comparable与comparator区别解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作
- Springboot的项目搭建在此省略,pom文件依赖什么的就不说了创建一个实体类@Data@EqualsAndHashCode(callS
- 一般项目做到后期,在测试的时候,需要在测试版本和正式版本之间进行频繁的切换,怎么办呢?土豪的话可以考虑使用两台机器,同时测试,然而为了方便测
- 前面有文章介绍了使用GridView实现表格的方法,本文就来说说如何用ListView实现自适应的表格。GridView比ListView更
- 提示:运行该程序可双击滚动字幕推出程序。(当然任务管理器也可以...)步骤如下:1.新建窗体,并设置窗体的FormBorderStyle属性
- 这几天在排查一个堆外内存泄漏的问题时看到很多人都提到了gperftools这个神器,想要尝试一下结果发现它对macOS的支持不太友好。而且大
- 题外由于idea原因 用注解test无法在控制台上输入所以写死到程序里了,版本都30.9102了为什么还是这样啊,intelJ你们该反思了!
- 设置OpenFeign的FeignClient的Header信息在微服务间使用Feign进行远程调用时需要在 header 中添加信息,那么
- 阅读目录(Content)•1.get与post的区别•1.1 get方法 jsp中的代码form表单代码•1.2 action包中serv
- 前言:我们每天都在编写Java代码,编译,执行。很多人已经知道Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.cl
- 一、同步容器 1、Vector——>ArrayList vector 是线程(Thread)同步(Synchron
- 本文实例为大家分享了Java判断对象是否为空的具体代码,供大家参考,具体内容如下package com.gj5u.publics.util;
- 其中第二级目录包含了五个预定义主键分别是:HKEY_CLASSES_ROOT,HKEY_CURRENT_USER,HKEY_LOCAL_MA
- C# 结构体在 C# 中,结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。struct 关键字用于创建结构体。定义
- 在项目中遇到需要批量更新的功能,原本想的是在Java中用循环访问数据库去更新,但是心里总觉得这样做会不会太频繁了,太耗费资源了,效率也很低,
- Android中图片的左右切换随处可见,今天我也试着查阅资料试着做了一下,挺简单的一个小Demo,却也发现了一些问题,话不多说,上代码~:使
- 前言本文将介绍通过Java编程在PDF文档中添加表格的方法。添加表格时,可设置表格边框、单元格对齐方式、单元格背景色、单元格合并、插入图片、
- Map接口Map类似y(x)=x;这样的函数(key对应x,value对应y)Map与Collection并列存在。用于保存具有映射关系的数
- 今天我们来看一下在C#中对象序列化XML的方法。不得不说,在这个Json横行的年代,XML虽然式微,但也的确是一股子清流。(个人感觉)不多说