ทำไม String ถึงไม่เปลี่ยนรูปใน Java?


78

ฉันไม่เข้าใจเหตุผลของมัน ฉันมักจะใช้คลาส String เหมือนนักพัฒนาอื่น ๆ แต่เมื่อฉันแก้ไขค่าของมันอินสแตนซ์ใหม่ของ String ที่สร้างขึ้น

สิ่งที่อาจเป็นสาเหตุของการไม่เปลี่ยนรูปแบบสำหรับคลาส String ใน Java?

ฉันรู้ว่ามีทางเลือกบางอย่างเช่น StringBuffer หรือ StringBuilder มันเป็นเพียงความอยากรู้


20
ในทางเทคนิคแล้วมันไม่ซ้ำกัน แต่ Eric Lippert ให้คำตอบที่ดีสำหรับคำถามนี้ที่นี่: programmers.stackexchange.com/a/190913/33843
Heinzi

คำตอบ:


105

เห็นพ้องด้วย

Java ถูกกำหนดตั้งแต่เริ่มต้นพร้อมข้อควรพิจารณาของภาวะพร้อมกัน บ่อยครั้งที่มีการกล่าวถึงความไม่แน่นอนที่ใช้ร่วมกันเป็นปัญหา สิ่งหนึ่งสามารถเปลี่ยนอีกหลังของเธรดอื่นได้โดยที่เธรดนั้นไม่ได้รับรู้

มีโฮสต์ของบั๊ก C ++ แบบมัลติเธรดที่มีการครอบตัดเนื่องจากสตริงที่แชร์ซึ่งหนึ่งโมดูลคิดว่ามันปลอดภัยที่จะเปลี่ยนเมื่อโมดูลอื่นในรหัสได้บันทึกตัวชี้ไปแล้วและคาดว่ามันจะยังคงเหมือนเดิม

'การแก้ปัญหา' ในเรื่องนี้คือทุก ๆ คลาสจะทำสำเนาการป้องกันของวัตถุที่ไม่แน่นอนที่ส่งผ่านไปให้ สำหรับสตริงที่ไม่แน่นอนนี่คือ O (n) เพื่อทำสำเนา สำหรับสตริงที่ไม่เปลี่ยนรูปการทำสำเนาคือ O (1) เนื่องจากไม่ใช่สำเนามันเป็นวัตถุเดียวกันที่ไม่สามารถเปลี่ยนแปลงได้

ในสภาพแวดล้อมแบบมัลติเธรดวัตถุที่ไม่เปลี่ยนรูปจะสามารถแบ่งปันกันได้อย่างปลอดภัยระหว่างกัน สิ่งนี้นำไปสู่การลดการใช้หน่วยความจำโดยรวมและปรับปรุงการแคชหน่วยความจำ

ความปลอดภัย

หลายครั้งที่สตริงถูกส่งผ่านเป็นอาร์กิวเมนต์สำหรับตัวสร้าง - การเชื่อมต่อเครือข่ายและ Protocals เป็นสองสิ่งที่ง่ายที่สุดที่จะนึกถึง ความสามารถในการเปลี่ยนแปลงในเวลาที่ไม่ได้ตั้งใจในภายหลังในการดำเนินการสามารถนำไปสู่ปัญหาด้านความปลอดภัย (ฟังก์ชั่นคิดว่ามันเชื่อมต่อกับเครื่องหนึ่ง แต่ถูกโอนไปยังเครื่องอื่น แต่ทุกอย่างในวัตถุดูเหมือนว่ามันเชื่อมต่อกับครั้งแรก ... แม้แต่สตริงเดียวกัน)

Java อนุญาตให้หนึ่งใช้การสะท้อน - และพารามิเตอร์สำหรับสิ่งนี้คือสตริง อันตรายจากการผ่านสายที่สามารถแก้ไขผ่านทางไปยังวิธีอื่นที่สะท้อน มันแย่มาก

กุญแจสู่การแฮช

ตารางแฮชเป็นหนึ่งในโครงสร้างข้อมูลที่ใช้มากที่สุด กุญแจสู่โครงสร้างข้อมูลมักจะเป็นสตริง การมีสตริงที่ไม่เปลี่ยนรูปแบบหมายความว่า (ตามข้างบน) ตารางแฮชไม่จำเป็นต้องทำสำเนาของคีย์แฮชในแต่ละครั้ง หากสตริงไม่สามารถเปลี่ยนแปลงได้และตารางแฮชไม่ได้ทำสิ่งนี้อาจเป็นไปได้ที่บางสิ่งบางอย่างจะเปลี่ยนคีย์แฮชในระยะไกล

วิธีการทำงานของ Object in java ก็คือทุกอย่างมีคีย์ hash (เข้าถึงได้ผ่านเมธอด hashCode ()) การมีสตริงที่ไม่เปลี่ยนรูปได้หมายความว่า hashCode สามารถถูกแคชได้ เมื่อพิจารณาว่ามีการใช้ Strings บ่อยแค่ไหนเป็นกุญแจสู่การแฮชสิ่งนี้จะช่วยเพิ่มประสิทธิภาพที่สำคัญ (แทนที่จะต้องคำนวณรหัสแฮชใหม่ทุกครั้ง)

สตริง

โดยการทำให้สตริงไม่สามารถเปลี่ยนแปลงได้อาร์เรย์ของอักขระพื้นฐานที่อยู่ด้านหลังโครงสร้างข้อมูลก็ไม่เปลี่ยนรูปเช่นกัน นี้จะช่วยให้เพิ่มประสิทธิภาพบางอย่างเกี่ยวกับsubstringวิธีการที่จะทำ (พวกเขาจะไม่จำเป็นต้องทำ - มันยังแนะนำเป็นไปได้ของการรั่วไหลของหน่วยความจำบางเกินไป)

ถ้าคุณทำ:

String foo = "smiles";
String bar = foo.substring(1,5);

ค่าของbarคือ 'ไมล์' อย่างไรก็ตามทั้งสองfooและbarสามารถสำรองข้อมูลโดยอาเรย์ตัวเดียวกันลดการเริ่มต้นของอาเรย์ตัวละครหรือคัดลอกมัน - เพียงแค่ใช้จุดเริ่มต้นและจุดสิ้นสุดที่แตกต่างกันภายในสตริง

