Java中的String类

认识 String 类

1、创建字符串

// 方式一
String str = "Hello Bit";
// 方式二
String str2 = new String("Hello Bit");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);

此外,在官方文档上还有其他常用构造方法用法,如下面的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、字符串的比较

String str = "abcdef";
String str2 = new String("abcdef");

回忆引用:可以把引用想象成一个标签, “贴” 到一个对象上. 一个对象可以贴一个标签, 也可以贴多个. 如果一个对
象上面一个标签都没有, 那么这个对象就会被 JVM 当做垃圾对象回收掉

两者的区别可以看下图(str是在栈上有个引用,直接指向存放在堆中的字符常量池上的字符串"abcdef";而定义str2的时候,是在堆上new一个对象出来,里面存放value,然后在常量池中去找"abcdef",有这个字符串,则直接把"abcdef"的地址引用拿来,str 不等于 str2)
在这里插入图片描述

Java内存

在这里回顾一下Java内存的布局:
在这里插入图片描述
看下面的代码之前先清楚在String类中,比较两个String类型用" == "比较的是两个量的引用,而不是值(内容),比较的大小要用**equals()**方法,在下面讲equals()方法的用法
String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象。

	public static void main(String[] args) {
		String str = "abcdef";//直接赋值
        String str2 = new String("abcdef");
        char[] array = {'a','b','c','d','e','f'};
        String str3 = new String(array);
        String str4 = "abc"+"def";//编译期已经确定-》"abcdef"
        String str5 = "abc"+new String("def");
        System.out.println(str == str2);//false
        System.out.println(str == str3);//false
        System.out.println(str == str4);//true
        System.out.println(str == str5);//false
        String str6 = "abc";
        String str7 = "def";
        String str8 = str6+str7;
        System.out.println(str == str8);//false
    }

在这里插入图片描述
上面str和str2已经解释过了,下面解释其余:

str3:是在堆上面有个存放value的对象,地址为0x300,然后在堆上new一个String数组对象出来,地址引用为0x400,然后value对象指向数组对象,把0x400放在value中。

str4:str4是在编译期已经确定—> “abcdef”,在字符常量池中与str是同一个引用,即0x100。

:常量在编译的时候就已经拼接了,如果是常量和其他的要借助new其他的对象。

str5:是在常量池中有一个字符串"abc"对象,此外new了一个value对象指向"def"对象,然后把"abc"对象和value对象合并在一起,形成一个新的对象0x999,然后str5的引用就是0x999。

str6:定义str6的时候,在字符常量池中已经有了"abc",直接指向它的引用。

str7:同str6。

str8:把str6和str7对象合并成一个新的对象,不等同于str。

上面的代码运行结果分别为

false
false
true
false
false

问:下面的代码在运行过程中一共产生了几个对象?

public static void main(String[] args) {
        int a = 360;
        String str = "abc"+"def" + a;//"abcdef360"
    }

答案:1个。
原因:在编译期间已经拼接好了,变量a存在在虚拟机栈中。这里注意Java中变量放在哪由static决定,不由final决定,final只是说它不能被修改,由static修饰的存在在方法区

Java 中要想比较字符串的内容, 必须采用String类提供的equals方法
要比较值是否相等,要用str.equals(str2) 点“ . ”前面的str不能为空null 括号里面的可以为空
字符串比较大小,可以使用compareTo(),不需要实现comparable方法,string已经实现过了

	public static void main(String[] args) {
        String str = null;//直接赋值
        String str2 = new String("abcdef");

        //System.out.println(str.equals("abcdef"));
        //这里的代码会报错,原因是str为空null,使用equals()方法时,点号前面的值不能为空
        

		System.out.println("abcdef".equals(str));//false
        //这里可以比较"abcdef"和str的内容
        
        //此外这里还可以用String类的compareTo()方法,不需要自己实现
       String str1 = "aecdef";
       String str2 = "abcdef";
       //str1与str2进行比较
        //str1 > str2     返回 > 0的数字,
        //str1 == str2     == 0
        //str1 < str2     < 0
       System.out.println(str2.compareTo(str1));
       //具体数字大小取决于比较值中Ascii码表中值的大小,
        //例如比较上面的str1和str2,第一个字母a相同,第二个字母,一个是e,一个是b,e的Ascii码值为101,b为98,101减去98等于3,
        //所以下面打印的结果为-3,表示str2比str1小了3,结果为-3
    }

上面代码的解释全部写在注释中了。
equals()方法返回true or false,comparTo()方法返回大于0等于0小于0的数字
equals 使用注意事项:
Java 中要想比较字符串的内容, 必须采用String类提供的equals方法

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者这样写也行
// 执行结果
true

现在需要比较 str 和 “Hello” 两个字符串是否相等, 我们该如何来写呢?

String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

在上面的代码中,更推荐使用方式二,因为 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会

String str = null;
// 方式一
System.out.println(str.equals("Hello")); // 执行结果 抛出 java.lang.NullPointerException 异常
// 方式二
System.out.println("Hello".equals(str)); // 执行结果 false 

