Java:浅谈Java的String类

1. 创建一个字符串

创建的构造String方法如下:

//方式1:直接赋值
String str1 = "hello world";
//方式2:采用构造方法
String str2 = new String("hello world");

其中方式1的代码引用指向关系如下:
在这里插入图片描述
如果我们将代码1进行修改

//方式1
String str1 = "hello world";
String str2 = str1;

则他们str1和str2的引用关系如下
在这里插入图片描述
如果我们修改了str1,那么str2会不会也改变呢,答案是否定的。我们通过引用关系图来观察。首先我们先将str1的值进行修改,代码如下:

String str1 = "hello world";
String str2 = str1;
String str1 = "hello";
System.out.println(str1);//结果仍然是hello

在这里插入图片描述
通过上图我们也可以理解了,“str1 = “hello” ”这个代码并不是修改字符串,而是将str1这个引用指向可一个新的对象。
小结:
创建一个字符串有两种方式分别是:直接赋值和采用构造方法。

2. 字符串比较相等

如果我们想比较两个整形变量是否相等可以用“==”来判断。

int a = 1;
int b = 1;
System.out.println(a == b);

//执行结果为 true

那我们是否可以用" == "来比较两个String变量呢?观察如下代码

我们利用第一节中的方式1来写代码1

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);

//执行结果为 true

结果看起来还不错,别急。看看如下(我们用第一节中的方式2来写代码2)

String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);

//执行结果为 false

两种代码都是实现了str引用指向“hello”对象,那为什么结果不一样呢。我们需要从内存布局来分析两种创建String的区别。
代码1的内存布局如下
在这里插入图片描述
也可以看出,这里的str1和str2是指向同一个对象的。这个时候的“hello”是存放在字符串常量池中的。有的朋友可能对字符串常量池有所疑惑,下面简单的解释一下。像“hello”这样的字符串字面值常量也是需要一定的内存空间来储存的,这样的字面量常量是无法修改的,并且常常是在和多个地方需要引用它的,如果我们用一个就进行一次存储,必然会浪费大量的储存空间,所以字符串常量池就应运而生。字符串常量池就是用来储存这样被大量引用的重复的字面量常量,直接将“hello”放到字符串常量池中,当我们需要引用“hello”时,直接用过常量池中“hello”所在的位置就可以了,如图中的0x100。
代码2的内存布局如下
在这里插入图片描述
代码2是通过String str = new String(“hello”);的方式来创建的String对象,虽然两个对象内容一样,但是并不是同一个对象。而“==”比较的是否是同一个对象。

那么问题来了,我们想要比较两个字符串的内容该怎么办呢?需要使用String中提供的equals方法。

String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1.equals(str2));

//结果为true

equals的注意事项
请思考如下代码:

String str = new String("hello");
System.out.println(“hello”.equals(str));//方式1
System.out.println(str.equals(“hello”));//方式2

上面两种方式,哪种方式比较好呢?
答案是方式1,原因是如果str如果是null,则会抛出空指针异常。

小结:

  1. String类比较两个字符串是否指向同一对象使用“ == ”;
  2. String类比较两个字符串内容是否相同使用“equals”。

3. 详解两种赋值方式

第二节已经对字符串常量池做了一定的介绍,本节继续分析。

3.1 直接赋值

即这种方式:

String str = "hello".

1) 采用直接赋值的模式进行String类的对象实例化操作,那么该实例化对象将直接保存在字符串常量池中。
2) 如果下次再采用直接赋值的模式进行String类的对象实例化操作,此时字符串常量池中已有相同的内容,将直接进行引用,这样将会节省大量的内存空间。

3.2 采用构造方法赋值

即这种方式:

String str = new String("hello").

咱们来详细分析一下这种赋值方式的内存布局。
在这里插入图片描述
上述做法有两个缺点:

  1. 这种构造方法会开辟两块堆空间内存,并且其中一块堆内存将成为垃圾空间(字符串常量“hello”也是一个匿名对象,用了一次就不再使用,就会成为垃圾空间,被JVM自动回收掉)
  2. 会导致资源浪费,同一个字符串会被储存多次。

如果我们采用了构造方法进行赋值后,又想把其放入字符串常量池中怎么办?采用String中的intern方法。观察如下代码

        String str1 = new String("hello");
        String str2 = "hello";
        System.out.println(str1 == str2);
        
        // 返回false

        String str1 = new String("hello").intern();
        String str2 = "hello";
        System.out.println(str1 == str2);
        
        //返回true

小结
两种赋值方式的区别:

  1. 直接赋值:只会开辟出一块堆空间,并且该字符串会保存在字符串常量池中以便下次使用。
  2. 构造方法:会开辟出两块堆空间,不会自动保存在字符串常量池中,但是利用intern()方法可以插入到字符串常量池中。

4. 理解字符串的不可变性

字符串本身是一种不可变对象,也就是说它的内容不可改变。但是有的朋友看到下面类似的代码可能有所疑惑。

        String str = "hello";
        str = str + " world";
        str = str + "!!!";
        System.out.println(str);
        
        //输出结果为hello world!!!

上述代码看起来好像str确实是改变了,其实不然。我们通过其内存变化来观察。
在这里插入图片描述
通过上图我们可以看出,并不是字符串对象本身发生了变化,而是str指向了新的对象。