foo | | (0, 6)
    VV
    รอยยิ้ม
     ^ ^
บาร์ | | (1, 5)

ตอนนี้ข้อเสียของมัน (หน่วยความจำรั่ว) คือถ้ามีสตริงยาว 1k และเอาซับสตริงของอักขระตัวแรกและตัวที่สองมันก็จะได้รับการสนับสนุนโดยอาร์เรย์อักขระยาว 1k อาร์เรย์นี้จะยังคงอยู่ในหน่วยความจำแม้ว่าสตริงต้นฉบับที่มีค่าของอาร์เรย์อักขระทั้งหมดจะถูกเก็บรวบรวมขยะ

หนึ่งสามารถเห็นสิ่งนี้ในString จาก JDK 6b14 (รหัสต่อไปนี้มาจากแหล่ง GPL v2 และใช้เป็นตัวอย่าง)

   public String(char value[], int offset, int count) {
       if (offset < 0) {
           throw new StringIndexOutOfBoundsException(offset);
       }
       if (count < 0) {
           throw new StringIndexOutOfBoundsException(count);
       }
       // Note: offset or count might be near -1>>>1.
       if (offset > value.length - count) {
           throw new StringIndexOutOfBoundsException(offset + count);
       }
       this.offset = 0;
       this.count = count;
       this.value = Arrays.copyOfRange(value, offset, offset+count);
   }

   // Package private constructor which shares value array for speed.
   String(int offset, int count, char value[]) {
       this.value = value;
       this.offset = offset;
       this.count = count;
   }

   public String substring(int beginIndex, int endIndex) {
       if (beginIndex < 0) {
           throw new StringIndexOutOfBoundsException(beginIndex);
       }
       if (endIndex > count) {
           throw new StringIndexOutOfBoundsException(endIndex);
       }
       if (beginIndex > endIndex) {
           throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
       }
       return ((beginIndex == 0) && (endIndex == count)) ? this :
           new String(offset + beginIndex, endIndex - beginIndex, value);
   }

สังเกตว่าซับสตริงใช้ตัวสร้างสตริงของแพ็กเกจระดับที่ไม่เกี่ยวข้องกับการคัดลอกอาเรย์และจะเร็วกว่ามาก (โดยมีค่าใช้จ่ายในการรักษาอาร์เรย์ขนาดใหญ่บางตัว

โปรดทราบว่ารหัสข้างต้นสำหรับ Java 1.6 วิธีที่ตัวสร้าง substring ถูกนำไปใช้กับ Java 1.7 ตามที่บันทึกไว้ในการเปลี่ยนแปลงการแสดงสตริงภายในที่ทำใน Java 1.7.0_06 - ปัญหา bing ที่หน่วยความจำรั่วที่ฉันกล่าวถึงข้างต้น Java น่าจะไม่ถูกมองว่าเป็นภาษาที่มีการจัดการสตริงจำนวนมากดังนั้นการเพิ่มประสิทธิภาพสำหรับสตริงย่อยจึงเป็นสิ่งที่ดี ตอนนี้ด้วยเอกสาร XML ขนาดใหญ่ที่เก็บไว้ในสตริงที่ไม่เคยถูกรวบรวมสิ่งนี้จะกลายเป็นปัญหา ... และทำให้การเปลี่ยนStringไม่ใช้อาร์เรย์ต้นแบบเดียวกันกับสตริงย่อยเพื่อให้สามารถรวบรวมอาร์เรย์อักขระขนาดใหญ่ได้เร็วขึ้น

อย่าใช้สแต็คในทางที่ผิด

หนึ่งสามารถส่งค่าของสตริงรอบแทนการอ้างอิงถึงสตริงที่ไม่เปลี่ยนรูปได้เพื่อหลีกเลี่ยงปัญหาเกี่ยวกับความไม่แน่นอน อย่างไรก็ตามด้วยสตริงขนาดใหญ่การส่งสิ่งนี้บนสแต็กจะเป็น ... ไม่เหมาะสมกับระบบ (การวางเอกสาร xml ทั้งหมดเป็นสตริงบนสแต็กจากนั้นนำพวกเขาออก

ความเป็นไปได้ของการขจัดข้อมูลซ้ำซ้อน

จริงอยู่นี่ไม่ใช่แรงจูงใจเริ่มต้นว่าทำไม Strings ถึงควรไม่เปลี่ยนรูป แต่เมื่อมีใครมองเหตุผลว่าทำไม Strings ที่ไม่เปลี่ยนรูปนั้นเป็นสิ่งที่ดีนี่เป็นสิ่งที่ต้องพิจารณาอย่างแน่นอน

ทุกคนที่ได้ทำงานกับ Strings จะรู้ว่าพวกเขาสามารถดูดความทรงจำได้ โดยเฉพาะอย่างยิ่งเมื่อคุณทำสิ่งต่าง ๆ เช่นดึงข้อมูลจากฐานข้อมูลที่ติดอยู่นาน หลายครั้งที่มี stings เหล่านี้มันเป็นสตริงเดียวกันซ้ำแล้วซ้ำอีก (หนึ่งครั้งสำหรับแต่ละแถว)

ขณะนี้แอปพลิเคชัน Java ขนาดใหญ่จำนวนมากมีปัญหาด้านคอขวด การวัดแสดงให้เห็นว่าประมาณ 25% ของชุดข้อมูลฮีพ Java heap live ในแอ็พพลิเคชันประเภทนี้จะถูกใช้โดยวัตถุ String นอกจากนี้ประมาณครึ่งหนึ่งของวัตถุ String เหล่านั้นซ้ำกันโดยที่ซ้ำกันหมายถึง string1.equals (string2) เป็นความจริง การมีออบเจ็กต์ String ซ้ำกันบนกองคือเพียงแค่สูญเสียความทรงจำ ...

ด้วยการอัพเดต Java 8 20, JEP 192 (แรงบันดาลใจที่ยกมาข้างต้น) จะถูกนำไปใช้แก้ไขปัญหานี้ โดยไม่ได้รับรายละเอียดของการทำงานซ้ำซ้อนของสตริงมันเป็นสิ่งสำคัญที่ Strings เองนั้นไม่สามารถเปลี่ยนแปลงได้ คุณไม่สามารถทำซ้ำ StringBuilders ซ้ำได้เพราะพวกเขาสามารถเปลี่ยนแปลงได้และคุณไม่ต้องการให้ใครบางคนเปลี่ยนอะไรบางอย่างจากคุณ สตริงที่ไม่เปลี่ยนรูป (เกี่ยวข้องกับกลุ่มของสตริงนั้น) หมายความว่าคุณสามารถผ่านไปได้และหากคุณพบสองสตริงที่เหมือนกันคุณสามารถชี้การอ้างอิงสตริงหนึ่งไปยังอีกสตริงหนึ่งและปล่อยให้ตัวรวบรวมขยะใช้อันที่ไม่ได้ใช้ใหม่

ภาษาอื่น ๆ

วัตถุประสงค์ C (ซึ่งถือกำเนิด Java) มีและNSStringNSMutableString

C # และ. NET ทำให้ตัวเลือกการออกแบบเดียวกันของสตริงเริ่มต้นเป็นไม่เปลี่ยนรูป

สายLuaยังไม่เปลี่ยนรูป

Pythonเช่นกัน

อดีตชัดโครงการสมอลล์ทอล์คทั้งหมดฝึกงานสตริงและทำให้มีมันจะไม่เปลี่ยนรูป ภาษาไดนามิกที่ทันสมัยกว่ามักใช้สตริงในบางวิธีที่ต้องการให้ไม่เปลี่ยนรูป (อาจไม่ใช่สตริงแต่เป็นรูปแบบที่ไม่เปลี่ยนรูป)

ข้อสรุป

ข้อควรพิจารณาในการออกแบบเหล่านี้ได้ทำซ้ำแล้วซ้ำอีกในหลายภาษา มันเป็นฉันทามติทั่วไปที่สตริงที่ไม่เปลี่ยนแปรสำหรับความอึดอัดใจทั้งหมดของพวกเขาดีกว่าทางเลือกและนำไปสู่รหัสที่ดีขึ้น


3
Java จัดเตรียมสตริงที่ไม่แน่นอนและไม่เปลี่ยนรูป คำตอบนี้ให้รายละเอียดเกี่ยวกับข้อได้เปรียบด้านประสิทธิภาพบางประการที่สามารถทำได้บนสตริงที่ไม่เปลี่ยนรูปและเหตุผลบางประการอาจเลือกข้อมูลที่ไม่เปลี่ยนรูป แต่ไม่ได้อธิบายถึงสาเหตุที่รุ่นไม่เปลี่ยนรูปเป็นรุ่นเริ่มต้น
Billy ONeal

3
@BillyONeal: ค่าเริ่มต้นที่ปลอดภัยและทางเลือกที่ไม่ปลอดภัยมักจะนำไปสู่ระบบที่ปลอดภัยกว่าวิธีที่ตรงกันข้าม
โจอาคิมซาวเออร์

4
@BillyONeal หากผู้เปลี่ยนไม่ได้เป็นค่าเริ่มต้นปัญหาของการเกิดพร้อมกันความปลอดภัยและแฮชจะเป็นเรื่องธรรมดามากขึ้น นักออกแบบภาษาได้เลือก (ส่วนหนึ่งในการตอบสนองต่อ C) เพื่อสร้างภาษาที่มีการตั้งค่าเริ่มต้นเพื่อพยายามป้องกันข้อผิดพลาดทั่วไปจำนวนหนึ่งเพื่อพยายามปรับปรุงประสิทธิภาพโปรแกรมเมอร์ (ไม่ต้องกังวลเกี่ยวกับข้อบกพร่องเหล่านี้อีกต่อไป) มีข้อบกพร่องน้อยลง (ชัดเจนและซ่อนอยู่) กับสตริงที่ไม่เปลี่ยนรูปกว่ากับที่ไม่แน่นอน

@ โจอาคิม: ฉันไม่ได้อ้างสิทธิ์เป็นอย่างอื่น
Billy ONeal

1
ในทางเทคนิค Common LISP มีสตริงที่ไม่แน่นอนสำหรับการดำเนินการ "เหมือนสตริง" และสัญลักษณ์ที่มีชื่อไม่เปลี่ยนรูปสำหรับตัวระบุที่ไม่เปลี่ยนรูป
Vatine

21

เหตุผลที่ฉันจำได้:

  1. สิ่งอำนวยความสะดวก String Pool ที่ไม่มีการทำให้สตริงไม่สามารถเปลี่ยนแปลงได้นั้นเป็นไปไม่ได้เพราะในกรณีของสตริงพูลหนึ่งสตริงอ็อบเจ็กต์ / ตัวอักษรเช่น "XYZ" จะถูกอ้างอิงโดยตัวแปรอ้างอิงจำนวนมากดังนั้นหากหนึ่งในนั้นเปลี่ยนค่าอื่น ๆ .

  2. String ถูกใช้อย่างกว้างขวางเป็นพารามิเตอร์สำหรับคลาส Java จำนวนมากเช่นสำหรับการเปิดการเชื่อมต่อเครือข่ายสำหรับการเปิดการเชื่อมต่อฐานข้อมูลการเปิดไฟล์ หาก String ไม่มีการเปลี่ยนไม่ได้สิ่งนี้จะนำไปสู่การคุกคามด้านความปลอดภัยที่ร้ายแรง

  3. Immutability ทำให้ String สามารถแคช hashcode

  4. ทำให้เธรดนั้นปลอดภัย


7

1) String Pool

ผู้ออกแบบ Java รู้ว่า String จะเป็นประเภทข้อมูลที่ใช้มากที่สุดในแอปพลิเคชัน Java ทุกประเภทและนั่นเป็นสาเหตุที่พวกเขาต้องการเพิ่มประสิทธิภาพตั้งแต่ต้น หนึ่งในขั้นตอนสำคัญในทิศทางนั้นคือแนวคิดของการจัดเก็บตัวอักษร String ใน String pool เป้าหมายคือการลดวัตถุ String ชั่วคราวด้วยการแบ่งปันและเพื่อที่จะแบ่งปันพวกเขาจะต้องมาจากคลาสที่ไม่เปลี่ยนรูป คุณไม่สามารถแชร์วัตถุที่ไม่แน่นอนกับสองฝ่ายซึ่งไม่รู้จักซึ่งกันและกัน ลองมาตัวอย่างสมมุติที่สองตัวแปรอ้างอิงชี้ไปที่วัตถุ String เดียวกัน:

String s1 = "Java";
String s2 = "Java";

ตอนนี้ถ้า s1 เปลี่ยนออบเจ็กต์จาก "Java" เป็น "C ++" ตัวแปรอ้างอิงก็จะได้ค่า s2 = "C ++" ซึ่งมันไม่รู้ด้วยซ้ำ การทำให้ String เปลี่ยนรูปไม่ได้การแบ่งปัน String อย่างแท้จริงจึงเป็นไปได้ กล่าวโดยสรุปแล้วแนวคิดหลักของ String pool ไม่สามารถนำไปใช้ได้โดยไม่ทำให้ String สุดท้ายหรือไม่เปลี่ยนรูปใน Java

2) ความปลอดภัย

