Java Learning 7:String、StringBuffer、StringBuilderのアプリケーション学習とソースコード分析

1. Stringクラスの学習と適用:

1.1。Stringクラスのオブジェクトは最終的なものです

まず、Javaでは、ほとんどの文字列はStringクラスを使用してインスタンス化および操作されます。Javaでは、文字列はオブジェクトであり、文字列変数はこのオブジェクトを実行するための参照です。ただし、この参照は定数参照(C ++の概念)です。つまり、参照されるオブジェクトのコンテンツは、定数参照を変更しても変更できません。Javaでは、定数参照を誤って変更してStringオブジェクトのコンテンツが変更されるのを防ぐため、添え字と角括弧を使用してStringオブジェクトのコンテンツにアクセスすることはできませんたとえば、次のプログラムは、添え字を介してアクセスするメソッドを提供します。

public static void main(String[] args){
    
    
		String str = new String("Hello World");
		System.out.println(str);
		
		for(int i = 0;i < str.length();++i)
			System.out.print(str.charAt(i) + " ");
			// 不支持写法:System.out.print(str[i] + " ");
	}

考えてみてください、組み込みメソッドはアクセスできるので、変更できますか?
直接変更することはできません!誰もがここで注意を払う必要があります:

原理:

Javaでは、いくつかの組み込みメソッドを介してStringオブジェクトを変更できますが、これらの変更はオブジェクトが指すコンテンツを実際に変更するのではなく、新しいオブジェクトを作成し、古いオブジェクトのコンテンツをコピーしてから追加します変更して、参照を再度ポイントさせてください!次に、参照で参照されていないオブジェクトは、ガベージコレクションメカニズムによって自動的にリサイクルされます。これは安全ですが、リソースの浪費でもあります。結局、ガベージコレクションには時間がかかります。

いくつかの便利な組み込みメソッドを学習します。

工法:

ここに画像の説明を挿入

ここでは、最後の3つに焦点を当てます。プログラムの例を見てください。
	public static void main(String[] args){
    
    
		char[] str1 = {
    
    'H', 'e', 'l', 'l', 'o'};
		System.out.println(str1);
		System.out.println(str1.length);
		
		String str2 = new String("Hello");
		String str3 = new String(str1);
		String str4 = new String(str3);
		
		System.out.println("长度的变化是   一开始是:" + str1.length + "后来是:" + str3.length());
		
		System.out.println(str2);
		System.out.println(str3);
		System.out.println(str4);
	}		

プログラムの出力は次のとおりです。
ここに画像の説明を挿入

これは何を示していますか?

注1: '\ 0'を文字配列から文字列の末尾に追加しないでください
注2:文字列は文字配列からインスタンス化することも、文字列をインスタンス化することもできます
その他の一般的に使用される方法:

ここに画像の説明を挿入
ここに画像の説明を挿入

最も重要な(個人的に考える)は次のとおりです:
length():文字列の長さを返します( '\ 0'なし)
charAt(index):(index + 1)文字を返します。これは、添え字アクセス(ただし読み取り専用)の
部分文字列と同じです。(s、t):区間[s、t)の部分文字列の最初のアドレスを返します(参照と同等)
compareTo(s1、s2):差(s1-s2)を返します。差は次のとおり
です。文字がパラメーターの最初の文字と等しい場合、2番目の文字はパラメーターの2番目の文字と比較され、以下同様に、比較された文字の1つまたは比較された文字が比較されるまで、それが長さになります。違いこれは、文字列のサイズを比較する効果的な方法です。

すべての比較が完了したら、文字の長さを比較します
replace(OldChar、NewChar):文字列内のすべてのOldCharをNewChar
valueOf(Obj)で置き換えます:オブジェクトインスタンスObjを完全に文字列(元の文字列)で置き換えます変化する)

例を試してみましょう:
	public static void main(String[] args){
    
    
		
		// 实验方法:charAt(index)、length():
		String str1 = new String("abcde");
		for(int i = 0;i < str1.length();++i)
			System.out.print(str1.charAt(i) + " ");
		System.out.println();
		
		// 实验方法:substring(s, t):
		String str2 = new String(str1.substring(0, 3));
		System.out.print("str1 = " + str1 + "  str2 = " + str2 + ", str1 - str2 = " + str1.compareTo(str2));
	
		// 实验方法:A.compareTo(B):
		System.out.println();
		String str3 = new String("abcd");
		String str4 = new String("abec");
		System.out.println("str4 - str3 = " + str4.compareTo(str3));
		
		String str5 = new String("abecg");
		System.out.println("str5 - str4 = " + str5.compareTo(str4));
		
		// 实验方法:A.replace(Old, New)、valueOf(Obj);
		String str6 = "aabbcdeea";
		str6 = str6.replace('a', 'x');
		System.out.println("str6 = " + str6);
		
		double d = 3.1415926;
		String str7 = new String();
		str7 = String.valueOf(d);// valueOf()是静态方法
		System.out.println("str7 = " + str7);
			
	}	

