代码之家  ›  专栏  ›  技术社区  ›  chrissie1

为什么字符串不能在Java和.NET中可变?

  •  184
  • chrissie1  · 技术社区  · 16 年前

    为什么他们决定在Java和.NET(以及其他一些语言)中实现字符串不可变?为什么他们不让它变?

    17 回复  |  直到 16 年前
        1
  •  200
  •   dantiston    8 年前

    根据 Effective Java ,第4章,第73页,第2版:

    “有很多很好的理由:不可变类更容易 设计、实现和使用可变类。他们不太容易 错误和更安全。

    […]

    不变的对象很简单。 不可变对象可以位于 只有一个状态,即创建它的状态。如果你确定 所有的构造函数都建立类不变量 保证这些不变量在任何时候都是真的, 你一点也不努力。

    […]

    不可变对象本质上是线程安全的;它们不需要同步。 它们不能被多个线程损坏 同时访问它们。这是最简单的方法 实现螺纹安全。事实上,没有一根线可以观察到 另一线程对不可变对象的影响。因此, 不可变对象可以自由共享

    […]

    同一章的其他要点:

    不仅可以共享不变的对象,还可以共享它们的内部。

    […]

    不可变对象是其他对象的很好的构建基块,无论是可变的还是不可变的。

    […]

    不可变类的唯一真正缺点是它们需要为每个不同的值使用单独的对象。

        2
  •  99
  •   Jorge Ferreira    16 年前

    至少有两个原因。

    第一-安全 http://www.javafaq.nu/java-article1060.html

    字符串产生的主要原因 安全是不变的。看看这个 示例:我们有一个文件打开方法 登录检查。我们将字符串传递给 此方法用于处理身份验证 打电话前需要什么 将传递给操作系统。如果字符串是 可变的,有可能 在 操作系统获取前的身份验证检查 程序请求,那么它是 可以请求任何文件。所以如果 您有权在中打开文本文件 用户目录,但在运行中 当你设法改变 可以请求打开的文件名 “passwd”文件或任何其他文件。然后一个 文件可以修改,它将 可以直接登录到操作系统。

    第二存储器效率 http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

    JVM内部维护“字符串 “游泳池”。实现记忆 效率,JVM将引用字符串 来自池的对象。它不会创建 新的字符串对象。所以,无论何时 创建一个新的字符串文本,jvm 会在游泳池里登记 是否已经存在。如果已经 在游泳池里,只要把 引用同一对象或创建 池中的新对象。将有 很多参考指向同一个 字符串对象,如果有人更改 值,它将影响所有 参考文献。所以,孙决定 不变的

        3
  •  57
  •   LordOfThePigs    15 年前

    实际上,原因字符串在Java中是不可变的,与安全性没有太大关系。两个主要原因如下:

    广告安全:

    字符串是广泛使用的对象类型。因此,它或多或少地保证在多线程环境中使用。字符串是不可变的,以确保在线程之间共享字符串是安全的。拥有不变的字符串可以确保在将字符串从线程A传递到另一个线程B时,线程B不能意外地修改线程A的字符串。

    这不仅有助于简化已经相当复杂的多线程编程任务,而且有助于提高多线程应用程序的性能。当可以从多个线程访问可变对象时,必须以某种方式同步对这些对象的访问,以确保一个线程在被另一个线程修改时不会尝试读取对象的值。正确的同步对于程序员来说既困难又昂贵。不能修改不可变对象,因此不需要同步。

    性能:

    虽然已经提到了字符串交互,但它仅代表Java程序的内存效率的一个小增益。只有字符串文本被实习生。这意味着只有在 源代码 将共享同一个字符串对象。如果您的程序动态地创建相同的字符串,它们将在不同的对象中表示。

    更重要的是,不可变的字符串允许它们共享内部数据。对于许多字符串操作,这意味着不需要复制底层的字符数组。例如,假设您想要取字符串的前五个字符。在爪哇中,您将调用MyStudio子字符串(0,5)。在这种情况下,substring()方法只需创建一个共享mystring的基础char[]的新string对象,但谁又知道它从索引0开始,到该char[]的索引5结束。要将其以图形形式显示,您将得到以下结果:

     |               myString                  |
     v                                         v
    "The quick brown fox jumps over the lazy dog"   <-- shared char[]
     ^   ^
     |   |  myString.substring(0,5)
    

    这使得这种操作非常便宜,O(1)因为操作既不依赖于原始字符串的长度,也不依赖于我们需要提取的子字符串的长度。此行为还具有一些内存优势,因为许多字符串可以共享其基础字符[]。

        4
  •  28
  •   Matt Howells    16 年前

    螺纹安全和性能。如果一个字符串不能被修改,那么在多个线程之间传递一个引用是安全和快速的。如果字符串是可变的,则必须将字符串的所有字节复制到新实例,或者提供同步。一个典型的应用程序每次需要修改一个字符串时,都会读取该字符串100次。参见维基百科 immutability .

        5
  •  11
  •   Community Shane    7 年前

    我们真的应该问,“为什么x应该是可变的?”最好是默认不变,因为前面已经提到了 Princess Fluff . 有些东西是可变的,这应该是个例外。

    不幸的是,大多数当前的编程语言都默认为可变性,但希望将来的默认值更多地是不可变性的(请参见 A Wish List for the Next Mainstream Programming Language )

        6
  •  7
  •   Evan DiBiase    16 年前

    一个因素是,如果字符串是可变的,那么存储字符串的对象必须小心地存储副本,以免它们的内部数据在没有通知的情况下发生更改。考虑到字符串是一个相当原始的类型,比如数字,当我们可以将它们视为通过值传递的,即使它们是通过引用传递的(这也有助于节省内存),也是很好的。

        7
  •  7
  •   Delimitry COLD TOLD    9 年前

    字符串不是基元类型,但是您通常希望将它与值语义一起使用,即像值一样。

    价值是你可以信任的东西,它不会在你背后改变。 如果你写: String str = someExpr(); 除非你对str做些什么,否则你不希望它改变。

    字符串作为一个对象具有自然的指针语义,要获得值语义,它也需要是不可变的。

        8
  •  6
  •   Jim Barton    15 年前

    真的!我不能相信这里的错误信息。不可变的字符串与安全性无关。如果有人已经可以访问正在运行的应用程序中的对象(如果你试图防止有人在你的应用程序中“黑客”一个字符串,就必须假设这一点),那么他们肯定是黑客的很多其他机会。

    字符串的不可变性正在解决线程问题,这是一个非常新颖的想法。六羟甲基三聚氰胺六甲醚。。。我有一个对象被两个不同的线程改变了。如何解决此问题?同步访问对象?Naawww…让我们不要让任何人更改对象——这将解决所有混乱的并发问题!事实上,让所有的对象都是不可变的,然后我们可以从Java语言中删除同步化的结构。

    真正的原因(由上面的其他人指出)是内存优化。在任何应用程序中,重复使用相同的字符串文字都很常见。事实上,这是很常见的,几十年前,许多编译器对只存储字符串文本的单个实例进行了优化。这种优化的缺点是,修改字符串文字的运行时代码引入了一个问题,因为它正在修改共享它的所有其他代码的实例。例如,对于应用程序中的某个函数来说,将字符串“dog”更改为“cat”是不好的。printf(“dog”)将导致“cat”被写入stdout。因此,需要有一种方法来防止代码试图更改字符串文本(即使其不可变)。一些编译器(在操作系统的支持下)可以通过将字符串文字放入一个特殊的只读内存段来实现这一点,如果进行写入尝试,该段将导致内存错误。

    在Java中,这就是所谓的交互。这里的Java编译器只是遵循编译器几十年来完成的标准内存优化。为了解决在运行时修改这些字符串文字的相同问题,Java只是使字符串类不可变(即,不给您设置任何允许更改字符串内容的设置)。如果没有出现字符串文本的interning,则字符串不必是不可变的。

        9
  •  6
  •   Bauss    9 年前

    我知道这是个肿块,但是… 它们真的是不变的吗? 考虑以下内容。

    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中的字符串不是完全不可变的。

        10
  •  3
  •   Triynko    15 年前

    在大多数情况下,“字符串”是(使用/处理为/认为为/假定为)有意义的 原子单位, 就像一个数字 .

    因此,询问一个字符串中的各个字符为何不可变就如同询问整数中的各个位为何不可变一样。

    你应该知道为什么。想想看。

    我不想这么说,但不幸的是,我们在辩论这件事,因为我们的语言很烂,我们试图用一个词, 一串 描述一个复杂的、上下文相关的概念或对象类别。

    我们使用“字符串”进行计算和比较,类似于使用数字的方式。如果字符串(或整数)是可变的,为了可靠地执行任何类型的计算,我们必须编写特殊的代码来将它们的值锁定到不可变的局部形式中。因此,最好将一个字符串看作一个数字标识符,而不是16、32或64位的长度,而是数百位的长度。

    当有人说“弦”时,我们都会想到不同的东西。那些把它简单地看作一组人物,而没有特别的目的的人,当然会对某人感到震惊。 刚刚决定 他们不应该操纵那些角色。但是“字符串”类不仅仅是一个字符数组。这是一个 STRING 不是 char[] . 对于我们称之为“字符串”的概念,有一些基本的假设,通常可以将其描述为有意义的、原子级的编码数据单位,如数字。当人们谈论“操纵弦”时,也许他们真的在谈论操纵 文字 建造 一个架线工是很好的。想想“字符串”这个词的真正含义。

    考虑一下,如果字符串是可变的,会是什么样子。如果 易变的 当此函数使用用户名字符串时,另一个线程有意或无意地修改了用户名字符串:

    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 );
        }
    }
    

    安全不仅仅是“访问控制”,还涉及“安全”和“保证正确性”。如果一个方法不能很容易地被编写出来并依赖于可靠地执行一个简单的计算或比较,那么调用它是不安全的,但是调用编程语言本身是安全的。

        11
  •  2
  •   aaronroyer    16 年前

    这是一种权衡。字符串进入字符串池,当您创建多个相同的字符串时,它们共享相同的内存。设计人员认为这种节省内存的技术在普通情况下可以很好地工作,因为程序往往会在相同的字符串上磨很多。

    缺点是连接会产生很多额外的字符串,这些字符串只是过渡的,只是变成垃圾,实际上会损害内存性能。您有StringBuffer和StringBuilder(在爪哇,StringBuilder也在.NET中)用来保存这些情况下的内存。

        12
  •  2
  •   Motti    16 年前

    在C++中有字符串可变的决定会引起很多问题,请参见Kelvin Henney的这篇优秀文章。 Mad COW Disease .

    COW=书面副本。

        13
  •  2
  •   user80494    15 年前

    Java中的字符串不是真正不可变的,可以使用反射和类加载来改变它们的值。你不应该为了安全而依赖那个财产。 有关示例,请参见: Magic Trick In Java

        14
  •  2
  •   Andrei Rînea    14 年前

    不变性与安全性没有那么紧密的联系。为此,至少在.NET中,您得到了SecureString类。

        15
  •  0
  •   Tom Hawtin - tackline    16 年前

    不变是好的。查看有效的Java。如果每次传递字符串时都必须复制它,那么这将是许多容易出错的代码。对于哪些修改会影响哪些引用,您也会感到困惑。正如整数必须是不可变的才能表现得像int一样,字符串必须表现得像不可变的才能表现得像基元一样。在C++中,按值传递字符串在源代码中没有明确提及。

        16
  •  0
  •   Lu4    11 年前

    几乎每一条规则都有例外:

    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();
                }
            }
        }
    }
    
        17
  •  -1
  •   Peter Mortensen Leslie    15 年前

    这主要是出于安全考虑。如果你不能相信你的字符串是防篡改的,那么保护系统就要困难得多。