Java มีเป้าหมายที่ชัดเจนในแง่ของการให้บริการสภาพแวดล้อมที่ปลอดภัยในทุกระดับของการบริการและ String มีความสำคัญอย่างยิ่งต่อความปลอดภัยทั้งหมด String มีการใช้กันอย่างแพร่หลายเป็นพารามิเตอร์สำหรับคลาส Java จำนวนมากเช่นสำหรับการเปิดการเชื่อมต่อเครือข่ายคุณสามารถส่งโฮสต์และพอร์ตเป็น String สำหรับการอ่านไฟล์ใน Java คุณสามารถส่งพา ธ ของไฟล์และไดเรกทอรีเป็นสตริงและสำหรับการเปิดการเชื่อมต่อฐานข้อมูล ส่ง URL ฐานข้อมูลเป็น String หาก String ไม่เปลี่ยนรูปผู้ใช้อาจได้รับอนุญาตให้เข้าถึงไฟล์บางไฟล์ในระบบ แต่หลังจากการตรวจสอบความถูกต้องเขาสามารถเปลี่ยน PATH เป็นอย่างอื่นได้ซึ่งอาจทำให้เกิดปัญหาด้านความปลอดภัยที่ร้ายแรง ในทำนองเดียวกันในขณะที่เชื่อมต่อกับฐานข้อมูลหรือเครื่องอื่น ๆ ในเครือข่ายการกลายพันธุ์ค่าสตริงสามารถคุกคามความปลอดภัย สตริงที่ไม่แน่นอนอาจทำให้เกิดปัญหาด้านความปลอดภัยใน Reflection เช่นกัน

3) การใช้งานสตริงในกลไกการโหลดคลาส

อีกเหตุผลที่ทำให้ String สุดท้ายหรือไม่เปลี่ยนรูปนั้นมาจากความจริงที่ว่ามันถูกใช้อย่างมากในกลไกการโหลดคลาส เนื่องจาก String เคยไม่เปลี่ยนรูปผู้โจมตีสามารถใช้ประโยชน์จากข้อเท็จจริงนี้และการร้องขอให้โหลดคลาส Java มาตรฐานเช่น java.io.Reader สามารถเปลี่ยนเป็นคลาสที่เป็นอันตราย com.unknown.DataStolenReader โดยทำให้ String สุดท้ายและไม่เปลี่ยนรูปเราอย่างน้อยสามารถมั่นใจได้ว่า JVM กำลังโหลดคลาสที่ถูกต้อง

4) ประโยชน์หลายเธรด

เนื่องจาก Concurrency และ Multi-threading เป็นการนำเสนอที่สำคัญของ Java จึงเหมาะสมอย่างยิ่งที่จะคิดเกี่ยวกับความปลอดภัยของเธรดของวัตถุ String เนื่องจากคาดว่าจะใช้งานสตริงกันอย่างแพร่หลายทำให้ไม่เปลี่ยนรูปได้หมายความว่าไม่มีการซิงโครไนซ์ภายนอกหมายถึงรหัสทำความสะอาดที่เกี่ยวข้องกับการแชร์สตริงระหว่างเธรดหลายเธรด ฟีเจอร์เดียวนี้ทำให้การทำให้เกิดความสับสนสับสนและเกิดข้อผิดพลาดเกิดขึ้นได้ง่าย ๆ นั้นง่ายขึ้น เนื่องจากสตริงนั้นไม่เปลี่ยนรูปและเราเพิ่งแชร์ระหว่างเธรดจึงทำให้โค้ดอ่านง่ายขึ้น

5) การเพิ่มประสิทธิภาพและประสิทธิภาพ

ตอนนี้เมื่อคุณสร้างคลาสที่ไม่เปลี่ยนรูปคุณจะรู้ล่วงหน้าว่าคลาสนี้จะไม่เปลี่ยนแปลงเมื่อสร้างขึ้น การรับประกันเส้นทางแบบเปิดสำหรับการเพิ่มประสิทธิภาพหลายประการเช่นการแคช สตริงเองรู้ว่าฉันจะไม่เปลี่ยนแปลงดังนั้น String ของแคชแฮชโค้ด มันยังคำนวณ hashcode อย่างเกียจคร้านและสร้างขึ้นเพียงแค่แคช ในโลกที่เรียบง่ายเมื่อคุณเรียกใช้เมธอด hashCode () ของอ็อบเจกต์ String ใด ๆ มันจะคำนวณรหัสแฮชและการเรียก hashCode () ที่ตามมาทั้งหมดจะคืนค่าที่แคชแล้ว ซึ่งส่งผลให้ได้รับประสิทธิภาพที่ดีโดยกำหนดให้มีการใช้งานสตริงอย่างหนักในแผนที่ที่ใช้แฮชเช่น Hashtable และ HashMap การแคช hashcode นั้นเป็นไปไม่ได้หากไม่ทำให้ไม่สามารถเปลี่ยนรูปแบบและสุดท้ายได้ขึ้นอยู่กับเนื้อหาของ String


