问大家一个关于字符串的基础问题

2019-07-14 09:28:50 +08:00
wumao  wumao

为什么字符串不可变呢 我给字符串重新赋值 js 底层是如果进行操作的呢 我看网上的理解是 当重新赋值的时候 这时候要把这个字符串当成对象去理解 这个字符串存在堆里面 然后 变量存储内存地址指向这个字符串 然后重新赋值之后 实际上是这个变量的内存地址改变了 然而原本的字符串还存在内存中 可以这么理解吗 但是字符串不是明明就是简单数据类型 存在栈吗

没学过计算机原理 想了解下 底层是怎么运行的。。

3613 次点击
所在节点   JavaScript  JavaScript
17 条回复
muzuiget
muzuiget
2019-07-14 09:41:52 +08:00
原本的字符串可能还在内存中,也有可能稍后被 GC 回收掉。「字符串」真不算简单的数据类型,因为各个语言怎么定义「字符串」都五花八门。
wsxyeah
wsxyeah
2019-07-14 09:43:00 +08:00
引用类型
Edward4074
Edward4074
2019-07-14 09:47:37 +08:00
这么理解,String s = "abc" 实际上是 String s = String(['a','b','c']),字符串本身是一种经过由语言封装后的基础类型,底层实际上是一个数组。而且由于字符串长度不可预知,不可能预留出足够的内存空间来支持可变长度。从 List 的角度来理解,List 本身也是初始化一个长度,持续往里面去添加对象,空间不够时再重新开辟一个更大的 List,再把旧的数据复制过来。
wumao
wumao
2019-07-14 09:48:08 +08:00
@wsxyeah 复杂数据类型才是引用类型啊 字符串不是简单数据类型吗
ipwx
ipwx
2019-07-14 09:49:27 +08:00
@wumao 从原理的角度(即 JS 引擎的实现的角度)来看,字符串不是简单数据类型。不要从语法层面去看问题。
LeeSeoung
LeeSeoung
2019-07-14 09:50:02 +08:00
js 不是很清楚 java 是有文本常量池的,当你赋值一个 String 类型的变量时,先看常量池有没有,有就直接对应上,没有就在常量池新建一个,然后再对应。。
geelaw
geelaw
2019-07-14 09:54:37 +08:00
从理解的角度你不需要知道字符串是怎么实现的。楼主实际上不明白的是“不可变”的含义。

说字符串不可变和说整数不可变是一样的,例如

var x=3; x+=1;

结果并不是 3 这个整数变成了 4 这个整数,而是 x 的内容从一个整数变成另一个整数。

同样,var x="1"; x+="1"; 结果并不是 1 这个字符串变成了 11 这个字符串,而是 x 的内容从一个字符串变成了另一个字符串。

此外,"11".replace("1","2") 的效果并不是让 11 这个字符串变成 22 这个字符串,而是从一个字符串算出来另一个字符串。
geelaw
geelaw
2019-07-14 10:00:34 +08:00
@geelaw #7 Hmmm 似乎 replace 只会替换第一个 occurrence,不过这不影响意思的传达。

一个生活化的例子是,假设“人”是不可变的,JohnAppleseed 是一个男人,而 JohnAppleseed.MakeGenderFemale() 的结果并不是 JohnAppleseed 变成了女人,而是用克隆技术造出了一个新的人,她其它和 JohnAppleseed 一样,只不过性别改变了。

如果“人”是可变的,那么结果就可以是 JohnAppleseed 做了变性手术,从此 JohnAppleseed 这个人就变成了女的。
lhx2008
lhx2008
2019-07-14 10:11:28 +08:00
可变与不可变完全是语言设计上的考量,与底层无关。

string 一般的实现都是实际内容存在堆,栈上有一个指针指过去,这样子函数传递、对象的时候可以被多个栈复用,减少内存复制的时间。

那么,在堆上,是不是就必须不可变呢?不是的。主要是,如果 string 可变,这样子就带来了另外一个问题,我公用的时候,我改了一下,你那边也跟着改了,改着改着就乱了,公用的意义就不大了。

所以,string 被最终设计成了不可变,意思是堆上的内容是不可以改的,如果用 Java 语言里面说,就是这个对象没有一个方法可以修改自己。

但是我要改一个 string 怎么办呢,就是在堆上面重建一个,再在栈上面弄一个指针指向新的堆上 string,旧的堆上 string 仍然不变,如果旧的没有人再引用它,那么它会被垃圾回收。

后面人们发现这样子效率也不高,又做了一些优化。比如 Java 又把一些常用的字符串的引用存到一个内置的 hashmap 里面,这样子可以不用老是被销毁又重建。
lqw3030
lqw3030
2019-07-14 10:21:28 +08:00
因为有常量池
misaka19000
misaka19000
2019-07-14 10:27:48 +08:00
js 不清楚,Java 中不可变是为了防止有人手贱去修改字符串的值从而发生一些预期之外的结果
azh7138m
azh7138m
2019-07-14 10:45:04 +08:00
底层怎么实现是 rt 自己的事情,不同的 rt 实现也不一定相同。
建议阅读 https://github.com/danbev/learning-v8
starsriver
starsriver
2019-07-14 10:51:59 +08:00
字符串是以顺序结构存储在寄存器的字节单元序列中。在 js 里面把这一块存储器按序列读取还是作为数组以字的方式读取在于你接下来的操作。

比如说 log(arr)或 arr[]会将存储数据类型改变后继续操作。
比如说 定义 arr='abcdef' //string
然后 bit = arr[0],这里括号里面的东西相当于是偏移地址,数据类型也会变为 数组。


接下来是重点。
主要是为了提高利用率,在重新定义 arr 的时候,会自动将 arr 的地址放在空寄存器的开始,然后就行序列存储。原先的数据不再被进程锁定,将被回收。

回收的意思是数据没有利用价值,下次定义新变量时可以直接替换 arr 原来空间里的内容,而不是立即清空数据,这样能够节约操作时间。
lihongjie0209
2019-07-14 11:01:13 +08:00
原理就是大家讲的那么多

如果字符串可以变, 你想想你的代码有多恐怖

function getFirstLetter;

API 使用者

var name = "test"


getFirstLetter(name)


assert name==='t'

还是

assert name==='test'


API 提供者

getFirstLetter 要不要有返回值? 既然入参可变, 那直接修改入参就可以了, 为什么还要使用返回值.


在异步场景下, 问题会更大


var name = "test"

setTimeout(()=>getLastLetter(name))

getFirstLetter(name)

现在我问你, name 的值是多少?


总的来说, 这种"基础类型"可变会给 API 的开发者和使用者带来极大的心智负担.
BinRelay
2019-07-14 11:10:08 +08:00
假如世界上只有 ascii,那么字符串的实现就简单多了吧……
dosmlp
2019-07-14 15:11:21 +08:00
那要看 js 引擎是怎么处理的了,可能不同引擎还不一样
no1xsyzy
2019-07-15 11:31:08 +08:00
Pascal string 是一个 256 字节的串,其中第一个字节表示字符串的长度,之后最多 255 个字节表示字符串的内容。只有这个才能算简单类型。
C String 连长度都不知道,以 '\0' 表示结尾,甚至比定长数组(或者说模拟元组)复杂。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/582791

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX