Thực tập chuỗi Java là gì?


234

Là gì chuỗi interning trong Java, khi tôi nên sử dụng nó, và tại sao ?



2
nếu String a = new String("abc"); String b = new String("abc"); sau đóa.intern() == b.intern()
Asanka Siriwardena

Ví dụ thực tập Chuỗi Thanh toán: alss4.cs.princeton.edu/12oop/MutableString.java.html
Ronak Poriya

String.intern()phụ thuộc vào ClassLoader, ý nghĩa, Do các trình nạp lớp khác nhau tạo ra các "khác nhau" String, gây ra các interns khác nhau không?
AlikElzin-kilaka

1
@ AlikElzin-kilaka không, trình nạp lớp hoàn toàn không liên quan đến thực tập chuỗi. Lần tới khi bạn có một câu hỏi, xin vui lòng mở một câu hỏi mới thay vì đăng nó như một bình luận cho một câu hỏi khác.
Holger

Câu trả lời:


233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#itern ()

Về cơ bản thực hiện String.i INTERN () trên một chuỗi các chuỗi sẽ đảm bảo rằng tất cả các chuỗi có cùng nội dung đều có chung bộ nhớ. Vì vậy, nếu bạn có danh sách các tên trong đó 'john' xuất hiện 1000 lần, bằng cách thực tập, bạn đảm bảo chỉ có một 'john' được cấp phát bộ nhớ.

Điều này có thể hữu ích để giảm yêu cầu bộ nhớ của chương trình của bạn. Nhưng hãy lưu ý rằng bộ đệm được JVM duy trì trong nhóm bộ nhớ vĩnh viễn thường bị giới hạn về kích thước so với heap vì vậy bạn không nên sử dụng intern nếu bạn không có quá nhiều giá trị trùng lặp.


Thêm về các hạn chế bộ nhớ của việc sử dụng intern ()

Một mặt, đúng là bạn có thể loại bỏ các chuỗi trùng lặp bằng cách nội hóa chúng. Vấn đề là các chuỗi được nội địa hóa đi đến Thế hệ vĩnh viễn, là một khu vực của JVM được dành riêng cho các đối tượng không sử dụng, như Classes, Phương thức và các đối tượng JVM bên trong khác. Kích thước của khu vực này là hạn chế, và thường nhỏ hơn nhiều so với đống. Gọi intern () trên Chuỗi có tác dụng chuyển nó từ heap sang thế hệ vĩnh viễn và bạn có nguy cơ chạy ra khỏi không gian PermGen.

- Từ: http : //www.codein cản.com/2009/01/busting-javalangopesi INTERN-myths.html


Từ JDK 7 (ý tôi là trong HotSpot), một cái gì đó đã thay đổi.

Trong JDK 7, các chuỗi nội bộ không còn được phân bổ trong thế hệ heap Java vĩnh viễn, mà thay vào đó được phân bổ trong phần chính của heap Java (được gọi là các thế hệ trẻ và già), cùng với các đối tượng khác được tạo bởi ứng dụng . Thay đổi này sẽ dẫn đến nhiều dữ liệu hơn trong vùng heap Java chính và ít dữ liệu hơn trong thế hệ cố định và do đó có thể yêu cầu kích thước heap được điều chỉnh. Hầu hết các ứng dụng sẽ chỉ thấy sự khác biệt tương đối nhỏ trong việc sử dụng heap do thay đổi này, nhưng các ứng dụng lớn hơn tải nhiều lớp hoặc sử dụng nhiều phương thức String.i INTERN () sẽ thấy sự khác biệt đáng kể hơn.

- Từ các tính năng và cải tiến của Java SE 7

Cập nhật: Các chuỗi thực tập được lưu trữ trong heap chính từ Java 7 trở đi. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes


1
"Nhưng hãy lưu ý rằng bộ đệm được JVM duy trì trong nhóm bộ nhớ vĩnh viễn thường bị giới hạn về kích thước ......" Bạn có thể giải thích điều này không? Tôi không hiểu
saplingPro

2
các chuỗi "được tập trung" được lưu trữ trong một vùng nhớ đặc biệt trong JVM. Vùng nhớ này thường có kích thước cố định và không phải là một phần của Vùng heap Java thông thường nơi lưu trữ dữ liệu khác. Do kích thước cố định, có thể xảy ra rằng vùng bộ nhớ vĩnh viễn này bị lấp đầy với tất cả các chuỗi của bạn, dẫn đến các vấn đề xấu (các lớp không thể được tải và các thứ khác).
cello