5

Java Virtual Machine ทำการปรับให้เหมาะสมหลายอย่างเกี่ยวกับการทำงานของสตริงซึ่งไม่สามารถทำได้เป็นอย่างอื่น ตัวอย่างเช่นหากคุณมีสตริงที่มีค่า "Mississippi" และคุณได้กำหนด "Mississippi" .substring (0, 4) ให้กับสตริงอื่นเท่าที่คุณทราบจะมีการคัดลอกอักขระสี่ตัวแรกเพื่อให้ "Miss" . สิ่งที่คุณไม่ทราบคือทั้งคู่ใช้สตริงเดิม "มิสซิสซิปปี" ร่วมกันโดยหนึ่งเป็นเจ้าของและอีกคนหนึ่งเป็นข้อมูลอ้างอิงของสตริงนั้นจากตำแหน่ง 0 ถึง 4 (การอ้างอิงถึงเจ้าของป้องกันไม่ให้เจ้าของรวบรวมโดย ตัวรวบรวมขยะเมื่อเจ้าของออกนอกขอบเขต)

นี่เป็นเรื่องเล็กน้อยสำหรับสตริงที่มีขนาดเล็กเท่ากับ "มิสซิสซิปปี" แต่ด้วยสตริงที่มีขนาดใหญ่กว่าและการดำเนินการหลายอย่างโดยไม่ต้องคัดลอกสตริงเป็นตัวช่วยครั้งใหญ่! หากสตริงไม่แน่นอนคุณไม่สามารถทำเช่นนี้ได้เนื่องจากการแก้ไขต้นฉบับจะส่งผลกระทบต่อสตริงย่อย "คัดลอก" เช่นกัน

นอกจากนี้เมื่อกล่าวถึง Donal ประโยชน์จะได้รับการชั่งน้ำหนักอย่างมากจากข้อเสียของมัน ลองนึกภาพว่าคุณเขียนโปรแกรมซึ่งขึ้นอยู่กับไลบรารีและคุณใช้ฟังก์ชันที่ส่งคืนสตริง คุณจะแน่ใจได้อย่างไรว่าค่านั้นจะคงที่ตลอดไป? เพื่อให้แน่ใจว่าไม่มีสิ่งใดเกิดขึ้นคุณจะต้องทำสำเนาเสมอ

ถ้าคุณมีสองเธรดที่แชร์สตริงเดียวกัน? คุณไม่ต้องการที่จะอ่านสตริงที่กำลังถูกเขียนใหม่โดยเธรดอื่นใช่ไหม? ดังนั้นจึงต้องมีสตริงที่ปลอดภัยสำหรับเธรดซึ่งเป็นคลาสทั่วไปที่เป็นอยู่จะทำให้โปรแกรม Java แทบทุกโปรแกรมทำงานช้ากว่ามาก มิฉะนั้นคุณจะต้องทำสำเนาสำหรับทุกเธรดที่ต้องการสตริงนั้นหรือคุณจะต้องวางโค้ดโดยใช้สตริงนั้นในบล็อกซิงโครไนซ์ซึ่งทั้งสองอย่างนี้จะทำให้โปรแกรมของคุณช้าลงเท่านั้น

ด้วยเหตุผลเหล่านี้ทั้งหมดนั่นเป็นหนึ่งในการตัดสินใจเริ่มต้นสำหรับ Java เพื่อแยกความแตกต่างจาก C ++


ในทางทฤษฎีคุณสามารถทำการจัดการบัฟเฟอร์หลายชั้นที่อนุญาตให้ใช้การคัดลอกบนการกลายพันธุ์ถ้าใช้ร่วมกัน แต่มันยากมากที่จะทำให้การทำงานมีประสิทธิภาพในสภาพแวดล้อมแบบมัลติเธรด
Donal Fellows

@ DonalFellows ฉันเพิ่งสันนิษฐานว่าเนื่องจาก Java Virtual Machine ไม่ได้เขียนใน Java (ชัด) จึงมีการจัดการภายในโดยใช้ตัวชี้ที่ใช้ร่วมกันหรือสิ่งที่ชอบ
Neil

5

สาเหตุของการเปลี่ยนแปลงไม่ได้ของสตริงมาจากความสอดคล้องกับชนิดดั้งเดิมอื่น ๆ ในภาษา หากคุณมีintค่า 42 และคุณเพิ่มค่า 1 ลงไปคุณจะไม่เปลี่ยน 42 คุณจะได้รับค่าใหม่ 43 ซึ่งไม่เกี่ยวข้องอย่างสมบูรณ์กับค่าเริ่มต้น การกลายพันธุ์ดั้งเดิมอื่น ๆ ที่ไม่ใช่สตริงทำให้รู้สึกไม่มีแนวคิด; และเช่นโปรแกรมที่จัดการกับสตริงที่ไม่เปลี่ยนรูปมักจะง่ายกว่าในการให้เหตุผลและเข้าใจ

นอกจากนี้ Java จริงๆมีทั้งสายที่ไม่แน่นอนและไม่เปลี่ยนรูปตามที่คุณเห็นด้วยStringBuilder; จริงๆเท่านั้นเริ่มต้นเป็นสตริงที่ไม่เปลี่ยนรูป หากคุณต้องการส่งต่อการอ้างอิงไปStringBuilderรอบ ๆ ทุกที่ที่คุณยินดีที่จะทำ Java ใช้ประเภทที่แยกต่างหาก ( StringและStringBuilder) สำหรับแนวคิดเหล่านี้เพราะมันไม่ได้รับการสนับสนุนสำหรับการแสดงความไม่แน่นอนหรือขาดหายไปในระบบประเภทของมัน ในภาษาที่รองรับการเปลี่ยนแปลงไม่ได้ในระบบชนิดของพวกเขา (เช่น C ++ const) มักจะมีประเภทสตริงเดียวที่ให้บริการทั้งสองวัตถุประสงค์

