java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?

大家好我是霜华,随着考试月的到来和开发工作的愈发繁忙,javaj基础篇的博客有时候经常断更,但无论怎么样我都会抽出时间把自己的知识全部展示出来, 毕竟我的背后是北风呐,那个高校中互联网人成长的土壤
java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?_第1张图片
其实,北风技术部的学生很多都是大一新生,这么多学生找我说他想要学技术其实我很开心,但同时我从他们身上也看到了我当年的影子,(也不是当年啦,就去年而已,只是这一年来自己实在成长太多,有种自己经历了很久的感觉)因为自己比大部分同龄人起步早些稍有骄纵,认为时间还有很多,也会经常懈怠。但其实很多同龄人已经比我们优秀很多,越优秀也更努力,天下之大,蓬勃汹涌的后浪们比比皆是,我们作为他们的其中一员,为什么不能成为冲在最前面的那朵浪花,我们年轻,我们有看似无限的美好年华,这段时间不就是拿来为自己的生活拼命的么,每天都在为自己的未来奔跑,
我们骄傲,自比天高,这有什么错,因为我们终将成为撑住天的栋梁!!
我们心气旺盛,不服一切,这有什么错,因为我们曾为夸下的每一句海口拼命着
或许年轻的我们还悟不到人生的真意,但有一天站在顶峰或许能看懂更多。
朋友啊,爬过一座座山峰,去看看外面的世界吧!!!
做那个独一无二的后浪!!!!

做完这期是5月31号是五月天的线上演唱会,某一年的夏天的5月于我而言有太多的故事,这时候听听那首突然好想你想想那个想见却永远见不到的人吧。
抱歉最近在调整自己的学习状态,同时也很忙,写每一次博客我都要找大量的资料,进行整和
这次的String很多知识点也要我研究完 JVM才能继续说
尽量在保证质量的情况下3天一更


工具类思维导图
java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?_第2张图片

工具类:

一、Math

1.构造方法私有 ,没有公有的构造方法,不能创建对象
2.属性和方法都是static修饰,作为类资源,可以通过类名直接访问

常用方法集:

  • abs(int/long/float/double)//绝对值int⾥可以放short和byte Cell floor
  • rint()//向下取整
  • round()取整
  • double = random(); [0.0–1.0) 之间的随机数/—/ (int)(Math.random()*10+1);先乘除再转化1-10整数
  • pow(double a,double b);;//开根号 取整:
  • a的b次⽅double = sqrt(double a)
  • double = ceil(double a) 向上取整 1.5—>1.0
  • floor(double a) 向下取整
  • int/long = round(float/double a) 四舍五⼊/
  • max(int/long/float/double,int/long/float/double)//两个数求最大值
  • min(int/long/float/double,int/long/float/double)

二、UUID

1.属于java.util包
2.通常不会用构造方法构造对象
方法集:
String uuid=UUID.randomUUID;//随机产生一组数字(绝对不重,开发中常常用来做数据库的索引ID)
开发实战运用:

String UUID =java.util.UUID.randomUUID().toString.replaceAll("-","");
return UUID;//UUID为32位,2的32次方的范围

三、Scanner类

1.属于lang 包
2.通过一个带输入流的构造方法创造对象
方法集
nextInt//int
nextFloat//float
next //返回String
nextLine//String
//实际使用

    Scanner input=new Scanner(System.in);
        int  x= input.nextInt();
        System.out.println(x);
  Scanner input=new Scanner(System.in);
        String  x= input.next();
        String  y= input.next();
        System.out.println(x);
        System.out.println(y);
        //next方法读的是空格,以空格作为结尾,开始调用下一个next方法//其他是以换行符作为结尾开始下一个方法

在这里插入图片描述

四 System类

1.java.lang包
2.三个属性out in err
3方法
GC()//提醒垃圾回收器回收内存
Exit()

五、Date

1.java.util//
2.⽆static调用属性方法 需要new对象
方法级

  • Date d = new Date (154786767092L)//构建之前的时间//加个L防⽌报错,F的话是double存进float Date date =new Date():
    System.out.println(date);输出格林威治时间格式

  • DateFormat df =new SimpleDateFormat(“yyyy-MM-dd KK:mm:ss”)//在括号⾥⾯给个格式String value =df.formate(date);//改时间格式

  • 通常调⽤⽆参数构造⽅法创建对象也可以调⽤带long参数Date d =new Date(154786767092L)Date date =new Date();

  • boolean b =date.before(d);//date是否在在传递参数(d)之前boolean b =date.before(d)//date是否在在传递参数(d)之后

  • setTime(long time)

  • long time = getTime() boolean = date1.after(date2)
    -boolean = date1.before(date2)
    -int = compareTo() 调⽤⽅法的对象实现接⼝Comparable int v = date.compareTo(d)date靠后
    -SimpleDateFormat
    1.java.text
    2.通过带String 参数的构造⽅法创建对象
    String pattern = “yyyy-MM-dd HH:mm:ss” 3.String = format(date)//date是传递的参数类型

