เหตุใดจึงไม่สามารถเปลี่ยนสตริงได้ใน Java และ. NET


191

ทำไมพวกเขาถึงตัดสินใจทำให้Stringไม่เปลี่ยนรูปแบบใน Java และ. NET (และภาษาอื่น ๆ )? ทำไมพวกเขาถึงไม่ทำให้มันเปลี่ยนแปลงได้?


13
ฉันมีความคิดแบบเดียวกัน แต่ตรวจสอบตำแหน่งโปสเตอร์ดั้งเดิมและพบว่ามาจากเบลเยียม ระบุว่านี่หมายความว่าพวกเขาไม่น่าจะเป็นเจ้าของภาษาอังกฤษ บวกกับความจริงที่ว่าชาวบ้านส่วนใหญ่เข้าใจภาษาฉันจึงตัดสินใจที่จะลดความหย่อนของเธอลง
belugabob

8
ขอบคุณ belugabob แต่ฉันไม่ใช่เธอฉันเป็นเขา เห็นได้ชัดว่าผู้คนไม่คำนึงถึงความแตกต่างทางวัฒนธรรม
chrissie1

7
คำขอโทษของฉัน - chrissie คือ (โดยทั่วไป) ชื่อเด็กหญิงในสหราชอาณาจักร - ทำให้ฉันตกเป็นเหยื่อของความแตกต่างทางวัฒนธรรม :-) อีกครั้ง
belugabob

เพียงแค่ทราบว่าใน. NET Stringเป็นจริงที่ไม่แน่นอนภายใน StringBuilderใน NET 2.0 แปรรูปสตริง ฉันจะทิ้งไว้ที่นี่
อัลวินหว่อง

ที่จริงสตริง .NET มีความไม่แน่นอน และมันก็ไม่ได้เป็นแฮ็คของแฮ็ค
Bitterblue

คำตอบ:


205

อ้างอิงจากJava ที่มีประสิทธิภาพ , บทที่ 4, หน้า 73, ฉบับที่ 2:

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

[ ... ]

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

[ ... ]

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

[ ... ]

จุดเล็ก ๆ อื่น ๆ จากบทเดียวกัน:

ไม่เพียง แต่คุณสามารถแบ่งปันวัตถุที่ไม่เปลี่ยนรูปแบบ แต่คุณสามารถแบ่งปันภายในของพวกเขา

[ ... ]

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

[ ... ]

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


22
อ่านประโยคที่สองของคำตอบของฉัน: ชั้นเรียนที่ไม่เปลี่ยนรูปนั้นง่ายต่อการออกแบบใช้งานและใช้งานมากกว่าชั้นเรียนที่ไม่แน่นอน พวกเขามีแนวโน้มน้อยที่จะเกิดข้อผิดพลาดและมีความปลอดภัยมากขึ้น
PRINCESS FLUFF

5
@PRINCESSFLUFF ฉันจะเพิ่มว่าการแบ่งปันสตริงที่ไม่แน่นอนเป็นอันตรายแม้ในหัวข้อเดียว report2.Text = report1.Text;ยกตัวอย่างเช่นการคัดลอกรายงาน: report2.Text.Replace(someWord, someOtherWord);แล้วที่อื่นการปรับเปลี่ยนข้อความ: สิ่งนี้จะเปลี่ยนรายงานฉบับแรกและฉบับที่สอง
phoog

10
@Sam เขาไม่ได้ถามว่า "ทำไมพวกเขาถึงเปลี่ยนแปลงไม่ได้" เขาถาม "ทำไมพวกเขาถึงตัดสินใจไม่เปลี่ยนรูปแบบ" ซึ่งคำตอบนี้สมบูรณ์แบบ
James

1
@PRINCESSFLUFF คำตอบนี้ไม่ได้ระบุสตริงโดยเฉพาะ นั่นคือคำถาม OPs มันน่าหงุดหงิดเหลือเกิน - สิ่งนี้เกิดขึ้นตลอดเวลาบน SO และด้วยคำถามที่ไม่เปลี่ยนรูปแบบของสตริงเช่นกัน คำตอบที่นี่พูดถึงประโยชน์ทั่วไปของการเปลี่ยนแปลงไม่ได้ แล้วทำไมทุกประเภทไม่เปลี่ยนรูปแบบไม่ได้? คุณกรุณากลับไปและจัดการกับสตริงได้ไหม?
Howiecamp

