การใช้ java.lang.String.intern () เป็นเรื่องที่ดีหรือไม่?


194

Javadoc เกี่ยวกับString.intern()ไม่ให้รายละเอียดมาก (โดยสรุป: มันจะส่งคืนการแทนค่าแบบบัญญัติของสตริงซึ่งอนุญาตให้เปรียบเทียบสตริงที่ใช้==)

  • ฉันจะใช้ฟังก์ชันนี้เมื่อใด String.equals() ?
  • มีผลข้างเคียงที่ไม่ได้กล่าวถึงใน Javadoc คือการเพิ่มประสิทธิภาพมากขึ้นหรือน้อยลงโดยคอมไพเลอร์ JIT หรือไม่?
  • มีการใช้เพิ่มเติมString.intern()หรือไม่

14
Calling ฝึกงาน () มีผลกระทบต่อการทำงานของตัวเองโดยใช้ฝึกงาน () เพื่อปรับปรุงประสิทธิภาพที่จำเป็นต้องได้รับการทดสอบเพื่อให้แน่ใจว่าโปรแกรมของคุณเร็วขึ้นอย่างมีนัยสำคัญเพื่อให้คุ้มค่ากับความซับซ้อนมากขึ้น นอกจากนี้คุณยังสามารถใช้สิ่งนี้เพื่อลดปริมาณการใช้หน่วยความจำสำหรับตารางขนาดใหญ่ที่มีค่าซ้ำที่เชื่อถือได้ อย่างไรก็ตามในทั้งสองกรณีมีตัวเลือกอื่น ๆ ซึ่งอาจจะดีกว่า
Peter Lawrey

ใช่ฝึกงาน () มีผลกระทบต่อประสิทธิภาพการทำงานของตัวเอง โดยเฉพาะอย่างยิ่งเนื่องจากค่าใช้จ่ายฝึกงาน () เพิ่มขึ้นเป็นเส้นตรงในขณะที่คุณฝึกงานสตริงและอ้างอิงถึงพวกเขา อย่างน้อยในดวงอาทิตย์ / oracle 1.6.0_30 vm
lacroix1547

คำตอบ:


125

เมื่อใดที่ฉันจะใช้ฟังก์ชันนี้แก่ String.equals ()

เมื่อคุณต้องการความเร็วเนื่องจากคุณสามารถเปรียบเทียบสตริงโดยการอ้างอิง (== เร็วกว่าเท่ากับ)

มีผลข้างเคียงที่ไม่ได้กล่าวถึงใน Javadoc หรือไม่?

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

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

(จาก JGuru)

ข้อเสียประการที่สาม (Java 7 หรือน้อยกว่าเท่านั้น): สตริงการฝึกงานอยู่ในพื้นที่ PermGen ซึ่งมักจะมีขนาดค่อนข้างเล็ก คุณอาจพบ OutOfMemoryError พร้อมพื้นที่ว่างมากมาย

(จาก Michael Borgwardt)


64
ข้อเสียประการที่สาม: Stred ฝึกงานอยู่ในพื้นที่ PermGen ซึ่งมักจะมีขนาดค่อนข้างเล็ก คุณอาจพบ OutOfMemoryError พร้อมพื้นที่ว่างมากมาย
Michael Borgwardt

15
VMAI ที่ใหม่กว่าของ AFAIK ยังเก็บรวบรวมพื้นที่ PermGen ด้วย
Daniel Rikowski

31
Intern เป็นเรื่องเกี่ยวกับการจัดการหน่วยความจำไม่ใช่การเปรียบเทียบความเร็ว ความแตกต่างระหว่างif (s1.equals(s2))และif (i1 == i2)น้อยที่สุดเว้นแต่ว่าคุณจะมีสตริงยาวมากที่มีอักขระนำหน้าเหมือนกัน ในการใช้งานจริงส่วนใหญ่ (นอกเหนือจาก URL) สตริงจะแตกต่างกันภายในอักขระสองสามตัวแรก และโซ่ที่ยาวถ้าเป็นอย่างอื่นจะมีกลิ่นรหัสอยู่แล้ว: ใช้แผนที่ enums และ functor
kdgregory

