วัตถุที่ไม่เปลี่ยนรูปเป็นวัตถุที่เขตข้อมูลภายใน (หรืออย่างน้อยที่สุดเขตข้อมูลภายในทั้งหมดที่มีผลต่อพฤติกรรมภายนอก) ไม่สามารถเปลี่ยนแปลงได้
มีประโยชน์มากมายกับสตริงที่ไม่เปลี่ยนรูปแบบ:
ประสิทธิภาพการทำงาน:ใช้การดำเนินการต่อไปนี้:
String substring = fullstring.substring(x,y);
C ต้นแบบสำหรับเมธอดย่อย () อาจเป็นดังนี้:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
โปรดทราบว่าไม่มีการคัดลอกอักขระ! หากวัตถุ String ไม่แน่นอน (ตัวอักษรสามารถเปลี่ยนแปลงได้ในภายหลัง) จากนั้นคุณจะต้องคัดลอกอักขระทั้งหมดมิฉะนั้นการเปลี่ยนแปลงตัวอักษรในสตริงย่อยจะสะท้อนให้เห็นในสตริงอื่นในภายหลัง
การเกิดขึ้นพร้อมกัน:หากโครงสร้างภายในของวัตถุที่เปลี่ยนรูปไม่ถูกต้องก็จะถูกต้องเสมอ ไม่มีโอกาสที่เธรดที่ต่างกันสามารถสร้างสถานะที่ไม่ถูกต้องภายในวัตถุนั้นได้ ดังนั้นวัตถุที่ไม่เปลี่ยนรูปเป็นด้ายปลอดภัย
การรวบรวมขยะ:เป็นเรื่องง่ายสำหรับตัวรวบรวมขยะในการตัดสินใจเชิงตรรกะเกี่ยวกับวัตถุที่ไม่เปลี่ยนรูป
อย่างไรก็ตามยังมีข้อเสียต่อการเปลี่ยนแปลงไม่ได้:
ประสิทธิภาพ:เดี๋ยวก่อนฉันคิดว่าคุณพูดว่าการแสดงนั้นเป็นสิ่งที่ไม่สามารถเปลี่ยนแปลงได้! บางครั้งก็เป็น แต่ก็ไม่เสมอไป ใช้รหัสต่อไปนี้:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
ทั้งสองบรรทัดแทนที่อักขระที่สี่ด้วยตัวอักษร "a" รหัสที่สองไม่เพียง แต่อ่านได้ง่ายขึ้นเท่านั้น แต่ยังเร็วกว่า ดูวิธีที่คุณจะต้องทำรหัสอ้างอิงสำหรับ foo สตริงย่อยนั้นง่าย แต่ตอนนี้เนื่องจากมีอักขระที่ช่องว่างที่ห้าอยู่แล้วและอย่างอื่นอาจอ้างถึง foo คุณจึงไม่สามารถเปลี่ยนได้ คุณต้องคัดลอกสตริงทั้งหมด (แน่นอนว่าฟังก์ชั่นนี้บางส่วนถูกแยกออกเป็นฟังก์ชั่นใน C จริงพื้นฐาน แต่จุดที่นี่คือการแสดงรหัสที่ได้รับการดำเนินการทั้งหมดในที่เดียว)
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
โปรดทราบว่า concatenate จะได้รับการเรียกสองครั้งซึ่งหมายความว่าสตริงทั้งหมดจะต้องวนลูปผ่าน! เปรียบเทียบสิ่งนี้กับรหัส C สำหรับbar
ดำเนินการ:
bar->characters[4] = 'a';
การดำเนินการกับสตริงที่ไม่แน่นอนนั้นเร็วกว่ามาก
สรุปแล้ว:ในกรณีส่วนใหญ่คุณต้องการสตริงที่ไม่เปลี่ยนรูป แต่ถ้าคุณต้องการต่อท้ายและแทรกลงในสตริงคุณต้องมีความผันแปรของความเร็ว หากคุณต้องการความปลอดภัยในการใช้งานพร้อมกันและการเก็บขยะด้วยกุญแจสำคัญคือการทำให้อ็อบเจกต์ที่เปลี่ยนแปลงไม่ได้ของคุณเป็นวิธี:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
เนื่องจากmutable
วัตถุนั้นเป็นข้อมูลอ้างอิงในพื้นที่คุณจึงไม่ต้องกังวลเกี่ยวกับความปลอดภัยในการทำงานพร้อมกัน (มีเพียงหนึ่งเธรดที่เคยสัมผัส) และเนื่องจากไม่มีการอ้างอิงที่ใดเลยจึงจัดสรรเฉพาะในสแต็กดังนั้นจึงถูกจัดสรรคืนทันทีที่การเรียกใช้ฟังก์ชันเสร็จสิ้น (คุณไม่ต้องกังวลเกี่ยวกับการรวบรวมขยะ) และคุณจะได้รับประโยชน์ด้านประสิทธิภาพทั้งความผันแปรและไม่เปลี่ยนไม่ได้