提问人:chrissie1 提问时间:9/18/2008 最后编辑:deHaarchrissie1 更新时间:9/16/2019 访问量:45493
为什么字符串在 Java 和 .NET 中不能可变?
Why can't strings be mutable in Java and .NET?
答:
这主要是出于安全原因。如果你不能相信你的系统是防篡改的,那么保护系统就更难了。String
评论
Object
至少有两个原因。
第一 - 安全 http://www.javafaq.nu/java-article1060.html
String 制作的主要原因 不可变的是安全性。看看这个 示例:我们有一个文件打开方法 通过登录检查。我们将一个字符串传递给 此方法处理身份验证 这是通话前必要的 将传递给操作系统。如果字符串是 可变的,有可能以某种方式 修改其内容后 操作系统获取之前的身份验证检查 从程序请求,然后是 可以请求任何文件。所以如果 您有权在以下位置打开文本文件 用户目录,但随后在飞行中 当您以某种方式设法更改 您可以请求打开的文件名 “passwd”文件或任何其他文件。然后一个 文件可以修改,它将 可以直接登录操作系统。
第二 - 内存效率 http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
JVM 在内部维护“字符串 游泳池“。为了实现记忆 efficiency,JVM 会引用 String 对象。它不会创建 新的 String 对象。所以,无论何时 创建一个新的字符串文本 JVM 将在池中检查它是否 是否已经存在。如果已经 存在于池中,只需给 引用同一对象或创建 池中的新对象。会有 许多参考资料都指向同一点 字符串对象,如果有人更改了 值,它会影响所有 引用。所以,Sun决定这样做 变。
评论
螺纹安全性和性能。如果字符串无法修改,则在多个线程之间传递引用是安全且快速的。如果字符串是可变的,则始终必须将字符串的所有字节复制到新实例,或提供同步。每次需要修改字符串时,典型的应用程序都会读取该字符串 100 次。参见维基百科关于不变性。
One factor is that, if s were mutable, objects storing s would have to be careful to store copies, lest their internal data change without notice. Given that s are a fairly primitive type like numbers, it is nice when one can treat them as if they were passed by value, even if they are passed by reference (which also helps to save on memory).String
String
String
评论
null
String
这是一个权衡。进入池中,当您创建多个相同的 s 时,它们共享相同的内存。设计者认为这种内存节省技术在常见情况下效果很好,因为程序往往会经常在相同的字符串上磨削。String
String
String
缺点是,串联会产生许多额外的 s,这些 s 只是过渡性的,只会变成垃圾,实际上会损害内存性能。在这些情况下,您有 and(在 Java 中,也在 .NET 中)用于保留内存。String
StringBuffer
StringBuilder
StringBuilder
评论
根据 Effective Java,第 4 章,第 73 页,第 2 版:
“这有很多很好的理由:不可变的类更容易 设计、实现和使用可变类。他们不太容易 出错,更安全。
[...]
"不可变对象很简单。不可变对象可以位于 正好是一个状态,即创建它的状态。如果您确定 所有构造函数都建立类不变量,那么它是 保证这些不变量将始终保持真,并且 你不费吹灰之力。
[...]
不可变对象本质上是线程安全的;它们不需要同步。它们不能被多个线程损坏 同时访问它们。这是最简单的方法 实现螺纹安全。事实上,没有一个线程可以观察到任何 另一个线程对不可变对象的影响。因此,不可变对象可以自由共享
[...]
同一章的其他小要点:
您不仅可以共享不可变对象,还可以共享其内部结构。
[...]
不可变对象是其他对象(无论是可变对象还是不可变对象)的绝佳构建块。
[...]
不可变类的唯一真正缺点是它们需要为每个不同的值提供单独的对象。
评论
report2.Text = report1.Text;
report2.Text.Replace(someWord, someOtherWord);
不变性是好的。请参阅有效的 Java。如果每次传递字符串时都必须复制它,那么这将是很多容易出错的代码。您还对哪些修改会影响哪些引用感到困惑。就像 Integer 必须是不可变的才能表现得像 int 一样,Strings 必须表现得像 int 一样不可变才能像原语一样。在 C++ 中,按值传递字符串时不会在源代码中明确提及。
String
不是一个原始类型,但你通常希望它与值语义一起使用,即像一个值。
价值是您可以信任的东西,不会在背后改变。
如果你写:你不希望它改变,除非你对 .String str = someExpr();
str
String
由于 an 具有自然的指针语义,因此要获得值语义,它也需要是不可变的。Object
在 C++ 中让字符串可变的决定会导致很多问题,请参阅 Kelvin Henney 关于疯牛病的这篇优秀文章。
COW = 写入时复制。
人们真的应该问,“为什么X应该是可变的?最好默认为不变性,因为 Princess Fluff 已经提到了好处。某些东西是可变的应该是一个例外。
不幸的是,大多数当前的编程语言都默认为可变性,但希望将来默认的更多是不变性(参见下一个主流编程语言的愿望清单)。
String
Java 中的 s 并不是真正不可变的,您可以使用反射和/或类加载来更改它们的值。您不应依赖该属性来确保安全性。
有关示例,请参阅: Java 中的魔术
评论
实际上,字符串在 java 中不可变的原因与安全性没有太大关系。主要有两个原因:
头部安全:
字符串是使用非常广泛的对象类型。因此,它或多或少可以保证在多线程环境中使用。字符串是不可变的,以确保在线程之间共享字符串是安全的。使用不可变字符串可确保在将字符串从线程 A 传递到另一个线程 B 时,线程 B 不会意外修改线程 A 的字符串。
Not only does this help simplify the already pretty complicated task of multi-threaded programming, but it also helps with performance of multi-threaded applications. Access to mutable objects must somehow be synchronized when they can be accessed from multiple threads, to make sure that one thread doesn't attempt to read the value of your object while it is being modified by another thread. Proper synchronization is both hard to do correctly for the programmer, and expensive at runtime. Immutable objects cannot be modified and therefore do not need synchronization.
Performance:
While String interning has been mentioned, it only represents a small gain in memory efficiency for Java programs. Only string literals are interned. This means that only the strings which are the same in your source code will share the same String Object. If your program dynamically creates string that are the same, they will be represented in different objects.
More importantly, immutable strings allow them to share their internal data. For many string operations, this means that the underlying array of characters does not need to be copied. For example, say you want to take the five first characters of String. In Java, you would calls myString.substring(0,5). In this case, what the substring() method does is simply to create a new String object that shares myString's underlying char[] but who knows that it starts at index 0 and ends at index 5 of that char[]. To put this in graphical form, you would end up with the following:
| myString |
v v
"The quick brown fox jumps over the lazy dog" <-- shared char[]
^ ^
| | myString.substring(0,5)
This makes this kind of operations extremely cheap, and O(1) since the operation neither depends on the length of the original string, nor on the length of the substring we need to extract. This behavior also has some memory benefits, since many strings can share their underlying char[].
评论
char[]
String.substring()
char[]
count
offset
哇!我简直不敢相信这里的错误信息。是不可变的,与安全性无关。如果有人已经可以访问正在运行的应用程序中的对象(如果您试图防止有人在您的应用程序中“入侵”,则必须假设这一点),那么它们肯定会有很多其他可供黑客攻击的机会。String
String
这是一个非常新颖的想法,即不变性正在解决线程问题。嗯......我有一个对象正在被两个不同的线程更改。如何解决此问题?同步对对象的访问?呜......我们不要让任何人更改对象 - 这将解决我们所有混乱的并发问题!事实上,让我们使所有对象都不可变,然后我们可以从 Java 语言中删除同步结构。String
真正的原因(上面其他人指出的)是内存优化。在任何应用程序中,重复使用相同的字符串文本是很常见的。事实上,它是如此普遍,以至于几十年前,许多编译器都进行了优化,只存储一个文本的单个实例。此优化的缺点是,修改文本的运行时代码会引入问题,因为它正在修改共享该文本的所有其他代码的实例。例如,应用程序中某处的函数将文本更改为 是不利的。A 将导致被写入 stdout。出于这个原因,需要有一种方法来防止试图更改文字的代码(即使它们不可变)。一些编译器(在操作系统的支持下)会通过将 literal 放入特殊的只读内存段来实现这一点,如果进行写入尝试,将导致内存故障。String
String
String
"dog"
"cat"
printf("dog")
"cat"
String
String
在 Java 中,这称为实习。这里的 Java 编译器只是遵循编译器几十年来所做的标准内存优化。为了解决这些文字在运行时被修改的相同问题,Java 只是使类不可变(即,没有给你任何允许你更改内容的 setter)。如果不发生文字实习,则 s 不必是不可变的。String
String
String
String
String
评论
String
StringBuffer
在大多数情况下,“字符串”是一个有意义的原子单位,就像一个数字一样。
因此,询问为什么字符串的各个字符不可变,就像询问为什么整数的各个位不可变一样。
你应该知道为什么。想想看。
我不想这么说,但不幸的是,我们正在争论这个问题,因为我们的语言很糟糕,我们试图用一个词,字符串来描述一个复杂的、上下文中的概念或对象类。
我们用“字符串”进行计算和比较,类似于我们对数字的处理方式。如果字符串(或整数)是可变的,我们必须编写特殊的代码来将它们的值锁定为不可变的局部形式,以便可靠地执行任何类型的计算。因此,最好将字符串视为数字标识符,但它的长度不是 16、32 或 64 位,而是数百位。
当有人说“字符串”时,我们都会想到不同的事情。那些简单地将其视为一组角色,没有特定目的的人,当然会感到震惊,因为有人刚刚决定他们不应该操纵这些角色。但是“string”类不仅仅是一个字符数组。这是一个,而不是一个。关于我们称之为“字符串”的概念有一些基本假设,它通常可以被描述为编码数据的有意义的原子单位,如数字。当人们谈论“操纵字符串”时,也许他们实际上是在谈论操纵字符来构建字符串,而 StringBuilder 非常适合这一点。想一想“字符串”这个词的真正含义。STRING
char[]
想一想,如果字符串是可变的,会是什么样子。如果可变用户名字符串在使用此函数时被另一个线程有意或无意地修改,则以下 API 函数可能会被诱骗返回其他用户的信息:
string GetPersonalInfo( string username, string password )
{
string stored_password = DBQuery.GetPasswordFor( username );
if (password == stored_password)
{
//another thread modifies the mutable 'username' string
return DBQuery.GetPersonalInfoFor( username );
}
}
安全不仅关乎“访问控制”,还关乎“安全”和“保证正确性”。如果一个方法不能轻易地编写并依赖于它来可靠地执行简单的计算或比较,那么调用它是不安全的,但对编程语言本身提出质疑是安全的。
评论
unsafe
不可变性与安全性的联系并不紧密。为此,至少在 .NET 中,您可以获得该类。SecureString
稍后编辑:在Java中,您会发现类似的实现。GuardedString
几乎每条规则都有例外:
using System;
using System.Runtime.InteropServices;
namespace Guess
{
class Program
{
static void Main(string[] args)
{
const string str = "ABC";
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
var handle = GCHandle.Alloc(str, GCHandleType.Pinned);
try
{
Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
}
finally
{
handle.Free();
}
}
}
}
我知道这是一个颠簸,但是...... 它们真的是不可变的吗? 请考虑以下几点。
public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
...
string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3
您甚至可以将其设置为扩展方法。
public static class Extensions
{
public static unsafe void MutableReplaceIndex(this string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
}
这使得以下工作
s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);
结论:它们处于编译器已知的不可变状态。当然,上述内容仅适用于 .NET 字符串,因为 Java 没有指针。但是,使用 C# 中的指针可以完全可变字符串。它不是指针的使用方式、实际用途或安全使用方式;然而,这是可能的,从而扭曲了整个“可变”规则。通常不能直接修改字符串的索引,这是唯一的方法。有一种方法可以通过禁止字符串的指针实例或在指向字符串时进行复制来防止这种情况,但两者都没有完成,这使得 C# 中的字符串并非完全不可变。
评论
StringBuilder
会更改字符串。我就把它留在这里。String