25
คุณยังสามารถใช้ไวยากรณ์ s1.equals ตลอดโปรแกรมของคุณอย่าใช้ ==, .equals ใช้ == ภายในเพื่อประเมินการลัดวงจร
gtrak

15
Michael Borgwardt ไม่ได้บอกว่าสายอักขระภายในไม่สามารถเก็บขยะได้ และนั่นคือการยืนยันที่ผิดพลาด สิ่งที่ความคิดเห็นของไมเคิล (ถูกต้อง) พูดนั้นละเอียดกว่านั้นมาก
Stephen C

193

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


4
ไม่ถูกต้อง Interning of Strings จะเกิดขึ้นเสมอโดยอัตโนมัติเมื่อแต่ละนิพจน์สตริงถูกประเมิน มีหนึ่งสำเนาเสมอสำหรับสตริงอักขระที่ไม่ซ้ำกันแต่ละตัวที่ใช้ & เป็น "การแบ่งปันภายใน" หากมีหลายประเพณีเกิดขึ้น การเรียกใช้ String.intern () ไม่ได้ทำให้สิ่งนี้เกิดขึ้นทั้งหมด - เพียงแค่คืนค่าการแทนค่าแบบบัญญัติภายใน ดู javadoc
เกลนที่ดีที่สุด

16
ต้องการความกระจ่าง - การฝึกงานเกิดขึ้นโดยอัตโนมัติเสมอสำหรับค่าคงที่เวลารวบรวม (ตัวอักษรและนิพจน์คงที่) นอกจากนี้มันจะเกิดขึ้นเมื่อ String.intern () ถูกเรียกใช้บนรันไทม์ที่ประเมินสตริงแบบไดนามิก
เกลนที่ดีที่สุด

ดังนั้นคุณหมายถึงถ้ามีวัตถุ "Hello" 1,000 รายการใน Heap และฉันดำเนินการฝึกงาน () กับหนึ่งในนั้นวัตถุ 999 ที่เหลือจะถูกทำลายโดยอัตโนมัติหรือไม่
อรุณ Raaj

@ArunRaaj ไม่คุณจะมี 1000 ของคุณยังคงอยู่บนกองและพิเศษหนึ่งในสระว่ายน้ำฝึกงานที่สามารถจะพร้อมสำหรับการกลับมาใช้โดยต่อมาstr.intern()เมื่อเป็นstr "Hello"
Matthieu

37

String.intern()มีการรวบรวมขยะใน JVM สมัยใหม่อย่างแน่นอน
ไม่เคยมีหน่วยความจำหมดเนื่องจากกิจกรรม GC:

// java -cp . -Xmx128m UserOfIntern

public class UserOfIntern {
    public static void main(String[] args) {
        Random random = new Random();
        System.out.println(random.nextLong());
        while (true) {
            String s = String.valueOf(random.nextLong());
            s = s.intern();
        }
    }
}

ดูข้อมูลเพิ่มเติม (จากฉัน) ในตำนานของที่ไม่ใช่ GCed String.intern ()


26
OutOfMemoryException- ไม่ไม่ใช่รหัสด้านบนในสมองของฉัน: ลิงก์ไปยังบทความ javaturning ซึ่งชี้ไปที่บทความนี้ซึ่งชี้ไปที่บทความ javaturning ซึ่ง ... :-)
user85421

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

3
คุณอาจต้องการพูดถึงว่าคุณเป็นผู้เขียนมากเกินไปจากการอ้างอิงภายนอกที่คุณเชื่อมโยงไปถึง
Thorbjørn Ravn Andersen

11
@Carlos การเชื่อมโยงการอ้างอิงภายนอกที่เชื่อมโยงกลับไปยัง stackoverflow ควรเป็นสาเหตุ .. Stackoverflow :)
Seiti

2
@Seiti มีการตรวจพบการอ้างอิงแบบวนได้ง่ายในวันนี้: p
Ajay

16

ฉันเพิ่งเขียนบทความเกี่ยวกับการใช้งาน String.intern () ใน Java 6, 7 และ 8: String.intern ใน Java 6, 7 และ 8 - string poolingสตริงร่วมกัน