5. 字符Char与字符串String的相互转换

由于字符串包含了一个字符数组,所以String可以和Char相互转换。

方法名称描述
public String(Char value[])将字符数组中所有的内容变为字符
public String(Char value[], int offset, int count)将部分字符数组中所有的内容变为字符
public Char CharAt(int index)取得从0开始到指定位置的字符
public Char toCharArray( )将字符串变为字符数组返回

代码例子

        String str = "hello";
        System.out.println(str.charAt(0));
		// 获取下标为0的字符

        String str2 = "helloworld";
        char[] data = str2.toCharArray();
        for (int i = 0; i < data.length; i++){
            System.out.println(data[i] + " ");
        }
		//将字符串变为字符数组并输出

        System.out.println(new String(data));
        //将data字符数组转换为字符串
        System.out.println(new String(data,5,5));
        //从data字符数组的第6个字符至往后的5个字符转换为字符串

6. 字符串常见操作

6.1 字符串比较

方法描述
==比较两个字符串引用是否指向同一对象
equals比较内容,区分大小写的比较
equalsIanoreCase比较内容,不区分大小写的比较
compareTo比较两个字符串大小关系,相等返回0,小于返回内容为负,大于反之

示例代码

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

        System.out.println("A".compareTo("a"));//-32
        System.out.println("a".compareTo("A"));//32
        System.out.println("A".compareTo("A"));//0

6.2 字符串查找

方法描述
contains判断字符串中是否包含某个元素或者某个子字符串,返回true或者false
indexOf和contains类似,从开头进行查找 ,找到返回其位置,没有找到返回-1
lastIndexOf和contains类似,从末尾进行查找 ,返回其位置,没有找到返回-1
startsWith判断是否以指定元素或者子字符串开头
endsWith判断是否以指定元素或者子字符串结尾
        String str = "helloworld";
        System.out.println(str.contains("w"));//ture

        System.out.println(str.indexOf("world"));//5
        System.out.println(str.indexOf("bit"));//-1
        System.out.println(str.indexOf("l"));//2
        System.out.println(str.indexOf("l",5));//8
        System.out.println(str.lastIndexOf("l"));//8
        System.out.println(str.startsWith("l"));//false
        System.out.println(str.startsWith("l",3));//true

6.3 字符串替换

方法描述
replaceAll替换所有指定内容
replaceFirst替换首个内容

代码示例:

        String str = "helloworld";
        System.out.println(str.replace("l", "_"));//返回he__owor_d
        System.out.println(str.replaceFirst("l", "_"));//返回he_loworld

**请注意:**由于字符串具有不可变性,替换并没有改变原字符,而是创建了新的对象。

6.4字符串拆分

方法描述
split将字符串拆分

代码示例:字符串拆分

        String str = "hello world hello bit";
        String[] result = str.split(" ");//以空格为拆分界线进行拆分
        for (String s: result
             ) {
            System.out.println(s);
        }

拆分结果为在这里插入图片描述
拆分是特别常用的操作,有一些特殊字符作为分隔符可能无法正确拆分,需要加上转义字符。转义字符的注意事项如下:

  1. 字符“|”,“*”,“+”都需要加上转义字符“\”.
  2. 如果是“.”,需要加上转义字符“\”.
  3. 如果一个字符具有多个分隔符,可以用“|”,作为连字符。

代码示例:拆分IP地址

        String ip = "192.168.1.1";
        String[] result3 = ip.split("\\.");
        for (String w:result3
             ) {
            System.out.println(w);
        }

拆分结果在这里插入图片描述
代码示例多次拆分:

        //多次拆分
        String strr = "name=zhangsan&age=18";
        String[] sub1 = strr.split("&");
        for (String a:sub1
             ) {
            String[] sub2 = a.split("=");
            System.out.println(sub2[0] + sub2[1]);
        }

运行结果在这里插入图片描述

6.5 字符串截取

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

方法描述
subString(int bginIndex)从指定索引截取到结尾
subString(int bginIndex, int endIndes)从指定索引截取到索引

代码示例

        //字符串截取
        String str2 = "helloworld";
        System.out.println(str2.substring(5));
        System.out.println(str2.substring(0,5));

运行结果在这里插入图片描述
注意:上述代码中(0,5)表示的意思是前闭后开,即包含0号下标的字符,不包含5号下标的字符。

7. StringBuffer和StringBuilder

由于字符串常量都是String对象,而String对象一旦声明无法改变。为了方便字符的修改,才有了StringBuffer和StringBuilder这两个方法,由于StringBuffer和StringBuilder大部分功能相同,这里主要介绍StringBuffer。
通过下面代码观察StringBuffer的使用:

        StringBuffer str = new StringBuffer();
        str.append("hello").append("world");
        System.out.println(str);
        fun(str);
        System.out.println(str);

   		 public static void fun(StringBuffer temp){
      		  temp.append("\n").append("www.bit.com.cn");
  		  }

运行结果:在这里插入图片描述
小结

  1. String的内容不可修改,StringBuffer和StringBuilder的内容可以修改
  2. StringBuffer和StringBuilder功能上是相似的
  3. StringBuffer采用同步处理,属于线程安全操作。StringBuilder未采用同步处理,属于线程不安全操作。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值