JavaRush /Java Blog /Random-JA /Java の文字列 (クラス java.lang.String)
Viacheslav
レベル 3

Java の文字列 (クラス java.lang.String)

Random-JA グループに公開済み

導入

プログラマーへの道は複雑で長いプロセスです。そしてほとんどの場合、画面に Hello World を表示するプログラムから始まります。Java も例外ではありません (「レッスン: 「Hello World!」アプリケーション」を参照)。ご覧のとおり、メッセージは以下を使用して出力されます。System.out.println("Hello World!"); Java API を見ると、System.out.printlnメソッドはString を入力パラメータとして受け取ります。このタイプのデータについて説明します。

一連の文字としての文字列

実際、英語を翻訳したStringは文字列です。そうです、String 型はテキスト文字列を表します。テキスト文字列とは何ですか? テキスト文字列は、互いに続くある種の順序付けされた文字のシーケンスです。記号はcharです。シーケンス – シーケンス。はい、まったくその通りです。String は の実装ですjava.lang.CharSequence。そして、String クラス自体の内部を見ると、その中には文字の配列以外には何もありません。かなり単純なコントラクトが private final char value[]; あります。java.lang.CharSequence
Java の文字列 (クラス java.lang.String) - 1
要素の数を取得するメソッド、特定の要素を取得するメソッド、要素のセットと toString メソッド自体を取得するメソッドがあり、これが返されます) Java 8 で導入されたメソッドを理解することはさらに興味深いものであり、これは次のとおりです。 :chars()そして、codePoints() Oracle のチュートリアル「プリミティブ データの型」を思い出してください。char は ですsingle 16-bit Unicode character。つまり、本質的に char は、0 から 65535 までの数値を表す int (32 ビット) の半分のサイズの型です (10 進数値を参照) ASCII テーブル) 。つまり、必要に応じて、char を int として表すことができます。そして Java 8 はこれを利用しました。Java のバージョン 8 以降、プリミティブ int を操作するためのストリームであるIntStream が導入されました。したがって、charSequence では、char または codePoint を表す IntStream を取得できます。それらに進む前に、このアプローチの便利さを示す例を見てみましょう。Tutorialspoint オンライン Java コンパイラーを使用してコードを実行してみ ましょう。
public static void main(String []args){
        String line = "aaabccdddc";
        System.out.println( line.chars().distinct().count() );
}
この簡単な方法で、多数のユニークなシンボルを取得できるようになりました。

コードポイント

以上、イワナについて見てきました。現時点では、これらがどのような種類のコードポイントであるかは明らかではありません。codePoint の概念が登場したのは、Java が登場したとき、文字をエンコードするには 16 ビット (int の半分) で十分だったためです。したがって、Java の char は UTF-16 形式 (「Unicode 88」仕様) で表されます。その後、文字をサロゲート ペア (2 文字) として表現するという概念の Unicode 2.0 が登場しました。これにより、可能な値の範囲を int 値まで拡張できるようになりました。詳細については、stackoverflow:「char をコードポイントと比較する?」を参照してください。UTF-16 については、 Characterの JavaDoc にも記載されています。JavaDoc には次のように書かれています。 In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogates range, (\uD800-\uDBFF), the second from the low-surrogates range (\uDC00-\uDFFF). これを標準のアルファベットで再現するのは非常に困難です (おそらく不可能です)。ただし、記号は文字や数字で終わるわけではありません。日本では、彼らは絵文字、つまり表意文字や顔文字の言語としてエンコードするのが非常に難しいものを思いつきました。これに関する興味深い記事がウィキペディアにあります:「絵文字」。絵文字の例を見つけてみましょう。たとえば、「Emoji Ghost」です。見てわかるように、同じ codePoint がそこにも示されています (値 = U+1F47B)。16 進数形式で表示されます。10 進数に変換すると、128123 が得られます。これは、許容される 16 ビットを超えています (つまり、65535 を超えています)。それをコピーしましょう:
Java の文字列 (クラス java.lang.String) - 2
残念ながら、JavaRush プラットフォームはテキスト内のそのような文字をサポートしていません。したがって、以下の例では、文字列に値を挿入する必要があります。したがって、ここでは簡単なテストを理解します。
public static void main(String []args){
	    String emojiString = "Вставте сюда эмоджи через ctrl+v";
	    //На один emojiString приходится 2 чара (т.к. не влезает в 16 бит)
	    System.out.println(emojiString.codePoints().count()); //1
	    System.out.println(emojiString.chars().count()); //2
}
ご覧のとおり、この場合、1 つの codePoint が 2 つの文字に対応します。これが魔法です。