@ Howiecamp ฉันคิดว่ามันเป็นนัยโดยคำตอบที่สตริงอาจไม่แน่นอน (ไม่มีอะไรป้องกันคลาสสตริงที่ไม่แน่นอนสมมุติจากที่มีอยู่) พวกเขาตัดสินใจที่จะไม่ทำแบบนั้นเพื่อความเรียบง่ายและเพราะมันครอบคลุมการใช้งาน 99% พวกเขายังคงให้บริการ StringBuilder สำหรับอีก 1 กรณี
Daniel García Rubio

102

มีเหตุผลอย่างน้อยสองประการ

First - ความปลอดภัย http://www.javafaq.nu/java-article1060.html

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

ที่สอง - ประสิทธิภาพของหน่วยความจำ http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

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


มันเป็นจุดที่ดีเกี่ยวกับการใช้ซ้ำและโดยเฉพาะอย่างยิ่งถ้าคุณใช้ String.intern () มันจะเป็นไปได้ที่จะนำกลับมาใช้ใหม่โดยไม่ทำให้สตริงไม่เปลี่ยนรูป แต่ชีวิตมีแนวโน้มที่จะซับซ้อน ณ จุดนั้น
jsight

3
ไม่มีเหตุผลข้อใดข้อหนึ่งสำหรับฉันในยุคนี้
Brian Knoblauch

1
ฉันไม่มั่นใจด้วยอาร์กิวเมนต์ประสิทธิภาพของหน่วยความจำ (เช่นเมื่อวัตถุ String สองอันหรือมากกว่านั้นใช้ข้อมูลเดียวกันร่วมกันและอีกอันหนึ่งได้รับการแก้ไขจากนั้นทั้งคู่ก็จะได้รับการแก้ไข) CString วัตถุใน MFC รับรอบนั้นโดยใช้การนับการอ้างอิง
RobH

7
ความปลอดภัยไม่ได้เป็นส่วนหนึ่งของ Raison d'isontre สำหรับสตริงที่ไม่เปลี่ยนรูปแบบ - ระบบปฏิบัติการของคุณจะคัดลอกสตริงไปยังบัฟเฟอร์โหมดเคอร์เนลและทำการตรวจสอบการเข้าถึงที่นั่นเพื่อหลีกเลี่ยงการโจมตีตามจังหวะเวลา มันคือทั้งหมดที่เกี่ยวกับความปลอดภัยและประสิทธิภาพของด้าย :)
snemarch

1
อาร์กิวเมนต์ประสิทธิภาพหน่วยความจำไม่ทำงานเช่นกัน ในภาษาท้องถิ่นเช่น C ค่าคงที่สตริงเป็นเพียงตัวชี้ไปยังข้อมูลในส่วนของข้อมูลเริ่มต้น - พวกเขาเป็นแบบอ่านอย่างเดียว / ไม่เปลี่ยนรูปต่อไป "ถ้ามีคนเปลี่ยนค่า" - อีกครั้งสตริงจากพูลเป็นแบบอ่านอย่างเดียว
wj32

57

ที่จริงแล้วสตริงเหตุผลไม่เปลี่ยนรูปแบบใน java ไม่ได้มีส่วนเกี่ยวข้องกับความปลอดภัยมากนัก เหตุผลหลักสองข้อต่อไปนี้:

ความปลอดภัยของ Thead:

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

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

ประสิทธิภาพ:

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

ที่สำคัญกว่านั้นคือสตริงที่ไม่เปลี่ยนแปลงทำให้พวกเขาสามารถแชร์ข้อมูลภายในของพวกเขาได้ สำหรับการดำเนินการกับสตริงจำนวนมากหมายความว่าไม่จำเป็นต้องคัดลอกอาร์เรย์ของอักขระพื้นฐาน ตัวอย่างเช่นสมมติว่าคุณต้องการใช้อักขระห้าตัวแรกของ String ใน Java คุณจะเรียก myString.substring (0,5) ในกรณีนี้สิ่งที่วิธีย่อย () วิธีการเพียงแค่การสร้างวัตถุสตริงใหม่ที่ใช้ร่วมกันพื้นฐานของ char [] แต่ใครจะรู้ว่ามันเริ่มต้นที่ดัชนี 0 และสิ้นสุดที่ดัชนี 5 ของถ่าน [] หากต้องการวางสิ่งนี้ในรูปแบบกราฟิกคุณจะต้องทำสิ่งต่อไปนี้:

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