六Calendar

//抽象类
Calendar c =new Calendar();//如果前后两都报错证明没导包,
如果后⾯红线:1没有⽆参数构造⽅法,2.⽆参数构造⽅法私有3.当前类是抽象类或者是接⼝
//SingTon
//getInstance();指的是创键对象的意思所以创建这个对象⽤
Calendar c=Calendar.getInstance();
输出 c 会有⼀堆码表示很多时间信息
c.setTime(YEAR,2015);// 更改局部信息
date c.getTime//

int year=c.get();获取某⼀时间年份⽉份0-11 1.java.util
2.不能构建对象 getInstance(); 3.getTime setTime

before after get set getTimeInMillis()

TimeZone l另⼀个类
TimeZone t= TimeZone.getDefault();//获取对象TimeZone tz =c.getTimeZone();
输出(tz.getID)//时区ID
输出(tz.getDisplayName())//中国标准时间
TimeZone = TimeZone.getDefault();
TimeZone = c.getTimeZone(); getID()
getDisplayName();

七、String

1.String//最常⽤的类 StringBuffer StringBuilder
2.java.lang 类 字⾯值// 两种赋初值的⽅法//对象创建

String s1 =new string (“abr”)
.String的构造⽅法有15个
下面列举常用的
String s=“abc”//可以⽤常量的形式赋值"abc"//只是看做常量 ,这是⼀个String类型的对象放在常量区中

private final char value[];//这是String中实际存数据的地方,是一个char的数组
 private int hash;//String对象的hash值//相当一个对象的ID
 //1-------------------------
new String("abc");
public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
}//带String的构造方法
//2--------------------
new String(char[])
public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
        //调用Arrays自带的复制数组的方法,将原资源的数组在内存中copy一份新数组,将这个新数组的地址引用指向我们的value属性
}
//Arrays的copyof源码:
public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];//建立一个新数组作为return
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }//其实看过ArrayList源码的同学对这个方法并不陌生,ArrayList底层数组的扩容就是用Arrays
//3------------------------------------
new String(StringBuffer)//三者之间可以互相构建new String(StringBuilder)//
 public String(StringBuffer buffer) {//StringBuffer是一个变种版的String 
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
//-----------------
new String(byte[]0,i)//从数组0到i变成String new String(byte[],charset)//按照什么样⼦来组合new String(byte[])//


是一个非常特殊的引用数据类型 可以像基本数据类型一样创建赋值
**1.String特点—不可变特性:
什么叫不可变特性:地址指向的内存中的String对象,长度不可变,元素不可变
什么导致不可变:
源码:

 private final char value[];
 //内部实际的存储数组是final修饰的,final修饰的变量,不可更改。
 //private 修饰,内部如果没有提供方法修改,不允许直接通过外部类访问甚至是进行更改

设置为不可变有什么好处:
1.提高效率和安全性,copy对象的内容是不复制内容本身而是复制一个地址;在多线程同时操作数据的情况下,数据不会改变,能保证线程安全。当然技术是把双刃剑,这确实在一定程度上,降低了程序的灵活性
2.字符串常量池的需要:当创建一个String对象时,字符串串值会存在与常量池中,如果在创建一个相同的对象引用直接指向常量池的内容(创建一个对象真的很耗费资源,在我正常开发中我一直都尽可能少的创建对象)
如String s1 =“abcd”
String s2=“abcd”
//两者其实是指向同一个内容,如何判断用hash码就能看到
java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?_第3张图片
在这里插入图片描述
看过我之前文章的人会发现,啊!之前不是说hashcode 的结果是:对象名@hash码 的形势么
遇事不决,源码解决:

//String内部的hashCode方法,很明显重写了Object方法
 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;
    }

String类中的常⽤⽅法 20+

(重写⽅法)

  • equals()//标记字⾯值

String 类型同时重写了equals方法//重写,子类修饰符权限大于等于父类,返回值类型小于等于父类,名字与参数列表必须与父类一致 抛出异常个数和类型都小于父类
重写后的源码:

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }//如果两个的地址引用相等,就是指向同一个对象,直接返回true
        if (anObject instanceof String) {//instanceof 是用于判断对象的类型 如果anObject 是String 对象或者是String 的子类返回true
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {//1.先看看两个字符串长度是否相等,相等我们再进行下一步
                char v1[] = value;//将String的value属性,也就是String内部的char数组赋值给v1
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//从头开始遍历一旦有错立刻返回flase
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

String 中 == equals( )的区别

== 比较对象底子
equals()比较两个String字符串的值

  • hashCode()//以前是⼗六进制的形式,如今的String hashCode计算方法
    abc 这个字符串换成
    a---->97//字符串首字母是a他的Unicode码是97
    h=97 //h显示97
    h=h31+98//乘以31 再加上 b的unicode码
    h=(h
    31+98)31+99//原数31再加c的码
    //
    重写的hashCode()方法如下
 private int hash; // Default to 0//
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;
    }
  • compareTo()
    s1 .compareTo(s2)
    //类型⽐较 两个字符串的,
    ⻓度较⼩的那个作为循环次数的计数,挨个⽐⾥⾯的char元素,任何⼀个字如果不⼀样直接计算code差,然后return差值结束方法
    如果到lim位置每个char都⼀样的话减去⻓度差
    String重写的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;
    }

所以返回值为0表示长度和字符都相等,返回值为负数或者正数不等,正数表示,前面的数都相等,只是另一个多出来了些值
相比于equals 它表示的信息稍微丰富些。

toString
//返回String对象的字面值
toString源码
public String toString() {
return this;
}


(开发中常⽤)
1、char = charAt(int index)//返回的是对应位置的字

2、int = codePointAt(int index)
//返回的是对应位置的字的code码前两个拥有加密,
for(int i=0;i int code=s.codePointAt(i)
code-=48 //密钥result+=(char)code;
}

3、String = concat("");//拼接,//需要接受 //concat是在堆内存⾥new对象,所以空间⼤,string
容器接收返回值String不可变特性()
例⼦:
String s =“abcdegf”;
s= s.concat(“g”)//空间相对较⼤
//因为String的不可变特性,s.concat 其实是底层做了一个char数组的处理,然后根据这个新char数组返回了一个新的堆内存对象
//所以其实我们需要将地址引用重新指向他

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

//我们感受一下这个不可变特性

public class TextMain {
    public static void main(String []args){
     String a="1";
     String b=a.concat("2");
     System.out.println("a的输出为"+a);
     System.out.println("b的输出为"+b);
    }
}

在这里插入图片描述
我这个a还是那个我没有一丝丝改变

4、boolean = contains("")//是否包含//但是字符必须连着
5、boolean = endsWith("")//是否以什么开头//扫描⽂件
6、boolean = startsWith("")//
7、byte[] = getBytes();//不传参数的话以平台(操作系统)默认的字符集拆分
8、byte[] = getBytes(“charset”);//按照给定的来拆//
9、char[] = toCharArray();//把字符串拆成数组
10、int = indexOf();//(‘a’)返回a第⼀次所在的位置(‘a’2)从第三个数,(97)找code是97的
11、int = lastIndexOf();//从后往前找第⼀个位置;
12、length();//当前字符串⻓度
13、*boolean = matches(String regex) regular expression
14、replace(‘old’,‘new’);//交换,需要接受新的
15.、replaceAll
16、replaceFirst//换第⼀个
*15、 String[] = split("",2)//拆分按照字符⾥的东⻄拆,2的意思是我就拆两段,后⾯这个2是重

16、String = substring(int beginIndex,int endIndex)//截取字符串,[,)
17、String = substring(int beginIndex)//从第⼏个截取
18、trim();//把旁边的空格去掉//需要返回第三个梯队(不是很常⽤)
19、toLowerCase()//字符串全变成⼩写字⺟
20、toUpperCase();
21、String.valueOf();//将所有基本类型转化成String//String x =String.valueOf(10);/因为是静态⽅法,将基本类型转化为String类型
22、s1.equalsIgnoreCase(s2)忽略⼤⼩写的⽐较,
23、compareToIgnoreCase

八、StringBuiler与StringBuffer

与String 三者的继承结构
java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?_第4张图片
//图片来源:https://blog.csdn.net/ifwinds/article/details/80849184

区别:
与String底层依然是一个字符数组只是没有final修饰
char[] value;
String是不可变字符, StringBuiler 与StringBuffer是可变字符串
StringBuiler 非线程安全 StringBuffer线程安全

1.属于java.lang

2.继承 实现
默认继承Object
实现接⼝Serializable, Appendable, CharSequence
3.构造⽅法
构造⽅法4个
private char[] value; 动态扩容 16⻓度//没有final
new StringBuilder(); 16
new StringBuilder(20); 20
new StringBuilder(“abc”); 3+16,来回互相构建吗
new StringBuilder(charSequence);
3.常⽤的⽅法
⾃⼰类中独有的⽅法 String类没有的

  • **StringBuilder = append();//字符串拼接
  • insert(int index,String str);//往字符前⾯加
  • int= length();
  • int = capacity();//容量
  • setLength();可以改变有效元素个数 很不安全,之后的空间被认为是有效的
  • StringBuilder = delete(int begin,int end);//独有把begin 与end间东⻄删掉,
  • substring(2,5);
  • deleteCharAt(index)//是删某个位置
  • ensureCapacity(int minimumCapacity)
  • reverse();//反转,string没有这个⽅法
  • setCharAt(int,char);//改某⼀个位置
  • trimToSize(); 删除底层数组多余⽆⽤的空间,删除底层数组多余容量
    与String类中同名的⽅法
  • length();
  • charAt();//获取
  • codePointAt();
  • String = substring();
  • int index = indexOf(String [,index])
  • lastIndexOf()
  • replace(int start,int end,string str)⽤法不太⼀致//挑⼀个范围改
  • toString()把builder对象变成String类型

2.String创建对象的特点
String s = “a”+“b”+“c”+“d”//中间产⽣7个对象a b ab c abc d abcd

九、 字符串的拼接:

String是一个不可变类,既然不可变,如果想改变字符串的信息做字符串的拼接该怎么做呢
1.String 的 + 号 拼接字符串

String a ="我是"
String end =a+“霜华”

注意:+号还是我们理解的那个作为运算符的+号,它不是其他语言的运算符重载,java本身也并不支持运算符重载,这是一个java的语法糖

运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

1.+号实现的底层原理:
a+“霜华”:
底层其实是jvm通过+号隐性的调用了StringBuiler:这里我运用了反编译工具去处理源代码:

计算机软件反向工程(Reverse engineering)也称为计算机软件还原工程,是指通过对他人软件的目标程序(比如可执行程序)进行“逆向分析、研究”工作,以推导出他人的软件产品所使用的思路、原理、结构、算法、处理过程、运行方法等设计要素,某些特定情况下可能推导出源代码。反编译作为自己开发软件时的参考,或者直接用于自己的软件产品中。


String end =new StringBuiler().append(a).append("杨龙").toString();

字符串常量+ 拼接实现原理是使用StringBuiler.append
2,String 本身的concat 方法

public String concat(String str) {
   int otherLen = str.length();
   if (otherLen == 0) {
       return this;
   }
   int len = value.length;
   char buf[] = Arrays.copyOf(value, len + otherLen);
   str.getChars(buf, len);
   return new String(buf, true);
}

创建一个新数组,长度是旧老字符串长度之和,再copyof数组再调用关于char[]的构造方法
3.StringBuffer 与StringBuiler 的appen方法:
//底层机制都是一样的
之前提到Stringbuffer是线程安全为什么线程安排是因为synchronized

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

一个synchronized 修饰的 线程锁,当一个线程使用了对象方法后,不允许其他线程访问这个对象
//大家都知道super是调用父类的子类覆盖掉的方法:我们来看看StringBuffer和StringBuiler共同继承的AbstractStringBuilder类的append方法:
直接拷贝字符到内部的字符数组中

   public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);//确定一下新数组是否够位置,如果不够,扩容。有ArrayList那味了
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

4.StringUtil.jion
除了java内置的字符串 还有一些开源库提供的一些工具(开源库:可以了解下关于开源的一些历史,个人理解就是一些非语言的创始开发者,提供的一些外部工具)如apache.comons提供的StringUtils类

String resource1="I am";
String resource2="杨龙";
String resource=(StringUtils.join(resource1,resource2));
//StringUtil 还有一个功能是:将数组和集合通过某些符号连接到一起形成新的字符串
String []  resource ={"i am","杨龙"}
String result =StringUtils.join(resourcee,",");

StringUtil.jion 实现原理:

public static String join(final double[] array, final char separator, final int startIndex, final int endIndex) {
       if (array == null) {
           return null;
       }
       final int noOfItems = endIndex - startIndex;
       if (noOfItems <= 0) {
           return EMPTY;
       }
       //对数组做一些长度判断
       final StringBuilder buf = new StringBuilder(noOfItems * 16);//依然还是用StringBuilder进行字符串的拼接
       for (int i = startIndex; i < endIndex; i++) {
           if (i > startIndex) {
               buf.append(separator);
           }
           buf.append(array[i]);
       }
       return buf.toString();
   }
  • ,concat,StringBuiler,StringBuffer,String.util 5个拼接效率的工具
    做同一件事情的时长比较
    StringBuilder< StringBuffer

+号如果是出现在循环体中,每一次执行都需要创建一次StringBuiler对象,一是效率太低二是因为频繁的创建对象,会对内存造成极大的内存资源浪费。
StringBuiler 就是为了定义可变字符串 和字符串可变操作的
不过在非循环中,+ 其实和StringBuilder是差不多的,最好 用+号

/—

十、字符串常量池
这块也很重要,有了这个次啊能理解接下来的笔试题
java内存分配中 总共有Class常量池、运行时常量池、字符串常量池、三种常量池
字符串的分配毕竟也是对象,内存需要需要耗费很多的空间时间,jvm为了提高性能创建字符串时候会进行一些优化:使用字符串常量池、每当创建字符串常量时候jvm会检查一遍常量池,如果有相同的东西,就把这个实例的地址以用指向它,因为是不可变嘛,如果你想操作变字符串就需要所以常量池一定不存在两个相同的,
笔试题:
String s1=”abc”;
String s2=”abc”;
String s3=new String(“abc”);
String s4=new String (“abc”);
System.out.println(s1s2)//true
System.out.println(s1
s3)//false
System.out.println(s3==s4)//false
System.out.println(s1.equals(s2))//true
System.out.println(s1.equals(s3))//true
System.out.println(s3.equals(s4))//true
/—
s1 和s2 指向的都是常量区的abc
s3 和 s4 是单独创建一个对象在堆内存开辟了一个内存空间
java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?_第5张图片
final与+号字符串
final修饰的变量,不可变,它是在编译时,产生的变量,存在静态元素区里面,这个知识对解下面这道题很重要

        final String a="a";//final是编译时就就定义了的变量
        String b="b";//在运行时在堆内存定义了
        String ab=a+b;//+ 看过源码都知道是StringBuffer append后toString 返回了一个新的对象
        String ab1="ab";
        System.out.println(ab==ab1);//false

        String a="a"; //这里就和上文一样了运行时在 常量区分别创建了a和b a+b,StringBuffer 在堆内存返回了一个对象//而ab1指向的”ab“是在静态常量区,地址不一样
        String b="b";
        String ab=a+b;
        String ab1="ab";
        System.out.println(ab==ab1);//false

		final String a="a";//final是编译时就设置的只
        final String b="b";
        String ab=a+b;//ab两个变量都是编译
        String ab1="ab";
        System.out.println(ab==ab1);//true

十、String字符串的长读限制
在String的构造方法中,有支持用户传入length来执行长度限定的重载方法

  public String(byte bytes[], int offset, int length) 

长度是int类型,理论上长度应该是int 的最大范围值 2^31-1//减的是0

String s =111111......1111“十万个1
执行javac时,会出现异常,提示常量字符串过长

但是要知道像这样String=”xxxx“ 是存在常量池里面的, 常量池也有它自己的相关的一些规定

关于常量池定义
CONSTANT_String_info 用于表示 java.long.String 类型的常量对象

CONSTANT_String_info{
	u1 tag;
	u2 string_index;//它的值必须是对常量池的有效索引,常量池在索引处项必须是CONSTANT_Utf8_info 结构 表示一组Unicode 马点序列,最终初始化形成一个String对象 
}
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];//指明bytes数组 u2是一个表示两字节的无符号数 ,两字节就是16个bit位 可表示的最大值为2^16-1=65535
}

so:常量池规定他内部的字符串长度不能超过65535(小于65535不能等于)
//但上面只是说在编译期常量区的规定

如果是运行期在堆内存区的化就是正常的int指代的2^32-1 的范围
如何让字符串是在运行期的堆内存中
答案如下:
s+“1”

参考
为什么阿里巴巴不建议在for循环中使用"+"进行字符串拼接
https://mp.weixin.qq.com/s/fLUf8V0Qahe8piNrRp1UVQ

你可能感兴趣的:(java工具类-常用工具类和String类使用和源码解析-你真的懂String吗?)