ฉันหวังว่ามันควรมีข้อมูลเพียงพอเกี่ยวกับสถานการณ์ปัจจุบันด้วยการรวมสตริงใน Java

โดยสังเขป:

  • หลีกเลี่ยงการ String.intern()ใน Java 6 เพราะจะเข้าสู่ PermGen
  • ชอบ String.intern()ใน Java 7 และ Java 8: มันใช้หน่วยความจำน้อยกว่า 4-5x กว่าการกลิ้งออบเจ็กต์ของคุณเอง
  • ตรวจสอบให้แน่ใจว่าได้ปรับ-XX:StringTableSize(ค่าเริ่มต้นอาจน้อยเกินไปตั้งค่าหมายเลขเฉพาะ)

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

3
ขอบคุณที่เขียน @ mik1! บทความที่ให้ข้อมูลชัดเจนและเป็นปัจจุบันมาก (ฉันกลับมาที่นี่เพื่อตั้งใจโพสต์ลิงค์ด้วยตัวเอง)
ลุค Usherwood

1
ขอบคุณที่พูดถึง AR -XX นอกจากนี้คุณยังสามารถใช้สิ่งนี้เพื่อดูสถิติของตาราง: -XX: + PrintStringTableStatistics
csadler

13

การเปรียบเทียบสตริงด้วย == นั้นเร็วกว่าเท่ากับ ()

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

String.intern () ดึงสตริงออกจาก Heap และใส่ใน PermGen

String internalized ถูกวางในพื้นที่เก็บข้อมูลที่ต่างกัน: Permanent Generationซึ่งเป็นพื้นที่ของ JVM ที่สงวนไว้สำหรับวัตถุที่ไม่ใช่ผู้ใช้เช่นคลาสวิธีและวัตถุ JVM ภายในอื่น ๆ ขนาดของพื้นที่นี้มี จำกัด และมีค่ามากกว่ากองมาก เนื่องจากพื้นที่นี้มีขนาดเล็กกว่ากองมีความเป็นไปได้มากกว่าที่จะใช้พื้นที่ทั้งหมดและรับ OutOfMemoryException

String.intern () สตริงรวบรวมขยะ

ในเวอร์ชันใหม่ของ JVM สตริงภายในจะถูกรวบรวมขยะเมื่อไม่ได้อ้างอิงโดยวัตถุใด ๆ

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


4
จากJava 7 สตริงฝึกงานอยู่ในกอง
assylias

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

7

เมื่อใดที่ฉันจะใช้ฟังก์ชันนี้แก่ String.equals ()

ให้พวกเขาทำสิ่งต่าง ๆ อาจจะไม่เคย

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

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

มันเกือบจะดีกว่าเสมอในการสร้างUserIdประเภทที่มีการฝึกงาน (มันง่ายที่จะสร้างกลไกการฝึกงานทั่วไปที่ปลอดภัยต่อเธรด) และทำหน้าที่เหมือน enum แบบเปิดแทนที่จะโหลดเกินjava.lang.Stringประเภทที่มีความหมายอ้างอิงหากเกิดขึ้นเป็น ID ผู้ใช้

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


6

ฉันไม่ได้ตระหนักถึงข้อได้เปรียบใด ๆ และหากมีอยู่ในอย่างใดอย่างหนึ่งจะคิดว่าเท่ากับ () ตัวเองจะใช้ฝึกงาน () ภายใน (ซึ่งมันไม่ได้)

busting ฝึกงาน () ตำนาน


7
แม้จะมีคุณบอกว่าคุณไม่ได้ตระหนักถึงประโยชน์ใด ๆ ที่โพสต์ที่เชื่อมโยงเปรียบเทียบระบุของคุณผ่านทาง == เป็น 5x เร็วขึ้นและมีความสำคัญสำหรับข้อความจึงเป็นศูนย์กลางรหัส performant
ไบรอัน Agnew