ใช่การมีสตริงไม่สามารถเปลี่ยนรูปแบบได้ทำให้สามารถใช้การเพิ่มประสิทธิภาพบางอย่างเฉพาะกับสตริงที่ไม่เปลี่ยนรูปเช่นการฝึกงานและอนุญาตให้ส่งผ่านการอ้างอิงสตริงโดยไม่ต้องซิงโครไนซ์ข้ามเธรด อย่างไรก็ตามสิ่งนี้ทำให้เกิดความสับสนกับกลไกโดยมีเป้าหมายที่ต้องการของภาษาด้วยระบบการพิมพ์ที่เรียบง่าย ฉันเปรียบสิ่งนี้กับวิธีที่ทุกคนคิดว่าการเก็บขยะเป็นวิธีที่ผิด การรวบรวมขยะไม่ใช่ "การเรียกคืนหน่วยความจำที่ไม่ได้ใช้"; มันคือ "การจำลองคอมพิวเตอร์ที่มีหน่วยความจำไม่ จำกัด" การปรับประสิทธิภาพให้เหมาะสมที่กล่าวถึงเป็นสิ่งที่ทำเพื่อให้เป้าหมายของสตริงที่ไม่เปลี่ยนรูปนั้นทำงานได้ดีบนเครื่องจริง ไม่ใช่เหตุผลที่สายเช่นนี้จะไม่เปลี่ยนรูปในตอนแรก


@ Billy-Oneal .. เกี่ยวกับ "ถ้าคุณมี int ที่มีค่า 42 และคุณเพิ่มค่า 1 ลงไปคุณจะไม่เปลี่ยน 42 คุณจะได้รับค่าใหม่ 43 ซึ่งไม่เกี่ยวข้องกับการเริ่มต้น ค่า." คุณแน่ใจหรือไม่
Shamit Verma

@Shamit: ใช่ฉันแน่ใจ การเพิ่มผลลัพธ์ 1 ถึง 42 ใน 43 มันไม่ได้ทำให้หมายเลข 42 หมายถึงสิ่งเดียวกันกับหมายเลข 43
Billy ONeal

@Shamit: ในทำนองเดียวกันคุณไม่สามารถทำสิ่งที่ชอบ43 = 6และคาดหวังว่าหมายเลข 43 จะหมายถึงสิ่งเดียวกันกับหมายเลข 6
Billy ONeal

int i = 42; i = i + 1; รหัสนี้จะเก็บ 42 ในหน่วยความจำแล้วเปลี่ยนค่าในตำแหน่งเดียวกันเป็น 43 ดังนั้นในความเป็นจริงตัวแปร "i" ได้รับค่าใหม่ 43
Shamit Verma

@Shamit: ในกรณีที่คุณกลายพันธุ์iไม่ 42. string s = "Hello "; s += "World";พิจารณา sคุณกลายพันธุ์ค่าของตัวแปร แต่สตริง"Hello ", "World"และ"Hello World"จะไม่เปลี่ยนรูป
Billy ONeal

4

การเปลี่ยนไม่ได้หมายความว่าค่าคงที่ที่จัดโดยคลาสที่คุณไม่ได้เป็นเจ้าของไม่สามารถแก้ไขได้ ชั้นเรียนที่คุณไม่ได้เป็นเจ้าของรวมถึงผู้ที่อยู่ในหลักของการดำเนินงานของ Java และสายที่ไม่ควรได้รับการแก้ไขรวมถึงสิ่งที่ชอบโทเค็นการรักษาความปลอดภัยที่อยู่บริการ ฯลฯ คุณจริงๆไม่ควรจะสามารถที่จะปรับเปลี่ยนประเภทเหล่านั้น ของสิ่งต่าง ๆ (และสิ่งนี้ใช้ได้เป็นสองเท่าเมื่อใช้งานในโหมดแซนด์บ็อกซ์)

ถ้า String ไม่เปลี่ยนรูปทุกครั้งที่คุณดึงมันมาจากบริบทที่ไม่ต้องการให้เนื้อหาของสตริงเปลี่ยนไปคุณจะต้องคัดลอก“ ในกรณี” มันมีราคาแพงมาก


4
อาร์กิวเมนต์เดียวกันนี้แน่นอนนำไปใช้กับใด ๆStringชนิดไม่เพียงเพื่อ ตัวอย่างเช่นArrays สามารถเปลี่ยนแปลงได้อย่างไรก็ตาม ดังนั้นทำไมStringไม่เปลี่ยนรูปและArrayไม่ได้ และหากการเปลี่ยนแปลงไม่ได้สำคัญมากทำไม Java ถึงทำให้มันยากที่จะสร้างและทำงานกับวัตถุที่เปลี่ยนแปลงไม่ได้?
Jörg W Mittag

1
@ JörgWMittag: ฉันคิดว่ามันเป็นคำถามที่ว่าพวกเขาต้องการที่จะเป็นรากฐาน การมีสตริงที่ไม่เปลี่ยนรูปนั้นค่อนข้างรุนแรงเมื่อย้อนกลับไปใน Java 1.0 วัน การมีกรอบการรวบรวมที่ไม่เปลี่ยนรูปแบบ (เป็นหลักหรือเฉพาะ) เช่นกันอาจรุนแรงเกินไปสำหรับการใช้ภาษาอย่างกว้างขวาง
Joachim Sauer

การทำเฟรมเวิร์กคอลเลคชันที่ไม่ได้ผลนั้นค่อนข้างยุ่งยากในการทำให้นักแสดงพูดได้ว่าเป็นคนที่เขียนสิ่งนี้ (แต่ไม่ใช่ใน Java) ฉันยังต้องการทั้งหมดที่ฉันมีอาร์เรย์อย่างไม่ลดละ ที่จะช่วยให้ฉันทำงานค่อนข้างน้อย
Donal Fellows

@ DonalFellows: pcollectionsมีจุดมุ่งหมายที่จะทำเช่นนั้น (ไม่เคยใช้มันด้วยตัวเอง)
Joachim Sauer

3
@ JörgWMittag: มีคน (โดยทั่วไปจากมุมมองการทำงานอย่างหมดจด) ที่จะยืนยันว่าทุกประเภทควรจะไม่เปลี่ยนรูป ในทำนองเดียวกันผมคิดว่าถ้าคุณเพิ่มขึ้นทั้งหมดของปัญหาที่หนึ่งที่เกี่ยวข้องกับการทำงานร่วมกับรัฐไม่แน่นอนในซอฟแวร์แบบขนานและพร้อมกันคุณอาจเห็นว่าการทำงานกับวัตถุที่ไม่เปลี่ยนรูปมักจะเป็นมากง่ายกว่าคนที่ไม่แน่นอน
Steven Evers

