byte\char\string\stringBuilder和stringBuffer

byte和char的区别于联系

byte 1字节,有符号,一般表示数字,-127--128
char 2字节,无符号(0-65535),java中一般用来表示一个字符如 a,’中‘;也可以用来表示数字;
java中 unicode编码 1char=2byte=16bit
https://blog.csdn.net/luoweifu/article/details/7770588 char和byte的区别

byte和char的转化---编解码
字符编码会得到字节
字节解码会得到字符
字符 <--编解码-->  字节
相同的字符因为不同的编码格式会转化为不同的字节


对计算机而言,任何数字都是二进制的,字符也是用十六进制(其实也是二进制)来表示:比如”中文”,正常情况下(即没有错误的时候)存储为”4e2d 6587”,如果charset为”gbk”,则被编码为”d6d0 cec4”,然后返回字节”d6 d0 ce c4”.如果charset为”utf8”则最后是”e4 b8 ad e6 96 87”.如果是”iso8859-1”,则由于无法编码,最后返回 “3f 3f”(两个问号)。

也就是说,在不同编码格式下,字符的底层是不一样的,但是,我们明白一个道理就好:把字符正常显示就是一个查表的过程,当我们从网络,web,DB中接受数据的时候,我们得到肯定是一串01二进制(最底层),然后我们要做到找个“表”,去翻译这些二进制串,这个表就是字符编码。如server给我的是gbk的01序列,我们却用utf-8的01序列来做翻译,当然会乱码。所以乱码的根本原因在于我们没有找到一个张合适“翻译表”去翻译对方给我们的语言。
原文链接:https://blog.csdn.net/zhiweiusetc/article/details/51582396


String

典型的 Immutable 类,被声明成为 final class,所有属性也是final的。因为其不可变性,所以所有的拼接、裁剪等都会产生新的String对象;

String的intern
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);// false

String s3 = new String("2");
s3 = s3.intern();
String s4 = "2";
System.out.println(s3 == s4);// true,因为intern方法会返回常量池中字符串的引用

常量池迁移:
1.6 常量池存在于持久代
1.7 常量池迁移到堆中
1.8 metaspace替代持久代,避免Pergemn OOM

stringBuilder部分源码

public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
先看扩容:
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}
Arrays.copyOf方法如下:
public static char[] copyOf(char[] original, int newLength) {
    char[] copy = new char[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

将String的内容拷贝到strignbuilder中
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

 System.arraycopy的参数解释如下:
     /**
     * @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied.
     */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

stringBuilder和String的关系

jdk9以前都是char[],从jdk9开始变成byte[],大于0xFF时候会占用两个字节;出于节约空间的考虑

char[],了解底层有什么作用?
声明stringBuilder的时候最好适当的声明初始化大小,避免无谓的扩容和拷贝

String a = "a";
String bc = "bc";
String s2 = a + bc;// 该代码jvm执行时等价于下面代码:
String s2 = new StringBuilder(a).append(bc).toString()
          = new StringBuilder().appen(a).append(bc).toString(); 
上面第二个等于是因为如下源码:

/**
 * Constructs a string builder initialized to the contents of the
 * specified string. The initial capacity of the string builder is
 * {@code 16} plus the length of the string argument.
 *
 * @param   str   the initial contents of the buffer.
 */
public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}

StringBuilder和StringBuffer的关系

主要是线程安全不同,这里以一个append方法为例看下源码
StringBuffer源码:

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }


StringBuilder源码:

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }


最主要的就是StringBuffer的方法中多了synchronized,加锁了,即同时仅能一个线程执行append,所以StringBuilder是线程安全的,但是效率比不上StringBuffer。

常量折叠

先看一个实例:
public static void main(String[] args) {
    String a = "hello2";
    final String b = "hello";
    String d = "hello";
    String c = b + 2;
    String e = d + 2;
    System.out.println((a == c));
    System.out.println((a == e));
}
这段的执行结果是
true
false

第一个返回true呢?

常量折叠的概念
常量折叠是一种编译器优化技术。
常量折叠主要指的是编译期常量加减乘除的运算过程会被折叠
对于 String s1 = "1" + "2";
编译器会给你优化成 String s1 = "12";
在生成的字节码中,根本看不到 "1" "2" 这两个东西。

1、源码文件
    public static void main(String[] args) {
        String s1 = "1"+"2";
    }
2、运行后,idea有个out文件夹,找到上面文件的class文件
    public static void main(String[] args) {
        String s1 = "12";
    }
确实如上面所说,编译器会给你进行优化

常量折叠发生的条件
必须是编译期常量之间进行运算才会进行常量折叠。
编译期常量就是“编译的时候就可以确定其值的常量”,
首先:字面量是编译期常量。(数字字面量,字符串字面量等)
其次:编译期常量进行简单运算的结果也是编译期常量,如1+2,"a"+"b"。
最后:被编译器常量赋值的 final 的基本类型和字符串变量也是编译期常量