注意事项: “Hello” 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法

String类中的intern()方法的规则:
在这里插入图片描述
关于intern方法的用法,可以参考:
https://blog.csdn.net/qq_41797857/article/details/93374654

3、字符串常量池

的出现,是为了解决效率问题

String类的设计使用了共享设计模式

在JVM底层实际上会自动维护一个对象池(字符串常量池)

	 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存 到这个对象池之中.
	 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用 
 	 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用

理解 “池” (pool)
"池" 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据 库连接池” …
然而池这样的概念不是计算机独有, 也是来自于生活中.
举个栗子: 现实生活中有一种女神, 称为 “绿茶”, 在和高富帅谈着对象的同时, 还可能和别的屌丝搞暧昧. 这时候这个屌丝被 称为 “备胎”. 那么为啥要有备胎? 因为一旦和高富帅分手了, 就可以立刻找备胎接盘, 这样 效率比较高. 如果这个女神, 同时在和很多个屌丝搞暧昧, 那么这些备胎就称为 备胎池

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

  1. 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
  2. 构造方法:会开辟两块堆内存空间,其中一块成为垃圾空间,不会自动保存在对象池中,可以使用
    intern()方法手工入池

4、 理解字符串不可变

字符串是一种不可变对象. 它的内容不可改变.
String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组
那么如果实在需要修改字符串,
例如, 现有字符串 str = “Hello” , 想改成 str = “hello” , 该怎么办?
a) 常见办法: 借助原字符串, 创建新的字符串

substring()方法的用法

String str = "Hello";
str = "h" + str.substring(1);
//提取一个子串:从1号下标开始提取子串
System.out.println(str);
// 执行结果
hello

b)特殊办法:反射
关于反射
反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”.
指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 “认清
自己”
反射->Java的自省,Java中的大bug,在类外能拿到私有的方法,私有的东西,就相当于类的所有东西对于我来说都是公开的

为什么 String 要不可变?(不可变对象的好处是什么?)

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

5、字符, 字节与字符串

代码示例: 给定字符串一个字符串, 判断其是否全部由数字所组成
思路: 将字符串变为字符数组而后判断每一位字符是否是" 0 “~”‘9’"之间的内容,如果是则为数字

public static void main(String[] args) {
 	String str = "1a23456" ;
 	System.out.println(isNumber(str)?"字符串由数字所组成!" : "字符串中有非数字成员!");
}
public static boolean isNumber(String str) {
 	char[] data = str.toCharArray() ;
 	for (int i = 0; i < data.length; i++) {
 		if (data[i]<'0' || data[i]>'9') {
 		return false ;
		}
	}
	return true ;
	/* for (int i = 0; i < str.length(); i++) {
            if(str.charAt(i) < '0' || str.charAt(i) > '9') {
                return false;
            }
       }
   return true;*/
} 
public static String func2() {
       char[] array = {'a','b','c','d','e','f'};
       //return new String(array);
        //也是把数组转换为字符串
        return String.copyValueOf(array);
    }

代码示例:字符串与字符数组的示例

String str = "helloworld" ;
// 将字符串变为字符数组
char[] data = str.toCharArray() ;
for (int i = 0; i < data.length; i++) {
 System.out.print(data[i]+" ");
}
// 字符数组转为字符串
System.out.println(new String(data)); // 全部转换
System.out.println(new String(data,5,5)); // 部分转换

关于上面介绍的字符串常用方法的补充:

equalsIgnoreCase() 方法:忽略大小写比较字符串

String str1 = "hello" ;
String str2 = "Hello" ;
System.out.println(str1.equals(str2)); // false
System.out.println(str1.equalsIgnoreCase(str2)); // true 

compareTo() 返回ASCII码差值
若字符串长度不一样,如果前面部分的字符一样,返回字符串长度的差值,如"ABC"和"ABCDEFGH"返回 -5

contains() :查找是否包含子串

String str2 = "DE";
String str4 = "ABCDEFDE";
System.out.println(str4.contains(str2));//true,  str4里面包含str2

indexOf(String str) 方法,查找指定字符串,找到后返回该下标,否则返回-1

System.out.println(str4.contains(str2));
返回str2在str4中的位置,从头开始找,第一次出现的位置,如果没有则返回-1
System.out.println(str4.indexOf(str2,4));
指定从哪个位置开始找,找到了返回下标,没有则返回-1

contains() 底层调用indexOf() 方法,indexOf() 效率比contains() 效率高

str4.lastIndexOf(str2),从后往前找,默认从最后一个元素从后往前找
str4.lastIndexOf(str2,3),从指定位置从后往前找
str4.startsWith("AB"),是否以指定字符串开始
str4.startsWith("AB",2),从指定位置开始判断
str4.endsWith("DE"),判断是否以"DE"结束

replace()、replaceAll()、replaceFirst()、 字符串的替换:

String str = "abcadab";
str = str.replace("ab","gb");把ab替换为gb
str = str.replaceFirst("ab","gb");替换首个内容

