Có phải là cách thực hành tốt để sử dụng java.lang.String.i INTERN () không?


194

Javadoc về String.intern()không cung cấp nhiều chi tiết. (Tóm lại: Nó trả về một biểu diễn chính tắc của chuỗi, cho phép so sánh các chuỗi được sử dụng ==)

  • Khi nào tôi sẽ sử dụng chức năng này để ủng hộ String.equals()?
  • Có các tác dụng phụ không được đề cập trong Javadoc, tức là tối ưu hóa ít nhiều bởi trình biên dịch JIT?
  • Có sử dụng thêm String.intern()?

14
Gọi intern () có tác động thực hiện riêng, sử dụng intern () để cải thiện hiệu năng cần phải được kiểm tra để đảm bảo chương trình thực sự tăng tốc đáng kể chương trình của bạn để có giá trị phức tạp thêm. Bạn cũng có thể sử dụng điều này để giảm mức tiêu thụ bộ nhớ cho các bảng lớn với các giá trị lặp lại tương đối. Tuy nhiên, trong cả hai trường hợp, có những lựa chọn khác có thể tốt hơn.
Peter Lawrey

Có, intern () có tác động hiệu suất riêng của nó. Đặc biệt bởi vì chi phí intern () tăng tuyến tính khi bạn thực hiện các chuỗi và giữ một tham chiếu đến chúng. Ít nhất là trên mặt trời / oracle 1.6.0_30 vm.
lacroix1547

Câu trả lời:


125

Khi nào tôi sẽ sử dụng hàm này để ủng hộ String.equals ()

khi bạn cần tốc độ vì bạn có thể so sánh các chuỗi theo tham chiếu (== nhanh hơn bằng)

Có tác dụng phụ không được đề cập trong Javadoc?

Nhược điểm chính là bạn phải nhớ để đảm bảo rằng bạn thực sự thực hiện intern () tất cả các chuỗi mà bạn sẽ so sánh. Thật dễ dàng để quên intern () tất cả các chuỗi và sau đó bạn có thể nhận được kết quả không chính xác một cách khó hiểu. Ngoài ra, vì lợi ích của mọi người, vui lòng đảm bảo ghi lại rất rõ ràng rằng bạn đang dựa vào chuỗi được nội hóa.

Nhược điểm thứ hai nếu bạn quyết định nội địa hóa chuỗi là phương thức intern () tương đối đắt tiền. Nó phải quản lý nhóm các chuỗi duy nhất để nó thực hiện một công việc hợp lý (ngay cả khi chuỗi đã được nội hóa). Vì vậy, hãy cẩn thận trong thiết kế mã của bạn để bạn ví dụ, intern () tất cả các chuỗi thích hợp trên đầu vào để bạn không phải lo lắng về nó nữa.

(từ JGuru)

Nhược điểm thứ ba (chỉ dành cho Java 7 trở xuống): Các chuỗi được thực hiện sống trong không gian PermGen, thường khá nhỏ; bạn có thể chạy vào OutOfMemoryError với nhiều không gian heap miễn phí.

(từ Michael Borgwardt)


64
Nhược điểm thứ ba: Chuỗi thực tập sống trong không gian PermGen, thường khá nhỏ; bạn có thể chạy vào OutOfMemoryError với nhiều không gian heap miễn phí.
Michael Borgwardt

15
Các máy ảo mới hơn của AFAIK cũng thu thập không gian PermGen.
Daniel Rikowski

31
Thực tập là về quản lý bộ nhớ, không phải tốc độ so sánh. Sự khác biệt giữa if (s1.equals(s2))if (i1 == i2)là tối thiểu trừ khi bạn có rất nhiều chuỗi dài với cùng các ký tự đầu. Trong hầu hết các sử dụng trong thế giới thực (trừ URL), các chuỗi sẽ khác nhau trong một vài ký tự đầu tiên. Và các chuỗi if-if dài khác dù sao cũng là một mùi mã: sử dụng enums và functor maps.
kdgregory

25
bạn vẫn có thể sử dụng cú pháp s1.equals trong suốt chương trình của mình, KHÔNG sử dụng ==, .equals sử dụng == nội bộ để đánh giá ngắn mạch
gtrak

