导航:首页 > 源码编译 > string字符串源码解析

string字符串源码解析

发布时间:2023-03-08 18:12:58

A. Swift进阶-String源码解析

Swift进阶-类与结构体
Swift-函数派发
Swift进阶-属性
Swift进阶-指针
Swift进阶-内存管理
Swift进阶-TargetClassMetadata和TargetStructMetadata数据结构源码分析
Swift进阶-Mirror解析
Swift进阶-闭包
Swift进阶-协议
Swift进阶-泛型
Swift进阶-String源码解析
Swift进阶-Array源码解析

创建一个空的字符串发生了什么?

这里并不能看出String的内存结构。那么接下来就借助 Swift源码 的方式看看String在内存中到底是如何存储的。

打开swift源码 -> stdib里的 String.swift

最直观地可以看到 String 是一个结构体,就是我们所说的值类型;它有一个成员变量 _StringGuts

其中最后有一个创建空字符串初始化方式 self.init(_StringGuts()) :

接下来看看这个 _StringGuts 到底是什么东西?
同样找到swift源码 -> stdib里的 StringGuts.swift

_StringGuts 也是一个结构体,它有一个成员变量是 _StringObject 类型的实例;
并且在最后是通过初始化出一个 _StringObject 类型的实例来初始化 _StringGuts 的。

所以真正swift的 String 的实质就是 _StringObject 。接下来看看 _StringObject 到底是什么玩意儿?

找到swift源码 -> stdib里的 StringObject.swift ,可以看到 _StringObject 是一个结构体,再找到空字符串的初始化函数:

ps: 注意这里初始化时的传参,下面会说到这几个成员

最终找到字符串最终初始化函数,该函数是对成员的初始化赋值,那么只要搞懂这几个成员是代表什么意思,那就能搞清楚字符串的底层实质了。

_StringObject 存储着一些成员变量,文章最开始使用x/8g格式化输出一个空字符串对象empty的时候,那我猜测:输出的内容应该就是 _StringObject 里的_count、_variant、_discriminator、_flags。

internal var _variant: Variant 是一个枚举值,默认是immortal 0:

internal var _discriminator: UInt8 在初始化的时候传递了一个Nibbles.emptyString( Nibbles 是一个枚举类型):

0xE000_0000_0000_0000 与文章最上面截图相对应起来了:

那接下来我们就能测试一下字符串了:

字符a的ASCII编码是97,97的16进制是61,注意那个2的字节位的输出

小于等于15个字符串时,会记录字符串的位数。
对于小字符串(小于等于15个字符串)来说,是优先直接存到内存当中,无需另外分配内存空间的。(和NSString差不多类似)

接下来看看中文字符

中文字符不是ASCII编码,一个中文字符占据3个字节(24位),也是我们上面通过源码分析得出的使用了 0xA000_0000_0000_0000

所以 _StringObject.Nibbles 是一个识别器,去识别字符串是不是ASCII编码。

对于大字符串(大于15个字符串)来说,原本的小字符串占据的15个字节已经不足以存储字符串了,那就会发生改变:

来看看0x8000000000000000在源码中出现的定义是一个大原始字符串:

那剩下的 0x000000010000b860 到底是什么东西呢?它是字符串的内存 相对地址;
那应该偏移多少呢?来看源码里的注解

意思是0x10000b860需要加上偏移量 nativeBias 即32,32的16进制是0x20:
0x10000b860 + 0x20 = 0x10000b880

在源码注解里找到大字符串标志位

大字符串前8位就记录着这些标志位信息,0xd000000000000012就是大字符串前8位,拿到科学计算器里看看标志位:

所以count是0x12,转换成10进制就是18,正好对应18个字符。

对于 String 来说,它并不支持通过下标的方式获取字符

只能通过 String.Index 的方式来访问

对于 Swift 来说, String 是一系列字符的集合,也就意味着 String 中的每一个元素是不等长的。那也就意味着我们在进行内存移动的时候步长是不一样的,什么意思?
比如我们有一个 Array 的数组(Int 类型),当我们遍历数组中的元素的时候,因为每个元素的内存大小是一致的,所以每次的偏移量就是 8 个字节。

但是对于字符串来说不一样,比如我要方位 str[1] 那么我是不是要把我这个字段遍历完成之后才能够确定是的偏移量?
依次内推每一次都要重新遍历计算偏移量,这个时候无疑增加了很多的内存消耗。这就是为什么我们不能通过 Int 作为下标来去访问 String 。

可以很直观的看到 Index 的定义:

position aka encodedOffset 一个 48 bit 值,用来记录码位偏移量;
transcoded offset : 一个 2 bit 的值,用来记录字符使用的码位数量;
grapheme cache : 一个 6 bit 的值,用来记录下一个字符的边界;
reserved : 7 bit 的预留字段;
scalar aligned : 一个 1 bit 的值,用来记录标量是否已经对齐过。

String.Index 的本质就是一个64位的位域信息,这个位域信息展示的就是上面的解释。

创建 String.Index 实际上就是通过 encodedOffset 或者 transcoded offset , encodedOffset 就是方便我们从内存中通过下标访问到字符串。

B. 关于java中String类!!!!

ava字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生。
一、从根本上认识java.lang.String类和String池
首先,我建议先看看String类的源码实现,这是从本质上认识String类的根本出发点。从中可以看到:
1、String类是final的,不可被继承。public final class String。
2、String类是的本质是字符数组char[], 并且其值不可改变。private final char value[];
然后打开String类的API文档,可以发现:
3、String类对象有个特殊的创建的方式,就是直接指定比如String x = "abc","abc"就表示一个字符串对象。而x是"abc"对象的地址,也叫
做"abc"对象的引用。
4、String对象可以通过“+”串联。串联后会生成新的字符串。也可以通过concat()来串联,这个后面会讲述。
6、Java运行时会维护一个String Pool(String池),JavaDoc翻译很模糊“字符串缓冲区”。String池用来存放运行时中产生的各种字符串,
并且池中的字符串的内容不重复。而一般对象不存在这个缓冲池,并且创建的对象仅仅存在于方法的堆栈区。
5、创建字符串的方式很多,归纳起来有三类:
其一,使用new关键字创建字符串,比如String s1 = new String("abc");
其二,直接指定。比如String s2 = "abc";
其三,使用串联生成新的字符串。比如String s3 = "ab" + "c";
二、String对象的创建
String对象的创建也很讲究,关键是要明白其原理。
原理1:当使用任何方式来创建一个字符串对象s时,Java运行时(运行中JVM)会拿着这个X在String池中找是否存在内容相同的字符串对象,
如果不存在,则在池中创建一个字符串s,否则,不在池中添加。
原理2:Java中,只要使用new关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。
原理3:使用直接指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String池中的字符串,池中没有就在池中创建一个,有则罢
了!但绝不会在堆栈区再去创建该String对象。
原理4:使用包含变量的表达式来创建String对象,则不仅会检查维护String池,而且还会在堆栈区创建一个String对象。
另外,String的intern()方法是一个本地方法,定义为public native String intern(); intern()方法的价值在于让开发者能将注意力集中到
String池上。当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池
中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
三、不可变类
不可改变的字符串具有一个很大的优点:编译器可以把字符串设置为共享。
不可变类String有一个重要的优点-它们不会被共享引用。
是这样的,JAVA为了提高效率,所以对于String类型进行了特别的处理---为string类型提供了串池
定义一个string类型的变量有两种方式:
string name= "tom ";
string name =new string( "tom ")
使用第一种方式的时候,就使用了串池,
使用第二中方式的时候,就是一种普通的声明对象的方式
如果你使用了第一种方式,那么当你在声明一个内容也是 "tom "的string时,它将使用串池里原来的那个内存,而不会重新分配内存,也就是说,string saname= "tom ",将会指向同一块内存
另外关于string类型是不可改变的问题:
string类型是不可改变的,也就是说,当你想改变一个string对象的时候,比如name= "madding "
那么虚拟机不会改变原来的对象,而是生成一个新的string对象,然后让name去指向它,如果原来的那个 "tom "没有任何对象去引用它,虚拟机的垃圾回收机制将接收它。
据说这样可以提高效率!!!

C. String类源码笔记(一):成员变量和构造器

String类表示字符串,所有类似"abc"形式的字符串(或魔法字符串)都被看作是这个类的实例。String是不可变的,当一个字符串在常量池中被创建时,他的值就不会被改变。

所在路径:javalangString.java

为了保证String类是一个不可变类,String类的成员变量多为私有和不可变的。

其中serialPersistentFields在序列化时使用:

JDK8的String类一共有16个构造器,其中两个是@Deprecated,一个是私有构造器,剩下的13个是可以调用的。

无参构造器直接将""的value赋值给当前类的value。""的value是一个空的char[],其length为0。调用使用了无参构造器的String对象的isEmpty()方法会得到true,调用length()方法会得到0,判断其==null会得到false。

入参为String对象时,构造器会对其进行直接取值。

入参为字符串数组时,构造器调用的是Arrays.Of()方法。

其中Arrays.Of()方法是为了将入参的字符串序列深拷贝到this.valuie中,他的源码为:

其中System.array()方法的源码为:

这个方法支持直接传入想要生成的String的母串,通过偏移量和有效长度找出需要赋值给this.value的部分,然后调用Arrays.OfRange()方法进行深拷贝。

当需要将一个Unicode编码序列转换为String时,可以使用以下构造器:

当需要将一个bytes[]转换为String时,可以使用以下构造器:

此外,还有一些将上述构造器进一步封装的构造器,其本质都是简化入参。另外,String类的构造器同样支持传入StringBuffer和StringBuilder。如果传入的是StringBuffer,构造器会为其加锁。如果传入的是StringBuilder则不会加锁。

事实上,StringBuffer和StringBuilder的toString()方法调用的也是String类的构造器,他们最终的底层实现都是Arrays.Of()。

最后,String类还提供了一个保护类型的构造方法。该方法相比入参为char[]的构造器多了一个share参数,这个参数并没有实际作用,只是用来和其他构造器进行区分。当String类内部调用该构造器时:

该构造器不能对外暴露的原因是需要保持String类的不可变性。

阅读全文

与string字符串源码解析相关的资料

热点内容
如何看服务器几核 浏览:262
找酒吧设计公司用什么app 浏览:680
基本初等函数的导数公式及导数的运算法则 浏览:915
为什么小米app启动广告关不了 浏览:877
空调压缩机一直不停 浏览:511
养殖系统开发源码 浏览:82
pdf的目录 浏览:406
光遇安卓如何一个人拍视频 浏览:277
怨女pdf 浏览:708
扭曲服务器什么时候开 浏览:23
加密货币换平台 浏览:610
手机内存压缩软件 浏览:33
生成树是否与遍历算法有关 浏览:728
python强化学习迷宫 浏览:450
老包子解压视频 浏览:885
服务器注册是什么意思 浏览:419
程序员群体焦虑如何破局 浏览:585
程序员在广州上班 浏览:803
androidlinuxadt 浏览:512
广联达软件加密锁原装芯片 浏览:338