สิ่งนี้ทำให้การดำเนินการชนิดนี้ราคาถูกมากและ O (1) เนื่องจากการดำเนินการไม่ขึ้นอยู่กับความยาวของสายอักขระดั้งเดิมหรือความยาวของสตริงย่อยที่เราต้องการแยก ลักษณะการทำงานนี้ยังมีประโยชน์หน่วยความจำบางอย่างเนื่องจากสตริงจำนวนมากสามารถแบ่งปันอักขระพื้นฐาน []


6
การนำไปใช้งานสตริงย่อยเป็นข้อมูลอ้างอิงที่ใช้ข้อมูลอ้างอิงร่วมกันchar[]นั้นเป็นการตัดสินใจออกแบบที่ค่อนข้างน่าสงสัย หากคุณอ่านไฟล์ทั้งหมดลงในสายอักขระเดียวและเก็บการอ้างอิงถึงสตริงย่อย 1 ตัวอักษรไฟล์ทั้งหมดจะต้องถูกเก็บไว้ในหน่วยความจำ
Gabe

5
ฉันวิ่งเข้าไปใน gotcha นั้นในขณะที่สร้างซอฟต์แวร์รวบรวมข้อมูลเว็บไซต์ที่ต้องการเพียงดึงคำสองสามคำจากทั้งหน้า โค้ด HTML ทั้งหน้าอยู่ในหน่วยความจำและเนื่องจาก substring แชร์ char [] ฉันเก็บโค้ด HTML ทั้งหมดไว้แม้ว่าฉันต้องการเพียงไม่กี่ไบต์ วิธีแก้ปัญหาสำหรับการใช้ String ใหม่ (original.substring (.. , .. )), Constructor String (String) สร้างสำเนาของช่วงที่เกี่ยวข้องของอาร์เรย์ที่เกี่ยวข้อง
LordOfThePigs

1
ภาคผนวกที่จะครอบคลุมการเปลี่ยนแปลงที่ตามมา: ตั้งแต่ Jave 7 ให้String.substring()ดำเนินการสำเนาแบบเต็มเพื่อป้องกันปัญหาที่กล่าวถึงในความคิดเห็นด้านบน ใน Java 8 ทั้งสองฟิลด์ที่เปิดใช้งานchar[]การแชร์คือcountและoffsetจะถูกลบออกซึ่งเป็นการลดการปล่อยหน่วยความจำของอินสแตนซ์ของสตริง
คริสเตียนเซมรู

ฉันเห็นด้วยกับส่วนความปลอดภัยของ Thead แต่สงสัยกรณีสายย่อย
Gqqnbig

@LoveRight: จากนั้นตรวจสอบซอร์สโค้ดของ java.lang.String ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/ ......มันเป็นแบบนั้นมาจนจาวา 6 (ซึ่ง เป็นปัจจุบันเมื่อเขียนคำตอบนี้) ฉันเห็นได้ชัดว่ามีการเปลี่ยนแปลงใน Java 7
LordOfThePigs

28

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


11

เราควรถามว่า "ทำไม X จึงไม่แน่นอน" เป็นการเริ่มต้นที่ดีกว่าความไม่สามารถเปลี่ยนแปลงได้เนื่องจากประโยชน์ที่ได้รับจากเจ้าหญิงฟลัฟ ควรเป็นข้อยกเว้นว่ามีบางอย่างไม่แน่นอน

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


7

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

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

เหตุผลที่แท้จริง (ชี้ให้เห็นโดยคนอื่นข้างต้น) คือการเพิ่มประสิทธิภาพหน่วยความจำ มันค่อนข้างทั่วไปในแอปพลิเคชันใด ๆ สำหรับตัวอักษรสตริงเดียวกันที่จะใช้ซ้ำ ๆ ในความเป็นจริงมันเป็นเรื่องธรรมดามากที่ทศวรรษที่ผ่านมาคอมไพเลอร์จำนวนมากได้ทำการเพิ่มประสิทธิภาพของการจัดเก็บเพียงตัวอย่างเดียวของStringตัวอักษร ข้อเสียเปรียบของการปรับให้เหมาะสมนี้คือรหัสรันไทม์ที่แก้ไขStringตัวอักษรแนะนำปัญหาเนื่องจากมีการปรับเปลี่ยนอินสแตนซ์สำหรับรหัสอื่น ๆ ทั้งหมดที่ใช้ร่วมกัน ยกตัวอย่างเช่นมันจะไม่ดีสำหรับบางฟังก์ชั่นในการประยุกต์ใช้ในการเปลี่ยนแปลงStringที่แท้จริงที่จะ"dog" จะส่งผลให้ถูกเขียนไป stdout ด้วยเหตุนี้จึงจำเป็นต้องมีวิธีการป้องกันโค้ดที่พยายามเปลี่ยน"cat"printf("dog")"cat"Stringตัวอักษร (เช่นทำให้ไม่เปลี่ยนรูป) คอมไพเลอร์บางตัว (ที่ได้รับการสนับสนุนจากระบบปฏิบัติการ) จะทำสิ่งนี้ได้โดยการวางตัวString แท้จริงในเซ็กเมนต์หน่วยความจำแบบอ่านอย่างเดียวที่จะทำให้เกิดความผิดพลาดของหน่วยความจำถ้ามีความพยายามในการเขียน