15
Michael Borgwardt KHÔNG nói rằng các chuỗi nội bộ không thể là rác được thu thập. Và đó là một khẳng định FALSE. Những gì Michael bình luận (chính xác) nói là tinh tế hơn thế.
Stephen C

193

Điều này có (hầu như) không có gì để làm với so sánh chuỗi. Thực tập chuỗi được dự định để lưu bộ nhớ nếu bạn có nhiều chuỗi có cùng nội dung trong ứng dụng của mình. Bằng cách sử String.intern()dụng ứng dụng sẽ chỉ có một trường hợp trong thời gian dài và tác dụng phụ là bạn có thể thực hiện so sánh đẳng thức tham chiếu nhanh thay vì so sánh chuỗi thông thường (nhưng điều này thường không được khuyến khích vì thực sự rất dễ bị phá vỡ bằng cách quên chỉ thực tập một ví dụ duy nhất).


4
Điều đó không đúng. Việc thực hiện các chuỗi xảy ra luôn luôn, tự động, khi mỗi biểu thức chuỗi được ước tính. Luôn có một bản sao cho mỗi chuỗi ký tự duy nhất được sử dụng và đó là "chia sẻ nội bộ" nếu có nhiều cách sử dụng. Gọi String.i INTERN () không làm cho tất cả điều này xảy ra - nó chỉ trả về biểu diễn chính tắc bên trong. Xem javadoc.
Glen tốt nhất

16
Cần làm rõ - thực tập luôn diễn ra tự động đối với các chuỗi hằng số thời gian biên dịch (nghĩa đen & biểu thức cố định). Ngoài ra, nó xảy ra khi String.i INTERN () được gọi trong chuỗi thời gian chạy được đánh giá động.
Glen tốt nhất

Vậy ý bạn là, nếu có 1000 đối tượng "Xin chào" trong Heap và tôi thực hiện intern () trên một trong số chúng, thì 999 đối tượng còn lại sẽ tự động bị hủy?
Arun Raaj

@ArunRaaj không, bạn sẽ có 1000 của bạn vẫn còn trên heap, và là một phụ trong hồ bơi thực tập, mà có thể sẵn sàng để tái sử dụng bằng cách sau str.intern()khi str"Hello".
Matthieu

37

String.intern()chắc chắn là rác được thu thập trong các JVM hiện đại.
KHÔNG BAO GIỜ hết bộ nhớ sau vì hoạt động của 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();
        }
    }
}

Xem thêm (từ tôi) về huyền thoại của Chuỗi không được phân loại. Nội bộ () .


26
OutOfMemoryException- Không, không phải là mã trên, trong tôi não : liên kết đến bài viết javaturning, được trỏ đến bài viết này, được trỏ đến bài viết javaturning, mà ... :-)
user85421

Mặc dù bạn có thể thấy rằng bài đăng đã được chỉnh sửa để thêm liên kết đó;)
Đi xe đạp vào

3
Bạn có thể muốn đề cập rằng bạn cũng là tác giả của tài liệu tham khảo bên ngoài mà bạn liên kết đến.
Thorbjørn Ravn Andersen

11
@Carlos liên kết một tham chiếu bên ngoài liên kết trở lại stackoverflow sẽ gây ra một .. Stackoverflow :)
Seiti

2
Tài liệu tham khảo @Seiti dễ dàng được phát hiện trong những ngày này: p
Ajay

16

Gần đây tôi đã viết một bài viết về triển khai String.i INTERN () trong Java 6, 7 và 8: String.i INTERN trong Java 6, 7 và 8 - gộp chuỗi .

Tôi hy vọng nó sẽ chứa đủ thông tin về tình hình hiện tại với việc gộp chuỗi trong Java.

Tóm lại:

  • Tránh String.intern()trong Java 6, vì nó đi vào PermGen
  • Thích String.intern()trong Java 7 & Java 8: nó sử dụng bộ nhớ ít hơn 4-5 lần so với cuộn nhóm đối tượng của riêng bạn
  • Đảm bảo điều chỉnh -XX:StringTableSize(mặc định có thể quá nhỏ; đặt số Prime)

