• 补坑:Java的字符串String类(1)


    常用方法

    字符串构造

    来看看源码里面String的构造方法

    普通字符串

    1. //"hello" 是字符串常量,没有\0标记结尾
    2. String str = "hello";
    3. System.out.println(str);//hello
    4. String str2 = new String();
    5. System.out.println(str2);//没有输出
    6. String str3 = new String("pppp");
    7. System.out.println(str3);//pppp

    字符串数组

    1. char[] array = {'a','b','c'};
    2. String str4 = new String(array);
    3. System.out.println(str4);//abc
    4. char[] array1 = {'a','b','c'};
    5. String str5 = new String(array1,1,2);
    6. System.out.println(str5);//bc

    ⚠String是引用类型,内部并不存储字符串本身,String类实例变量

    也就是说,一个String其实是长这样的

    我们来尝试分析这段代码

    1. String s1 = new String("hello");
    2. String s2 = new String("world");
    3. String s3 = s1;

    假设s1的地址是0x9,new String之后在堆中创建一个新的String,value是0x8,把"hello"扔给这个String,那这个"hello"的地址也是0x8,s1根据value的地址找到"hello"对象

    字符串长度 

    辨析

    1. String str4 = "";
    2. System.out.println(str4.length());
    3. String str5 = null;
    4. System.out.println(str5.length());

    str4指向的是一个空的字符串,空的字符串也是字符串,也是有长度的,只不过长度为0

    str5不指向任何一个字符串,那它怎么可能会有长度呢

    我们也可以用isEmpty()来检验

    1. String str4 = "";
    2. System.out.println(str4.isEmpty());
    3. String str5 = null;
    4. System.out.println(str5.isEmpty());

    因为str4是空的字符串,那必然返回true,而str5什么都不指向,那就会返回空指针异常 


    字符串比较

    equals()

    辨析:这个打印的结果是true吗?

    1. String s1 = new String("hello");
    2. String s2 = new String("hello");
    3. System.out.println(s1 == s2);

    答案是false,== 比较的是s1和s2的地址,因为s1和s2本来在申请内存空间的时候,地址就不一样,怎么可能相等

    那我们就要邀请String里面的一个方法.equals()来帮忙字符串比较了

    1. public boolean equals(Object anObject) {
    2. if (this == anObject) {
    3. return true;
    4. }
    5. if (anObject instanceof String) {
    6. String anotherString = (String)anObject;
    7. int n = value.length;
    8. if (n == anotherString.value.length) {
    9. char v1[] = value;
    10. char v2[] = anotherString.value;
    11. int i = 0;
    12. while (n-- != 0) {
    13. if (v1[i] != v2[i])
    14. return false;
    15. i++;
    16. }
    17. return true;
    18. }
    19. }
    20. return false;
    21. }

    从代码我们可以看出来,equals是按照字符串里的字符一个一个进行比较的,返回值是布尔类型

    compareTo()

    假设我有两个字符串,我想要比较这两个字符串谁大谁小

    1. String s1 = new String("abc");
    2. String s2 = new String("acd");

    String里面实现了Comparable接口,那就可以调用compareTo方法轻松帮助我们解决问题

         System.out.println(s1.compareTo(s2));

    该方法返回类型是int类型,如果s1>s2返回正数;s1=s2返回0;s1

    所以我们一般用>0或=0或<0来进行字符串比较

    平时我们在输入验证码的时候,系统在比较两个字符串是否相等的时候是忽略大小写的

    其实就是调用compareToIgnoreCase方法


    字符串查找

    charAt()

    如果数字给大了,下标越界

    indexOf()

    从头到后查找字符第一次出现的下标

     也可以找子串在主串的位置

    从指定位置开始查找

    lastIndexOf()

    倒着往回找

    这就能延伸到BF算法和KMP算法,我后续会出博客,欢迎大家关注


    字符串转化

    1.数值和字符串转化

    valueOf()方法,支持多种形式的数值,都能将其转化成字符串

    1. String s = String.valueOf(19.9);
    2. System.out.println(s);

    那字符串怎么转成数字呢?

    1. int data = Integer.parseInt("198");
    2. System.out.println(data);

    哪种数据类型对应哪种parsexx,假如给parseInt传入19.8,程序会报错数值格式错误

    2.字符串大小写转化

    toUpperCase()小写转大写;toLowerCase()大写转小写

    1. String s1 = "hello";
    2. //转变为大写不是在原来的基础上转变
    3. //转变成大写后是一个新的对象
    4. //如果只打印s1的话还只是小写
    5. String ret = s1.toUpperCase();
    6. System.out.println(ret);

    3.字符串转数组

    上面我们提到了数组怎么转成字符串(直接new String(array)暴力转换), 其实字符串也可以转数组(使用toCharArray())

    toCharArray() 方法返回值是一个char类型的数组,所以我们要用一个新的array来接收它

    4.格式化

    1. String s = String.format("%d-%d-%d",2023,11,9);
    2. System.out.println(s);


    字符串替换

    先来看看replace方法

    比如这个,字符串里面所有的ab都被99代替了

    replaceFirst():将首个内容进行替换

    这里只对第一个ab进行了替换,其他ab都不动


    字符串拆分

    split():分割字符串

    1. String str = "hello abc world";
    2. String[] ret = str.split(" ");//将上面的字符串按照空格拆分
    3. for (int i = 0; i < ret.length; i++) {
    4. System.out.println(ret[i]);
    5. }

    问题:

    为什么用"."号分割字符串后打印出来没结果呢?

    1. 字符"|","*","+"都得加上转义字符,前面加上 "\\" .

    2. 而如果是 "\\" ,那么就得写成 "\\\\" .

    3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.

    给你这个字符串,把每个单词拆分出来

    String str = "name=zhangsan&age=18" ;
    

    第一种写法,用|把两个分割判断符号分开来

    1. String[] ret = str.split("=|&");
    2. for (int i = 0; i < ret.length; i++) {
    3. System.out.println(ret[i]);
    4. }

    第二种写法:多次分割法

    1. String[] ret = str.split("&");
    2. for (int i = 0; i < ret.length; i++) {
    3. System.out.println(ret[i]);
    4. String x = ret[i];
    5. //在第一次分割的基础上按照"="进行第二次分割
    6. String[] ret2 = x.split("=");
    7. for (int j = 0; j < ret2.length; j++) {
    8. System.out.println(ret2[j]);
    9. }
    10. }

    字符串截取

    substring(): 两个参数代表区间,一个参数代表从哪里开始截

    1. String str = "ababc";
    2. String ret = str.substring(0,3);//截取字符串范围[0,3)
    3. System.out.println(ret);

    1. String ret1 = str.substring(2);
    2. System.out.println(ret1);

    其他的方法


    字符串的不可变性

    String类在设计时就是不可变的

    String被final修饰,表示这个类不能被继承

    而String的两个实例变量均被private修饰,外部根本拿不到这两个值,也无法修改

    ⚠String的不可变不是因为value被final修饰了,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象的内容是可以修改的,接下来我们就来讲讲具体是怎么修改的。


    String的修改

    看看下面的例子

    1. String str = "hello";
    2. //System.out.println(str);//hello
    3. str = str + "abc";
    4. System.out.println(str);//helloabc

    hello加上一个abc变成helloabc的过程,不是说把str的值进行修改(因为被private修饰根本拿不到value),而是创建了一个新的对象“helloabc",再让str指向这个新对象

    但是如果单纯采用String产生新对象的方法来修改字符串,这样的效率是十分低下的,因为会创建一堆新对象。其实java编辑器采用了另一种方法

    我们拿上面的代码去窥探它的底层 

    底层看不懂?我用代码来翻译一下

    1. public static void main(String[] args) {
    2. String str = "hello";
    3. StringBuilder stringBuilder = new StringBuilder();
    4. stringBuilder.append(str);
    5. stringBuilder.append("abc");
    6. str = stringBuilder.toString();
    7. System.out.println(str);
    8. }

    底层代码构造了一个新的对象stringBuilder,传统的String对象是不可变的,但是有了StringBuilder这个可变对象,字符串就可以被修改了,而且效率很高(原因看下面)。那我们接下来就来讲讲StringBuilder这个对象


    StringBuilder和StringBuffer

    我们首先来比较一下String自创新String对象和采用StringBuilder或StringBuffer的效率

    1. public static void main(String[] args) {
    2. long start = System.currentTimeMillis();
    3. String s = "";
    4. for(int i = 0; i < 10000; ++i){
    5. s += i;
    6. }
    7. long end = System.currentTimeMillis();
    8. System.out.println("String: "+(end - start));
    9. start = System.currentTimeMillis();
    10. StringBuffer sbf = new StringBuffer("");
    11. for(int i = 0; i < 10000; ++i){
    12. sbf.append(i);
    13. }
    14. end = System.currentTimeMillis();
    15. System.out.println("StringBuffer: "+(end - start));
    16. start = System.currentTimeMillis();
    17. StringBuilder sbd = new StringBuilder();
    18. for(int i = 0; i < 10000; ++i){
    19. sbd.append(i);
    20. }
    21. end = System.currentTimeMillis();
    22. System.out.println("StringBuilder: "+(end - start));
    23. }

    看看内层

    内层里面每一次循环StringBuilder一直存在,说明这个对象在创建之后就一直靠它来修改字符串,相比起String那种不断创建新对象再销毁旧对象的方法,这种方式显然效率更高。

    StringBuffer也是这个道理

    StringBuilder和StringBuffer的区别

    StringBuilder的append方法底层

    Stringbuffer的append方法底层

    StringBuffer多了一个synchronized,表示保证线程安全,也就是说StringBuffer是一个线程安全的类

    线程安全是什么?

    可以参考这篇文章什么是线程安全,你真的了解吗? - 知乎

    这里可以简单用个厕所的例子来解释。假设有一个人要上厕所,一个厕所只能容纳一个人,这个人可以当成一个线程。厕所得有门锁才能保证里面的人的安全吧,诶这个锁就是所谓的保障线程安全,能够保证外面的人(其他线程)进不来。当这个人上完厕所后出来锁打开,换下一个人进去锁又闭上

     

    上面的synchronized其实可以当成一个锁,阻止别的线程进入

  • 相关阅读:
    OpenCV + sklearnSVM 实现手写数字分割和识别
    Fiddler Classic 替换本地JS并远程调试
    vite+rollup
    Xilinx FPGA 7系列 GTX/GTH Transceivers (4) Aurora 8b10b 递增数收发验证
    78 # koa 中间件的实现
    Yuan 2.0-M32 是一个基于 Yuan 2.0 架构的双语混合专家 (MoE) 语言模型,旨在以更少的参数和计算量实现更高的准确率
    Cluster API 检索从未如此简单
    编程时遇到的Python语法问题汇总
    想考【软考高级】,但不具备计算机基础?“系规”适合你
    Linux学习笔记
  • 原文地址:https://blog.csdn.net/hellg/article/details/134318925