ในชวาสิ่งนี้เรียกว่าการฝึกงาน คอมไพเลอร์ Java ที่นี่เป็นเพียงการติดตามการเพิ่มประสิทธิภาพหน่วยความจำมาตรฐานที่ทำโดยคอมไพเลอร์มานานหลายทศวรรษ และเพื่อแก้ไขปัญหาเดียวกันของStringตัวอักษรเหล่านี้ที่ถูกแก้ไขที่รันไทม์ Java เพียงทำให้Stringคลาสไม่เปลี่ยนรูป (i. e ไม่มีตัวตั้งค่าที่อนุญาตให้คุณเปลี่ยนStringเนื้อหา) Strings จะไม่ต้องไม่เปลี่ยนรูปถ้าStringไม่ได้เกิดจากการฝึกตัวอักษร


3
ฉันไม่เห็นด้วยอย่างยิ่งเกี่ยวกับความไม่สามารถเปลี่ยนแปลงได้และการทำเกลียวความคิดเห็นดูเหมือนว่าสำหรับคุณแล้ว และถ้า Josh Bloch หนึ่งในผู้พัฒนา Java กล่าวว่านั่นเป็นหนึ่งในปัญหาด้านการออกแบบ
javashlook

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

5
@ จิม: การเพิ่มประสิทธิภาพหน่วยความจำไม่ได้เป็น 'THE' เหตุผลมันเป็นเหตุผล 'A' ความปลอดภัยของด้ายก็เป็นเหตุผล 'A' ด้วยเพราะวัตถุที่ไม่เปลี่ยนรูปนั้นมีความปลอดภัยต่อเธรดและไม่จำเป็นต้องซิงโครไนซ์ราคาแพงอย่างที่เดวิดพูดถึง ความปลอดภัยของเธรดเป็นผลข้างเคียงของวัตถุที่ไม่เปลี่ยนรูป คุณสามารถคิดว่าการซิงโครไนซ์เป็นวิธีในการทำให้วัตถุเป็น "ชั่วคราว" ไม่เปลี่ยนรูปแบบ (ReaderWriterLock จะทำให้อ่านอย่างเดียวและการล็อคปกติจะทำให้ไม่สามารถเข้าถึงได้โดยสิ้นเชิง
Triynko

1
@DavidThornley: การสร้างหลาย ๆ พา ธ อ้างอิงอิสระไปยังผู้ถือค่าที่ไม่แน่นอนกลายเป็นเอนทิตีได้อย่างมีประสิทธิภาพและทำให้มันยากมากที่จะให้เหตุผลนอกเหนือจากปัญหาเธรด โดยทั่วไปแล้ววัตถุที่เปลี่ยนแปลงไม่ได้จะมีประสิทธิภาพมากกว่าที่วัตถุที่เปลี่ยนแปลงไม่ได้ในกรณีที่เส้นทางการอ้างอิงเดียวจะมีอยู่ แต่วัตถุที่เปลี่ยนแปลงไม่ได้จะอนุญาตให้แบ่งปันเนื้อหาของวัตถุได้อย่างมีประสิทธิภาพโดยการแบ่งปันการอ้างอิง รูปแบบที่ดีที่สุดได้รับการยกตัวอย่างโดยStringและStringBufferแต่น่าเสียดายที่มีเพียงไม่กี่ประเภทที่ตามมา
supercat

7

String ไม่ใช่ประเภทดึกดำบรรพ์ แต่ปกติแล้วคุณต้องการใช้กับซีแมนทิกส์ค่าเช่นเช่นค่า

คุณค่าคือสิ่งที่คุณสามารถเชื่อถือได้ว่าจะไม่เปลี่ยนแปลงหลังของคุณ ถ้าคุณเขียน: คุณไม่ต้องการที่จะเปลี่ยนจนกว่าคุณจะทำอะไรกับString str = someExpr(); str

Stringในฐานะที่Objectมีความหมายตามธรรมชาติเพื่อให้ได้ความหมายตามตัวอักษรมันจะต้องไม่เปลี่ยนรูป


7

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


6

ฉันรู้ว่านี่เป็นชน แต่ ... พวกเขาไม่เปลี่ยนรูปจริงเหรอ? พิจารณาดังต่อไปนี้

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 # ไม่เปลี่ยนรูปทั้งหมด


1
+1 สตริง. NET นั้นไม่เปลี่ยนรูปไม่ได้จริงๆ ในความเป็นจริงนี้จะทำตลอดเวลาในคลาส String และ StringBuilder ด้วยเหตุผลที่สมบูรณ์
James Ko

3

สำหรับวัตถุประสงค์มากที่สุดเป็น "สตริง" จะ (ใช้ / ถือว่าเป็น / ความคิดของการ / สันนิษฐานว่าจะเป็น) มีความหมายหน่วยอะตอม เช่นเดียวกับตัวเลข

การถามว่าเหตุใดอักขระแต่ละตัวของสตริงจึงไม่สามารถเปลี่ยนแปลงได้ดังนั้นจึงถามว่าทำไมบิตแต่ละตัวของจำนวนเต็มจึงไม่สามารถเปลี่ยนแปลงได้

คุณควรรู้ว่าทำไม แค่คิดเกี่ยวกับมัน

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

เราทำการคำนวณและการเปรียบเทียบด้วย "สตริง" ซึ่งคล้ายกับวิธีที่เราทำกับตัวเลข หากสตริง (หรือจำนวนเต็ม) ไม่แน่นอนเราจะต้องเขียนรหัสพิเศษเพื่อล็อคค่าของพวกเขาในรูปแบบที่ไม่เปลี่ยนรูปแบบในท้องถิ่นเพื่อทำการคำนวณชนิดใด ๆ ได้อย่างน่าเชื่อถือ ดังนั้นจึงควรคิดถึงสตริงเหมือนตัวระบุตัวเลข แต่แทนที่จะเป็น 16, 32, หรือ 64 บิตยาวมันอาจยาวหลายร้อยบิต

เมื่อมีคนพูดว่า "สตริง" เราทุกคนต่างนึกถึงสิ่งต่าง ๆ ผู้ที่คิดว่ามันเป็นเพียงชุดของตัวละครโดยไม่มีจุดประสงค์เป็นพิเศษแน่นอนว่าจะมีคนตกใจเมื่อมีคนตัดสินใจว่าพวกเขาไม่ควรจัดการกับตัวละครเหล่านั้นได้ แต่คลาส "string" ไม่ได้เป็นเพียงอาเรย์ของอักขระ มันเป็นไม่ได้STRING char[]มีสมมติฐานพื้นฐานบางอย่างเกี่ยวกับแนวคิดที่เราอ้างถึงว่าเป็น "สตริง" และโดยทั่วไปสามารถอธิบายได้ว่าเป็นหน่วยปรมาณูที่มีความหมายและมีความหมายของข้อมูลรหัสเช่นตัวเลข เมื่อผู้คนพูดถึง "จัดการสตริง" บางทีพวกเขากำลังพูดถึงการจัดการอักขระเพื่อสร้างสตริงและ StringBuilder ยอดเยี่ยมสำหรับสิ่งนั้น

พิจารณาสักครู่ว่ามันจะเป็นอย่างไรถ้ามีการเปลี่ยนแปลงสตริง ฟังก์ชัน 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 );
    }
}

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


