JavaSE基础语法之 String 类

目录

前言

一、常用方法

(一)字符串构造

(二)String 对象的比较

1. == 比较是否引用同一个对象

2.boolean equals(Object anObject) 方法:按照字典序比较

3.int compareTo(String str)方法:按照字典序进行比较

4.int compareTolgnoreCase(String str)方法:与compareTo方式相同,但是忽略大小写比较

(三)字符串查找

(四)转化

1.数值和字符串转化

2.大小写转换

3.字符串转数组

4.格式化

(五)字符串替换

(六)字符串拆分

(七)字符串截取

(八)去掉字符串中的左右空格,保留中间空格

(九)字符串常量池

1.创建对象的思考

2.字符串常量池(StringTable)

3.再谈 String 对象创建

(十)字符串的不可变性

1.String 类在设计时就是不可改变的,String 类实现描述中已经说明了

2.所有涉及到可能修改字符串内容的操作都是创建一个新对象 ,改变的是新对象

(十一)字符串修改

二、StringBuilder 和 StringBuffer

(一)StringBuilder 和 StringBuffer的介绍

(二)面试题

1.String、StringBuilder、StringBuffer 的区别

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

总结


前言

String 类在 Java 中是极为特殊但又经常使用的一个类,所以我们需要重点学习

一、常用方法

(一)字符串构造

String 类提供的构造方法非常多,常用的就以下三种:

public static void main1(String[] args) {
    //使用常量串构造
    String s1 = "hello world!";
    //可以直接输出对象的原因就是在 String 类中重写了 toSting 方法
    System.out.println(s1);

    //使用 new String 对象
    String s2 = new String("hehe");
    System.out.println(s2);

    //使用字符数组进行构造
    char[] array = {'a','b','c','d'};
    String s3 = new String(array);
    System.out.println(s3);
}

注意:

  1. String 是引用类型,内部并不存储字符串本身
  2. 在 Java 中""引起来的也是 String 类型对象

(二)String 对象的比较

1. == 比较是否引用同一个对象

2.boolean equals(Object anObject) 方法:按照字典序比较

字典序:字符大小的顺序

String类重写了父类 Object 中 equals 方法,Object 中 equals 默认按照 == 比较,String 重写 equals 方法后,按照字典序逐个字符进行比较

3.int compareTo(String str)方法:按照字典序进行比较

于 equals 不同的是,equals 返回的是 boolean 类型,而 compareTo 返回的是 int 类型。具体比较方式:

  1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
  2. 如果前k个字符相等(k为两个字符串长度最小值),返回两个字符串长度之间的差值

4.int compareTolgnoreCase(String str)方法:与compareTo方式相同,但是忽略大小写比较

public static void main2(String[] args) {
    String s1 = "hello world!";
    String s2 = new String("hello world!");
    String s3 = new String("hehe");
    String s4 = new String("Hehe");
    System.out.println(s1 == s2);
    //返回 boolean 类型
    System.out.println(s1.equals(s2));
    //返回 int 类型,
    //1.出现的第一个不同的字符之间的差值
    //2.当长度短的那个字符串遍历结束还没有找到不同的,返回两个字符串长度的差值
    System.out.println(s2.compareTo(s3));
    //与 compareTo 比较,忽略字母的大小写
    System.out.println(s3.compareToIgnoreCase(s4));
}

(三)字符串查找

  • 顺序查找
//字符串顺序查找
public static void main3(String[] args) {
    //char charAt(int index);
    //返回以 index 为下标的字符
    String s1 = "hello world!";
    System.out.println(s1.charAt(2));
    //int indexOf(char ch)
    //返回ch第一次出现的位置,没有返回-1
    System.out.println(s1.indexOf('w'));
    //int indexOf(int ch, int fromIndex)
    //从fromIndex位置开始找ch第一次出现的位置,没有返回-1
    System.out.println(s1.indexOf('o',5));
    //int indexOf(String str)
    //返回str第一次出现的位置,没有返回-1
    System.out.println(s1.indexOf("or"));
    //int indexOf(String str, int fromIndex)
    //从fromIndex位置开始找str第一次出现的位置,没有返回-1\
    String s2 = new String("abc bcd abd abc bbb");
    System.out.println(s2.indexOf("abc",5));

}
  • 逆序查找