3
เมื่อคุณมีการเปรียบเทียบข้อความจำนวนมากในที่สุดคุณก็จะหมดพื้นที่ใน PermGen เมื่อมีการเปรียบเทียบข้อความไม่มากนักในการทำความเร็วที่แตกต่างก็ไม่สำคัญ ไม่ว่าจะด้วยวิธีใดก็ตามอย่าเพิ่งฝึกงาน () สตริงของคุณ มันไม่คุ้มค่า.
Bombe

นอกจากนี้ยังกล่าวต่อไปอีกว่าอัตราการขยายตัวโดยรวมโดยทั่วไปจะมีค่าน้อย
วัตถุ

ฉันไม่คิดว่าตรรกะประเภทนั้นถูกต้อง ลิงค์ที่ดีแม้ว่า!
Daniel Rikowski

1
@DR: ตรรกะอะไร นั่นคือการเข้าใจผิดครั้งใหญ่ @objects: ขออภัย แต่เหตุผลของคุณยังไม่เพียงพอ มีเหตุผลที่ดีมากที่จะใช้internและเหตุผลที่ดีมากที่equalsไม่ได้ทำไว้โดยค่าเริ่มต้น ลิงค์ที่คุณโพสต์นั้นเป็น bollocks ที่สมบูรณ์ ย่อหน้าสุดท้ายยังยอมรับว่าinternมีสถานการณ์การใช้งานที่ถูกต้อง: การประมวลผลข้อความหนัก (เช่น parser) การสรุปว่า“ [XYZ] นั้นอันตรายถ้าคุณไม่รู้ว่ากำลังทำอะไรอยู่” นั้นซ้ำซากจนเจ็บปวด
Konrad Rudolph

4

Daniel Brücknerพูดถูก String interning มีไว้เพื่อบันทึกหน่วยความจำ (ฮีป) ขณะนี้ระบบของเรามีแฮชแมปขนาดยักษ์สำหรับเก็บข้อมูลบางอย่าง เมื่อปรับขนาดระบบ hashmap จะใหญ่พอที่จะทำให้ heap ออกจากหน่วยความจำ (ดังที่เราได้ทดสอบ) โดยการทำให้สตริงทั้งหมดซ้ำกับวัตถุทั้งหมดใน hashmap จะช่วยให้เรามีพื้นที่ฮีพจำนวนมาก

นอกจากนี้ใน Java 7 สตริง interned ไม่ได้อยู่ใน PermGen นาน แต่กองแทน ดังนั้นคุณไม่จำเป็นต้องกังวลเกี่ยวกับขนาดของมันและใช่มันจะได้รับการเก็บขยะ:

ใน JDK 7 สตริงการฝึกงานไม่ได้ถูกจัดสรรในฮีป Java รุ่นถาวรอีกต่อไป แต่จะถูกจัดสรรในส่วนหลักของฮีป Java แทน (รู้จักกันในชื่อรุ่นเยาว์และรุ่นเก่า) พร้อมกับวัตถุอื่นที่สร้างโดยแอปพลิเคชัน . การเปลี่ยนแปลงนี้จะส่งผลให้มีข้อมูลมากขึ้นที่อยู่ในฮีป Java หลักและข้อมูลน้อยลงในการสร้างถาวรและดังนั้นจึงอาจต้องปรับขนาดฮีป แอปพลิเคชันส่วนใหญ่จะเห็นความแตกต่างเพียงเล็กน้อยในการใช้งานฮีปเนื่องจากการเปลี่ยนแปลงนี้ แต่แอปพลิเคชันขนาดใหญ่ที่โหลดคลาสจำนวนมากหรือใช้เมธอด String.intern () จำนวนมากจะเห็นความแตกต่างที่สำคัญกว่า


ฉันต้องคำนึงถึงข้อสองว่า: ในซอฟต์แวร์ของฉันการถ่ายโอนข้อมูล heap แสดงให้เห็นว่าStringอินสแตนซ์พื้นที่ส่วนใหญ่ถูกใช้โดยอินสแตนซ์ เมื่อดูเนื้อหาของพวกเขาฉันเห็นสิ่งที่ซ้ำกันจำนวนมากและตัดสินใจเปลี่ยนเป็นintern()ซึ่งบันทึกหลายร้อย MB
Matthieu