ใน C # สตริงสามารถเปลี่ยนแปลงได้โดยตัวชี้ (ใช้unsafe) หรือเพียงแค่ผ่านการสะท้อน (คุณสามารถรับข้อมูลพื้นฐานได้อย่างง่ายดาย) นี้จะทำให้จุดบนโมฆะการรักษาความปลอดภัยเป็นใครที่จงใจต้องการที่จะเปลี่ยนสตริงสามารถทำได้ค่อนข้างง่าย อย่างไรก็ตามมันให้ความปลอดภัยแก่โปรแกรมเมอร์: ถ้าคุณไม่ทำอะไรเป็นพิเศษสตริงนั้นจะไม่เปลี่ยนไม่ได้ (แต่ไม่ใช่ threadsafe!)
Abel

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

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

1
'ชี้ในรหัสเชิงวัตถุที่ไม่ปลอดภัยโดยเนื้อแท้' จนกว่าคุณจะเรียกพวกเขาอ้างอิง การอ้างอิงใน Java ไม่แตกต่างจากพอยน์เตอร์ใน C ++ (ปิดใช้งานการคำนวณทางคณิตศาสตร์เท่านั้น) แนวคิดที่แตกต่างคือการจัดการหน่วยความจำที่สามารถจัดการได้หรือด้วยตนเอง แต่นั่นเป็นสิ่งที่แตกต่าง คุณอาจมีความหมายอ้างอิง (ตัวชี้ไม่มีทางคณิตศาสตร์) โดยไม่ต้อง GC (ตรงข้ามจะยากกว่าในแง่ที่ว่าความหมายของการเชื่อมจะยากที่จะทำให้การทำความสะอาด แต่ไม่ unfeasable)
เดวิดRodríguez - dribeas