キャラクター

上で見たように、Java の文字列は char で構成されます。プリミティブ型を使用すると値を格納できますが、java.lang.Characterプリミティブ型のラッパーを使用すると、このシンボルを使用して多くの便利な操作を行うことができます。たとえば、文字列を大文字に変換できます。
public static void main(String[] args) {
    String line = "организация объединённых наций";
    char[] chars = line.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        if (i == 0 || chars[i - 1] == ' ') {
            chars[i] = Character.toUpperCase(chars[i]);
        }
    }
    System.out.println(new String(chars));
}
そうですね、さまざまな興味深い点があります: isAlphabetic()isLetter()isSpaceChar()isDigit()isUpperCase()isMirrored()(たとえば、括弧。「(」には鏡像 ')' があります)。

文字列プール

Java の文字列は不変、つまり定数です。これは、 java.lang.Stringクラス自体の JavaDoc にも示されています。2 番目に、これも非常に重要ですが、文字列はリテラルとして指定できます。
String literalString = "Hello, World!";
String literalString = "Hello, World!";
つまり、上で述べたように、引用符で囲まれた文字列は実際にはオブジェクトです。ここで疑問が生じます。文字列を頻繁に使用し、それらが同じになることがよくある場合 (たとえば、「エラー」または「成功」というテキスト)、文字列が毎回作成されないようにする方法はあるのでしょうか? ちなみに、キーを文字列にすることができる Maps がまだあります。したがって、同じ文字列を異なるオブジェクトにすることは絶対にできません。そうしないと、マップからオブジェクトを取得できなくなります。Java 開発者は考え、考え、そして String Pool を思いつきました。これは文字列が保存される場所であり、文字列キャッシュと呼ぶことができます。すべての行自体がそこに到達するわけではなく、リテラルによってコード内で指定された行のみがそこに到達します。自分でプールにラインを追加することもできますが、それについては後ほど説明します。したがって、メモリ内のどこかにこのキャッシュがあります。当然の質問です。このプールはどこにありますか? これに対する答えは、stackoverflow で見つけることができます。「Java の文字列定数プールはヒープまたはスタックのどこにありますか?」」これは、ヒープ メモリ内の特別なランタイム定数プール領域に配置されます。ランタイム定数プールは、仮想マシンによってクラスまたはインターフェイスがメソッド領域(Java 仮想マシン内のすべてのスレッドがアクセスできるヒープ内の特別な領域) から作成されるときに割り当てられます。ストリングプールは私たちに何をもたらしますか? これにはいくつかの利点があります。
  • 同じ種類のオブジェクトは作成されません
  • 参照による比較は、等号による文字ごとの比較よりも高速です
しかし、作成したオブジェクトをこのキャッシュに入れたい場合はどうすればよいでしょうか? 次に、特別なメソッドを用意します。String.intern このメソッドは、文字列を文字列プールに追加します。これは、(整数の場合のように) 配列形式の単なるある種のキャッシュではないことに注意してください。インターンメソッドは「ネイティブ」として指定されます。これは、メソッド自体が別の言語 (主に C++) で実装されていることを意味します。基本的な Java メソッドの場合、その他のさまざまな最適化を JVM レベルで適用できます。一般に、ここでは魔法が起こります。インターンに関する次の投稿は興味深いものです: https://habr.com/post/79913/#comment_2345814 そして、それは良いアイデアのように思えます。しかし、これは私たちにどのような影響を与えるのでしょうか? でも本当に影響はあります)
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal");
    System.out.println(test == test2);
}
ご覧のとおり、行は同じですが、結果は false になります。== は値ではなく参照によって比較するためです。そして、これがその仕組みです:
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test == test2);
}
まだ新しい文字列を作成することに注意してください。つまり、インターンはキャッシュから文字列を返しますが、キャッシュ内で検索した元の文字列はクリーニングのために破棄されます。他の誰も彼のことを知りません。これは明らかにリソースの不必要な消費です =( したがって、突発的で検出が難しいエラーをできるだけ避けるために、常に等号を使用して文字列を比較する必要があります。
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test.equals(test2));
}
Equals は文字列の文字ごとの比較を実行します。