3
Xin đừng chỉ đăng liên kết đến blog của bạn, điều này được một số người coi là SPAM. Cộng với các liên kết blog có xu hướng đáng chú ý là chết một cái chết 404. Vui lòng tóm tắt nội dung bài viết của bạn ở đây, hoặc để lại liên kết đó trong một bình luận cho câu hỏi.
Mat

3
Cảm ơn bạn đã viết rằng @ mik1! Rất nhiều thông tin, rõ ràng và cập nhật bài viết. (Tôi đã quay lại đây với ý định tự mình đăng một liên kết tới nó.)
Luke Usherwood

1
Cảm ơn đã đề cập đến -XX arg. Bạn cũng có thể sử dụng điều này để xem số liệu thống kê của bảng: -XX: + PrintStringTableStatistic
csadler

13

So sánh các chuỗi với == nhanh hơn nhiều so với bằng ()

5 Thời gian nhanh hơn, nhưng vì so sánh Chuỗi thường chỉ chiếm một tỷ lệ nhỏ trong tổng thời gian thực hiện của ứng dụng, nên mức tăng tổng thể nhỏ hơn nhiều so với mức tăng đó và mức tăng cuối cùng sẽ bị pha loãng đến vài phần trăm.

String.i INTERN () kéo chuỗi ra khỏi Heap và đặt nó vào PermGen

Chuỗi được nội địa hóa được đặt trong một vùng lưu trữ khác: Thế hệ vĩnh viễn là một khu vực của JVM 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à là quý hơn nhiều so với đống. Là khu vực này nhỏ hơn Heap, có nhiều khả năng sử dụng tất cả không gian và nhận OutOfMemoryException.

Chuỗi String.i INTERN () là rác được thu thập

Trong các phiên bản mới của JVM, chuỗi nội bộ cũng là rác được thu thập khi không được tham chiếu bởi bất kỳ đối tượng nào.

Hãy ghi nhớ 3 điểm trên bạn có thể trừ rằng String intern () chỉ có thể hữu ích trong một số trường hợp khi bạn thực hiện nhiều so sánh chuỗi, tuy nhiên tốt hơn là không sử dụng chuỗi nội bộ nếu bạn không biết chính xác bạn là gì đang làm ...



1
Chỉ cần thêm, ngoại lệ bộ nhớ Heap đôi khi có thể được phục hồi, đặc biệt là trong các mô hình luồng như ứng dụng web. Khi permgen cạn kiệt, một ứng dụng thường sẽ không hoạt động vĩnh viễn và thường sẽ phá hủy tài nguyên cho đến khi bị giết.
Taylor

7

Khi nào tôi sẽ sử dụng hàm này để ủng hộ String.equals ()

Cho họ làm những điều khác nhau, có lẽ không bao giờ.

Chuỗi thực tập vì lý do hiệu suất để bạn có thể so sánh chúng với đẳng thức tham chiếu sẽ chỉ có ích nếu bạn giữ tham chiếu đến chuỗi trong một thời gian - chuỗi đến từ đầu vào của người dùng hoặc IO sẽ không được thực hiện.

Điều đó có nghĩa là trong ứng dụng của bạn, bạn nhận được đầu vào từ một nguồn bên ngoài và xử lý nó thành một đối tượng có giá trị ngữ nghĩa - một định danh nói - nhưng đối tượng đó có một loại không thể phân biệt được với dữ liệu thô và có các quy tắc khác nhau về cách lập trình viên nên sử dụng nó.

Hầu như luôn luôn tốt hơn để tạo một UserIdloại được thực hiện (thật dễ dàng để tạo một cơ chế thực tập chung chung an toàn cho luồng) và hoạt động như một enum mở, hơn là làm quá tải java.lang.Stringloại với ngữ nghĩa tham chiếu nếu nó là ID người dùng.

Bằng cách đó, bạn không bị nhầm lẫn giữa việc một Chuỗi cụ thể đã được thực hiện hay chưa và bạn có thể gói gọn bất kỳ hành vi bổ sung nào bạn yêu cầu trong enum mở.


6

Tôi không nhận thức được bất kỳ lợi thế nào, và nếu có một người sẽ nghĩ rằng bằng () chính nó sẽ sử dụng intern () bên trong (mà nó không).

Busting intern () huyền thoại