@cello vậy, nó có giống với bộ nhớ đệm không?
saplingPro

8
@grassPro: Vâng, nó là một loại bộ đệm, một loại được cung cấp bởi JVM. Lưu ý, do sự hợp nhất của Sun / Oracle JVM và JRockit, các kỹ sư JVM cố gắng thoát khỏi vùng bộ nhớ vĩnh viễn trong JDK 8 ( openjdk.java.net/jeps/122 ), vì vậy sẽ không có bất kỳ giới hạn kích thước trong tương lai.
cello

9
Các lập trình viên cũng nên lưu ý rằng thực tập chuỗi có thể có ý nghĩa bảo mật. Nếu bạn có văn bản nhạy cảm như mật khẩu như chuỗi trong bộ nhớ, nó có thể tồn tại trong bộ nhớ trong một thời gian rất dài ngay cả khi các đối tượng chuỗi thực tế đã được sử dụng từ lâu. Điều đó có thể gây rắc rối nếu kẻ xấu bằng cách nào đó có quyền truy cập vào một bãi chứa bộ nhớ. Vấn đề này tồn tại ngay cả khi không thực tập (vì GC không có tính quyết định để bắt đầu với vv), nhưng nó làm cho nó có phần tồi tệ hơn. Luôn luôn là một ý tưởng tốt để sử dụng char[]thay vì Stringcho văn bản nhạy cảm và loại bỏ nó ngay khi không còn cần thiết.
chris

71

Có một số câu hỏi "phỏng vấn hấp dẫn", chẳng hạn như tại sao bạn có được bằng! nếu bạn thực thi đoạn mã dưới đây.

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

Nếu bạn muốn so sánh Chuỗi bạn nên sử dụng equals(). Ở trên sẽ in bằng vì trình biên dịch testStringđã được thực hiện cho bạn. Bạn có thể tự thực hiện các chuỗi bằng phương thức intern như được hiển thị trong các câu trả lời trước ....


5
Ví dụ của bạn là khó khăn vì nó sẽ dẫn đến cùng một bản in ngay cả khi bạn sử dụng equalsphương pháp. Bạn có thể muốn thêm một new String()so sánh để hiển thị sự khác biệt rõ ràng hơn.
gianni christofakis 30/03/19

@giannischristofakis nhưng nếu chúng ta sử dụng String () mới, thì == có thất bại không? Có phải java cũng tự động nội bộ các chuỗi mới không?
Deepak Selvakumar

@giannischristofakis tất nhiên nếu bạn sử dụng String mới () thì nó sẽ thất bại vào ==. nhưng Chuỗi mới (...). intern () sẽ không bị lỗi trên == vì intern sẽ trả về cùng một chuỗi. Trình biên dịch giả định đơn giản đang thực hiện String (). Intern bằng chữ
maslan

42

JLS

JLS 7 3.10.5 định nghĩa nó và đưa ra một ví dụ thực tế:

Hơn nữa, một chuỗi ký tự luôn luôn đề cập đến cùng một thể hiện của Chuỗi lớp. Điều này là do các chuỗi ký tự - hay nói chung hơn là các chuỗi là các giá trị của biểu thức hằng (§15.28) - được "thực hiện" để chia sẻ các thể hiện duy nhất, sử dụng phương thức String.i INTERN.

Ví dụ 3.10.5-1. Chuỗi ký tự