連結

覚えているように、行は追加できます。覚えているとおり、文字列は不変です。それで、それはどのように機能するのでしょうか?そうです、追加されるオブジェクトのシンボルで構成される新しい行が作成されます。プラス連結の仕組みには何百万ものバージョンがあります。毎回新しいオブジェクトがあると考える人もいれば、何か別のオブジェクトがあると考える人もいます。しかし、正しいのは 1 人だけかもしれません。そして、その誰かが javac コンパイラです。オンライン コンパイラサービスを使用して、次を実行してみましょう。
public class HelloWorld {

    public static void main(String[] args) {
        String helloMessage = "Hello, ";
        String target = "World";
        System.out.println(helloMessage + target);
    }

}
これを zip アーカイブとして保存し、ディレクトリに抽出して実行しましょう。javap –c HelloWorld そして、ここですべてがわかります。
Java の文字列 (クラス java.lang.String) - 3
もちろん、ループ内では StringBuilder を介して連結を自分で行う方が良いでしょう。これは、ある種の魔法によるものではなく、サイクルの前に StringBuilder が作成され、サイクル自体では追加のみが行われるようにするためです。ところで、ここでもう一つ興味深いことがあります。「 Java での文字列処理」という素晴らしい記事があります。パート I: String、StringBuffer、StringBuilder。」コメントには有益な情報がたくさんあります。たとえば、ビューを連結するときに、new StringBuilder().append()...toString()デフォルトで有効になっている -XX:+OptimizeStringConcat オプションによって制御される固有の最適化が有効になることが指定されています。本質的 - 「内部」と訳されます。JVM はそのようなものを特別な方法で処理し、JNI の追加コストを発生させずにネイティブとして処理します。詳細は「HotSpot VM の組み込みメソッド」を参照してください。

StringBuilder と StringBuffer

上で見たように、StringBuilder は非常に便利なツールです。文字列は不変です。不変。そして畳んでみたいと思います。したがって、私たちに役立つ 2 つのクラス、StringBuilder と StringBuffer が与えられています。2 つの主な違いは、StringBuffer が JDK1.0 で導入されたのに対し、StringBuilder は不要なメソッド同期によるオーバーヘッドの増加を排除するために StringBuffer の非同期バージョンとして Java 1.5 に導入されたことです。これらのクラスは両方とも、抽象クラス AbstractStringBuilder (変更可能な文字シーケンス) の実装です。チャームの配列が内部に保存され、value.length * 2 + 2 のルールに従って展開されます。デフォルトでは、StringBuilder のサイズ (容量) は 16 です。

匹敵します

文字列は比較可能です。CompareTo メソッドを実装します。これは、文字ごとの比較を使用して行われます。興味深いことに、2 つの文字列から最小の長さが選択され、それに対してループが実行されます。したがって、compareTo は、最小の文字列長までの最初の不一致文字の int 値の差を返すか、すべての文字が最小文字列長内で一致する場合は文字列の長さの差を返します。この比較は「辞書編集的」と呼ばれます。

Java 文字列の操作

String には便利なメソッドが多数あります。
Java の文字列 (クラス java.lang.String) - 4
文字列を操作するためのタスクは数多くあります。たとえば、Coding Batの場合です。coursera には「 Algorithms on Strings 」というコースもあります。

結論

このクラスの概要を簡単に説明するだけでも、かなりのスペースを占めます。それだけではありません。JPoint 2015 のレポートを見ることを強くお勧めします: Alexey Shipilev - Catechism java.lang.String
#ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION