详解.Net中字符串不变性与相等判断的特殊场景

目录
  • 问题
  • 答案
  • 如何在visual studio中查看变量的内存布局
  • 字符串变量在内存中的布局
  • 比较与解答
  • 结论

今天写bug的时候帮同事解决了一个有趣的问题,可能很多人都会答错。分享给大家。

问题

请看以下例子,并回答问题。

var s1 = "12";
var s2 = "12";

//序列化方式1
var o3 = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(Newtonsoft.Json.JsonConvert.SerializeObject(s1));
//序列化方式2
MemoryStream stream = new MemoryStream();
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
bf.Serialize(stream, s1);
stream.Seek(0, SeekOrigin.Begin);
var o4 = bf.Deserialize(stream);

//====分割线===================================================

var e1 = object.ReferenceEquals(s1, s2);

var e2 = o4 == s1;

var e3 = s1.Equals(o4);

var e4 = o3 == o4;

Console.ReadKey();

请回答分割线后e1, e2, e3, e4 值为true还是false。

人人都知道在.Net中字符串是享元模式的经典范例。字符串具有不变性。(至少在托管层,事实上可以在非托管层修改字符串的值),但你真的能回答对上面的问题么?

答案

e1 = true;
e2 = false;
e3 = true;
e4 = false;

要了解这个问题首先可以看下字符串在内存中的布局。

如何在visual studio中查看变量的内存布局

在VS中可以非常方便的查看托管或非托管变量的内存值。方法如下。

  • 依次在调试模式下打开 调试 -> 窗口 -> 内存 -> 内存1(1~4均可) 打开内存对话框。
  • 在地址栏中输入变量名即可。

字符串变量在内存中的布局

在.Net中字符串是以UTF-16格式在内存中保存的。在本例中s1的内存如下。

00 00 00 00 00 00 00 00 98 d6 fc e5 fb 7f 00 00 02 00 00 00 31 00 32 00

这里可能与你拿到的结果不一样。你可能并没有前8位0x00,因为我把对象头带上了。下面依次解释各段含义。

  • 00 00 00 00 00 00 00 00 最开始的8比特是对象头。其中,在64位下,高4位为0,低4位为一个不为0的数(这里由于并没有执行lock或Gethashcode操作,所以这里为0,感兴趣的自行实验.)
  • 98 d6 fc e5 fb 7f 00 00对象的MethodTable,根据类型而不同,对象的引用指向的位置。
  • 02 00 00 00 字符串长度,这里是2。
  • 31 00 32 00 字符串数组* char,注意都是小端模式。

拿以上s1 s2 o3 o4分别实验可以发现他们的内存一模一样,其中s1 s2直接就是同一块内存地址,但剩下的内存地址都不一样

比较与解答

  • e1 = true; 通过内存看合情合理,毕竟都同一块内存了。
  • e2 = false; 这里如果用的VS的版本比较高的话,也能看出来。因为这里VS会提示:

    可能非有意的引用比较。

    既然是引用比较,内存地址都不一样,肯定是false了。但是如果vs版本不高的话则迷惑性就较大了,其实这里做的是ReferenceEquals的比较。

  • e3 = true; 这里问题出在.Net代码里。字符串类型Equals方法被重载了。
        // Determines whether two strings match.
        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            if (object.ReferenceEquals(this, obj))
                return true;

            if (!(obj is string str))
                return false;

            if (this.Length != str.Length)
                return false;

            return EqualsHelper(this, str);
        }

EqualsHelper方法最终则调用如下。(在.Net 6下)

        // Optimized byte-based SequenceEquals. The "length" parameter for this one is declared a nuint rather than int as we also use it for types other than byte
        // where the length can exceed 2Gb once scaled by sizeof(T).
        public static unsafe bool SequenceEqual(ref byte first, ref byte second, nuint length)

由于实现过于复杂(.Net framework 4.5.2下则较简单,直接按长度比较char,有兴趣的自行查阅),这里就不贴具体实现了。我们很容易看出这里比较的目的是比较两段内存是否相等,显然为true

  • e4 = false;这里是为了比较不同序列化方式的影响,和e2类似,结果显然是false

结论

虽然.Net中字符串是享元模式创建的,但并不能保证同一字符串在内存里只有一份。比如序列化情况等例外情况。如果读者知道其他情况也可以告诉我,提前说声感谢