Chương trình bao gồm đơn vị biên dịch (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

và đơn vị biên dịch:

package other;
public class Other { public static String hello = "Hello"; }

tạo ra đầu ra:

true true true true false true

Liên doanh

JVMS 7 5.1 nói rằng thực tập được thực hiện một cách kỳ diệu và hiệu quả với một CONSTANT_String_infocấu trúc chuyên dụng (không giống như hầu hết các đối tượng khác có các biểu diễn chung hơn):

Một chuỗi ký tự là một tham chiếu đến một thể hiện của Chuỗi lớp và được lấy từ cấu trúc CONSTANT_String_info (§4.4.3) trong biểu diễn nhị phân của một lớp hoặc giao diện. Cấu trúc CONSTANT_String_info đưa ra chuỗi các điểm mã Unicode cấu thành chuỗi ký tự.

Ngôn ngữ lập trình Java yêu cầu các chuỗi ký tự chuỗi giống hệt nhau (nghĩa là các chữ có chứa cùng một chuỗi các điểm mã) phải tham chiếu đến cùng một thể hiện của Chuỗi lớp (JLS §3.10.5). Ngoài ra, nếu phương thức String.i INTERN được gọi trên bất kỳ chuỗi nào, kết quả là một tham chiếu đến cùng thể hiện của lớp sẽ được trả về nếu chuỗi đó xuất hiện dưới dạng một chữ. Do đó, biểu thức sau phải có giá trị đúng:

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

Để lấy được một chuỗi ký tự, Máy ảo Java kiểm tra chuỗi các điểm mã được đưa ra bởi cấu trúc CONSTANT_String_info.

  • Nếu phương thức String.i INTERN trước đây đã được gọi trong một thể hiện của Chuỗi lớp chứa một chuỗi các điểm mã Unicode giống hệt với cấu trúc CONSTANT_String_info, thì kết quả của dẫn xuất chuỗi ký tự là một tham chiếu đến cùng thể hiện của Chuỗi lớp.

  • Mặt khác, một thể hiện mới của Chuỗi lớp được tạo có chứa chuỗi các điểm mã Unicode được cung cấp bởi cấu trúc CONSTANT_String_info; một tham chiếu đến thể hiện của lớp đó là kết quả của đạo hàm chuỗi. Cuối cùng, phương thức intern của thể hiện String mới được gọi.

Mã byte

Chúng ta hãy dịch ngược một số mã byte OpenJDK 7 để xem thực tập.

Nếu chúng ta dịch ngược:

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

chúng tôi có trên nhóm liên tụ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

Lưu ý cách làm:

  • 03: ldc #2hằng số tương tự được tải (bằng chữ)
  • 12: một phiên bản chuỗi mới được tạo (với #2đối số)
  • 35: acđược so sánh như các đối tượng thông thường vớiif_acmpne

Việc biểu diễn các chuỗi không đổi là khá kỳ diệu trên mã byte:

  • nó có cấu trúc CONSTANT_String_info chuyên dụng , không giống như các đối tượng thông thường (ví dụ new String)
  • cấu trúc trỏ đến Cấu trúc CONSTANT_Utf8_info có chứa dữ liệu. Đó là dữ liệu cần thiết duy nhất để đại diện cho chuỗi.

và trích dẫn của JVMS ở trên dường như nói rằng bất cứ khi nào Utf8 được chỉ ra là giống nhau, thì các thể hiện giống hệt nhau được tải bởi ldc.

Tôi đã thực hiện các bài kiểm tra tương tự cho các trường và:

  • static final String s = "abc"trỏ đến bảng hằng số thông qua Thuộc tính ConstantValue
  • các trường không phải là cuối cùng không có thuộc tính đó, nhưng vẫn có thể được khởi tạo với ldc

Kết luận : có hỗ trợ mã byte trực tiếp cho nhóm chuỗi và biểu diễn bộ nhớ là hiệu quả.

Phần thưởng: so sánh với nhóm Integer , không có hỗ trợ mã byte trực tiếp (nghĩa là không có CONSTANT_String_infotương tự).


19

Cập nhật cho Java 8 trở lên . Trong Java 8, không gian PermGen (Thế hệ vĩnh viễn) được loại bỏ và thay thế bằng Meta Space. Bộ nhớ nhóm chuỗi được chuyển đến đống JVM.

So với Java 7, kích thước nhóm Chuỗi được tăng lên trong heap. Do đó, bạn có nhiều không gian hơn cho các chuỗi nội bộ, nhưng bạn có ít bộ nhớ hơn cho toàn bộ ứng dụng.

Một điều nữa, bạn đã biết rằng khi so sánh 2 (tham chiếu) các đối tượng trong Java, ' ==' được sử dụng để so sánh tham chiếu của đối tượng, ' equals' được sử dụng để so sánh nội dung của đối tượng.

Hãy kiểm tra mã này:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

Kết quả:

value1 == value2 ---> đúng

value1 == value3 ---> sai

value1.equals(value3) ---> đúng

value1 == value3.intern() ---> đúng

Đó là lý do tại sao bạn nên sử dụng ' equals' để so sánh 2 đối tượng Chuỗi. Và đó là cách intern()hữu ích.


2

Chuỗi interning là một kỹ thuật tối ưu hóa bởi trình biên dịch. Nếu bạn có hai chuỗi ký tự giống hệt nhau trong một đơn vị biên dịch thì mã được tạo đảm bảo rằng chỉ có một đối tượng chuỗi được tạo cho tất cả phiên bản của chữ đó (các ký tự được đặt trong dấu ngoặc kép) trong cụm.

Tôi đến từ nền tảng C #, vì vậy tôi có thể giải thích bằng cách đưa ra một ví dụ từ đó:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

đầu ra của các so sánh sau:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Lưu ý1 : Đối tượng được so sánh bằng tham chiếu.

Lưu ý2 : typeof (int) .Name được đánh giá bằng phương pháp phản chiếu để nó không được đánh giá tại thời điểm biên dịch. Ở đây những so sánh được thực hiện tại thời gian biên dịch.

Phân tích kết quả: 1) đúng vì cả hai đều chứa cùng một nghĩa đen và do đó mã được tạo sẽ chỉ có một đối tượng tham chiếu "Int32". Xem chú thích 1 .

2) đúng vì nội dung của cả hai giá trị được kiểm tra giống nhau.

