String和StringBuffer和StringBuilder的常识性知识

String StringBuilder StringBuffer的使用

一、String:

1.String对象是不可变的:

      String对象是不可变的。查看JDK文档就可以发现,String类中每一个看起来会修改String值的方法,实际上都是重新创建一个全新的String对象,以包含修改后的字符串内容,而最初的String对象则丝毫没有改变。

public class Test {
	public static void main(String[] args) {
		String s0= "hello";
		System.out.println("s0 之前的值 : "+s0); //第一次打印
		String s1 = s0.toUpperCase();  //将s0包含的字符串转成大写
		System.out.println("s0在toUpperCase 之后的值 : " + s0); //s0的值没有变化
		System.out.println("s1的值 : " + s1);
		System.out.println("比较s1与s0的引用 如果结果为false 则表示二者不是同一个引用,也就是重新生成了对象。");
		System.out.println(s1 == s0);
	}
}
运行结果:

s0 之前的值 : hello
s0在toUpperCase 之后的值 : hello
s1的值 : HELLO
比较s1与s0的引用 如果结果为false 则表示二者不是同一个引用,也就是重新生成了 对象。
false

2.当需要经常改变字符串内容时,应该避免使用String:

         因为String对象是不可变得,因而在改变String对象的内容时,都需要借助于StringBuilder类和它的append()方法,因而当更改 一次String对象的内容,会产生一个String对象和一个StringBuilder对象。下面是借助于javap工具查看编译后的字节码:

public class Test {
	public static void main(String[] args) {
		String s0 = "booth";
		s0 = s0 + ".sun";
		System.out.println(s0);

	}
}
利用JDK自带的javap -c Test 反汇编生成的字节码( 只截取对本问题分析有用的部分 ):

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String booth
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder</span>
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V</span>
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String .sun
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;</span>
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_1
      23: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_1
      27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      30: return
}

         注意代码段中红色部分,从这部分可以看出在改变字符串内容时是需要额外产生一个StringBuilder对象的。在加上重新生成的String对象(上面javap生成的字节码中第19行,调用了StringBuildertoString方法,重新生成一个String对象),这样每次改变字符串内容时,隐含的将产生两个额外对象。这样如果这个改变字符串的操作方法一个for循环,那么将会产生很多的无用对象,这将增加GC的工作量。

3.编译器对于String的优化:

String对象的字符串”+”操作是要借助于StringBuilder操作的,因而这个时候String对象的运行速度和效率是低于StringBuilder 的,但特别的是以下的字符串拼接操作中,Stirng效率要远远比StringBuffer快的很多:

String s1 = “This is only a ” + “simple” + “test”;

StringBuilder sb = new StringBuilder(“This is only a ”).append(“ simple ”).append(“ test ”);

这里是因为javac在编译的时候,会进行相应的优化操作,在编译器眼里:

String s1 = “This  is  only  a ” + “ simple ” + “ test ”;其实就是;

String s1 = “This  is  only  a  simple test”; 

    因而这个变量在编译的时候就已经构建好了,运行时,肯定就速度很快了。但是如果你的字符串是来自于另外的Stirng对象的话,则又需要借助于StringBuilder。如;

String s1 = “This is only a ”;

String s2 = “simple”;

String s3 = “test”;

s1 = s1 + s2 + s3;

    这时候 JVM 会规规矩矩的按照原来的方式去做, S1 对象的生成速度就不像刚才那么快了。

二、StirngBufferStringBuilder

1.简介(复制于JDK API):

       Java.lang.StringBuffer 线程安全的可变字符序列。类似于 String 的字符串缓冲区,但不能修改。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。

       每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。 JDK 5.0 开始,为该类增添了一个单个线程使用的等价类,即 StringBuilder 。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。

但是如果将 StringBuilder 的实例用于多个线程是不安全的。需要这样的同步,则建议使用 StringBuffer 

2.修改操作:

StringBuffer对象内容进行修改时,每次结果都是对StringBuffer对象本身进行操作,而不是生成一个新的对象,在改变对象的引用。 

源代码:
public static void main(String[] args) {
		StringBuffer sb = new StringBuffer("booth");
		StringBuffer sb2 = sb.append("sun").append("jxau");
		System.out.println(sb == sb2);
}
输出结果 : true<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

三、 总结:

在大部分情况下:

StringBuilder > StringBuffer  > String

既然有这样的推导结果了,我们做个测试验证一下:

测试代码如下:

package com.booth.sun.string;

import java.io.StringBufferInputStream;

public class StringTest {

	/** Creates a new instance of Splitting */
	final static int ttime = 10000;// 测试循环次数

	public StringTest() {
	}

	public void test(String s) {
		long begin = System.currentTimeMillis();
		for (int i = 0; i < ttime; i++) {
			s += "add";
		}
		long over = System.currentTimeMillis();
		System.out.println(" 操作 " + s.getClass().getName() + " 类型使用的时间为: "
				+ (over - begin) + " 毫秒 ");
	}

	public void test(StringBuffer s) {
		long begin = System.currentTimeMillis();
		for (int i = 0; i < ttime; i++) {
			s.append("add");
		}
		long over = System.currentTimeMillis();
		System.out.println(" 操作 " + s.getClass().getName() + " 类型使用的时间为: "
				+ (over - begin) + " 毫秒 ");
	}

	public void test(StringBuilder s) {
		long begin = System.currentTimeMillis();
		for (int i = 0; i < ttime; i++) {
			s.append("add");
		}
		long over = System.currentTimeMillis();
		System.out.println(" 操作 " + s.getClass().getName() + " 类型使用的时间为: "
				+ (over - begin) + " 毫秒 ");
	}

	// 对 String 直接进行字符串拼接的测试
	public void test2() {
		String s2 = "abadf";
		long begin = System.currentTimeMillis();
		for (int i = 0; i < ttime; i++) {
			String s = s2 + s2 + s2;
		}
		long over = System.currentTimeMillis();
		System.out.println(" 操作字符串对象引用相加类型使用的时间为: " + (over - begin) + " 毫秒 ");
	}

	public void test3() {
		long begin = System.currentTimeMillis();
		for (int i = 0; i < ttime; i++) {
			String s = "abadf" + "abadf" + "abadf";
		}
		long over = System.currentTimeMillis();
		System.out.println(" 操作字符串相加使用的时间为: " + (over - begin) + " 毫秒 ");
	}

	public static void main(String[] args) {
		String s1 = "abc";
		StringBuffer sb1 = new StringBuffer("abc"); // 线程安全的 速度慢点
		StringBuilder sb2 = new StringBuilder("abc"); // 线程不安全的 速度快点
		StringTest t = new StringTest();
		t.test(s1);
		t.test(sb1);
		t.test(sb2);
		t.test2();
		t.test3();
	}
}
运行结果:

 操作 java.lang.String 类型使用的时间为: 238 毫秒 
 操作 java.lang.StringBuffer 类型使用的时间为: 1 毫秒 
 操作 java.lang.StringBuilder 类型使用的时间为: 1 毫秒 
 操作字符串对象引用相加类型使用的时间为: 3 毫秒 
 操作字符串相加使用的时间为: 0 毫秒 
    其实我这里测试并不是很公平,因为都放在了一起以先后顺序进行,测试方法中间没有考虑到JVM的GC收集前面产生的无引用对象垃圾而对执行过程的中断时间。

参考资料: 是 String , StringBuffer 还是 StringBuilder 


深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值