2

ลองนึกภาพระบบที่คุณยอมรับข้อมูลบางอย่างตรวจสอบความถูกต้องแล้วส่งต่อ (เพื่อเก็บไว้ในฐานข้อมูล)

สมมติว่าข้อมูลเป็น a Stringและต้องมีความยาวอย่างน้อย 5 ตัวอักษร วิธีการของคุณมีลักษณะเช่นนี้:

public void handle(String input) {
  if (input.length() < 5) {
    throw new IllegalArgumentException();
  }
  storeInDatabase(input);
}

ตอนนี้เราสามารถตกลงกันได้ว่าเมื่อstoreInDatabaseมีการเรียกที่นี่ความinputประสงค์จะสอดคล้องกับความต้องการ แต่ถ้าStringมีความไม่แน่นอนแล้วโทรสามารถเปลี่ยนinputวัตถุ (จากหัวข้ออื่น) ทันทีหลังจากที่มันได้รับการตรวจสอบและก่อนที่มันจะถูกเก็บไว้ในฐานข้อมูล สิ่งนี้จะต้องใช้เวลาที่ดีและอาจไม่ได้ดีทุกครั้ง แต่บางครั้งเขาจะสามารถให้คุณเก็บค่าที่ไม่ถูกต้องในฐานข้อมูล

ประเภทข้อมูลที่ไม่เปลี่ยนรูปนั้นเป็นวิธีการแก้ปัญหาที่ง่ายมาก (และที่เกี่ยวข้องมาก): เมื่อใดก็ตามที่คุณตรวจสอบค่าบางอย่างคุณสามารถขึ้นอยู่กับความจริงที่ว่าเงื่อนไขการตรวจสอบยังคงเป็นจริงในภายหลัง


ขอบคุณสำหรับคำอธิบาย ถ้าฉันเรียกใช้วิธีจัดการแบบนี้ จัดการ (สตริงใหม่ (ใส่ + "naberlan")) ฉันเดาว่าฉันสามารถเก็บค่าที่ไม่ถูกต้องใน db เช่นนี้
yfklon

1
@blank: ดีตั้งแต่inputของhandleวิธีการที่มีอยู่แล้วยาวเกินไป (ไม่ว่าสิ่งที่เป็นต้นฉบับ inputเป็น) มันก็จะโยนข้อยกเว้น คุณกำลังสร้างอินพุตใหม่ก่อนที่จะ เรียกวิธีการ นั่นไม่ใช่ปัญหา.
Joachim Sauer

0

โดยทั่วไปแล้วคุณจะพบค่าชนิดและประเภทของการอ้างอิง ด้วยประเภทค่าคุณไม่สนใจเกี่ยวกับวัตถุที่แสดงถึงมันคุณสนใจเกี่ยวกับค่า ถ้าฉันให้คุณค่าแก่คุณคุณคาดหวังว่าคุณค่านั้นจะยังคงเหมือนเดิม คุณไม่ต้องการให้มันเปลี่ยนแปลงในทันที หมายเลข 5 คือค่า คุณไม่คิดว่ามันจะเปลี่ยนเป็น 6 ในทันที สตริง "Hello" เป็นค่า คุณไม่คิดว่ามันจะเปลี่ยนเป็น "P *** off" ในทันที

ด้วยประเภทอ้างอิงที่คุณสนใจเกี่ยวกับวัตถุและคุณคาดหวังว่ามันจะมีการเปลี่ยนแปลง ตัวอย่างเช่นคุณมักจะคาดหวังว่าอาร์เรย์จะเปลี่ยนแปลง ถ้าฉันให้อาเรย์กับคุณและคุณต้องการที่จะเก็บมันไว้อย่างที่เป็นอยู่คุณต้องเชื่อใจฉันว่าจะไม่เปลี่ยนมันหรือคุณก็ทำสำเนามัน

ด้วยคลาสสตริงของ Java ผู้ออกแบบต้องทำการตัดสินใจ: มันจะดีกว่าไหมถ้าสตริงทำงานเหมือนชนิดของค่าหรือพวกมันควรทำตัวเหมือนเป็นประเภทอ้างอิง? ในกรณีของสตริง Java การตัดสินใจถูกทำให้พวกเขาควรเป็นประเภทของค่าซึ่งหมายความว่าเนื่องจากพวกเขาเป็นวัตถุพวกเขาจะต้องเป็นวัตถุที่ไม่เปลี่ยนรูป

การตัดสินใจในทางตรงข้ามอาจเกิดขึ้นได้ แต่ในความคิดของฉันจะทำให้ปวดหัวมาก ดังที่กล่าวไว้ที่อื่น ๆ หลายภาษาทำการตัดสินใจแบบเดียวกันและมาถึงข้อสรุปเดียวกัน ข้อยกเว้นคือ C ++ ซึ่งมีหนึ่งคลาสสตริงและสตริงสามารถเป็นค่าคงที่หรือไม่คงที่ แต่ใน C ++ ซึ่งแตกต่างจาก Java พารามิเตอร์ของวัตถุสามารถส่งผ่านเป็นค่าและไม่เป็นการอ้างอิง


0

ฉันประหลาดใจจริงๆไม่มีใครชี้เรื่องนี้

คำตอบ: มันจะไม่เป็นประโยชน์ต่อคุณอย่างมีนัยสำคัญแม้ว่ามันจะไม่แน่นอน มันจะไม่เป็นประโยชน์กับคุณมากเท่าที่ทำให้เกิดปัญหาเพิ่มเติม มาตรวจสอบการกลายพันธุ์สองกรณีที่พบบ่อยที่สุด:

การเปลี่ยนอักขระหนึ่งตัวของสตริง

เนื่องจากอักขระแต่ละตัวในสตริง Java มีขนาด 2 หรือ 4 ไบต์ถามตัวเองคุณจะได้รับอะไรไหมถ้าคุณสามารถกลายพันธุ์สำเนาที่มีอยู่ได้