3) SAI vì str2 và obj không có cùng một nghĩa đen. Xem chú thích 2 .


3
Nó mạnh hơn thế. Bất kỳ chuỗi ký tự nào được tải bởi cùng một trình nạp lớp sẽ tham chiếu đến cùng một Chuỗi. Xem đặc tả JLS và JVM.
Hầu tước Lorne

1
@ user207421 trong thực tế, nó thậm chí không liên quan đến trình nạp lớp nào mà chuỗi ký tự thuộc về.
Holger

1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.

0

Từ cuốn sách Lập trình viên Deshmukh của OCP Java SE 11, tôi đã tìm thấy lời giải thích dễ nhất cho Thực tập như sau: Vì các chuỗi là các đối tượng và vì tất cả các đối tượng trong Java luôn được lưu trữ trong không gian heap, nên tất cả các chuỗi được lưu trữ trong không gian heap. Tuy nhiên, Java giữ các chuỗi được tạo mà không sử dụng từ khóa mới trong một khu vực đặc biệt của không gian heap, được gọi là "chuỗi chuỗi". Java giữ các chuỗi được tạo bằng cách sử dụng từ khóa mới trong không gian heap thông thường.

Mục đích của nhóm chuỗi là duy trì một tập hợp các chuỗi duy nhất. Bất cứ khi nào bạn tạo một chuỗi mới mà không sử dụng từ khóa mới, Java sẽ kiểm tra xem cùng một chuỗi đã tồn tại trong nhóm chuỗi chưa. Nếu có, Java trả về một tham chiếu đến cùng một đối tượng String và nếu không, Java sẽ tạo một đối tượng String mới trong nhóm chuỗi và trả về tham chiếu của nó. Vì vậy, ví dụ, nếu bạn sử dụng chuỗi "xin chào" hai lần trong mã của mình như được hiển thị bên dưới, bạn sẽ nhận được một tham chiếu đến cùng một chuỗi. Chúng ta thực sự có thể kiểm tra lý thuyết này bằng cách so sánh hai biến tham chiếu khác nhau bằng cách sử dụng toán tử == như được hiển thị trong đoạn mã sau:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

Toán tử == chỉ đơn giản là kiểm tra xem hai tham chiếu có trỏ đến cùng một đối tượng hay không và trả về true nếu chúng thực hiện. Trong đoạn mã trên, str2 lấy tham chiếu đến cùng một đối tượng String đã được tạo trước đó. Tuy nhiên, str3str4 get tham chiếu đến hai đối tượng String hoàn toàn khác nhau. Đó là lý do tại sao str1 == str2 trả về true nhưng str1 == str3str3 == str4 trở lại sai. Trong thực tế, khi bạn thực hiện Chuỗi mới ("xin chào"); hai đối tượng Chuỗi được tạo thay vì chỉ một nếu đây là lần đầu tiên chuỗi "hello" được sử dụng ở bất kỳ đâu trong chương trình - một trong nhóm chuỗi vì sử dụng chuỗi được trích dẫn và một trong không gian heap thông thường vì của việc sử dụng từ khóa mới.

Việc gộp chuỗi là cách lưu bộ nhớ chương trình của Java bằng cách tránh tạo ra nhiều đối tượng Chuỗi chứa cùng một giá trị. Có thể lấy một chuỗi từ nhóm chuỗi cho một chuỗi được tạo bằng từ khóa mới bằng cách sử dụng phương thức intern của String. Nó được gọi là "interning" của các đối tượng chuỗi. Ví dụ,

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.