String str = "helloworld" ;
System.out.println(str.replaceAll("l", "_"));
System.out.println(str.replaceFirst("l", "_")); 
由于字符串是不可变对象,替换不修改当前字符串, 而是产生一个新的字符串

split() 字符串的分割

String str = "abc,ad,ab";
String[] strings = str.split(",");
以逗号分割
String[] strings = str.split("," , 2);
分割为两组
Systrm.out.println(Arrays.toString(strings));
String str = "192.168.1.1" ;
String[] result = str.split("\\.") ;// .点号要转义再转义
for(String s: result) {
 System.out.println(s);
} 

1. 字符"|","*","+"都得加上转义字符,前面加上"\".
2. 而如果是"",那么就得写成"\\".
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符
多次分割,拿出来s再进行分割
多次拆分的代码:
		String str3 = "abc=def ght=gb";
        String[] strings3 = str3.split(" ");
        System.out.println(Arrays.toString(strings3));

        for (String s : strings3  ) {
            String[] ss = s.split("=");
            System.out.println(Arrays.toString(ss));
        }
最后打印:可以看成分割了两次,先将str3分为2组,再分割一次
		[abc=def, ght=gb]
		[abc, def]
		[ght, gb]

牛客网
字符串连接:不借用任何字符串库函数实现无冗余地接受两个字符串,然后把它们无冗余的连接起来。
输入:abc def 输出:abcdef

import java.util.Scanner;
public class Main{
    public static String func(String str){
        String[] strs = str.split(" ");
        //String ret = "";
        //for(String s : strs){
        //    ret += s;
        //}
        return strs[0] + strs[1];
    }
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        String str = scan.nextLine();
        String ret = func(str);
        System.out.println(ret);
    }
}

substring(int beginIndex)、substring(int beginIndex,int endIndex) :字符串截取

str.substring(0,3),0号位置开始,截取到3号位置,左闭右开

String str = "helloworld" ;
System.out.println(str.substring(5));
System.out.println(str.substring(0, 5));
1. 索引从0开始
2. 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标

KMP算法:查找子串在主串中的位置

6、其他字符串操作方法

trim(): 删除字符串前后的空格,中间的空格保留

String str = "   abc    def    ";
str = str.trim(str);

toUpperCase()、toLowerCase() :字符串转大小写

str = str.toUpperCase(str);
str = str.toLowerCase(str);

isEmpty() :判断是否为空字符串"",而不是判断是否为null

String str1 = null;
String str2 = "";
str1.isEmpty()会空指针异常
str2..isEmpty()不会空指针异常

牛客网:
逆置字符串

public static String reverse(String str){
        char[] value = str.toCharArray();
        int left = 0;
        int right = value.length - 1;
        while(left < right){
            char tmp = value[left];
            value[left] = value[right];
            value[right] = tmp;
            left++;
            right--;
        }
        return String.copyValueOf(value);
        //return new String(value);
    }

翻转字符串(2)

import java.util.Scanner;
public class Main{
    public static String reverse(String str,int left,int right){
        char[] value = str.toCharArray();
        while(left < right){
            char tmp = value[left];
            value[left] = value[right];
            value[right] = tmp;
            left++;
            right--;
        }
        return String.copyValueOf(value);
    }
    public static String func(String str,int k){
        if(str == null || k <= 0){
            return null;
        }
        str = reverse(str,0,k - 1);
        str = reverse(str,k,str.length() - 1);
        str = reverse(str,0,str.length() - 1);
        return str;
    }
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int k = scan.nextInt();
        String str = scan.nextLine();
        String ret = func(str,k);
        System.out.println(ret);
    }
}
//这段代码在牛客网上执行不通过,报数组越界异常

7、 StringBuffer 和 StringBuilder

首先回顾一下String类的特点
任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指
向而已。
通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer
StringBuilder类。
StringBuffer 和 StringBuilder 大部分功能是相同的
在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为**append()**方法

StringBuilder不会产生新对象
其实String字符串的" + "拼接,底层被优化为append()

在拼接的时候调用了append()方法,可以连续点好几个append();
toString();

public class TesetDemo{
	public static void fun(StringBuffer temp) {
        temp.append("\n").append("www.baidu.com.cn");
    }
    public static void main(String[] args){
		StringBuffer sb = new StringBuffer();
        sb.append("Hello").append("World").append("!!!");
        fun(sb);
        System.out.println(sb);
	}
}
执行结果:
HelloWorld!!!
www.baidu.com.cn
以后拼接用StringBuffer和StringBuilder,以及append()方法

面试题:请解释String、StringBuffer、StringBuilder的区别?

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

StringBuffersynchronized所修饰,代表它是线程安全的,使用场景就是在多线程下使用,如果在多线程下实现的话尽量用StringBuffer

如果一个方法被synchronized所修饰,代表它是线程安全的,使用场景就是在多线程下使用,如果在多线程下实现的话尽量用StringBuffer

StringBuffer适用于多线程,里面的方法除了构造方法都是被synchronized所修饰,StringBuilder适用于单线程
StringBuffer里面的方法比StringBuilder多,比如reverse()方法

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值