JAVAスタックの違い
著者:Eve Cole
更新時間:2009-11-30 17:08:19
-
1. スタックとヒープは、Java が Ram にデータを保存するために使用する場所です。 C++ とは異なり、Java はスタックとヒープを自動的に管理するため、プログラマはスタックやヒープを直接設定できません。
2. スタックの利点は、CPU に直接配置されているレジスタに次いでアクセス速度がヒープよりも速いことです。ただし、スタックに保存されるデータのサイズと有効期間を決定する必要があり、柔軟性に欠けるという欠点があります。さらに、スタック データを共有することもできます。詳細についてはポイント 3 を参照してください。ヒープの利点は、メモリ サイズを動的に割り当てることができ、Java のガベージ コレクターが使用されなくなったデータを自動的に収集するため、有効期間を事前にコンパイラーに伝える必要がないことです。ただし、実行時に動的にメモリを割り当てる必要があるため、アクセス速度が遅いという欠点があります。
3. Java には 2 種類のデータ型があります。
1 つはプリミティブ型で、int、short、long、byte、float、double、boolean、char の合計 8 種類があります (文字列の基本型はないことに注意してください)。このような定義は int a = 3; long b = 255L; のような形式で定義されており、自動変数と呼ばれます。自動変数はクラスのインスタンスではなくリテラル値を格納することに注意してください。つまり、ここにはクラスがありません。たとえば、 int a = 3; ここで、 a は int 型を指す参照であり、リテラル値 3 を指します。これらのリテラル値のデータは、サイズと有効期間がわかっています(これらのリテラル値は特定のプログラム ブロック内で固定的に定義され、フィールド値はプログラム ブロックが終了すると消えます)。スタック上に存在します。
さらに、スタックには、スタックに格納されたデータを共有できるという非常に重要な特殊機能があります。次のようにも定義するとします。
int a = 3;
int b = 3;
コンパイラはまず int a = 3 を処理し、まずスタック上に変数 a への参照を作成し、次にリテラル値 3 を持つアドレスを検索します。見つからない場合は、リテラルを格納するアドレスを開きます。値が 3 の場合、a はアドレス 3 を指します。次に、int b = 3 が処理されます。b の参照変数を作成した後、スタック上にリテラル値 3 がすでに存在するため、b は 3 のアドレスを直接指します。このように、a と b が同時に 3 を指す状況が発生します。
このリテラル値の参照はクラス オブジェクトの参照とは異なることに注意することが重要です。 2 つのクラス オブジェクトの参照が同時に同じオブジェクトを指しているとします。一方のオブジェクト参照変数がオブジェクトの内部状態を変更すると、もう一方のオブジェクト参照変数はその変更を即座に反映します。対照的に、リテラル参照の値を変更しても、そのリテラルへの別の参照の値も変更されることはありません。上の例のように、a と b の値を定義した後、a=4 に設定すると、b は 4 にはなりませんが、3 に等しくなります。コンパイラ内で、a = 4; が発生すると、スタック上にリテラル値 4 があるかどうかを再検索し、存在しない場合は、値 4 がすでに存在する場合はアドレスを再度開きます。を指定すると、このアドレスを直接指すことになります。したがって、a の値が変化しても b の値には影響しません。
もう 1 つは、Integer、String、Double、および対応する基本データ型をラップするその他のクラスなどのラッパー クラス データです。これらのタイプのデータはすべてヒープ内に存在します。Java は new () ステートメントを使用して、実行時に必要に応じて動的に作成されることをコンパイラーに明示的に伝えるため、より柔軟ですが、時間がかかるという欠点があります。 4. 文字列は特殊なラッパー型データです。つまり、 String str = new String( "abc" ); の形式で作成することも、 String str = "abc" ; の形式で作成することもできます (比較のために、JDK 5.0 より前では、 Integer i = 3; 式は、String を除いてクラスとリテラル値を区別して使用できないため、コンパイラは Integer i = new Integer(3) の変換を実行します。背景) 。前者は標準化されたクラス作成プロセスです。つまり、Java ではすべてがオブジェクトであり、オブジェクトはクラスのインスタンスであり、すべて new() の形式で作成されます。 DateFormat クラスなどの Java の一部のクラスは、クラスの getInstance() メソッドを通じて新しく作成されたクラスを返すことができますが、これはこの原則に違反しているようです。あまり。このクラスはシングルトン パターンを使用してクラスのインスタンスを返しますが、このインスタンスは new() を通じてクラス内で作成され、getInstance() によってこの詳細が外部から隠蔽されます。では、なぜ String str = "abc" ; では new() を通じてインスタンスが作成されないのでしょうか。それは上記の原則に違反しているのでしょうか。実際には違います。
5. String str = "abc" の内部の仕組みについて。 Java は内部でこのステートメントを次のステップに変換します。
(1) まず、String クラスに str という名前のオブジェクト参照変数を定義します。
(2) スタックを検索して値「abc」を持つアドレスがあるかどうかを確認します。存在しない場合は、リテラル値「abc」を持つアドレスを開き、String クラスの新しいオブジェクト o を作成し、 o の文字 文字列値はこのアドレスを指し、参照されるオブジェクト o はスタック上のこのアドレスの隣に記録されます。値「abc」を持つアドレスがすでに存在する場合、オブジェクト o が見つかり、o のアドレスが返されます。
(3) str はオブジェクト o のアドレスを指します。
通常、String クラスの文字列値は直接保存されることに注意してください。ただし、この場合の String str = "abc"; と同様に、文字列値はスタックに格納されたデータへの参照を保存します。
この問題をよりよく説明するために、次のコードを通じて検証できます。
文字列 str1 = "abc" ;
文字列 str2 = "abc" ;
System.out.println(str1==str2); //true
これは 2 つの文字列の値が等しいかどうかを比較するため、ここでは str1.equals(str2); を使用しないことに注意してください。 == 記号は、JDK の指示に従って、両方の参照が同じオブジェクトを指している場合にのみ true を返します。ここで確認したいのは、str1 と str2 が両方とも同じオブジェクトを指しているかどうかです。
結果は、JVM が 2 つの参照 str1 と str2 を作成しましたが、オブジェクトは 1 つだけ作成し、両方の参照がこのオブジェクトを指していることを示しています。
さらに一歩進んで、上記のコードを次のように変更してみましょう。
文字列 str1 = "abc" ;
文字列 str2 = "abc" ;
str1 = "bcd" ;
System.out.println(str1 + "," + str2);
System.out.println(str1==str2); //false
これは、割り当ての変更によりクラス オブジェクト参照が変更され、str1 が別の新しいオブジェクトを指すことを意味します。そして、str2 は依然として元のオブジェクトを指します。上の例では、str1 の値を「bcd」に変更すると、JVM はスタック上にこの値を格納するアドレスがないことを検出したため、このアドレスを開いて、文字列値がこのアドレスを指す新しいオブジェクトを作成しました。 。
実際、String クラスは不変になるように設計されています。値を変更したい場合は変更できますが、JVM は実行時に新しい値に基づいて新しいオブジェクトを静かに作成し、このオブジェクトのアドレスを元のクラスへの参照に返します。この作成プロセスは完全に自動化されていますが、結局のところ、より多くの時間がかかります。時間要件に敏感な環境では、特定の悪影響が生じる可能性があります。
元のコードを再度変更します。
文字列 str1 = "abc" ;
文字列 str2 = "abc" ;
str1 = "bcd" ;
文字列 str3 = str1;
System.out.println(str3);
文字列 str4 = "bcd" ;
System.out.println(str1 == str4); //true
オブジェクト str3 への参照は、str1 が指すオブジェクトを直接指します (str3 は新しいオブジェクトを作成しないことに注意してください)。 str1 が値を変更した後、文字列参照 str4 を作成し、str1 が値を変更したために作成された新しいオブジェクトをポイントします。今回は str4 が新しいオブジェクトを作成していないことがわかり、スタック内のデータの共有が再び実現されました。
もう一度次のコードを見てみましょう。
文字列 str1 = 新しい文字列( "abc" );
文字列 str2 = "abc" ;
System.out.println(str1==str2); //false
2 つの参照が作成されます。 2 つのオブジェクトが作成されます。 2 つの参照はそれぞれ 2 つの異なるオブジェクトを指します。
文字列 str1 = "abc" ;
文字列 str2 = 新しい文字列( "abc" );
System.out.println(str1==str2); //false
2 つの参照が作成されます。 2 つのオブジェクトが作成されます。 2 つの参照はそれぞれ 2 つの異なるオブジェクトを指します。
上記の 2 つのコードは、 new () を使用して新しいオブジェクトを作成する限り、そのオブジェクトはヒープ内に作成され、その文字列値がスタック内のデータと同じであっても個別に保存されることを示しています。 、スタック内のデータとは共有されません。
6. データ型ラッパークラスの値は変更できません。 String クラスの値を変更できないだけでなく、すべてのデータ型ラッパー クラスも内部値を変更できません。 7. 結論と提案:
(1) String str = "abc"; のような形式でクラスを定義する場合、String クラスのオブジェクト str を作成することが当然と考えられます。罠の心配も!オブジェクトが作成されていない可能性があります。確かなのは、String クラスへの参照が作成されたことだけです。この参照が新しいオブジェクトを指すかどうかについては、 new () メソッドを通じて明示的に新しいオブジェクトを作成しない限り、コンテキストに応じて考慮する必要があります。したがって、より正確な記述は、String クラスのオブジェクトを指す参照変数 str を作成するということです。このオブジェクト参照変数は、値が "abc" の String クラスを指します。これを明確に理解することは、プログラム内の見つけにくいバグを排除するのに非常に役立ちます。
(2) String str = "abc"; を使用すると、JVM がスタック内のデータの実際の状況に基づいて新しいオブジェクトを作成する必要があるかどうかを自動的に判断するため、プログラムの実行速度がある程度向上します。 。 String str = new String("abc"); のコードは、文字列値が等しいかどうか、新しいオブジェクトを作成する必要があるかどうかに関係なく、常に新しいオブジェクトがヒープに作成されるため、負荷が増加します。番組上で。この考え方はフライウェイトモードの考え方であるはずですが、JDKの内部実装がこのモードを適用しているかどうかは不明です。
(3) パッケージ化クラスの値が等しいかどうかを比較する場合は、equals() メソッドを使用します。2 つのパッケージ化クラスの参照が同じオブジェクトを指しているかどうかをテストする場合は、== を使用します。
(4) String クラスの不変の性質により、String 変数の値を頻繁に変更する必要がある場合は、プログラムの効率を向上させるために StringBuffer クラスの使用を検討する必要があります。