7
Mặc dù bạn nói rằng bạn không nhận thấy bất kỳ lợi thế nào, nhưng liên kết được đăng của bạn xác định so sánh qua == là nhanh hơn 5x và do đó rất quan trọng đối với mã biểu diễn tập trung vào văn bản
Brian Agnew

3
Khi bạn có nhiều văn bản so sánh, cuối cùng bạn sẽ hết dung lượng PermGen. Khi không có quá nhiều so sánh văn bản để làm chênh lệch tốc độ thì không thành vấn đề. Dù bằng cách nào, chỉ cần không tập () chuỗi của bạn. Nó không đáng.
Bombe

Nó cũng tiếp tục nói rằng mức tăng tương đối tổng thể thường sẽ nhỏ.
các đối tượng

Tôi không nghĩ rằng loại logic đó là hợp lệ. Liên kết tốt mặc dù!
Daniel Rikowski

1
@DR: logic gì? Đó là một sai lầm lớn. @objects: xin lỗi nhưng lập luận của bạn không có lý do. Có những lý do rất tốt để sử dụng internvà những lý do rất tốt mà equalskhông làm như vậy theo mặc định. Các liên kết bạn đăng là bollocks hoàn chỉnh. Đoạn cuối thậm chí thừa nhận interncó kịch bản sử dụng hợp lệ: xử lý văn bản nặng (ví dụ: trình phân tích cú pháp). Kết luận rằng, [XYZ] rất nguy hiểm nếu bạn không biết những gì bạn đang làm là rất nghiêm trọng đến nỗi nó bị tổn thương về thể chất.
Konrad Rudolph

4

Daniel Brückner hoàn toàn đúng. Chuỗi thực tập có nghĩa là để tiết kiệm bộ nhớ (heap). Hệ thống của chúng tôi hiện có một hashmap khổng lồ để chứa dữ liệu nhất định. Khi quy mô hệ thống, hashmap sẽ đủ lớn để tạo ra bộ nhớ ngoài (như chúng tôi đã thử nghiệm). Bằng cách thực hiện tất cả các chuỗi trùng lặp tất cả các đối tượng trong hàm băm, nó giúp chúng ta tiết kiệm một lượng không gian heap đáng kể.

Cũng trong Java 7, các chuỗi được thực hiện không còn tồn tại trong PermGen mà thay vào đó là heap. Vì vậy, bạn không cần phải lo lắng về kích thước của nó và vâng, nó sẽ được thu gom rác:

Trong JDK 7, các chuỗi được tập trung 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ôi phải nói thứ hai rằng: trên phần mềm của tôi, một đống heap cho thấy hầu hết không gian heap được sử dụng bởi các Stringthể hiện. Khi nhìn vào nội dung của chúng, tôi thấy nhiều bản sao và quyết định chuyển sang intern(), giúp tiết kiệm hàng trăm MB.
Matthieu

4

Có các tác dụng phụ không được đề cập trong Javadoc, tức là tối ưu hóa ít nhiều bởi trình biên dịch JIT?

Tôi không biết về cấp độ JIT, nhưng có hỗ trợ mã byte trực tiếp cho nhóm chuỗi , được triển khai 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ó biểu diễn chung hơn).

Liên doanh

JVMS 7 5.1 nói :

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 có 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

Nó cũng được khuyến khích để xem việc triển khai mã byte trên OpenJDK 7.

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

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


2

Tôi sẽ kiểm tra intern và == - so sánh thay vì chỉ bằng trong trường hợp so sánh bằng là nút cổ chai trong nhiều so sánh của chuỗi. Điều này rất khó có thể giúp với số lượng nhỏ so sánh, bởi vì intern () không miễn phí. Sau khi tích cực thực hiện các chuỗi, bạn sẽ thấy các lệnh gọi intern () ngày càng chậm hơn.


2

Một loại rò rỉ bộ nhớ có thể đến từ việc sử dụng subString()khi kết quả nhỏ so với chuỗi nguồn và đối tượng có tuổi thọ cao.

Giải pháp thông thường là sử dụng new String( s.subString(...))nhưng khi bạn có một lớp lưu trữ kết quả của tiềm năng / có khả năng subString(...)và không có quyền kiểm soát đối với người gọi, bạn có thể xem xét để lưu trữ các intern()đối số Chuỗi được truyền cho hàm tạo. Điều này giải phóng bộ đệm lớn tiềm năng.