public static void main4(String[] args) {
    String s1 = new String("abc as ghd hac ca gac");
    //int lastIndexOf(int ch)
    //从后往前找,返回ch第一次出现的位置,没有返回-1
    System.out.println(s1.lastIndexOf('a'));
    //int lastIndexOf(int ch, int fromIndex)
    //从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返回-1
    System.out.println(s1.lastIndexOf('h',10));
    //int lastIndexOf(String str)
    //从后往前找,返回str第一次出现的位置,没有返回-1
    System.out.println(s1.lastIndexOf("ac"));
    //int lastIndexOf(String str, int fromIndex)
    //从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返回-1
    System.out.println(s1.lastIndexOf("ac",12));
}

(四)转化

1.数值和字符串转化

public static void main5(String[] args) {
    //数字转字符串
    String s1 = String.valueOf(1234);
    String s2 = String.valueOf(12.45);
    String s3 = String.valueOf(true);
    String s4 = String.valueOf(new Student("zhangsan",20));
    System.out.println(s1);
    System.out.println(s2);
    System.out.println(s3);
    System.out.println(s4);
    System.out.println("==============================");
    //字符串转数字
    int data1 = Integer.parseInt("1234");
    double data2 = Double.parseDouble("12.34");
    System.out.println(data1);
    System.out.println(data2);
}

2.大小写转换

public static void main6(String[] args) {
    String s1 = "HELlo";
    String s2 = "HEllO";
    //小写转大写
    System.out.println(s1.toLowerCase());
    //大写转小写
    System.out.println(s2.toUpperCase());
}

3.字符串转数组

public static void main7(String[] args) {
    String s = "hello world";
    //字符串转数组
    char[] array = s.toCharArray();
    for (int i = 0; i < s.length(); i++) {
        System.out.print(array[i]);
    }
    System.out.println();
    //数组转字符串
    String s2 = new String(array);
    System.out.println(s2);
}

4.格式化

public static void main8(String[] args) {
    String s = String.format("%d-%d-%d",2019,9,14);
    System.out.println(s);
}

(五)字符串替换

使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:

public static void main9(String[] args) {
    //String replaceAll(String regex, String replacement)
    //字符串不能修改,只能重新创建一个新的字符串接收返回值
    //第一个形参是被修改的字符串,第二个形参是用来修改的字符串
    //替换所有指定内容
    String s1 = new String("abcabcbde");
    String s2 = s1.replaceAll("ab","pq");
    System.out.println(s2);

    //String replaceFirst(String regex, String replacement)
    //替换首个内容
    String s3 = s1.replaceFirst("ab","pq");
    System.out.println(s3);
}

注意事项:由于字符串是不可变对象,替换不修改当前的字符串,而是产生一个新的字符串

(六)字符串拆分

可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串

public static void main17(String[] args) {
    String s = new String(", , , , ,        ab. sdf");
    String[] str1 = s.split(" ");
    System.out.println(str1);
    /*String s1 = new String("hello world and IDEA");
    //String[] split(String regex)
    //将字符串以形参字符串 regex 为边界全部拆分
    String[] str = s1.split(" ");
    for (String s: str) {
        System.out.println(s);
    }
    //部分拆分
    System.out.println("=================");
    String[] str2 = s1.split(" ",2);
    for (String s: str2) {
        System.out.println(s);
    }

    //拆分ip地址
    //字符"|","*","+"都得加上转义字符,前面加上 "\\"
    String s2 = new String("198.168.6.1");
    String[] str3 = s2.split("\\.");
    for (String s: str3) {
        System.out.println(s);
    }

    //如果一个字符串中有多个分隔符,可以用"|"作为连字符
    System.out.println("======================");
    String s3 = new String("abv.sajd?fj sd");
    String[] str4 = s3.split("\\.|\\?| ");
    for (String s: str4) {
        System.out.println(s);
    }

    //如果是 "\\" ,那么就得写成 "\\\\"
    //具体 Java 的"\"知识点查看默认收藏夹
    String s4 = new String("abc\\ade");
    String[] str5 = s4.split("\\\\");
    for (String s: str5) {
        System.out.println(s);
    }*/
}

注意事项:

  1. 字符"|","*","+"都得加上转义字符,前面加上"\\"
  2. 而如果是"\",那么就得写成"\\\\"
  3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符

(七)字符串截取

从一个完整的字符串之中截取出部分内容

public static void main11(String[] args) {
    String s1 = new String("abcdbcefabc");
    //String substring(int beginIndex)
    //从指定索引截取到结尾
    String str1 = s1.substring(5);
    System.out.println(str1);

    //String substring(int beginIndex, int endIndex)
    //截取部分内容,左闭右开
    String str2 = s1.substring(5,10);
    System.out.println(str2);
}

注意事项:

  1. 索引从0开始
  2. 注意前闭后开区间的写法,substring(0,5)表示包含 0 号下标的字符,不包含 5 号下标

(八)去掉字符串中的左右空格,保留中间空格

public static void main12(String[] args) {
    String s1 = new String("   hello world  !  ");
    //String trim()
    //去掉字符串中的左右空格,保留中间空格
    String str1 = s1.trim();
    System.out.println(s1);
    System.out.println("[" + str1 + "]");
    
}

trim() 方法会去掉字符串开头和结尾的空白字符(空格,换行,制表符等)

(九)字符串常量池

1.创建对象的思考

在 Java 程序中,由于会有某些常量经常使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和 String 类都提供了常量池

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

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

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

2.字符串常量池(StringTable)

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

JDK版本

字符串常量池位置

大小设置(单位:字节)

Java6

方法区

固定大小:1009

Java7

堆中

可设置,没有大小限制,默认大小:60013

Java8

堆中

可设置,有范围限制,最小是1009

3.再谈 String 对象创建

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

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

//直接使用字符串常量进行赋值
public static void main13(String[] args) {
    String s1 = "hello";
    String s2 = "hello";
    System.out.println(s1 == s2);
}

图示说明:

说明:

  1. 在字节码文件加载时,"hello" 常量串已经创建好了,并保存在字符串常量池中
  2. 当使用 String s1 = "hello";创建对象时,先在字符串常量池中找,找到了,将该字符串引用赋值给s1,否则在字符串常量池创建"hello"字符串常量对象,并将引用赋值给s1

(2)通过 new 创建 String 类对象

//通过 new 创建 String 类对象
public static void main14(String[] args) {
    String s1 = new String("hello");
    String s2 = new String("hello");
    //只要是 new 的对象,都是唯一的
    System.out.println(s1 == s2);
}

图示说明:

通过上面例子可以看出:如果 new 一个常量字符串,那么如果哈希表中没有,就会先在哈希表创建一个常量字符串对象,然后再创建一个新的对象,用新的对象指向哈希表中的常量字符串。使用常量串创建String类型对象的效率更高,而且更节省空间。用户也可以将创建的字符串对象通过 intern 方式添加进字符串常量池中

(3)intern方法

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

// intern 方法
public static void main15(String[] args) {
    char[] ch = new char[]{'a', 'b', 'c'};
    String s1 = new String(ch); // s1对象并不在常量池中
    //如果常量池中存在 s1 对象中的字符串,那么 s1的对象引用 将不会放入到常量池中
    s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
    String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
    System.out.println(s1 == s2);
}

面试题:请解释 String 类中两种对象实例化的区别

JDK1.8中

  • String str = "hello";

        只会开辟一个字符串对象,创建一个字符数组对象

  • String str = new String("hello");

        开辟两个字符串对象(一个在),创建一个字符数组对象

  • String str = new String(new char[]{'h',''e,'l','l','o'});

        开辟一个字符串对象,创建两个字符数组对象(new 首先创建了一个字符数组,字符串对象创建时,会将这个字符数组进行拷贝,即返回一个新的地址的字符数组,然后再指向新的字符数组)

(十)字符串的不可变性

String 是一种不可变对象。字符串中的内容是不可改变的。字符串不可被修改,是因为:

1.String 类在设计时就是不可改变的,String 类实现描述中已经说明了

String 类中的字符实际保存在内部维护的 value 字符数组中,该图还可以看出:

  1. String 类被 final 修饰,表明该类不能被继承
  2. value 被 final 修饰,表明 value 自身的值不能改变,即不能引用其他字符数组,但是其引用空间中的内容可以修改

2.所有涉及到可能修改字符串内容的操作都是创建一个新对象 ,改变的是新对象

【误区警示】:final 修饰类表明该类不能被继承,final 修饰引用类型表明该引用类型不能再引用其他对象,但是其引用对象中的内容是可以修改的。因此,之所以说 String 类创建的对象无法被修改,是因为 val 引用被 final 修饰,不能引用其他对象,如果想要改变 val 内部的值,val 被 private 修饰,只能通过类内部进行修改,但是 String 类本身并没有提供此方法,因此说 String 是一种不可变对象

为什么 String 要设计成不可变的?

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

(十一)字符串修改

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

如果要修改建议使用 StringBuffer 和 StringBuilder

二、StringBuilder 和 StringBuffer

(一)StringBuilder 和 StringBuffer的介绍

由于 String 的不可更改特性,为了方便字符串的修改,Java 中又提供了 StringBuilder 和 StringBuffer 两大类。这两大类大部分功能是相同的。

  1. StringBuilder 和 StringBuffer 以及 String 是三个不同的类,三者不能混为一谈,但是前两个直接重写了 toString 方法,可以通过调用 toString 方法进行转换
  2. StringBuilder 和 StringBuffer都不能直接赋值,必须 new 对象(就是为了保证字符串的可变性,不能直接接收一个常量字符串)

StringBuilder 的常用方法较少,适用于单线程,StringBuffer 的方法较多,适用于多线程,下面介绍一些 StringBuilder 和 StringBuffer 常用的一些方法(前面写类型的就是返回调用方法的对象的类型)

方法

说明

类型 append(String str)

在尾部追加,相当于 String 的 +=,可以追加:boolean,char,char[],double,float,int,long,Object,String,和自身类型的变量

char charAt(int index)

获取 index 位置的字符

int length()

获取字符串的长度

int capacity()

获取底层保存字符串空间总的大小

void ensureCapacity(int mininmumCapacity)

扩容

void setCharAt(int index,char ch)

将 index 位置的字符设置为ch

int indexOf(String str)

返回 str 第一次出现的位置

int indexOf(String str,int fromIndex)

从fromIndex位置开始查找 str 第一次出现的位置

int lastIndexOf(String str)

返回最后一次出现 str 的位置

int lastIndexOf(String str,int fromIndex)

从 fromIndex 位置开始找 str 最后一次出现的位置

类型 insert(int offset, String str)

在 offset 位置插入:八种基本类型 & String类型 & Object 类型数据

类型 deleteCharAt(int index)

删除index位置字符

类型 delete(int start, int end)

删除[start,end)区间内的字符

类型 replace(int start, int end, String str)

将[start,end)位置的字符替换为str

String substring(int start)

从 start 开始一直到末尾的字符以 String 的方式返回

String substring(int start,int end)

将 [start,end) 范围内的字符以 String 的方式返回

类型 reverse()

反转字符串

String toString()

将所有字符按照 String 的方式返回

从方法上就可以看出:String 和 StringBuilder 和 StringBuffer 最大的区别在于 String 的内容无法修改,而 StringBuilder 的内容可以修改。频繁修改字符串的情况考虑使用 StringBuilder 或者 StringBuffer

注意:String 和 StringBuilder 类不能直接转换。如果要想互相转换,可以采用下面的方法:

  • String 转为 StringBuilder:利用 StringBuilder 的构造方法或 append() 方法
  • StringBuilder 变为 String:调用 toString() 方法

(二)面试题

1.String、StringBuilder、StringBuffer 的区别

  1. String 内容不可被修改,StringBuilder、StringBuffer的内容可以修改
  2. StringBuilder 和 StringBuffer 的大部分功能是相似的
  3. StringBuffer 采用同步处理,属于线程安全操作;而 StringBuilder 未采用同步处理,属于线程不安全操作

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

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

解答:

  1. 第一个,首先会在常量池创建一个字符串对象和一个字符数组对象,新的字符串 new 了之后再创建一个新的字符串对象,里面 val 引用指向常量池中的字符数组,所以会创建两个字符串对象,一个字符数组对象
  2. 第二个,首先会在常量池创建字符串为 "a" 和 "b" 的字符串对象,然后因为 new 所以会再在堆里创建两个字符串对象, String 连接创建新的 StringBuilder 对象,再然后调用 toString() 方法再创建一个 String 对象,所以会创建六个对象

总结

以上就是今天要讲的内容,与诸君共勉

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求索1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值