実験結果を見てください:
ここに画像の説明を挿入

学んだことを見てみましょうか?

まず、valueOf(Obj)は静的メソッドです!静的メソッドはオブジェクトレベルではなくクラスレベルにあるため、静的メソッドを呼び出すためにメソッドを使用する必要があります:クラス名
。2番目を呼び出すためのメソッド名:新しいオブジェクトへの参照を返す種類のメソッドについては、戻り値を受け取る参照名。それ以外の場合、このメソッドは無効になる可能性があります。(覚えて!)

1.2。Stringクラスのソースコードの選択された読み取り:

1.2.1、compareTo();ソースコード:

 public int compareTo(String anotherString) {
    
    
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
    
    
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
    
    
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }
ソースコード分析:

このコードは高度なものではなく、主に一般的に使用されるメソッドの一部をソースコードを通じて学習します。このコードから、2つの文字列を比較した場合、共通の長さに等しくない文字がある場合、2つの文字の差が返されます。この差は2つの文字のASCiiコードです差。共通の長さ部分の文字列がまったく同じである場合、長さの違いが返されます。この方法は確かに文字列を効果的に比較できますが、効率はそれほど高くありません。1つは、Java文字列のソースコードの効率が高すぎないことです。文字マッチングではBFアルゴリズムが使用されるため、解釈されません。