1.第一个栗子
    public static void main(String[] args) {
        String s1="a"+"bc";
        String s2="ab"+"c";
        System.out.println(s1 == s2);
    }
相信大家都知道了,输出为true
并且只创建了一个 "abc" 字符串对象,且位于字符串常量池中。

2、第二个栗子
    public static void main(String[] args) {
        String a = "a";
        String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2);
    }
这个结果呢?false
s1 是字符串字面量相加,但是 s2 却是两个非 final 的变量相加,所以不会进行常量折叠。
而是根据 String 类特有的 + 运算符重载,变成类似这样的代码 (jdk1.8)
String s2 = new StringBuilder().append(a).append(b).toString(); 
这里toString()会生成新的String变量,显然用 == 运算符比较是会返回 false。

3、第三个栗子
    public static void main(String[] args) {
        final String a = "a";
        final String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2);
    }
这里的结果就是true
因为 被编译器常量赋值的 final 的基本类型和字符串变量也是编译期常量

4、第四个栗子
    public static void main(String[] args) {
        String x ="a";
        final String a = x;
        final String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2);
    }
这里的结果是false
这里需要注意的是:final的变量,不是被编译期常量初始化的也不是编译器常量
这里的a 就不是编译器常量

5、第五个栗子
    public static void main(String[] args) {
        String x ="a";
        final String a = x;
        final String bc = "bc";
        String s1 = "a" + "bc";
        String s2 = a + bc;
        System.out.println(s1 == s2.intern());
    }
这里的结果是true

其实,这里大家要明白intern这个方法的意思就会明白了
// 一个字符串池,最初是空的,是由类字符串私有维护的。
1、A pool of strings, initially empty, is maintained privately by the class String. 
// 如果常量池中已经有了这个字符串,那么直接返回常量池中它的引用,如果没有,那就将它的引用保存一份到字符串常量池,然后直接返回这个引用。
2、When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. 
3、It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.
总结
现在看完,是不是对上面打印的结果为什么是true。
所以。只要牢记常量折叠主要指的是编译期常量加减乘除的运算过程会被折叠

字符串拼接一般不要使用String,那String的"+"一定差于StringBuilder的append吗?
代码如下:
public static String s1() {  
    String result = "";  
  
    result += "A";  
    result += "B";  
    result += "C";  
  
    return result;  
}  
  
public static String s2() {  
    String result = "";  
    result = "A" + "B" + "C";  
  
    return result;  
}  
  
public static String s3() {  
    StringBuilder result = new StringBuilder();  
    result.append("A").append("B").append("C");  
    return result.toString();  

性能上s2 > s3 > s1
3)分析:
Javap查看字节码:


由上图可知:
s1方法:3次创建StringBuilder对象,6次调用append方法添加字符串,3次调用toString方法把添加后的结果返回给result,三者之中,性能最差。
s2方法:编译器对“+”进行优化,合并"A"+"B"+"C"为“ABC”,直接赋给result,三者之中,性能最好。
s3方法:1次创建StringBuilder对象,3次调用append方法添加字符串,1次调用toString方法把添加后的结果返回给result,三者之中,性能居中。

4)总结:
在做字符串的连接操作时,append方法未必是最好的。
等号右侧有变量参与的字符串“+”操作(例如:result += "A"),jvm在做连接时,是创建一个新的StringBuilder对象,然后使用其append方法来实现连接,等号右侧没有变量参与的字符串“+”操作(例如:result = "A" + "B" + "C"),jvm在做连接时,会对右侧的连接操作进行优化,将其合并成一个字符串,然后赋给左边的变量,此时就变成了一次简单的赋值过程。

问题2,写一段代码证明StringBuilder和StringBuffer线程安全区别。

实例如下:

public class Runner implements Runnable{

    public    StringBuilder builder ;
    public    StringBuffer buffer ;

    public Runner(StringBuilder builder, StringBuffer buffer){
        this.buffer = buffer;
        this.builder =  builder;
    }

    @Override
    public void run(){
        for(int i=0;i<1000;i++){
            builder.append("1");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            buffer.append("1");
        }
    }

    public static void main(String[] args) {
        StringBuilder builder = new  StringBuilder();
        StringBuffer buffer =new StringBuffer();

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
        for(int i=0; i<10; i++){
            fixedThreadPool.execute(new Runner(builder,buffer));
        }

        // 线程执行完毕才会继续执行主线程
        while (!fixedThreadPool.isTerminated()) {
        }

        System.out.println(builder.toString().length());
        System.out.println(buffer.toString().length());
    }
}

参考 https://blog.csdn.net/a1104277306/article/details/80821578
https://www.cnblogs.com/zhenghengbin/p/9683990.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值