4

มีผลข้างเคียงที่ไม่ได้กล่าวถึงใน Javadoc คือการเพิ่มประสิทธิภาพมากขึ้นหรือน้อยลงโดยคอมไพเลอร์ JIT หรือไม่?

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

JVMs

JVMS 7 5.1 พูดว่า :

สตริงตัวอักษรคือการอ้างอิงถึงอินสแตนซ์ของคลาสสตริงและได้มาจากโครงสร้าง CONSTANT_String_info (§4.4.3) ในการเป็นตัวแทนไบนารีของชั้นเรียนหรืออินเตอร์เฟซ โครงสร้าง CONSTANT_String_info ให้ลำดับของจุดโค้ด Unicode ที่ประกอบเป็นตัวอักษรสตริง

ภาษาการเขียนโปรแกรม Java ต้องการตัวอักษรสตริงที่เหมือนกัน (นั่นคือตัวอักษรที่มีลำดับของจุดรหัสเดียวกัน) ต้องอ้างถึงอินสแตนซ์เดียวกันของคลาส String (JLS §3.10.5) นอกจากนี้หากเมธอด String.intern ถูกเรียกบนสตริงใด ๆ ผลลัพธ์จะเป็นการอ้างอิงไปยังอินสแตนซ์ของคลาสเดียวกันที่จะถูกส่งคืนหากสตริงนั้นปรากฏเป็นตัวอักษร ดังนั้นการแสดงออกต่อไปนี้จะต้องมีค่าจริง:

("a" + "b" + "c").intern() == "abc"

เพื่อรับสตริงตัวอักษร Java Virtual Machine จะตรวจสอบลำดับของจุดโค้ดที่กำหนดโดยโครงสร้าง CONSTANT_String_info

  • หากก่อนหน้านี้มีการเรียกใช้เมธอด String.intern ในอินสแตนซ์ของคลาส String ที่มีลำดับของจุดโค้ด Unicode เหมือนกับที่กำหนดโดยโครงสร้าง

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

bytecode

นอกจากนี้ยังแนะนำให้ดูที่การใช้ bytecode ใน OpenJDK 7

ถ้าเราแปลความหมาย:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

เรามีสระว่ายน้ำคงที่:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

และmain:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