อีกอย่างคือถ้าสตริงเกือบจะไม่เปลี่ยนรูป แต่ไม่มาก (ฉันไม่รู้ CLI มากพอที่นี่) นั่นอาจไม่ดีสำหรับเหตุผลด้านความปลอดภัย ใน Java implementations รุ่นเก่าที่คุณสามารถทำได้และฉันพบข้อมูลโค้ดที่ใช้ในการทำให้สตริง (ลองค้นหาสตริงภายในอื่น ๆ ที่มีค่าเดียวกันแบ่งปันตัวชี้ถอดบล็อกหน่วยความจำเก่า) และใช้แบ็คดอร์ เพื่อเขียนเนื้อหาสตริงบังคับให้มีพฤติกรรมที่ไม่ถูกต้องในคลาสอื่น (ลองเขียนใหม่ "SELECT *" เป็น "DELETE")
David Rodríguez - dribeas

3

การเปลี่ยนไม่ได้นั้นไม่เกี่ยวข้องกับความปลอดภัยอย่างใกล้ชิด อย่างน้อยที่สุดใน. NET คุณจะได้รับSecureStringคลาส

แก้ไขในภายหลัง: ใน Java คุณจะพบGuardedStringการใช้งานที่คล้ายกัน


2

การตัดสินใจที่จะมีแน่นอนสตริงใน C ++ ทำให้เกิดปัญหามากดูบทความที่ดีเยี่ยมนี้โดยเคลวินเฮนนีย์เกี่ยวกับโรควัวบ้า

COW = คัดลอกเมื่อเขียน


2

มันเป็นการแลกเปลี่ยน Stringเข้าสู่กลุ่มStringและเมื่อคุณสร้างหลาย ๆ รายการเหมือนกันStringพวกเขาแชร์หน่วยความจำเดียวกัน นักออกแบบคิดว่าเทคนิคการบันทึกหน่วยความจำนี้จะทำงานได้ดีสำหรับกรณีทั่วไปเนื่องจากโปรแกรมมักจะบดทับสายเดียวกันเป็นจำนวนมาก

ข้อเสียคือการต่อข้อมูลทำให้Strings พิเศษจำนวนมากเป็นเพียงการนำส่งและกลายเป็นขยะจริง ๆ แล้วส่งผลเสียต่อประสิทธิภาพของหน่วยความจำ คุณมีStringBufferและStringBuilder(ใน Java StringBuilderยังอยู่ใน. NET) เพื่อใช้ในการรักษาหน่วยความจำในกรณีเหล่านี้


1
โปรดทราบว่า "กลุ่มสตริง" จะไม่ถูกใช้โดยอัตโนมัติสำหรับสตริงทั้งหมดยกเว้นว่าคุณใช้สตริง "inter ()" 'ed อย่างชัดเจน
jsight

2

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


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

ที่จริงฉันเชื่อว่าคุณสามารถเปลี่ยนวัตถุที่ไม่เปลี่ยนรูปใด ๆ โดยการสะท้อนกลับ
Gqqnbig

0

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


0

มีข้อยกเว้นสำหรับเกือบทุกกฎ:

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

-1

เป็นส่วนใหญ่ด้วยเหตุผลด้านความปลอดภัย มันเป็นเรื่องยากมากที่จะรักษาความปลอดภัยระบบถ้าคุณไม่สามารถไว้วางใจที่คุณStrings มี tamperproof


1
คุณสามารถให้ตัวอย่างของสิ่งที่คุณหมายถึงโดย "tamperproof" คำตอบนี้ให้ความรู้สึกนอกบริบทจริงๆ
Gergely Orosz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.