Thú vị, nhưng có lẽ đây là phụ thuộc thực hiện.
akostadinov

1
Rò rỉ bộ nhớ tiềm năng đã đề cập ở trên không xảy ra trong java 1.8 và 1.7.06 (và mới hơn) xem Thay đổi biểu diễn bên trong Chuỗi được thực hiện trong Java 1.7.0_06 .
eremmel

xác nhận tối ưu hóa vi mô chỉ được áp dụng khi cần thiết sau khi cấu hình hiệu năng và / hoặc bộ nhớ. Cảm ơn bạn.
akostadinov

2

Thực hiện chuỗi là hữu ích trong trường hợp equals()phương thức được gọi thường xuyên bởi vì equals()phương thức kiểm tra nhanh để xem các đối tượng có giống nhau ở đầu phương thức không.

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

Điều này thường xảy ra khi tìm kiếm thông qua một Collectionmã khác cũng có thể thực hiện kiểm tra tính bằng chuỗi.

Mặc dù có một chi phí liên quan đến thực tập, tôi đã thực hiện một microbenchmark của một số mã và thấy rằng quá trình thực tập làm tăng thời gian chạy lên gấp 10 lần.

Nơi tốt nhất để thực tập thường là khi bạn đang đọc các khóa được lưu trữ bên ngoài mã vì các chuỗi trong mã được tự động thực hiện. Điều này thường xảy ra ở các giai đoạn khởi tạo ứng dụng của bạn để ngăn chặn hình phạt của người dùng đầu tiên.

Một nơi khác có thể được thực hiện là khi xử lý đầu vào của người dùng có thể được sử dụng để thực hiện tra cứu chính. Điều này thường xảy ra trong bộ xử lý yêu cầu của bạn, lưu ý rằng các chuỗi nội bộ nên được truyền lại.

Ngoài ra, không có nhiều điểm thực hiện trong phần còn lại của mã vì nó thường sẽ không mang lại bất kỳ lợi ích nào.


1

Tôi sẽ bỏ phiếu cho nó không có giá trị rắc rối bảo trì.

Hầu hết thời gian, sẽ không có nhu cầu và không có lợi ích hiệu suất, trừ khi mã của bạn làm rất nhiều việc với các chuỗi con. Trong trường hợp đó, lớp String sẽ sử dụng chuỗi gốc cộng với phần bù để lưu bộ nhớ. Nếu mã của bạn sử dụng các chuỗi con rất nhiều, thì tôi nghi ngờ rằng nó sẽ chỉ khiến các yêu cầu bộ nhớ của bạn bùng nổ.


1

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

khẳng định String.equals()sử dụng "=="để so sánh Stringcác đối tượng trước đó, theo

http://www.codeinemony.com/2009/01/busting-javalangopesi INTERN-myths.html

nó so sánh độ dài của Chuỗi và sau đó là nội dung.

. có lẽ bạn nên đặt một chiếc mũ bảo hiểm an toàn cùng một lúc.)

Vì vậy, có vẻ như bạn nhận được lợi ích từ việc thay thế Chuỗi của mình bằng intern()phiên bản của chúng , nhưng bạn có được sự an toàn - và khả năng đọc và tuân thủ tiêu chuẩn - không sử dụng "==" equals()trong lập trình của bạn. Và hầu hết những gì tôi sẽ nói phụ thuộc vào điều đó là sự thật, nếu nó là sự thật.

Nhưng String.equals()kiểm tra rằng bạn đã truyền cho nó một Chuỗi chứ không phải một số đối tượng khác, trước khi sử dụng "=="? Tôi không đủ điều kiện để nói, nhưng tôi đoán là không, bởi vì hầu hết các equals()hoạt động như vậy sẽ là String to String, do đó bài kiểm tra hầu như luôn được thông qua. Thật vậy, việc ưu tiên "==" bên trong String.equals()ngụ ý rằng bạn thường xuyên so sánh Chuỗi với cùng một đối tượng thực tế.