สังเกตว่า:

  • 0และ3: เหมือนกันldc #2โหลดค่าคงที่ (ตัวอักษร)
  • 12: สร้างสตริงอินสแตนซ์ใหม่ (ด้วย #2อาร์กิวเมนต์เป็น)
  • 35: aและcถูกเปรียบเทียบเป็นวัตถุปกติด้วยif_acmpne

การเป็นตัวแทนของสตริงคงที่นั้นค่อนข้างน่าอัศจรรย์ใน bytecode:

  • มันมีโครงสร้างCONSTANT_String_infoเฉพาะซึ่งแตกต่างจากวัตถุปกติ (เช่นnew String )
  • struct ชี้ไปที่โครงสร้าง CONSTANT_Utf8_infoที่มีข้อมูล นั่นเป็นข้อมูลที่จำเป็นเพียงอย่างเดียวในการแสดงสตริง

และการอ้างอิง JVMS ข้างต้นดูเหมือนจะบอกว่าเมื่อใดก็ตามที่ Utf8 ชี้ไปที่เหมือนกันจากนั้นอินสแตนซ์ที่เหมือนกันจะถูกโหลดโดย ldcชี้ไปที่จะเหมือนกันกรณีแล้วเหมือนจะถูกโหลดโดย

ฉันได้ทำการทดสอบที่คล้ายกันสำหรับสาขาและ:

  • static final String s = "abc"ชี้ไปที่ตารางค่าคงที่ผ่านแอตทริบิวต์ค่าคงที่
  • เขตข้อมูลที่ไม่ใช่ครั้งสุดท้ายไม่มีแอตทริบิวต์นั้น แต่ยังสามารถเริ่มต้นได้ด้วย ldc

โบนัส : เปรียบเทียบกับพูลจำนวนเต็มซึ่งไม่มีการสนับสนุนไบต์โดยตรง (เช่นไม่มีCONSTANT_String_infoอะนาล็อก)


2

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


2

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

วิธีแก้ปัญหาปกติคือการใช้new String( s.subString(...))แต่เมื่อคุณมีคลาสที่เก็บผลลัพธ์ของโอกาส / แนวโน้มsubString(...)และไม่มีการควบคุมผู้เรียกคุณอาจพิจารณาจัดเก็บintern()อาร์กิวเมนต์สตริงที่ส่งผ่านไปยังตัวสร้าง สิ่งนี้จะปล่อยบัฟเฟอร์ขนาดใหญ่ที่มีศักยภาพ


น่าสนใจ แต่บางทีนี่อาจขึ้นอยู่กับการใช้งาน
akostadinov

1
หน่วยความจำรั่วที่อาจเกิดขึ้นดังกล่าวข้างต้นไม่ได้เกิดขึ้นใน java 1.8 และ 1.7.06 (และใหม่กว่า) เห็นการเปลี่ยนแปลงการแสดงภายใน String ทำใน Java 1.7.0_06
eremmel

ที่ยืนยันว่าการปรับให้เหมาะสมขนาดเล็กนั้นจะใช้ได้เฉพาะเมื่อจำเป็นหลังจากการทำโปรไฟล์และ / หรือการทำโปรไฟล์หน่วยความจำ ขอบคุณ.
akostadinov

2

String interning มีประโยชน์ในกรณีที่equals()วิธีการถูกเรียกใช้บ่อยเพราะequals()วิธีการตรวจสอบอย่างรวดเร็วเพื่อดูว่าวัตถุเหมือนกันที่จุดเริ่มต้นของวิธีการ

if (this == anObject) {
    return true;
}

สิ่งนี้มักจะเกิดขึ้นเมื่อค้นหาCollectionรหัสอื่นแม้ว่าอาจทำการตรวจสอบความเท่าเทียมกันของสตริง

มีค่าใช้จ่ายที่เกี่ยวข้องกับการฝึกงานแม้ว่าฉันทำการ microbenchmark ของรหัสบางส่วนและพบว่ากระบวนการฝึกงานเพิ่มขึ้น runtime โดยปัจจัยที่ 10

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

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

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


1

ฉันจะลงคะแนนให้มันไม่คุ้มกับความยุ่งยากในการบำรุงรักษา

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


1

http://kohlerm.blogspot.co.uk/2009/01/is-javalangstringintern-really-evil.html

ยืนยันที่String.equals()ใช้"=="ในการเปรียบเทียบStringวัตถุก่อนตาม

http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html

มันเปรียบเทียบความยาวของสตริงแล้วเนื้อหา

(โดยวิธีการสตริงรหัสผลิตภัณฑ์ในแคตตาล็อกการขายจะต้องมีความยาวเท่ากัน - BIC0417 เป็นหมวกนิรภัยของนักปั่นจักรยาน TIG0003 เป็นเสือเพศผู้ผู้ใหญ่ที่มีชีวิต - คุณอาจต้องใช้ใบอนุญาตทุกประเภทเพื่อสั่งซื้อหนึ่งใบและ บางทีคุณควรสั่งหมวกนิรภัยพร้อมกัน)

ดังนั้นดูเหมือนว่าคุณจะได้รับประโยชน์จากการแทนที่สตริงของคุณด้วยintern()เวอร์ชันของพวกเขาแต่คุณจะได้รับความปลอดภัย - และความสามารถในการอ่านและการปฏิบัติตามมาตรฐาน - โดยไม่ต้องใช้ "==" สำหรับequals()การเขียนโปรแกรมของคุณ และสิ่งที่ฉันจะพูดส่วนใหญ่ขึ้นอยู่กับความเป็นจริงถ้าเป็นจริง

แต่String.equals()ทดสอบว่าคุณส่งผ่านสตริงไม่ใช่วัตถุอื่นก่อนใช้งาน"=="หรือไม่ ฉันไม่มีคุณสมบัติที่จะพูด แต่ฉันคาดเดาไม่ได้เพราะequals()การดำเนินการดังกล่าวส่วนใหญ่จะเป็น String to String ดังนั้นการทดสอบจึงเกือบจะผ่าน แท้จริงแล้วการจัดลำดับความสำคัญ "==" ภายในString.equals()บ่งบอกถึงความมั่นใจว่าคุณมักจะเปรียบเทียบสตริงกับวัตถุจริงเดียวกัน

ฉันหวังว่าจะไม่มีใครประหลาดใจที่บรรทัดต่อไปนี้ให้ผลลัพธ์ของ "false":

    Integer i = 1;
    System.out.println("1".equals(i));

แต่ถ้าคุณเปลี่ยนiไปในบรรทัดที่สองของหลักสูตรมันi.toString()true

สถานที่ที่คุณอาจหวังว่าจะได้รับประโยชน์จากการฝึกงานรวมถึงSetและMapแน่นอน ฉันหวังว่าสตริงภายในจะมีแฮชโค้ดของพวกเขา ... ฉันคิดว่ามันจะเป็นข้อกำหนด และฉันหวังว่าฉันจะไม่ได้ให้ความคิดที่จะทำให้ฉันได้รับเงินหนึ่งล้านเหรียญ :-)

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

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

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