到此这篇关于详解.Net中字符串不变性与相等判断的特殊场景的文章就介绍到这了,更多相关.Net中字符串不变性与相等判断内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • ASP.NET web.config中数据库连接字符串connectionStrings节的配置方法

    在ASP.NET开发的网站根目录,有一个名为web.config的文件,顾名思义,这是为整个网站进行配置的文件,其格式为XML格式.这里主要谈谈文件中的<connectionStrings>节. <connectionStrings>节是对连接到数据库的字符串进行配置,由于MS SQL Server与ASP.NET同属于微软的产品,因此是使用ASP.NET开发时首选的数据库是MS SQL Server,本文只讨论对MS SQL Server的连接字符串情况.第一种情况,本地开发时,

  • asp.net Split分割字符串的方法

    例如下面我要根据[jb51.net]分割的话 复制代码 代码如下: string str = "reterry[jb51.net]是我们[jb51.net]的站长"; string[] arrstr = str.Split(new char[] { '[', 's', 'o', 's', 'u', 'o', '8', '.', 'c', 'o', 'm', ']' }); for (int i = 0; i < arrstr.Length; i++) { Response.Wri

  • C#、.Net中把字符串(String)格式转换为DateTime类型的三种方法

    方式一:Convert.ToDateTime(string) 复制代码 代码如下: Convert.ToDateTime(string) 注意:string格式有要求,必须是yyyy-MM-dd hh:mm:ss 方式二:Convert.ToDateTime(string, IFormatProvider) 复制代码 代码如下: DateTimeFormatInfo dtFormat = new System.GlobalizationDateTimeFormatInfo(); dtFormat

  • asp.net 字符串加密解密技术

    复制代码 代码如下: using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using Syste

  • c# 连接字符串数据库服务器端口号 .net状态服务器端口号

    正常的数据库连接字符串配置,这是在MSSQL服务器端口是1433(默认)的情况下. <add key="Article" value="server=.;uid=Admin;pwd=admin;database=db;"></add> 但是有时候,为了数据库服务器安全,这个端口会被改成其它的,这时再连接数据库可能报出以下错误: 在建立与服务器的连接时出错.在连接到 SQL Server 2005 时,在默认的设置下 SQL Server 不允

  • asp.net(c#) 使用Rex正则来生成字符串数组的代码

    看这儿.如果你熟悉正则表达式 ,让我们进入正题.这个TOOL的名称叫Regular Expression Exploration. 你可以从这儿下载 .目前的版本是1.0 release. Rex是一个命令行工具, 具体用法可以在CMD下执行便可以看到用法,这个是.net的程序.我们可以引用它,然后用下面的Code来生成我们想要的字符串数组. 复制代码 代码如下: /// <summary> /// Generates the test. /// </summary> /// &l

  • ASP.NET 字符串截取

    复制代码 代码如下: **//// /// 截取字符串,不限制字符串长度 /// /// 待截取的字符串 /// 每行的长度,多于这个长度自动换行 /// public string CutStr(string str,int len) { string s=""; for(int i=0;i 11 { int r= i% len; int last =(str.Length/len)*len; if (i!=0 && i<=last) { if( r==0) {

  • C#.net格式化时间字符串达到不同的显示效果

    有时候我们要对时间进行转换,达到不同的显示效果 默认格式为:2005-6-6 14:33:34 如果要换成成200506,06-2005,2005-6-6或更多的该怎么办呢 我们要用到:DateTime.ToString的方法(String, IFormatProvider) using System; using System.Globalization; String format="D"; DateTime date=DataTime,Now; Response.Write(da

  • 详解.Net中字符串不变性与相等判断的特殊场景

    目录 问题 答案 如何在visual studio中查看变量的内存布局 字符串变量在内存中的布局 比较与解答 结论 今天写bug的时候帮同事解决了一个有趣的问题,可能很多人都会答错.分享给大家. 问题 请看以下例子,并回答问题. var s1 = "12"; var s2 = "12"; //序列化方式1 var o3 = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(Newtonsoft.J

  • 详解Golang中字符串的使用

    目录 1.字符串编码 2.字符串遍历 3.字符串中的字符数 4.字符串trim 5.字符串连接 6.字节切片转字符串 1.字符串编码 在go中rune是一个unicode编码点. 我们都知道UTF-8将字符编码为1-4个字节,比如我们常用的汉字,UTF-8编码为3个字节.所以rune也是int32的别名. type rune = int32 当我们打印一个英文字符hello的时候,我们可以得到s的长度为5,因为英文字母代表1个字节: package main import "fmt"

  • 一文详解Java中字符串的基本操作

    目录 一.遍历字符串案例 二.统计字符次数案例 三.字符串拼接案例 四.字符串反转案例 五.帮助文档查看String常用方法 一.遍历字符串案例 需求:键盘录入一个字符串,使用程序实现在控制台遍历该字符串 思路: 1.键盘录入一个字符串,用 Scanner 实现 2.遍历字符串,首先要能够获取到字符串中的每一个字符 public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的 3.遍历字符串,其次要能够获取到字符串的长度 public int

  • 详解Python中字符串前“b”,“r”,“u”,“f”的作用

    1.字符串前加 u 例:u"我是含有中文字符组成的字符串." 作用: 后面字符串以 Unicode 格式 进行编码,一般用在中文字符串前面,防止因为源码储存格式问题,导致再次使用时出现乱码. 2.字符串前加 r 例:r"\n\n\n\n" 表示一个普通生字符串 \n\n\n\n,而不表示换行了. 作用: 去掉反斜杠的转移机制. (特殊字符:即那些,反斜杠加上对应字母,表示对应的特殊含义的,比如最常见的"\n"表示换行,"\t"

  • 详解Python3中字符串中的数字提取方法

    逛到一个有意思的博客在里面看到一篇关于ValueError: invalid literal for int() with base 10错误的解析,针对这个错误,博主已经给出解决办法,使用的是re.sub 方法 totalCount = '100abc' totalCount = re.sub("\D", "", totalCount) 但是没有说明什么含义,于是去查了其他的资料,做一下记录: 在Python3.5.2 官方文档re模块中sub函数的定义是: re

  • 详解Java中字符串缓冲区StringBuffer类的使用

    StringBuffer 是一个线程安全的可变的字符序列.它继承于AbstractStringBuilder,实现了CharSequence接口. StringBuilder 也是继承于AbstractStringBuilder的子类:但是,StringBuilder和StringBuffer不同,前者是非线程安全的,后者是线程安全的. StringBuffer 和 CharSequence之间的关系图如下: StringBuffer类和String一样,也用来代表字符串,只是由于StringB

  • 详解Swift中对C语言接口缓存的使用以及数组与字符串转为指针类型的方法

    详解Swift中对C语言接口缓存的使用以及数组与字符串转为指针类型的方法 由于Swift编程语言属于上层编程语言,而Swift中由于为了低层的高性能计算接口,所以往往需要C语言中的指针类型,由此,在Swift编程语言刚诞生的时候就有了UnsafePointer与UnsafeMutablePointer类型,分别对应为const Type*类型与Type *类型. 而在Swift编程语言中,由于一般数组(Array)对象都无法直接用于C语言中含有指针类型的函数参数(比如:void*),所以往往需要

  • 详解C++中十六进制字符串转数字(数值)

    详解C++中十六进制字符串转数字(数值) 主要有两个方法,其实都是对现有函数的使用: 方法1: sscanf()   函数名: sscanf 功  能: 从字符串格式化输入 用  法: int sscanf(char *string, char *format[,argument,...]); 以上的 format 为 %x 就是将字符串格式化为 16 进制数 例子:  #include <stdio.h> void main() { char* p = "0x1a"; i

  • 详解java中String值为空字符串与null的判断方法

    Java空字符串与null的区别 1.类型 null表示的是一个对象的值,而不是一个字符串.例如声明一个对象的引用,String a=null. ""表示的是一个空字符串,也就是说它的长度为0.例如声明一个字符串String s="". 2.内存分配 String a=null:表示声明一个字符串对象的引用,但指向为null,也就是说还没有指向任何的内存空间. String s="":表示声明一个字符串类型的引用,其值为""空

  • 详解Python中神奇的字符串驻留机制

    目录 1 什么是字符串驻留机制 2 如何使用字符串驻留机制 3 简单拼接驻留, 运行时不驻留 4 总结 5 全部代码 今天有一个初学者在学习Python的时候又整不会了. 原因是以下代码: a = [1, 2, 3] b = [1, 2, 3] if a is b: print("a and b point to the same object") else: print("a and b point to different objects") 运行结果是a an

随机推荐