1.2.2、等しい(Obj);ソースコード:

 public boolean equals(Object anObject) {
    
    
        if (this == anObject) {
    
    
            return true;
        }
        if (anObject instanceof String) {
    
    
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
    
    
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
    
    
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
このソースコードは2つのことを教えてくれます。

最初のポイント:
オブジェクトのパラメーターに関しては、まずそれを判断して例外処理を行う必要があります!
2番目のポイント:
Stringオブジェクトのインスタンス化のため、そのようなディープコピーの場合、2つのオブジェクトは同じアドレスを指し、これら2つのオブジェクトの参照は同じであるため、判断する必要はなく、直接trueを返します。操作は本当に学ぶ価値があります。

1.2.3、hash'code();のソースコード:

3番目のソースコード:Stringオブジェクトのhashcode()を書き換えます。
public int hashCode() {
    
    
        int h = hash;
        if (h == 0 && value.length > 0) {
    
    
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
    
    
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
        // 要我我会改进:
        // return h & 0x7fffffff;
    }

このコードのコードは、非常に一般的な文字列ハッシュアルゴリズムであるBKDRHashアルゴリズムです。一般に、ハッシュシードが使用されます。シード= 31、17、...、およびその他の素数で、多項式がハッシュの値として計算されますが、一般的にこの方法で処理されるハッシュ値は非常に大きく、モジュロである必要があり、モジュロはハッシュの競合を引き起こすため、通常、ハッシュの競合に対処するために多くのコードが追加されます。正直に言うと、このソースコードを見てがっかりしたのは、Stringのハッシュコードが実際にビットビットの高いデータと競合を処理する適切な方法を思い付くと思っていたからです...実際、アルゴリズムの本に書かれているほどではありません。 ...

2つ、StringBufferクラスとStringBuilderクラス

之前我们说了,String类的对象是final的,也就是说,如果我们想要改变String对象的值,其实是开辟了新的空间,获取新的内容,然后让对象引用指向新的内容,这样会浪费空间和时间,效率很低,所以我们有了在 字符串需要被修改的时候 ,运用StringBuffer和StringBuilder去提高效率,但是这俩的区别也还是蛮大的,我们来学习一下。

2.1、它俩和String类对象的区别:

最主要的区别就是StringBuffer和StringBuilder的对象不是final的,是可以直接在字符串上进行修改的,不需要开辟新的空间,
相比较之下,StringBuilder的效率要高于StringBuffer!但是StringBuilder不能做同步访问(多线程),所以在要求线程安全(能同步访问)的情况下,就要用效率相对较低的StringBuffer了!但是由于效率最高的还是StringBuilder ,所以推荐在一般情况下(不需要同步访问),就使用StringBuilder。
我们用一张图来描述它们的继承关系:
ここに画像の説明を挿入

2.2、StringBuffer和StringBuilder的实例化:

StringBuffer和StringBuilder的实例化都必须使用构造函数去实例化,不能像String那样,类似拷贝构造地去构造,那样是不可以的(其实那样也是不安全的,那是一种深拷贝……)
实例化方法:

public static void main(String[] args){
    
    
		
		StringBuffer str1 = new StringBuffer("I am StringBuffer");
		StringBuilder str2 = new StringBuilder("I am StringBuilder");
		
		System.out.println(str1);
		System.out.println(str2);
	}	

结果:
ここに画像の説明を挿入

但是凡事都有例外,这个null就是可以不借助构造函数构造空字符串:

public static void main(String[] args){
    
    
		StringBuilder str1 = null;
		if(null == str1)
			System.out.println("Yes, str1 is null");
		StringBuffer str2 = null;
		if(null == str2)
			System.out.println("Yes, str2 is null");
	}	

结果:
ここに画像の説明を挿入
但是这样初始化和StringBuffer str = new StringBuffer();是完全不一样的,因为后面这个初始化空串其实还分配了16个缓冲区,是有空间的,是可以访问的。

常用的StringBuffer的构造函数:

ここに画像の説明を挿入
从这个构造函数我们可以知晓,为什么StringBuffer和StringBuilder是可以直接在字符串上进行操作,不需要重新开辟新的字符串对象去引用了。因为这些字符串本身自带缓冲区,这个缓冲区是适应于那种字符串长度改变的操作。
ここに画像の説明を挿入
是否可以自动增加容量呢?我们待会看源码就知晓了!

2.3、StringBuffer的常用方法:

ここに画像の説明を挿入

这些方法的几点说明:
第一:

println();是不会接受StringBuffer和StringBuilder的参数,如果需要访问需要先用toString();转换成String类型,但是这个我通过实验发现并不需要这样啊……

	public static void main(String[] args){
    
    
		StringBuffer str = new StringBuffer();
		str.append("Hello").append(" Java!");
		System.out.println(str);
	}	

ここに画像の説明を挿入
完全不受影响啊,难道是老师讲错了???我们来看看源码便知晓原因内涵:
ここに画像の説明を挿入
看到这个源码我们就知晓了,只要是输出,println();的函数参数可以是任何类型的对象 ,但是在真正输出的print里面,就只能把这个对象变成string类型,才能输出 !!

第二:

一般来说,我们要减少字符串连接符’+‘的操作,我们可以来解读一下这个过程:
比如:String str = "Hello" + "World";
这个过程实际上是:先实例化一个StringBuffer的对象Hello,然后调用"Hello".append(“World”);这个方法,去把这个字符串拼接起来,然后调用toString();方法,再最后返回一个构造函数给str,相当于把str实例化成这个整个的字符串,这样操作是很麻烦的,如果’+'的操作过多,是很不好的!效率会很低!

第四:

必要な容量が比較的大きい(16より大きい)場合は、インスタンス化時に必要な容量を追加することをお勧めします。それ以外の場合は、デフォルトの容量(16)を使用すると、比較的大きなオーバーヘッドが発生します(オーバーヘッドは拡張中です)。これは、Javaプログラミングのアイデアに由来します。この拡張アルゴリズムが後でどのように実装されるかを見てみましょう(これはc ++のベクトルと同じかもしれません。ビットごとの演算を行い、2 ^ kで拡張します)。

3つ、StringBuffer&StringBuilderソースコード:

サブクラスのソースコードは読みづらく、継承とポリモーフィズムですので、話さない場合は親クラスのソースコードを見てください。

3.1。拡張コード:

 private void ensureCapacityInternal(int minimumCapacity) {
    
    
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
    
    
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

非常に大きな容量が必要であるが、定義された容量を自分で実装しない場合(つまり、デフォルトの容量である16を使用する場合)、このメソッドを呼び出すことがよくあります。copyofメソッドはOである必要があることがわかります。メソッド(n)が何度も呼び出されるため、効率が非常に低くなります。したがって、大きな容量を使用する場合は、インスタンス化するときに容量を指定する必要があります。

他のコード:同じように感じます

しかし、私が本当に学ぶ必要があるのは、このコードの堅牢性であると思います。このソースコードの実行効率は確かに高くはありませんが、堅牢性は整っています!さらに、このスレッドの安全性の問題は、マルチスレッド、スレッドプール、およびその他の関連する知識について学ぶまで待って、それを調べてみましょう!ソースコードに関連するコードが見つからないようですが、後で追加します!

おすすめ

転載: blog.csdn.net/qq_44274276/article/details/104955546