0

เหตุผลที่แท้จริงในการใช้งาน Intern ไม่ใช่สิ่งที่กล่าวมาข้างต้น คุณจะใช้มันหลังจากที่คุณได้รับข้อผิดพลาดหน่วยความจำไม่เพียงพอ สตริงจำนวนมากในโปรแกรมทั่วไปคือ String.substring () ของสตริงขนาดใหญ่อื่น [คิดว่าจะเอาชื่อผู้ใช้ออกจากไฟล์ 100K xml การใช้งานจาวาคือสตริงย่อยเก็บการอ้างอิงไปยังสตริงต้นฉบับและเริ่มต้น + สิ้นสุดในสตริงขนาดใหญ่นั้น (ความคิดที่อยู่เบื้องหลังมันเป็นการใช้ซ้ำของสตริงขนาดใหญ่เดียวกัน)

หลังจากไฟล์ขนาดใหญ่ 1,000 ไฟล์ซึ่งคุณบันทึกชื่อย่อเพียง 1,000 ชื่อคุณจะเก็บไฟล์ทั้ง 1,000 ไฟล์ไว้ในหน่วยความจำ! การแก้ไข: ในสถานการณ์นี้ให้ใช้ smallsubstring.intern ()


ทำไมไม่สร้างสตริงใหม่จากซับสตริงถ้าคุณต้องการ
Thorbjørn Ravn Andersen

0

ฉันใช้ฝึกงานเพื่อบันทึกหน่วยความจำฉันเก็บข้อมูล String จำนวนมากในหน่วยความจำและย้ายไปใช้ฝึกงาน () บันทึกหน่วยความจำจำนวนมาก น่าเสียดายที่แม้ว่าจะใช้หน่วยความจำน้อยกว่ามากหน่วยความจำที่ใช้จะถูกเก็บไว้ในหน่วยความจำ PermGen ไม่ใช่ Heap และเป็นการยากที่จะอธิบายให้ลูกค้าทราบถึงวิธีเพิ่มการจัดสรรหน่วยความจำประเภทนี้

ดังนั้นจึงมีทางเลือกอื่นในการฝึกงาน () เพื่อลดปริมาณการใช้หน่วยความจำ (== เมื่อเทียบกับผลประโยชน์ด้านประสิทธิภาพไม่ใช่ aissue สำหรับฉัน)


0

ลองดู: สถานการณ์การใช้งานหลักคือเมื่อคุณอ่านกระแสข้อมูล (ผ่านอินพุตสตรีมหรือจาก JDBC ResultSet) และมีสตริงจำนวนเล็กน้อยที่ซ้ำกันตลอด

นี่เป็นเคล็ดลับเล็ก ๆ ที่ให้คุณควบคุมกลไกที่คุณต้องการใช้ในการ Strings และ immutables อื่น ๆ และการใช้งานตัวอย่าง:

/**
 * Extends the notion of String.intern() to different mechanisms and
 * different types. For example, an implementation can use an
 * LRUCache<T,?>, or a WeakHashMap.
 */
public interface Internalizer<T> {
    public T get(T obj);
}
public static class LRUInternalizer<T> implements Internalizer<T> {
    private final LRUCache<T, T> cache;
    public LRUInternalizer(int size) {
        cache = new LRUCache<T, T>(size) {
            private static final long serialVersionUID = 1L;
            @Override
            protected T retrieve(T key) {
                return key;
            }
        };
    }
    @Override
    public T get(T obj) {
        return cache.get(obj);
    }
}
public class PermGenInternalizer implements Internalizer<String> {
    @Override
    public String get(String obj) {
        return obj.intern();
    }
}

ฉันใช้บ่อยครั้งเมื่อฉันอ่านฟิลด์จากสตรีมหรือจาก ResultSets หมายเหตุ: มีการแคชที่เรียบง่ายซึ่งเป็นไปตามLRUCache LinkedHashMap<K,V>มันจะเรียกวิธีการที่ผู้ใช้ระบุretrieve()เพื่อแคชทั้งหมดโดยอัตโนมัติ

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

Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
    s = internalizer.get(s);
    // store s...
}

