ทำไมการผนวก“” เข้ากับ String จะบันทึกหน่วยความจำ


193

String dataผมใช้ตัวแปรที่มีข้อมูลเป็นจำนวนมากในการพูด ฉันต้องการใช้ส่วนเล็ก ๆ ของสายนี้ในวิธีต่อไปนี้:

this.smallpart = data.substring(12,18);

หลังจากทำการดีบักได้หลายชั่วโมง (ด้วย visualizer หน่วยความจำ) ฉันพบว่าฟิลด์ออบเจ็กต์smallpartจดจำข้อมูลทั้งหมดจากdataแม้ว่ามันจะมีซับสตริงเท่านั้น

เมื่อฉันเปลี่ยนรหัสเป็น:

this.smallpart = data.substring(12,18)+""; 

.. ปัญหาได้รับการแก้ไข! ตอนนี้แอปพลิเคชันของฉันใช้หน่วยความจำน้อยมากในตอนนี้!

เป็นไปได้อย่างไร? มีใครอธิบายเรื่องนี้ได้บ้าง ฉันคิดว่านี่ขนาดเล็กเก็บอ้างอิงถึงข้อมูล แต่ทำไม?

อัปเดต: ฉันจะล้างสตริงขนาดใหญ่ได้อย่างไร data = new String (data.substring (0,100)) จะทำสิ่งนั้นหรือไม่


อ่านเพิ่มเติมเกี่ยวกับความตั้งใจสูงสุดของคุณด้านล่าง: สตริงขนาดใหญ่มาจากที่ใดในตอนแรก หากอ่านจากไฟล์หรือฐานข้อมูล CLOB หรือบางอย่างดังนั้นการอ่านเฉพาะสิ่งที่คุณต้องการในขณะที่การแยกวิเคราะห์จะดีที่สุดตลอดทาง
PSpeed

4
น่าอัศจรรย์ ... ฉันใช้งานจาวามานานกว่า 4 ถึง 5 ปี แต่มันก็ยังใหม่สำหรับฉัน :) ขอบคุณสำหรับข้อมูลครับ
Parth

1
มีความฉลาดในการใช้เป็นnew String(String); ดูstackoverflow.com/a/390854/8946
Lawrence Dol

คำตอบ:


159

ทำดังต่อไปนี้:

data.substring(x, y) + ""

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

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

ดูที่วิธีการสตริงย่อย ()ในแหล่ง JDK String สำหรับข้อมูลเพิ่มเติม

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

หมายเหตุ (ม.ค. 2556) พฤติกรรมดังกล่าวมีการเปลี่ยนแปลงในชวา 7u6 รูปแบบฟลายเวทไม่ได้ใช้อีกต่อไปและsubstring()จะทำงานได้ตามที่คุณคาดหวัง


89
นั่นเป็นหนึ่งในไม่กี่กรณีที่ตัวString(String)สร้าง (เช่นตัวสร้างสตริงที่ใช้สตริงเป็นอินพุต) มีประโยชน์: new String(data.substring(x, y))ทำสิ่งเดียวกันได้อย่างมีประสิทธิภาพเหมือนกับการผนวก""แต่มันทำให้เจตนาค่อนข้างชัดเจน
Joachim Sauer

3
เพื่อความแม่นยำสตริงย่อยใช้valueคุณลักษณะของสตริงต้นฉบับ ฉันคิดว่านั่นเป็นสาเหตุที่มีการอ้างอิง
Valentin Rocher

@Bishiboosh - ใช่ถูกต้องแล้ว ฉันไม่ต้องการเปิดเผยลักษณะเฉพาะของการนำไปใช้ แต่นั่นคือสิ่งที่เกิดขึ้น
Brian Agnew

5
เทคนิคมันเป็นรายละเอียดการใช้งาน แต่มันก็น่าหงุดหงิดและดึงดูดผู้คนได้มากมาย
Brian Agnew

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

28

หากคุณดูที่แหล่งที่มาของsubstring(int, int)คุณจะเห็นว่ามันกลับมา:

new String(offset + beginIndex, endIndex - beginIndex, value);

ที่เป็นต้นฉบับvalue char[]ดังนั้นคุณจะได้รับใหม่ String แต่มีเดียวกันchar[]พื้นฐาน

เมื่อคุณทำdata.substring() + ""คุณจะได้รับใหม่ String กับใหม่char[]พื้นฐาน

จริงๆแล้วกรณีการใช้งานของคุณเป็นสถานการณ์เดียวที่คุณควรใช้ตัวString(String)สร้าง:

String tiny = new String(huge.substring(12,18));

1
มีความฉลาดในการใช้เป็นnew String(String); ดูstackoverflow.com/a/390854/8946
Lawrence Dol

17

เมื่อคุณใช้substringมันไม่ได้สร้างสตริงใหม่จริง ๆ มันยังคงอ้างอิงถึงสายอักขระดั้งเดิมของคุณพร้อมด้วยข้อ จำกัด ออฟเซ็ตและขนาด