ในสถานการณ์ที่คุณกำลังแทนที่อักขระ 2 ไบต์ด้วย 4 ไบต์หนึ่ง (หรือในทางกลับกัน) คุณต้องเลื่อนส่วนที่เหลือของสตริงโดย 2 ไบต์ไปทางซ้ายหรือไปทางขวา ซึ่งไม่แตกต่างจากการคัดลอกสตริงทั้งหมดพร้อมกันจากมุมมองการคำนวณ

นี่เป็นพฤติกรรมที่ผิดปกติซึ่งไม่พึงประสงค์ ลองนึกภาพคนที่กำลังทดสอบแอปพลิเคชันด้วยข้อความภาษาอังกฤษและเมื่อแอปพลิเคชันได้รับการนำไปใช้กับต่างประเทศเช่นจีนสิ่งทั้งหมดก็เริ่มทำงานได้อย่างประหลาด

ต่อท้ายสตริงอื่น (หรืออักขระ) กับสตริงที่มีอยู่

หากคุณมีสองสตริงโดยพลการสตริงเหล่านั้นจะอยู่ในตำแหน่งหน่วยความจำที่ต่างกันสองตำแหน่ง หากคุณต้องการเปลี่ยนหน่วยแรกโดยต่อท้ายหน่วยที่สองคุณไม่สามารถขอหน่วยความจำเพิ่มเติมที่ส่วนท้ายของสตริงแรกเนื่องจากอาจมีอยู่แล้ว

คุณต้องคัดลอกสตริงที่ต่อกันไปยังตำแหน่งใหม่ทั้งหมดซึ่งจะเหมือนกับว่าทั้งสองสตริงนั้นไม่เปลี่ยนรูป

หากคุณต้องการผนวกอย่างมีประสิทธิภาพคุณอาจต้องการใช้งานStringBuilderซึ่งสงวนพื้นที่จำนวนมากไว้ที่ท้ายสตริงเพื่อจุดประสงค์ในการผนวกในอนาคต


-2
  1. พวกเขามีราคาแพงและทำให้พวกเขาไม่สามารถเปลี่ยนแปลงได้ช่วยให้สิ่งต่าง ๆ เช่นสายย่อยที่ใช้ร่วมกันอาร์เรย์ไบต์ของสายหลัก (เพิ่มความเร็วยังไม่จำเป็นต้องสร้างอาร์เรย์ไบต์ใหม่และคัดลอกไป)

  2. ความปลอดภัย - ไม่ต้องการให้ชื่อแพคเกจหรือรหัสคลาสของคุณถูกตั้งชื่อใหม่

    [ลบเก่า 3 ดู StringBuilder src - มันไม่แชร์หน่วยความจำกับสตริง (จนกว่าจะแก้ไข) ฉันคิดว่ามันเป็น 1.3 หรือ 1.4]

  3. แคชแฮชโค้ด

  4. สำหรับสตริง mutalble ใช้ SB (ตัวสร้างหรือบัฟเฟอร์ตามต้องการ)


2
1. แน่นอนว่ามีบทลงโทษที่ไม่สามารถทำลายส่วนที่ใหญ่กว่าของสายได้หากเกิดเหตุการณ์เช่นนี้ การฝึกงานไม่ฟรี แม้ว่ามันจะปรับปรุงประสิทธิภาพสำหรับโปรแกรมในโลกแห่งความเป็นจริง 2. อาจมี "สตริง" และ "ImmutableString" ซึ่งสามารถตอบสนองความต้องการนั้นได้อย่างง่ายดาย 3. ฉันไม่แน่ใจว่าฉันเข้าใจว่า ...
Billy ONeal

0.3 ควรแคชรหัสแฮชแล้ว สิ่งนี้ก็สามารถทำได้ด้วยสตริงที่ไม่แน่นอน @ billy-oneal
tgkprog

-4

สตริงควรเป็นชนิดข้อมูลดั้งเดิมใน Java หากพวกเขาได้รับแล้วสตริงจะเริ่มต้นที่จะไม่แน่นอนและคำหลักสุดท้ายจะสร้างสตริงที่ไม่เปลี่ยนรูป สตริงที่ไม่แน่นอนจะมีประโยชน์และมีแฮ็กจำนวนมากสำหรับสตริงที่ไม่แน่นอนในคลาส stringbuffer, stringbuilder และ charsequence


3
สิ่งนี้ไม่ตอบมุมมอง "ทำไม" ของสิ่งที่เป็นอยู่ในตอนนี้ที่คำถามถาม นอกจากนี้ java final ยังใช้งานไม่ได้ สตริงที่ไม่แน่นอนไม่ได้เป็นแฮ็ก แต่เป็นข้อควรพิจารณาในการออกแบบจริงโดยพิจารณาจากการใช้สตริงส่วนใหญ่และการปรับแต่งที่สามารถทำได้เพื่อปรับปรุง jvm

1
คำตอบของ "ทำไม" คือการตัดสินใจออกแบบภาษาที่ไม่ดี สามวิธีที่แตกต่างกันเล็กน้อยในการสนับสนุนสตริงที่ไม่แน่นอนคือแฮ็กที่คอมไพเลอร์ / JVM ควรจัดการ
CWallach

3
String และ StringBuffer เป็นต้นฉบับ StringBuilder ถูกเพิ่มเข้ามาในภายหลังตระหนักถึงปัญหาการออกแบบด้วย StringBuffer สตริงที่ไม่แน่นอนและไม่เปลี่ยนแปลงเป็นวัตถุที่แตกต่างกันพบในหลายภาษาเนื่องจากการพิจารณาการออกแบบได้ทำซ้ำแล้วซ้ำอีกและตัดสินใจว่าแต่ละวัตถุนั้นแตกต่างกันในแต่ละครั้ง C # "สตริงไม่เปลี่ยนรูป"และทำไม. NET String ไม่เปลี่ยนรูป? วัตถุประสงค์ C NSString จะไม่เปลี่ยนรูปในขณะที่ NSMutableString จะไม่แน่นอน stackoverflow.com/questions/9544182
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.