0

ฉันใช้มันเพื่อแคชเนื้อหาประมาณ 36,000 รหัสซึ่งลิงค์ไปยังชื่อที่เกี่ยวข้อง ฉันฝึกงานสตริงในแคชเพราะรหัสจำนวนมากชี้ไปที่สตริงเดียวกัน

โดยการฝึกสตริงในแคชของฉันฉันมั่นใจว่ารหัสที่ชี้ไปที่สตริงเดียวกันจริง ๆ ชี้ไปที่หน่วยความจำเดียวกันจึงประหยัดพื้นที่แรมฉัน

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


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

0

ค่าใช้จ่ายในการฝึกงานสตริงนั้นมากกว่าเวลาที่บันทึกไว้ในการเปรียบเทียบ stringA.equals (B) เดียว ใช้มันเท่านั้น (สำหรับเหตุผลด้านประสิทธิภาพ) เมื่อคุณซ้ำ ๆ โดยใช้ตัวแปรสตริงที่ไม่เปลี่ยนแปลงเหมือนกัน ตัวอย่างเช่นหากคุณวนซ้ำอย่างสม่ำเสมอในรายการสตริงที่มีความเสถียรเพื่ออัปเดตแผนที่บางอย่างที่มีคีย์ในฟิลด์สตริงเดียวกันคุณจะได้รับการบันทึกที่ดี

ฉันขอแนะนำให้ใช้การฝึกสตริงเพื่อปรับแต่งประสิทธิภาพเมื่อคุณเพิ่มประสิทธิภาพส่วนต่าง ๆ ของรหัสของคุณ

โปรดจำไว้ว่าสตริงนั้นไม่เปลี่ยนรูปและไม่ทำผิดพลาด

String a = SOME_RANDOM_VALUE
a.intern()

อย่าลืมทำ

String a = SOME_RANDOM_VALUE.intern()

0

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

private static WeakHashMap<String, WeakReference<String>> internStrings = new WeakHashMap<>();
public static String internalize(String k) {
    synchronized (internStrings) {
        WeakReference<String> weakReference = internStrings.get(k);
        String v = weakReference != null ? weakReference.get() : null;
        if (v == null) {
            v = k;
            internStrings.put(v, new WeakReference<String>(v));
        }
        return v;
    }
}

แน่นอนถ้าคุณประมาณสามารถประเมินได้ว่าหลายสายที่แตกต่างกันจะมีแล้วก็ใช้ String.intern () กับ -XX: StringTableSize = highEnoughValue


SoftRef จะทำให้รู้สึกมากกว่าเดิม
vach

@vach โดยใช้ WeakReference (แทน SoftReference) หน่วยความจำจะถูกปลดปล่อยก่อนหน้านี้เพื่อให้การจัดสรรอื่นอาจเร็วขึ้น ขึ้นอยู่กับแอปพลิเคชันอื่นที่กำลังทำอยู่
bdruemen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.