Tôi hy vọng không ai ngạc nhiên khi các dòng sau tạo ra kết quả là "sai":

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

Nhưng nếu bạn đổi isang i.toString()dòng thứ hai, dĩ nhiên là vậy true.

Địa điểm mà bạn có thể hy vọng cho một lợi ích từ thực tập bao gồm SetMap, rõ ràng. Tôi hy vọng rằng các chuỗi được thực hiện có mã băm của họ được lưu trữ ... Tôi nghĩ đó sẽ là một yêu cầu. Và tôi hy vọng tôi đã không đưa ra một ý tưởng có thể kiếm cho tôi một triệu đô la. :-)

Đối với bộ nhớ, rõ ràng đó là một giới hạn quan trọng nếu âm lượng Chuỗi của bạn lớn hoặc nếu bạn muốn bộ nhớ được sử dụng bởi mã chương trình của bạn rất nhỏ. Nếu khối lượng -distinc- String của bạn rất lớn, thì có lẽ đã đến lúc bạn nên cân nhắc sử dụng mã chương trình cơ sở dữ liệu chuyên dụng để quản lý chúng và một máy chủ cơ sở dữ liệu riêng biệt. Tương tự như vậy, nếu bạn có thể cải thiện một chương trình nhỏ (cần chạy trong 10000 trường hợp đồng thời) bằng cách nó hoàn toàn không lưu trữ Chuỗi của nó.

Cảm thấy lãng phí khi tạo một Chuỗi mới và sau đó loại bỏ nó ngay lập tức để intern()thay thế, nhưng không có sự thay thế rõ ràng, ngoại trừ việc giữ Chuỗi trùng lặp. Vì vậy, thực sự chi phí thực hiện là tìm kiếm chuỗi của bạn trong nhóm thực tập và sau đó cho phép trình thu gom rác xử lý bản gốc. Và nếu đó là một chuỗi ký tự thì dù sao nó cũng đã được thực hiện.

Tôi tự hỏi liệu intern()có thể bị lạm dụng bởi mã chương trình độc hại để phát hiện xem một số Chuỗi và tham chiếu đối tượng của chúng đã tồn tại trong nhóm intern()hay không và do đó tồn tại ở nơi khác trong phiên Java, khi điều đó không được biết. Nhưng điều đó chỉ có thể xảy ra khi mã chương trình đã được sử dụng theo cách đáng tin cậy, tôi đoán vậy. Tuy nhiên, đây là điều cần xem xét về các thư viện của bên thứ ba mà bạn đưa vào chương trình để lưu trữ và ghi nhớ số PIN ATM của bạn!


0

