水平有限,有误的地方望批评指正。

文章从两大方面讲解,1、介绍,2、源码解析

String类代表了java中的字符串,

一、String介绍

1、不可变类,线程安全。

不可变类在java语言中使用final关键字实现,final有三个作用,简单的说修饰的类不可继承,方法不可重写,变量不可修改。而String类和存储字符数据的Char数组就是用final修饰的,因此string类不可继承,内部数据(char数组)不能修改,对String进行替换,裁剪,连接都是新生成一个String对象,因此String是不可变类,不可变类都是线程安全的,最典型的就是JAVA中的包装类Integer,Long等。为什么不可变类是线程安全的呢?因为String对象是无状态对象,无状态对象可以理解为状态不能改变的对象,这里的状态也可以理解为对象里的数据。

2、可共享

jdk7运行时常量池存储在方法区,也就是永久代(hotspot),jdk8永久代被移除,运行时常量池存储在本地内存的元空间中,运行时常量池存储了字符串常量的引用,字符串常量存储在堆中。java就是使用常量池来实现字符串共享的,下面我们看下例子

    public static void main(String[] args) {String a = "test";String b = "test";System.out.println(a == b);String c = new String("test");System.out.println(a == c);}

结果是一个true,一个false。a和b都指向了常量池中的引用,因此他们的地址相同的,a==b也就为true,而c指向的是在堆上新创建的对象的引用,和a不是指向同一个字符串地址,因此a==c为false,javap对应的指令

大家查看划线的几个地方,从上往下第一处,第二处,第四处都是从常量池获取字符传test的引用,而第三处是new返回的引用,所以a和b地址一样,a,b和c的地址不一样。

3、简单高效

程序中最常见的就是字符串拼接操作,java中可以直接使用+运算符来表示字符串拼接,我们看以下代码。

    public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = s1 + s2 + "c";System.out.println(s3);}

查看编译后的指令

1、创建一个StringBuilder对象

2、调用StringBuilder的构造函数

3、3,4,5步骤都是调用StringBuilder的append方法将s1,s2,和"c"拼接起来。

结论:字符串+运算被java编译器编译后变成新建一个StringBuilder对象,然后每个+会编译成append方法,因此我们在一般情况下可以直接使用+拼接字符串,java为什么要这么做呢,就是因为性能,如果不使用StringBuilder拼接过程会产生很多中间String对象。当然有些情况下需要显式使用StringBuilder,看下面代码。

    public static void main(String[] args) {String s1 = "a";for (int i = 0; i < 10; i++) {s1 = s1 + i;}System.out.println(s1);}

javap查看指令

1、1弹出栈顶两个元素进行比较,如果第二个元素大于等于第一个元素就跳转到36代码处,36代码处就是跳出for循环输出结果System.out的地方,栈中弹出第一个元素是10第二个是常量i(先入后出),说明i大于等于10则循环结束。

2、2和3表示创建StringBuilder对象,4,5表示调用append拼接s1和i

3、6表示将拼接后的结果存储在索引为1的局部变量中

4、7表示将局部变量索引为2的变量加1也就是1

5、8表示跳转到指令5处,进行下一次比较和循环

结论:分析指令后我们发现for循环中使用+进行字符串拼接,编译器编译后每次都会在for循环内部生成一个StringBuilder对象,然后调用append拼接字符串,如果for循环过大会产生大量StringBuilder对象,引起性能问题,所以我们在循环外新建StringBuilder,循环内调用append操作,代码比较简单我们就不做展示了。

二、源码解析

1、String实现了Serializable,Comparable<String>,CharSequence代表String是可序列化,可比较,字符序列。

2、主要属性

char value[] 字符存储数组

private int hash  hash值

static final Comparator<String> CASE_INSENSITIVE_ORDER 忽略大小写的比较器

3、常用方法

int hashCode() 计算对象的hashcode
    public int hashCode() {int h = hash;if (h == 0 && value.length > 0) { //1char val[] = value;for (int i = 0; i < value.length; i++) { //2h = 31 * h + val[i]; }hash = h; //3}return h; //3}

1、如果h不为0或者value的长度为0则直接返回h,h不为0的情况说明h已经被计算过直接返回,所以hashCode只会计算一次。

2、遍历char数组中的每个字符值加上h*31,数组中的每个元素都参加运算让结果更加准确,h*31使hashcode值更加分散,乘以31也可以被优化成位数操作,计算效率更快。

3、给h赋值后返回

static String join(CharSequence delimiter, CharSequence... elements) elements使用delimiter分隔开拼接后返回
   public static String join(CharSequence delimiter, CharSequence... elements) {Objects.requireNonNull(delimiter); //1Objects.requireNonNull(elements); //1// Number of elements not likely worth Arrays.stream overhead.StringJoiner joiner = new StringJoiner(delimiter); //2for (CharSequence cs: elements) { //3joiner.add(cs);}return joiner.toString(); //4}

1、验证参数是否正确
2、创建分隔符为delimiter的StringJoiner
3、遍历可变长字符串参数,对每个字符串调用joiner.add,joiner.add内部由一个StringBuilder的value属性,每次add就会使用value添加分隔符然后再添加字符串,value为空则添加前缀再添加字符串,此处无前缀。
4、调用joiner.toString()返回,如果没有后缀则返回value.toString,否则添加后缀后再返回toString,此处无后缀

static String format(String format, Object... args)
    public Formatter format(Locale l, String format, Object ... args) {ensureOpen();// index of last argument referencedint last = -1; //1// last ordinary indexint lasto = -1; //1FormatString[] fsa = parse(format); //2for (int i = 0; i < fsa.length; i++) { FormatString fs = fsa[i];int index = fs.index();try {switch (index) {case -2:  // fixed string, "%n", or "%%" //3fs.print(null, l);break;case -1:  // relative index //4if (last < 0 || (args != null && last > args.length - 1))throw new MissingFormatArgumentException(fs.toString());fs.print((args == null ? null : args[last]), l);break;case 0:  // ordinary index //5lasto++;last = lasto;if (args != null && lasto > args.length - 1)throw new MissingFormatArgumentException(fs.toString());fs.print((args == null ? null : args[lasto]), l);break;default:  // explicit index //6last = index - 1;if (args != null && last > args.length - 1)throw new MissingFormatArgumentException(fs.toString());fs.print((args == null ? null : args[last]), l);break;}} catch (IOException x) {lastException = x;}}return this; //7}

1、last,lasto都指向参数索引位置。

2、parse(format)解析字符串,将分割后的普通字符串和格式化字符串添加到FormatString[]中,FormatString是个接口,有两个实现类FixedString代表普通字符串,索引index为-2,FormatSpecifier代表格式化字符,用来格式化参数,index有三种取值,0代表正常索引,也就是不指定索引位置的格式化字符,例如%s,%d,-1代表相对位置,取last位置的参数使用此格式化字符进行格式化,例如%<s,%<d, default代表显式指定参数索引的参数,例如%1$s,%2$s

3、遍历FormatString,index为-2表示普通字符串,则直接使用Formatter里的属性a(StringBuilder)进行拼接

4、index为-1,格式化字符使用相对位置,则使用上一个索引last的参数调用print进行格式化,格式化后的字符串使用属性a拼接。

5、index为0,格式化字符使用顺序位置,lasto加1,指向当前位置的后一个参数,last赋值,对下一个位置字符串调用print格式化后,使用a拼接

6、default,格式化字符使用指定参数位置,使用index-1指向的参数调用print格式化,格式化后用a拼接

7、返回a.toString

String concat(String str) 当前字符串拼接str返回拼接后的字符串
    public String concat(String str) {int otherLen = str.length();if (otherLen == 0) { //1return this;}int len = value.length;char buf[] = Arrays.copyOf(value, len + otherLen); //2str.getChars(buf, len); //3return new String(buf, true); //4}

1、如果字符串参数长度为0,直接返回当前字符串。

2、Arrays.copyOf创建一个大小为当前字符串长度加上参数字符串长度的数组,包含value的值,方法内部是新建一个大小为len+otherLen的数组,然后调用System.arraycopy将value数组的内容复制到新建的数组中,并返回新数组,System.arraycopy是本地方法,由c,c++提供更高效的实现。

3、将Str字符的数组数据放入新数组len开头到len+Str.length处,内部也是调用System.arraycopy。

4、新建字符串,构造参数为新数组buf。

String[] split(String regex, int limit) 根据regrex分隔字符串,limit为分隔字符串数量的限制
public String[] split(String regex, int limit) {/* fastpath if the regex is a(1)one-char String and this character is not one of theRegEx's meta characters ".$|()[{^?*+\\", or(2)two-char String and the first char is the backslash andthe second is not the ascii digit or ascii letter.*/char ch = 0;if (((regex.value.length == 1 &&".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||(regex.length() == 2 &&regex.charAt(0) == '\\' &&(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&((ch-'a')|('z'-ch)) < 0 &&((ch-'A')|('Z'-ch)) < 0)) &&(ch < Character.MIN_HIGH_SURROGATE ||ch > Character.MAX_LOW_SURROGATE)) //1{int off = 0; //2int next = 0; //2boolean limited = limit > 0;ArrayList<String> list = new ArrayList<>();while ((next = indexOf(ch, off)) != -1) { //3if (!limited || list.size() < limit - 1) {list.add(substring(off, next));off = next + 1;} else {    // last one//assert (list.size() == limit - 1);list.add(substring(off, value.length));off = value.length;break;}}// If no match was found, return thisif (off == 0) //4return new String[]{this};// Add remaining segmentif (!limited || list.size() < limit) //5list.add(substring(off, value.length));// Construct resultint resultSize = list.size();if (limit == 0) { //6while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {resultSize--;}}String[] result = new String[resultSize];return list.subList(0, resultSize).toArray(result); //7}return Pattern.compile(regex).split(this, limit); //1}

1、判断regrex是否是一个字符的字符串,.并且不是$|()[{^?*+\\字符之一,或者含有两个字符的字符串第一个字符是反斜杠,第二个字符不是ascll数字或字母,否则采用正则表达式处理

2、off表示截取起始位置,next字符匹配的位置,limited是否有分隔字符串数量的限制

3、调用while循环,寻找字符匹配的位置,如果到达分隔数量的限制,直接将剩余字符串添加到list中,否则截取off到next的字符串,也就是起始位置或上一个分隔符到当前分隔符的字符串。

3、如果off为0,说明没有匹配的字符,直接返回字符串

4、如果没有分隔数量的限制或者没有到达分隔数量的限制则添加剩余字符串

5、如果没有分隔数量的限制则去除尾部空字符串,4步骤有可能添加空字符串

6、字符串列表转为字符串数组后并返回。

String replace(char oldChar, char newChar) 替换所有oldchar字符为newChar
    public String replace(char oldChar, char newChar) {if (oldChar != newChar) { //1int len = value.length;int i = -1;char[] val = value; /* avoid getfield opcode */while (++i < len) { //2if (val[i] == oldChar) {break;}}if (i < len) {char buf[] = new char[len];for (int j = 0; j < i; j++) { //3buf[j] = val[j];}while (i < len) { //4char c = val[i];buf[i] = (c == oldChar) ? newChar : c;i++;}return new String(buf, true);}}return this;}

1、如果两个字符相等直接返回当前字符串

2、找到第一个oldChar和newChar相等的位置i,新建char数组将小于i的字符放入新数组中

3、从i开始遍历,如果i下标的字符和oldChar相等,则新数组下标i赋值newChar,否则新数组下标赋值原来的字符。

4、返回新建的String,参数数组为新建的buf.

String substring(int beginIndex, int endIndex) 截取beginIndex到endIndex的字符串
    public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) { //1throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > value.length) { //1throw new StringIndexOutOfBoundsException(endIndex);}int subLen = endIndex - beginIndex;if (subLen < 0) { //1throw new StringIndexOutOfBoundsException(subLen);}return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen); //2}

1、判断参数是否合法

2、直接调用String的构造函数创建新字符串,构造函数中使用Arrays.copyOfRange复制数据。

String trim() 清空字符串中前后的空格
    public String trim() {int len = value.length;int st = 0;char[] val = value;    /* avoid getfield opcode */while ((st < len) && (val[st] <= ' ')) { //1st++;}while ((st < len) && (val[len - 1] <= ' ')) { //2len--;}return ((st > 0) || (len < value.length)) ? substring(st, len) : this; //3}

1、第一个while循环计算索引从0开始第一个字符不为空格的索引

2、第二个while循环计算索引从len开始第一个不为空格的索引

3、存在空格则调用subString返回截取的去掉前后空格的字符串,否则返回当前字符串。

其他的方法代码比较简单,就不再赘述了,大家有时间可以自己看一下。

public boolean startsWith(String prefix, int toffset)
public boolean endsWith(String suffix)
lastIndexOf(int ch)
public int indexOf(int ch)
public boolean equals(Object anObject)

下篇文章给大家讲解StringBuilder和StringBuffer

关于String你了解多少相关推荐

  1. Java知识——精华总结

    Java知识--精华总结 一.java概述与基础知识 1.何为编程? 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程. 为了使计算机能够理解人的意图,人类就必须 ...

  2. mybatis查询报错:com.mysql.cj.exceptions.DataConversionException: Cannot determine value type from string

    mybatis查询报错: com.mysql.cj.exceptions.DataConversionException: Cannot determine value type from strin ...

  3. Go 学习笔记(60)— Go 第三方库之 go-redis(初始化 redis、操作 string、操作 list、操作 set、操作 hset)

    1. 第三方库 go-redis 因为 Go 标准库中是没提供 redis 的库,所以我们选择用 go-redis 这个第三方库.源码地址为 https://github.com/go-redis/r ...

  4. Redis 笔记(03)— string类型(设置key、获取key、设置过期时间、批量设置获取key、对key进行加减、对key值进行追加、获取value子串)

    字符串 string 是 Redis 最简单的数据结构.Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据.不同类型的数据结构的 ...

  5. python中的raw string的使用

    背景 我们经常需要使用raw string,在应用过程中,比如要使字符串中带一些转义字符或者其他的一些符号,我们就需要保持我们的字符成为raw string. 实例 输入 s = 'fadfafa\n ...

  6. Java中如何实现Date与String之间的数据类型转换

    String 数据类型转换成 Date String inputDate = "2021-04-11";Date outputDate = null;SimpleDateForma ...

  7. C++ string字符串的比较是否相等

    C++ string字符串的比较是否相等 可以使用compare 也可以使用"==" 1 使用比较运算符 == #include <iostream> #include ...

  8. python string 转bytes 以及bytes 转string

    string转 bytes 使用 encode str_a = "Python" str_bytes = str_a.encode() # encode默认编码方式是utf-8 所 ...

  9. TypeError: string argument without an encoding

    这个错误是把string 类型转byte类型的时候出现的问题 下面就会报错 print(bytes(str_a)) 修改为如下就好了 print(bytes(str_a.encode())) 或者就不 ...

  10. TypeError: ord() expected string of length 1, but int found

    错误如下 ord 里面要求的是一个string 的字符, 这里使用了int  ,所以修改了print(ord("1")) 转ASCII 的时候需要注意了,

最新文章

  1. flutter 切图重命名脚本
  2. 网站优化助力网站在同行中更加出类拔萃
  3. 蒲公英怎么加入别人的网络_挖蒲公英吃的,抓紧看看吧!
  4. Cppunit 源码 SynchronizedObject
  5. Ubuntu 14.04 ThinkPad E431无线网卡驱动安装
  6. datatables隐藏列设置及获取隐藏列的值
  7. 容器环境自建数据库、中间件一键接入阿里云 Prometheus 监控
  8. 【java】java jps 命令
  9. iOS利用代理实现界面跳转
  10. 三星on 7刷android7,三星On7(G6100)安卓8.0版官方固件rom刷机包
  11. js基础-点击切换div背景颜色
  12. [导入]北京地区铁路客票代售点列表
  13. 中文分词词库汇总(一)
  14. ios测试版软件永久,iOS10.3.3 Beta6开发者测试版
  15. 【赚钱应跟Apple,做大不忘Google】移动开发时代的开发者选择
  16. 如何成为一名优秀的后端工程师
  17. 解决蓝奏云的文件分享链接打不开的问题
  18. 点击率预测模型Embedding层的学习和训练
  19. Apche Kylin启动报错:UnknownHostException: node1:2181: invalid IPv6 address
  20. 中国使用互联网每年向美国付5000亿超国防预算数额

热门文章

  1. 机器学习及其应用2015 [高新波,张军平 主编] 2015年版
  2. android 9.0 toast不显示,9.0 toast定位+WebDriverWait显示等待
  3. OpenWRT路由器使用ipv6拨号上网教程
  4. 甲方、乙方、监理三者之间的关系及其在项目管理过程中的若干事项
  5. 初评《凉宫春日的忧郁》
  6. 数学之路(3)-数据分析(4)
  7. 下水盖为什么是圆的?
  8. git 基本命令总结
  9. 面试某东测试岗:“如果开发的Bug比较多,你会如何处理?”
  10. c语言赋值符号,运算符赋值运算符 - C语言教程