เห็นพ้องด้วย
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) มีและNSString
NSMutableString
C # และ. NET ทำให้ตัวเลือกการออกแบบเดียวกันของสตริงเริ่มต้นเป็นไม่เปลี่ยนรูป
สายLuaยังไม่เปลี่ยนรูป
Pythonเช่นกัน
อดีตชัดโครงการสมอลล์ทอล์คทั้งหมดฝึกงานสตริงและทำให้มีมันจะไม่เปลี่ยนรูป ภาษาไดนามิกที่ทันสมัยกว่ามักใช้สตริงในบางวิธีที่ต้องการให้ไม่เปลี่ยนรูป (อาจไม่ใช่สตริงแต่เป็นรูปแบบที่ไม่เปลี่ยนรูป)
ข้อสรุป
ข้อควรพิจารณาในการออกแบบเหล่านี้ได้ทำซ้ำแล้วซ้ำอีกในหลายภาษา มันเป็นฉันทามติทั่วไปที่สตริงที่ไม่เปลี่ยนแปรสำหรับความอึดอัดใจทั้งหมดของพวกเขาดีกว่าทางเลือกและนำไปสู่รหัสที่ดีขึ้น