ดังนั้นเพื่อให้สามารถรวบรวมสตริงดั้งเดิมของคุณคุณต้องสร้างสตริงใหม่ (ใช้new Stringหรือสิ่งที่คุณมี)


5

ฉันคิดว่านี่ขนาดเล็กเก็บอ้างอิงถึงข้อมูล แต่ทำไม?

เนื่องจากสตริง Java ประกอบด้วยอาร์เรย์ char การเริ่มต้นการชดเชยและความยาว (และ hashCode ที่แคชไว้) การดำเนินงานของ String บางอย่างเช่นsubstring()สร้างวัตถุ String ใหม่ที่แชร์อาร์เรย์ char ดั้งเดิมและเพียงแค่มีเขตข้อมูลชดเชยและ / หรือความยาว วิธีนี้ใช้งานได้เนื่องจากอาร์เรย์ถ่านของสตริงไม่เคยถูกแก้ไขเมื่อถูกสร้างขึ้น

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

วิธีการ "แก้ไข" ที่ถูกต้องในการแก้ไขนี้คือตัวnew String(String)สร้างเช่น

this.smallpart = new String(data.substring(12,18));

BTW ทางออกที่ดีที่สุดโดยรวมคือหลีกเลี่ยงการใช้งาน Strings ที่มีขนาดใหญ่มากในตอนแรกและทำการประมวลผลอินพุตใด ๆ


มีความฉลาดในการใช้เป็นnew String(String); ดูstackoverflow.com/a/390854/8946
Lawrence Dol

5

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

เมื่อคุณเรียกใช้เมธอด substring, Java จะไม่สร้างสตริงใหม่ trully แต่เพียงเก็บช่วงของอักขระภายในสตริงเดิม

ดังนั้นเมื่อคุณสร้างสตริงใหม่ด้วยรหัสนี้:

this.smallpart = data.substring(12, 18) + ""; 

คุณสร้างสตริงใหม่จริง ๆ เมื่อคุณต่อผลลัพธ์กับสตริงว่าง นั่นเป็นเหตุผล


3

บันทึกโดยjwz ในปี 1997 :

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


2

เพื่อสรุปหากคุณสร้างสตริงย่อยจำนวนมากจากสตริงขนาดใหญ่จำนวนน้อยให้ใช้

   String subtring = string.substring(5,23)

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

   String substring = new String(string.substring(5,23));

จะทำให้หน่วยความจำของคุณใช้งานไม่ได้เนื่องจากสตริงขนาดใหญ่สามารถเรียกคืนได้เมื่อไม่ต้องการใช้อีกต่อไป

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


มีความฉลาดในการใช้เป็นnew String(String); ดูstackoverflow.com/a/390854/8946
Lawrence Dol

2

ประการแรกการโทรjava.lang.String.substringจะสร้างหน้าต่างใหม่บนต้นฉบับStringด้วยการใช้อ็อฟเซ็ตและความยาวแทนที่จะคัดลอกส่วนสำคัญของอาเรย์พื้นฐาน

ถ้าเราใช้เวลามองใกล้ที่substringวิธีการที่เราจะสังเกตเห็นสตริงคอนสตรัคโทรString(int, int, char[])และผ่านมันทั้งหมดchar[]ที่แสดงถึงสตริง นั่นหมายถึงการย่อยจะครอบครองเป็นจำนวนมากของหน่วยความจำเช่นเดิมสตริง

ตกลง แต่ทำไม+ ""ผลลัพธ์ถึงความต้องการหน่วยความจำน้อยลงกว่าที่ไม่มี?

การ+เปิดstringsจะดำเนินการผ่านStringBuilder.appendวิธีการโทร ดูการใช้งานวิธีนี้ในAbstractStringBuilderชั้นเรียนจะบอกเราว่าในที่สุดมันก็จะทำอย่างไรarraycopyกับส่วนที่เราต้องการจริงๆ ( substring)

วิธีแก้ไขอื่น ๆ

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();

0

ผนวก "" เพื่อสตริงจะบางครั้งบันทึกความทรงจำ

สมมติว่าฉันมีสายอักขระจำนวนมากที่มีทั้งเล่มหนึ่งล้านตัวอักษร

จากนั้นฉันสร้างสตริง 20 เส้นที่มีบทของหนังสือเป็นสตริงย่อย

จากนั้นฉันจะสร้าง 1,000 สายที่มีย่อหน้าทั้งหมด

จากนั้นฉันสร้าง 10,000 สายที่มีประโยคทั้งหมด

จากนั้นฉันสร้าง 100,000 สายที่มีคำทั้งหมด

ฉันยังคงใช้เพียง 1,000,000 ตัวอักษร หากคุณเพิ่ม "" ในแต่ละบทวรรคประโยคและคำคุณใช้ 5,000,000 ตัวอักษร

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

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

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