• 【深入了解Java String类】


     🌠作者:@TheMyth.

    🎇座右铭:不走心的努力都是在敷衍自己,让自己所做的选择,熠熠发光。

     

    目录

    String类

    常用方法

    字符串的不可变性

    String的内存分析 

    StringBuilder类

    解释可变和不可变字符串

    常用方法

    面试题:String,StringBuilder,StringBuffer之间的区别和联系

    字符串常量池  

    String类的OJ练习


    String类

    【1】直接使用,无需导包:


    【2】形象说一下字符串:

    【3】


    String str = “abc”;
    "abc"就是String类下的一个具体的对象

    【4】字符串是不可变的:

    【5】这个String类不可以被继承,不能有子类:


    【6】String底层是一个char类型的数组


    验证:

    注意:没有用'\0'来标识结尾

    String类中的字符实际保存在内部维护的value字符数组
    value被final修饰,表明value自身的值不能改变,即不能引用其它字符数组
    但是其引用空间中的内容可以修改

    常用方法

    【1】构造器:底层就是给对象底层的value数组进行赋值操作。

    1. //通过构造器创建对象:
    2. String s1 = new String();
    3. String s2 = new String("abc");
    4. String s3 = new String(new char[]{'a', 'b', 'c'});

    【2】常用方法:  

    1. String s4= "abc";
    2. System.out.println("字符串的长度为:"+s4.length());
    3. String s5 = new String("abc");
    4. System.out.println("字符串是否为空:"+s5.isEmpty());
    5. System.out.println("获取字符串的下表对应的字符为:"+s5.charAt(1));

     【3】equals:比较两个值的内容是否相同

    1. String s6 = new String("abc");
    2. String s7 = new String("abc");
    3. System.out.println(s6.equals(s7));
    4. /*String s6 = new String("abc");
    5. String s7 = new String("abc");
    6. System.out.println(s6.equalsIgnoreCase(s7));//忽略大小写*/

    equals底层:

    【4】String类实现了Comparable接口,里面有一个抽象方法叫compareTo,所以String中一定要对这个方法进行重写
    比较两个值的内容的大小,相等返回0,大于返回一个整数,小于返回一个负数

    1. String s8 = new String("abc");
    2. String s9 = new String("accdef");
    3. System.out.println(s8.compareTo(s9));
    4. /*String s8 = new String("abc");
    5. String s9 = new String("Abc");
    6. System.out.println(s8.compareToIgnoreCase(s9));//忽略大小写
    7. */

    compareTo底层:

    【5】其它常用方法: 

    1. //字符串的截取:
    2. String s10 = "abcdefghijk";
    3. System.out.println(s10.substring(3));
    4. System.out.println(s10.substring(3,6));//[3,6)
    5. //字符串的拼接/合并:
    6. System.out.println(s10.concat("pppp"));
    7. //字符串中的字符的替换:
    8. String s11 = "abcdeahija";
    9. System.out.println(s11.replace('a','u'));
    10. //按照指定的字符串进行分裂为数组的形式:
    11. String s12 = "a-b-c-d-e-f";
    12. String[] strs = s12.split("-");
    13. System.out.println(Arrays.toString(strs));
    14. //转大小写的方法:
    15. String s13 = "abc";
    16. System.out.println(s13.toUpperCase());
    17. System.out.println(s13.toUpperCase().toLowerCase());
    18. //去除首尾空格:
    19. String s14 = " a b c ";
    20. System.out.println(s14.trim());
    21. //toString()
    22. String s15 = "abc";
    23. System.out.println(s15.toString());
    24. //转换为String类型:传入任何类型的数据都将转换为String类型
    25. System.out.println(String.valueOf(String.valueOf(false)));

    【6】String类的练习: 

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. String str = " hello themyth ";
    5. System.out.println(str);
    6. String ret = str.trim();//去掉的字符串开头和结尾的空白字符(空格、换行、制表符等)
    7. System.out.println(ret);
    8. String string = "themyth@ws";
    9. System.out.println(string.contains("ws"));//判断字符串里面是否包含了这个内容。
    10. }
    11. //这是一个main方法,是程序的入口:
    12. public static void main2(String[] args) {
    13. //字符串的拆分:
    14. String str1 = "hello handsome themyth";
    15. String[] strs = str1.split(" ");
    16. /*for (String s : strs) {
    17. System.out.println(s);
    18. }*/
    19. for (int i = 0; i < strs.length; i++) {
    20. System.out.println(strs[i]);
    21. //hello
    22. //handsome
    23. //themyth
    24. }
    25. String[] strs2 = str1.split(" ", 2);
    26. for (int i = 0; i < strs2.length; i++) {
    27. System.out.println(strs2[i]);
    28. //hello
    29. //handsome themyth
    30. }
    31. String str2 = "192.168.0.1";
    32. String[] strs3 = str2.split("\\.");
    33. for (int i = 0; i < strs3.length; i++) {
    34. System.out.println(strs3[i]);
    35. //192
    36. //168
    37. //0
    38. //1
    39. }
    40. System.out.println("===");
    41. String str3 = "192\\168\\0\\1";
    42. String[] strs4 = str3.split("\\\\");// "\\"表示一个"\"
    43. for (int i = 0; i < strs4.length; i++) {
    44. System.out.println(strs4[i]);
    45. //192
    46. //168
    47. //0
    48. //1
    49. }
    50. System.out.println("===");
    51. String str4 = "192&168=1";
    52. String[] strs5 = str4.split("&|=");//
    53. for (int i = 0; i < strs5.length; i++) {
    54. System.out.println(strs5[i]);
    55. //192
    56. //168
    57. //1
    58. }
    59. System.out.println("===");
    60. //多次拆分:
    61. String message = "name=themyth&age=22";
    62. String[] ms = message.split("&|=");
    63. for (String s : ms) {
    64. System.out.println(s);
    65. //name
    66. //themyth
    67. //age
    68. //22
    69. }
    70. System.out.println("===");
    71. String message2 = "name=themyth&age=22";
    72. String[] ms2 = message2.split("&");
    73. for (int i = 0; i < ms2.length; i++) {
    74. String[] ms3 = ms2[i].split("=");
    75. for (String s : ms3) {
    76. System.out.println(s);
    77. //name
    78. //themyth
    79. //age
    80. //22
    81. }
    82. }
    83. //字符串的截取:
    84. String str = "abcdefgh";
    85. String s1 = str.substring(2);//cdefgh 返回从2位置开始所截去的字符
    86. System.out.println(s1);
    87. String s2 = str.substring(2,6);//cdef 截取下标区间[2,6)的字符
    88. System.out.println(s2);
    89. }
    90. //这是一个main方法,是程序的入口:
    91. public static void main1(String[] args) {
    92. String str1 = "themyth";
    93. char ch = str1.charAt(1);//h 下标对应的字符
    94. System.out.println(ch);
    95. for (int i = 0; i < str1.length(); i++) {
    96. if (i != str1.length() - 1) {
    97. System.out.print(str1.charAt(i) + " ");
    98. } else {
    99. System.out.println(str1.charAt(i));
    100. }
    101. }//t h e m y t h
    102. String str2 = "ababcabcabc";
    103. int b = str2.indexOf('b');//1 字符第一次出现位置的下标,默认从下标为0开始查找
    104. System.out.println(b);
    105. int b1 = str2.indexOf('b', 5);//6 从下标为5的字符开始查找,字符第一次出现位置的下标
    106. System.out.println(b1);
    107. //在主串中 查找子串
    108. int abc = str2.indexOf("abc");
    109. System.out.println(abc);//2 返回"abc"首字符a第一次出现的位置
    110. int b2 = str2.lastIndexOf('b');//9 从后往前找,字符第一次出现位置的下标
    111. System.out.println(b2);
    112. int b3 = str2.lastIndexOf('b', 5);//3 从下标为5的字符从后往前开始查找,字符第一次出现位置的下标
    113. System.out.println(b3);
    114. int b4 = str2.lastIndexOf("abc", 7);//5 从下标为7的字符从后往前找子串,如果找到了第一个能够匹配的字串,返回该子串的第一个字符
    115. System.out.println(b4);
    116. String str3 = String.valueOf(123);//将...转换为字符串
    117. System.out.println(str3);
    118. String str4 = "123";
    119. int data = Integer.parseInt(str4);
    120. System.out.println(data);
    121. Double data2 = Double.parseDouble(str4);
    122. System.out.println(data2);//123.0
    123. String str5 = "TheMyth";
    124. String str6 = "themyth";
    125. System.out.println(str5.toLowerCase());//themyth
    126. System.out.println(str6.toUpperCase());//THEMYTH
    127. String str7 = "abcdef";
    128. char[] chars = str7.toCharArray();//字符串--->字符数组
    129. for (int i = 0; i < chars.length; i++) {
    130. if (i != chars.length - 1) {
    131. System.out.print(chars[i] + " ");
    132. } else {
    133. System.out.println(chars[i]);
    134. }
    135. }//a b c d e f
    136. String str8 = new String(chars);//字符数组->字符串
    137. System.out.println(str8);
    138. String str9 = String.format("%d-%d-%d", 2021, 6, 16);//格式化打印(用的很少)
    139. System.out.println(str9);
    140. //参数不同。replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替
    141. //换;replaceAll的参数是regex,即基于规则表达式的替换。
    142. //替换方式不同。replace是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或
    143. //字符串;replaceAll也是全部替换,但如果所用的参数据不是基于规则表达式的,则只替换第一
    144. //次出现的字符串。
    145. String str10 = "abcabcdabcde";
    146. String replace = str10.replace('a', 'o');//obcobcdobcde
    147. System.out.println(replace);
    148. String replace1 = str10.replace("abc", "x");//xxdxde
    149. System.out.println(replace1);
    150. String replace2 = str10.replaceAll("abc", "ooo");//oooooodooode
    151. System.out.println(replace2);
    152. String replace3 = str10.replaceFirst("abc", "520");//520abcabcde
    153. System.out.println(replace3);
    154. //注意:字符串是不可变的对象,替换不修改当前字符串!
    155. }
    156. }

    字符串的不可变性

    所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
    例如:substring,返回的都是一个新的字符串对象

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. String str1 = "abc";
    5. String str2 = new String("abc");
    6. System.out.println(str1 == str2);//false
    7. }
    8. }

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. String str1 = "abc";
    5. String str2 = "abc";
    6. System.out.println(str1 == str2);//true
    7. }
    8. }

    总结“”双引号里面的值存在字符串常量池中,
    如果已经存过一次了,就不会进行第二次存储,
    直接返回字符串常量池的对象即可 

    【纠正】网上有些人说:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变。这种说法是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。
    final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的
    例如: 

    1. import java.util.Arrays;
    2. public class Test {
    3. //这是一个main方法,是程序的入口:
    4. public static void main(String[] args) {
    5. final int[] array = new int[]{1, 2, 3, 4};
    6. array[0] = 666;//指向的内容可以修改
    7. //array = new int[]{1, 2, 3, 4, 5};指向本身不能修改
    8. System.out.println(Arrays.toString(array));//[666, 2, 3, 4]
    9. }
    10. }

    真实的原因是:
    在String类中是压根拿不到value[ ]的值,因为根本就没有提供设置和获取value的方法
    所以字符串不可变的

    为什么 String 要设计成不可变的?(不可变对象的好处是什么?) 
    1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了. 
    2. 不可变对象是线程安全的. 
    3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.

    String的内存分析 

    【1】字符串拼接:

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. String s1 = "a"+"b"+"c";
    5. String s2 = "ab"+"c";
    6. String s3 = "a"+"bc";
    7. String s4 = "abc";
    8. String s5 = "abc"+"";
    9. }
    10. }

    上面的字符串,会进行编译器优化,直接合并成为完整的字符串,我们可以反编译验证: 

    然后在常量池中,常量池的特点是第一次如果没有这个字符串,就放进去,如果有这个字符串,就直接从常量池中取:

    内存:

     【2】new关键字创建对象:

    String s6 = new String("abc");

    内存:开辟两个空间(1.字符串常量池中的字符串 2.堆中的开辟的空间) 

    【3】有变量参与的字符串拼接: 

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. String a = "abc";
    5. String b = a + "def";
    6. System.out.println(b);
    7. }
    8. }

    a变量在编译的时候不知道a是“abc”字符串,所以不会进行编译期优化,不会直接合并为“abcdef” 

    反汇编过程:为了更好的帮我分析字节码文件是如何进行解析的:

    利用IDEA中的控制台:

    字符串修改:

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. /*String str = "hello";
    5. str += "abc";
    6. System.out.println(str);
    7. for (int i = 0; i < 10; i++) {
    8. str += "abc";//不建议在循环中使用+对字符进行拼接,要创建很多临时的对象,效率低
    9. }*/
    10. //在底层的代码:
    11. String str = "hello";//对象
    12. StringBuilder sb = new StringBuilder();//对象
    13. sb.append(str);
    14. sb.append("abc");//对象
    15. str = sb.toString();//对象
    16. //共创建了4个对象,所以上面那种写法很不好,如果还是在循环里面进行字符拼接,那效率更低
    17. System.out.println(str);
    18. }
    19. }

    尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. long start = System.currentTimeMillis();
    5. String s = "";
    6. for (int i = 0; i < 10000; ++i) {
    7. s += i;
    8. }
    9. long end = System.currentTimeMillis();
    10. System.out.println(end - start);
    11. start = System.currentTimeMillis();
    12. StringBuffer sbf = new StringBuffer("");
    13. for (int i = 0; i < 10000; ++i) {
    14. sbf.append(i);
    15. }
    16. end = System.currentTimeMillis();
    17. System.out.println(end - start);
    18. start = System.currentTimeMillis();
    19. StringBuilder sbd = new StringBuilder("");
    20. for (int i = 0; i < 10000; ++i) {
    21. sbd.append(i);
    22. }
    23. end = System.currentTimeMillis();
    24. System.out.println(end - start);
    25. }
    26. }

    可以看到在对String类进行修改时,效率是非常慢的,
    因此:尽量避免对String的直接修改,如果要修改建议尽量使用StringBuffer或StringBuilder 

    StringBuilder类

    【1】字符串的分类:
    (1)不可变字符串:String
    (2)可变字符串:StringBuilder,StringBuffer

    【2】StringBuilder底层:非常重要的两个属性: 

    【3】对应内存分析:  

    从源码分析:

    因为value数组被使用了,所以count的值要发生改变
    最后返回当前构建的那个对象,实际上在底层就是返回一个复制之后的数组。 

    注意:count是StringBuilder底层那个value数组被使用的长度
    上述说的第几步到第几步不是真正程序执行的步骤,是自定义,方便自己理解的步骤。 

     

     从源码分析新传入的7个o在底层怎样运作的:

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. //创建StringBuilder的对象:
    5. StringBuilder sb3 = new StringBuilder();
    6. //表面上调用StringBuilder的空构造器,实际底层是对value数组进行初始化,长度为16
    7. StringBuilder sb2 = new StringBuilder(3);
    8. //表面上调用StringBuilder的有参构造器,传入一个int类型的数,实际底层就是对value数组进行初始化,长度为你传入的数字
    9. StringBuilder sb = new StringBuilder("abc");
    10. System.out.println(sb.append("def").append("aaaaaaaa").append("bbb").append("ooooooo").toString());//链式调用方式:return this
    11. }
    12. }

     

    解释可变和不可变字符串

    【1】String---》不可变

    【2】StringBuilder---》可变
    可变,在StringBuilder这个对象的地址不变的情况下,想把“abc”变成“abcdef”是可能的,直接追加即可  

    1. public class Test {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. StringBuilder sb = new StringBuilder();
    5. System.out.println(sb.append("abc") == sb.append("def"));//true,同一个对象
    6. }
    7. }

    常用方法

    【1】StringBuilder常用方法:

    1. public class TestStringBuilder {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. StringBuilder sb = new StringBuilder("nihaojavawodeshijie");
    5. //增
    6. sb.append("这是梦想");
    7. System.out.println(sb);//nihaojavawodeshijie这是梦想
    8. //删
    9. sb.delete(3,6);//删除位置在[3,6)上的字符
    10. System.out.println(sb);//nihavawodeshijie这是梦想
    11. sb.deleteCharAt(16);//删除位置在16上的字符
    12. System.out.println(sb);//nihavawodeshijie是梦想
    13. //改-->插入
    14. StringBuilder sb1 = new StringBuilder("$23445980947");
    15. sb1.insert(3,",");//在下标为3的位置上插入","
    16. System.out.println(sb1);//$23,445980947
    17. //改-->替换
    18. StringBuilder sb2 = new StringBuilder("$2你好吗5980947");
    19. sb2.replace(3,5,"我好累");//在下表[3,5)位置上替换字符串
    20. System.out.println(sb2);//$2你我好累5980947
    21. sb.setCharAt(3,'!');
    22. System.out.println(sb);//nih!vawodeshijie是梦想
    23. //查
    24. StringBuilder sb3 = new StringBuilder("asdfa");
    25. for (int i = 0; i < sb3.length(); i++) {
    26. System.out.print(sb3.charAt(i)+"\t");//a s d f a
    27. }
    28. System.out.println();
    29. //截取
    30. String str = sb3.substring(2,4);//截取[2,4)返回的是一个新的String,对StringBuilder没有影响
    31. System.out.println(str);//df
    32. //反转
    33. sb3.reverse();
    34. System.out.println(sb3);//afdsa
    35. }
    36. }

    【2】StringBuffer常用方法:   与StringBuilder一致,他们的父类都是AbstractStringBuilder

    1. public class Test03 {
    2. //这是一个main方法,是程序的入口:
    3. public static void main(String[] args) {
    4. StringBuffer sb = new StringBuffer("nihaojavawodeshijie");
    5. //增
    6. sb.append("这是梦想");
    7. System.out.println(sb);//nihaojavawodeshijie这是梦想
    8. //删
    9. sb.delete(3,6);//删除位置在[3,6)上的字符
    10. System.out.println(sb);//nihavawodeshijie这是梦想
    11. sb.deleteCharAt(16);//删除位置在16上的字符
    12. System.out.println(sb);//nihavawodeshijie是梦想
    13. //改-->插入
    14. StringBuffer sb1 = new StringBuffer("$23445980947");
    15. sb1.insert(3,",");//在下标为3的位置上插入 ,
    16. System.out.println(sb1);//$23,445980947
    17. //改-->替换
    18. StringBuffer sb2 = new StringBuffer("$2你好吗5980947");
    19. sb2.replace(3,5,"我好累");//在下表[3,5)位置上插入字符串
    20. System.out.println(sb2);//$2你我好累5980947
    21. sb.setCharAt(3,'!');
    22. System.out.println(sb);//nih!vawodeshijie是梦想
    23. //查
    24. StringBuffer sb3 = new StringBuffer("asdfa");
    25. for (int i = 0; i < sb3.length(); i++) {
    26. System.out.print(sb3.charAt(i)+"\t");//a s d f a
    27. }
    28. System.out.println();
    29. //截取
    30. String str = sb3.substring(2,4);//截取[2,4)返回的是一个新的String,对StringBuilder没有影响
    31. System.out.println(str);//df
    32. //反转
    33. sb3.reverse();
    34. System.out.println(sb3);//afdsa
    35. }
    36. }

    String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。
    频繁修改字符串的情况考虑使用StringBuilder。

    注意:String和StringBuilder类不能直接转换。
    如果要想互相转换,可以采用如下原则: 
    String变为StringBuilder: 利用StringBuilder的构造方法或append()方法 
    StringBuilder变为String: 调用toString()方法。

    面试题:String,StringBuilder,StringBuffer之间的区别和联系

    (1)String、StringBuffer、StringBuilder区别与联系

    1. String类是不可变类,即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,直至这个对象销毁。

    2. StringBuffer类则代表一个字符序列可变的字符串,可以通过append、insert、reverse、setChartAt、setLength等方法改变其内容。一旦生成了最终的字符串,调用toString方法将其转变为String。

    3. JDK1.5新增了一个StringBuilder类,与StringBuffer相似,构造方法和方法基本相同。

    StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作,即StringBuffer是线程安全的,而StringBuilder是线程不安全的,所以性能略高。

    通常情况下,创建一个内容可变的字符串,应该优先考虑使用StringBuilder

    StringBuilder:JDK1.5开始  效率高   线程不安全

    StringBuffer:JDK1.0开始   效率低    线程安全

    StringBuilder没有synchronized
    而StringBuffer有synchronized

    (2)以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】

    1. String str = new String("ab"); // 会创建多少个对象
    2. String str = new String("a") + new String("b"); // 会创建多少个对象

    字符串常量池  

    1.创建对象的思考  

    下面两种创建String对象的方式相同吗?  

    1. public static void main(String[] args) {
    2. String s1 = "hello";
    3. String s2 = "hello";
    4. String s3 = new String("hello");
    5. String s4 = new String("hello");
    6. System.out.println(s1 == s2); // true
    7. System.out.println(s1 == s3); // false
    8. System.out.println(s3 == s4); // false
    9. }

    上述程序创建方式类似,为什么s1和s2引用的是同一个对象,而s3和s4不是呢?
    在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。 

    "池" 是编程中的一种常见的, 重要的提升效率的方式, 未来的学习中会遇到各种 "内存池", "线程池", "数据库连接池" ....

    比如:家里给大家打生活费的方式

    1)家里经济拮据,每月定时打生活费,有时可能会晚,最差情况下可能需要向家里张口要,速度慢
    2)家里有矿,一次性打一年的生活费放到银行卡中,自己随用随取,速度非常快
    方式2)就是池化技术的一种示例,钱放在卡上,随用随取,效率非常高。常见的池化技术比如:数据库连接池、线程池等。

    为了节省存储空间以及程序的运行效率,Java中引入了  

    • Class文件常量池:每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息
    • 运行时常量池:在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
    • 字符串常量池  

    2. 字符串常量池(StringTable)  

    字符串常量池在JVM中是StringTable类,实际是一个固定大小HashTable(一种高效用来进行查找的数据结构),不同JDK版本下字符串常量池的位置以及默认大小是不同的:

    3. 再谈String对象创建  

    由于不同JDK版本对字符串常量池的处理方式不同,此处在 Java8 HotSpot 上分析  

    1)直接使用字符串常量进行赋值  

    1. public static void main(String[] args) {
    2. String s1 = "hello";
    3. String s2 = "hello";
    4. System.out.println(s1 == s2); // true
    5. }

    2)通过new创建String类对象  

    练习题:观察程序的输出结果

    结果:good and gbc 

    结论:只要是new的对象,都是唯一的。
    通过上面例子可以看出:使用常量串创建String类型对象的效率更高,而且更节省空间。
    用户也可以将创建的字符串对象通过 intern 方式添加进字符串常量池中。   

    3)intern方法  

    intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),
    该方法的作用是手动将创建的String对象添加到常量池中。

    1. public static void main(String[] args) {
    2. char[] ch = new char[]{'a', 'b', 'c'};
    3. String s1 = new String(ch); // s1对象并不在常量池中
    4. //s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
    5. String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
    6. System.out.println(s1 == s2);// 输出false
    7. }
    8. // 将上述s1.intern()方法打开之后,就会输出true

    注意:在Java6 和 Java7、8中Intern的实现会有些许的差别。 

    String类的OJ练习

    (1)字符串中的第一个唯一的字符

    题目链接:https://leetcode.cn/problems/first-unique-character-in-a-string/

    1. class Solution {
    2. public int firstUniqChar(String s) {
    3. //int[] count = new int[256];
    4. int[] count = new int[26];
    5. // 统计每个字符出现的次数
    6. for (int i = 0; i < s.length(); ++i) {
    7. count[s.charAt(i) - 'a']++;
    8. }
    9. // 找第一个只出现一次的字符
    10. for (int i = 0; i < s.length(); ++i) {
    11. if (1 == count[s.charAt(i) - 'a']) {
    12. return i;
    13. }
    14. }
    15. return -1;
    16. }
    17. }

    (2)字符串中最后一个单词的长度

    题目链接:https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking

    1. import java.util.Scanner;
    2. // 注意类名必须为 Main, 不要有任何 package xxx 信息
    3. public class Main {
    4. public static void main(String[] args) {
    5. Scanner in = new Scanner(System.in);
    6. // 注意 hasNext 和 hasNextLine 的区别
    7. while (in.hasNextLine()) { // 注意 while 处理多个 case
    8. String s = in.nextLine();
    9. // 1. 找到最后一个空格
    10. // 2. 获取最后一个单词:从最后一个空格+1位置开始,一直截取到末尾
    11. // 3. 打印最后一个单词长度
    12. int index = s.lastIndexOf(" ");
    13. String ret = s.substring(index+1);
    14. System.out.println(ret.length());
    15. }
    16. }
    17. }

    (3)验证回文串

    题目链接:https://leetcode.cn/problems/valid-palindrome/

    1. class Solution {
    2. public boolean isPalindrome(String s) {
    3. s = s.toLowerCase();
    4. int left = 0;
    5. int right = s.length() - 1;
    6. while (left < right) {
    7. //不是有效字符就++遍历下个字符,有可能有多个空格,所以要用while,还有个前提是全程一直要left < right,因为有可能一直都没有遇到有效字符,left >= right
    8. while (left < right && !isValidChar(s.charAt(left))) {
    9. left++;
    10. }
    11. //left是有效字符了
    12. while (left < right && !isValidChar(s.charAt(right))) {
    13. right--;
    14. }
    15. //right是有效字符了
    16. if (s.charAt(left) != s.charAt(right)) {
    17. return false;
    18. } else {
    19. left++;
    20. right--;
    21. }
    22. }
    23. return true;
    24. }
    25. private boolean isValidChar(char ch) {
    26. if (Character.isDigit(ch) || Character.isLetter(ch)) {
    27. return true;
    28. }
    29. return false;
    30. /*if (ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z') {
    31. return true;
    32. }
    33. return false;*/
    34. }
    35. }

     (4)华为研发工程师编程题——字符集合

    1. import java.util.Scanner;
    2. public class Main {
    3. public static void main(String[] args) {
    4. Scanner sc = new Scanner(System.in);
    5. while (sc.hasNext()) {
    6. String str = sc.nextLine();
    7. StringBuilder sb = new StringBuilder();
    8. for (int i = 0; i < str.length(); i++) {
    9. char ch = str.charAt(i);
    10. if (!sb.toString().contains(ch + "")) {//contains里面只能是字符串序列,ch是单个字符,ch+""就变成了字符串
    11. sb.append(ch);
    12. }
    13. }
    14. System.out.println(sb.toString());
    15. }
    16. sc.close();
    17. }
    18. }
  • 相关阅读:
    湖北银行冲刺上市:不良率高于行业均值,有公司欠5亿元未能追回
    网络读卡器开发,带你智能感知无线设备
    TCP/IP、DTN网络通信协议族
    golang grpc protoc 环境配置
    安卓TextView调整下划线颜色、与文本底部的距离
    猿创征文 |《深入浅出Vue.js》打卡Day3
    变电站监控视频中运动异常运动物检测和跟踪技术的研究
    词向量word2vec(图学习参考资料)
    golang执行asynq.Client的redis执行脚本任务错误记录
    业务定制型异地多活架构设计
  • 原文地址:https://blog.csdn.net/TheMyth142857/article/details/133385201