Lý do thực sự để sử dụng intern không phải là ở trên. Bạn có thể sử dụng nó sau khi bạn gặp lỗi hết bộ nhớ. Rất nhiều chuỗi trong một chương trình điển hình là String.subopes () của chuỗi lớn khác [nghĩ đến việc lấy tên người dùng từ tệp xml 100K. Việc triển khai java là ở chỗ, chuỗi con giữ một tham chiếu đến chuỗi gốc và start + end trong chuỗi lớn đó. (Ý nghĩ đằng sau nó là việc sử dụng lại cùng một chuỗi lớn)

Sau 1000 tệp lớn, từ đó bạn chỉ lưu 1000 tên ngắn, bạn sẽ giữ trong bộ nhớ toàn bộ 1000 tệp! Giải pháp: trong trường hợp này chỉ cần sử dụng smallsubopes.i INTERN ()


Tại sao không chỉ tạo một chuỗi mới từ chuỗi con nếu bạn cần nó?
Thorbjørn Ravn Andersen

0

Tôi đang sử dụng intern để tiết kiệm bộ nhớ, tôi giữ một lượng lớn dữ liệu String trong bộ nhớ và chuyển sang sử dụng intern () đã tiết kiệm một lượng lớn bộ nhớ. Thật không may, mặc dù nó sử dụng rất ít bộ nhớ nhưng bộ nhớ mà nó sử dụng được lưu trữ trong bộ nhớ PermGen chứ không phải Heap và rất khó để giải thích cho khách hàng cách tăng phân bổ loại bộ nhớ này.

Vì vậy, có một giải pháp thay thế cho intern () để giảm mức tiêu thụ bộ nhớ, (== so với lợi ích hiệu suất không phải là vấn đề đối với tôi)


0

Hãy đối mặt với nó: kịch bản trường hợp sử dụng chính là khi bạn đọc một luồng dữ liệu (thông qua luồng đầu vào hoặc từ Tập kết quả JDBC) và có vô số Chuỗi nhỏ được lặp lại xuyên suốt.

Dưới đây là một mẹo nhỏ cung cấp cho bạn một số quyền kiểm soát đối với loại cơ chế nào bạn muốn sử dụng để nội địa hóa Chuỗi và các bất biến khác và triển khai ví dụ:

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

Tôi sử dụng nó thường xuyên khi tôi đọc các trường từ luồng hoặc từ Kết quả. Lưu ý: LRUCachelà một bộ đệm đơn giản dựa trên LinkedHashMap<K,V>. Nó tự động gọi retrieve()phương thức do người dùng cung cấp cho tất cả các lỗi bộ nhớ cache.

Cách sử dụng này là tạo một cái LRUInternalizertrước khi bạn đọc (hoặc đọc), sử dụng nó để nội hóa Chuỗi và các đối tượng bất biến nhỏ khác, sau đó giải phóng nó. Ví dụ:

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

Tôi đang sử dụng nó để lưu trữ nội dung của khoảng 36000 mã liên kết đến các tên liên quan. Tôi thực hiện các chuỗi trong bộ đệm vì nhiều mã trỏ đến cùng một chuỗi.

Bằng cách thực hiện các chuỗi trong bộ đệm của tôi, tôi đảm bảo rằng các mã trỏ đến cùng một chuỗi thực sự trỏ đến cùng một bộ nhớ, do đó tiết kiệm không gian RAM cho tôi.

Nếu các chuỗi nội bộ thực sự là rác được thu thập, thì nó hoàn toàn không hoạt động với tôi. Điều này về cơ bản sẽ phủ nhận mục đích của thực tập. Của tôi sẽ không được thu gom rác bởi vì tôi đang giữ một tham chiếu đến từng chuỗi trong bộ đệm.


Không, tất cả các chuỗi bằng nhau được lưu trong bộ nhớ tại một thời điểm nhất định, vẫn sẽ là cùng một đối tượng. Nó sẽ là một đối tượng khác với chuỗi bằng nhau trong bộ nhớ trước khi nó được thu gom rác. Nhưng điều này không có vấn đề gì vì chuỗi cũ không còn nữa.
bdruemen

0

Chi phí thực hiện một chuỗi nhiều hơn nhiều so với thời gian được lưu trong một so sánh chuỗiA.equals (B). Chỉ sử dụng nó (vì lý do hiệu suất) khi bạn liên tục sử dụng cùng một biến chuỗi không thay đổi. Ví dụ: nếu bạn thường xuyên lặp lại một danh sách các chuỗi ổn định để cập nhật một số bản đồ được khóa trên cùng một trường chuỗi, bạn có thể có được một khoản tiết kiệm tốt.

Tôi sẽ đề nghị sử dụng thực tập chuỗi để điều chỉnh hiệu suất khi bạn đang tối ưu hóa các phần cụ thể của mã.

Cũng nên nhớ rằng String là bất biến và đừng phạm sai lầm ngớ ngẩn của

String a = SOME_RANDOM_VALUE
a.intern()

nhớ làm

String a = SOME_RANDOM_VALUE.intern()

0

Nếu bạn đang tìm kiếm một sự thay thế không giới hạn cho String.i INTERN, cũng là rác được thu thập, thì những thứ sau đây hoạt động tốt với tôi.

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

Tất nhiên, nếu bạn có thể ước tính khoảng bao nhiêu chuỗi khác nhau, thì chỉ cần sử dụng String.i INTERN () với -XX: StringTableSize = highEnoughValue .


SoftRef sẽ làm cho nhiều hơn.
vach

@vach Bằng cách sử dụng bộ nhớ WeakReference (thay vì SoftReference) được giải phóng sớm hơn để các phân bổ khác có thể đi nhanh hơn. Nó phụ thuộc vào những gì ứng dụng đang làm, một trong hai có thể có ý nghĩa.
bdruemen
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.