Stringオブジェクトを達成
String
オブジェクトは、Javaの企業が上常にあるので、Javaで最も頻繁に使用されるオブジェクトの一つであるString
強化するために最適化されたオブジェクトの実現String
に沿って見て、パフォーマンスオブジェクトを、以下の絵を見てString
、オブジェクトの最適化プロセス。
1. Java6の以前のバージョン
String
char配列、オフセット、オフセット、文字数カウント、ハッシュのハッシュ値:オブジェクトの実装をカプセル化するオブジェクトの文字配列では、4つのメンバ変数があります。
String
オブジェクトは、オフセットでのchar []配列を検索し、2つの属性カウント、文字列を取得することです。メモリ空間を節約しながら、効率的かつ迅速に、オブジェクトの配列を共有してそう、このアプローチは、メモリリークが発生する可能性があります。
当初からJava8バージョンバージョンJava7に2
最初からJava7のバージョンは、Javaは、のためにString
クラスいくつかの変更を行いました。String
クラスはもはやオフセットなく、2つの変数をカウントしています。利点はあるString
この方法の使用から生じ得る、メモリリークの問題を解決するようString.substring方法は、[]もはや共有チャーであるオブジェクトは、メモリの少しを占有しません。
3. Java9版を開始
代わりに、バイト[]配列の文字[]配列、なぜあなたがそれを行う必要がありますか?私たちは、バイト文字列を格納するためのバイトに、スペースを節約するために、廃棄物バイト文字のビットを格納するために使用される場合、charは2バイトであることをJavaの会社を知っています。そのようなメモリバイト文字では廃棄物を避けるためです。
Java9では、フォーマット識別符号化される新しい属性コーダを維持文字列の長さの計算またはのindexOf()関数は、どのフィールド決意計算に応じて文字列の必要な長さを呼び出します。デフォルト属性コーダラテン-1のための2つの値は0と1、0(シングルバイトのエンコーディング)を有し、UTF-16コードを表します。場合String
決意列のみラテン1、ゼロの符号化値を含み、それ以外の場合は1です。
Stringオブジェクトを作成する方法
1、文字列定数を経由して
String str= "pingtouge"
フォームは、このフォームを使用する場合、文字列を作成し、JVMが存在する場合、オブジェクトが文字列定数プール内に存在するかどうかをチェックするオブジェクトのアドレスへの参照を返し、存在しない場合、文字列定数プールでそれを作成します文字列オブジェクトと参照を返します。このアプローチを使用して作成した利点は次のとおりです。メモリを節約、作成するために、同じ値の文字列の繰り返しを避けるために、
構築2、文字列()関数
String str = new String("pingtouge")
フォームは、この方法を使用して文字列オブジェクトを作成するプロセスは、最初のコンパイル時に、文字列は、二段階、より複雑であるpingtouge
クラスローダが定数プール内の文字列を作成する際に一定の構造に追加されます。あなたは、JVMが呼び出されます)(新しい呼び出したときに、それはあるString
定数プールへのコンストラクタと同様に、参照pingtouge
、文字列の
中にヒープメモリの作成String
オブジェクトをヒープのアドレスへの参照を返します。
理解String
二つの方法で作成されたオブジェクトは、我々はこれらの2つの方法は、次のコードシートの我々の理解を深めて、以下のコードを分析する必要がある、str
と等しいstr1
こと?
String str = "pingtouge";
String str1 = new String("pingtouge");
system.out.println(str==str1)
私たちは、からのすべての最初の、コードの1つのこれらの数行づつを分析するString str = "pingtouge"
文字列定数の使用文字列オブジェクトを作成する方法が作成された当初、pingtouge
文字列オブジェクト、JVM定数プールは、文字列の存在を探しに行きます、ここで答えが肯定的ですそれがないので、JVMは、文字列オブジェクトの定数プール内に作成され、オブジェクト参照のアドレスを返し、そうstr
する点pingtouge
定数プールのアドレスの文字列オブジェクト参照。
そして、String str1 = new String("pingtouge")
ここで使用されるコードのこの行は、理解コンストラクタ文字列オブジェクトを作成するための我々の方法の顔に、文字列オブジェクトのコンストラクタを作成する方法であるstr1
結果のヒープがしなければならないpingtouge
文字列のアドレスを参照します。ためstr
に点pingtouge
定数プールのアドレスの文字列オブジェクトの参照はながらstr1
ヒープを指すpingtouge
文字列の引用符で囲まれたアドレス、それはstr
確かに等しくありませんstr1
。
Stringオブジェクトの不変性
我々は知っているからString
、私たちはすべてを知っていると思う瞬間、開始オブジェクトString
のオブジェクトは不変です。それは不変であることをどのようにそれを行うには?Java
もたらすことができるそうすることの利点は何ですか?私たちは、簡単な探検を見てとるために協力しString
、ソースコードのオブジェクトセクションを:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
}
このソースからわかるように、String
最後の修飾子を持つクラスは、私たちは、クラスは、最終的な変更であるとき、それはこのクラスは継承できないことを示し、そのことを知っているString
クラスが継承することはできません。これは、String
第1変化点ではありません
そして、文字列の格納に使用され、見下しchar value[]
ている配列をprivate
し、final
私たちが知っているため、修正final
変数の基本的なデータ型は、その値が初期化後に一度に変更することはできません。これは、String
第二の点は不変。
なぜJavaの会社はあなたがしたいString
、主に以下の3つの考察から、不変に設定します。
- 1は、Stringオブジェクトの安全性を確保します。Stringオブジェクトを想定する変数であり、文字列オブジェクトが悪意を持って変更することができます。
- 2は、ハッシュプロパティ値を確保するためには、適切なキーと値のキャッシュ機能を実現するためにはHashMapのコンテナに似て一意性を保証するために、頻繁に変更されません。
- 図3は、文字列定数プールを達成することができます
Stringオブジェクトの最適化
私たちは、文字列がある使用Java
のいずれかのタイプなので、文字列の操作は、文字列操作の過程で避けられない不適切に使用すると、パフォーマンスが行うには悪い日となります。だから、文字列の操作中に、我々はそれに注意を払う必要がある領域がありますか?
エレガントな文字列の連結
我々はそれを知っているので、文字列の連結文字列操作は、最も頻繁に使用される操作の一つであるString
不変オブジェクトをので、我々は少し使用してスプライスされない場合は、+
文字列のステッチをまたは無意識のうちにあなたが使用できないと信じている+
文字列を使用することをスプライシング、+
文字列のスプライシングは、多くの無駄なオブジェクトを生成します。事実は、それが本当にありますか?私たちは、実験を行います。私たちは、使用され+
た文字列の次の部分をスプライスします。
String str8 = "ping" +"tou"+"ge";
一緒にこのコードが生成されますどのように多くのオブジェクトを分析するには?あなたが意味の我々の理解に合わせてケースを分析する場合は、最初に作成しping
たオブジェクトを、次に作成しpingtou
たオブジェクトを、そして最終的に作成するpingtouge
3つのオブジェクトの合計を作成し、オブジェクトを。それは本当にですか?Javaプログラマの企業は、私たちが間違った手の恐れていたので、コンパイラは最適化された事実ではない場合、この文字列の連結の上に最適化され、私たちのコンパイラに最適化されているString str8 = "pingtouge";
ターゲット。最適化されたスプライス以外の文字列定数に加えて、使用するための+
番号文字列連結動的コンパイラも改善するために最適化された対応製String
、例えば、次のコードの性能:
String str = "pingtouge";
for(int i=0; i<1000; i++) {
str = str + i;
}
そうするコンパイラの最適化は、私たちを助けます
String str = "pingtouge";
for(int i=0; i<1000; i++) {
str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}
Javaは不注意につながるプログラマを防ぐために、この会社は、最適化の多くを持っていたことがわかるString
Javaコンパイラでは、同社は、最適化に対応する、このいずれかを実行しますが、パフォーマンスの急速な減少が、我々はまだJavaの最適化会社を見ることができます欠点は、動的文字列の連結の間、および文字列の連結のためのStringBuilderが、各サイクル新しいStringBuilderのインスタンスを生成するが、また、システム性能を低下させるであろう。
私たちは、文字列の連結を行うときに、我々は、コード・レベルを最適化する必要がある場合は、スレッドセーフを含まない場合は、それが関係している場合、我々は、システムのパフォーマンスを向上させ、スプライシングを示し、動的な文字列連結でのStringBuilderを使用してスレッドセーフ、我々は文字列の連結にStringBufferのを使用します
賢い使用インターン()メソッド
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
public native String intern();
これはおそらく、文字列定数プールがすでに存在する場合、それは定数プール参照のオブジェクトに直接返され、文字列定数プールを返すインターン機能を意味し、公式注釈のインターン()関数です。そうでない場合、オブジェクトは、定数プールに追加され、その後、参照を返します。
一つのTwitter
エンジニアがQCon
で彼らの世界的なソフトウェア開発会議を共有String
対象に最適化する場合、彼らが使うString.intern()
メモリのほんの数メガバイトを必要とする前に、メモリストレージの最適化の20Gを必要とするメソッドを。これは反映するために十分であるString.intern()
だけで見て、例を見て、私たちの力をString.intern()
用法。
public static void main(String[] args) {
String str = new String("pingtouge");
String str1 = new String("pingtouge");
System.out.println("未使用intern()方法:"+(str==str1));
System.out.println("未使用intern()方法,str:"+str);
System.out.println("未使用intern()方法,str1:"+str1);
String str2= new String("pingtouge").intern();
String str3 = new String("pingtouge").intern();
System.out.println("使用intern()方法:"+(str2==str3));
System.out.println("使用intern()方法,str2:"+str2);
System.out.println("使用intern()方法,str3:"+str3);
}
結果から分かるように、使用されていないString.intern()
方法は、同じ値を異なる文字列オブジェクトのオブジェクトの参照アドレスを返すように構成されている場合使用、String.intern()
方法、構成文字列オブジェクトは、オブジェクトが同一のアドレスを参照返す同じ値。これは、私たちは多くのスペースを節約することができます
String.intern()
方法虽然好,但是我们要结合场景使用,不能乱用,因为常量池的实现是类似于一个HashTable
的实现方式,HashTable
存储的数据越大,遍历的时间复杂度就会增加。如果数据过大,会增加整个字符串常量池的负担。
灵活的字符串的分割
字符串的分割是字符串操作的常用操作之一,对于字符串的分割,大部分人使用的都是 Split() 方法,Split() 方法大多数情况下使用的是正则表达式,这种分割方式本身没有什么问题,但是由于正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致 CPU 居高不下。在以下两种情况下 Split() 方法不会使用正则表达式:
- 传入的参数长度为1,且不包含“.$|()[{^?*+\”regex元字符的情况下,不会使用正则表达式
- 传入的参数长度为2,第一个字符是反斜杠,并且第二个字符不是ASCII数字或ASCII字母的情况下,不会使用正则表达式
所以我们在字符串分割时,应该慎重使用 Split() 方法,首先考虑使用 String.indexOf() 方法进行字符串分割,如果 String.indexOf() 无法满足分割要求,再使用 Split() 方法,使用 Split() 方法分割字符串时,需要注意回溯问题。
文章不足之处,望大家多多指点,共同学习,共同进步
参考资料
- Java性能调优实战 刘超
最后
打个小广告,欢迎扫码关注微信公众号:「平头哥的技术博文」,一起进步吧。