Java基础 字符串

字符串介绍

字符串是程序开发中,使用最频繁的类型之一,与基本类型有着相同的地位。在使用字符串拼接时,JVM(Java虚拟机)有时会对字符串做特殊处理,使其合成一个最终的字符串,从而达到高效的运行目的。

String特性

String 是不可变类(immutable),底层是被final修饰的数组 private final byte[] value,对它进行任何改动,将会重新创建对象;

String对象赋值后就会在常量池中缓存,如果再次被创建会判断常量池中是否已有缓存对象,如果有将会直接返回该对象的引用,这是为了节省空间。

字符串创建

创建字符串的两种方式:

String str = "abc";
String str = new String("abc");

区别:

package testDemo1;

public class TestOne {
	public static void main(String[] args) {
		String s1 = "a";
		String s2 = new  String("a");		
		System.out.println(s1==s2);	//false
		String s3 = "a";
		System.out.println(s1==s3);	//true
		System.out.println(s2==s3);	//false
	}
}

第一种:仅仅是一个赋值语句,在创建的时候,JVM 会检查在字符串池中,是否已经存在该字符串,如果已经存在了,那么会返回这个字符串的引用给s3。如果不存在,那么会创建一个 a 字符串对象,再赋值给 s1。因此,第一种可能只创建 1 个或者 0 个对象。(即上面提到的常量池)

第二种:会在内存中创建 1 个或者 2 个对象。把 new String(“a”) 这句话拆成两个部分来看,一个是”a”, 另一个是 new String()。如果 a 字符串已经在字符串池中存在了,那么就不需要在创建 a 字符串的对象了,但是 new String 这行代码会再构造出一个和 a 一样的字符串,并且是放在堆上。

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200109110255454.png?x-oss-

字符串的使用

字符串的拼接

String str = “hello” + “world”;
String str = “hello”; str += “world”;
String str = “hello”; String str2 = str + “world”;

JVM对拼接字符串的优化

package testDemo1;

public class TestTwo {
	public static void main(String[] args) {		
		String str = "hello " + "world";
		String str2 = "hello world";
		System.out.println(str==str2); //true
	}
}

通过javap -c Main的反编译代码,可以看出JVM对此做了如下的优化:
在这里插入图片描述
说明 JVM 在某些情况下会特殊处理 String 类型。

字符串截取

使用 substring() 方法:

package testDemo1;

public class TestThree {

	public static void main(String[] args) {
		String str = "123abc456";
		int i = 3;
		System.out.println(str.substring(0,i));  //取字符串前i个字符 ==>123	
		System.out.println(str.substring(i));  //去掉字符串前i个字符 ==>abc456	
		System.out.println(str.substring(str.length() - i));  //从右边开始取i个字符 ==>456
		System.out.println(str.substring(0,str.length() - i));  //从右边开始去掉i个字符 ==>123abc
	}
}

通过StringUtils提供的方法 (运用StringUtils需要导入相关jar包,commons-lang3-3.1.jar):

package testDemo1;

public class TestThree {

	public static void main(String[] args) {
		System.out.println(StringUtils.substringBefore(“dssswabww”, “w”))  // dsss    这里是以第一个”w”,为标准。
		System.out.println(StringUtils.substringBeforeLast(“dssswabww”, “w”))  //dssswabw   这里以最后一个“w”为准。		
	}
}

字符串格式化

String 类中,可以使用 format() 方法格式化字符串:
1)String.format(String format, Object… args)
2)String.format(Locale locale, String format, Object… args)
区别:前者使用本地语言环境,后者使用指定语言环境

源码如下:

public static String format(String format, Object... args) {
   return new Formatter().format(format, args).toString();
}

public static String format(Locale l, String format, Object... args) {
   return new Formatter(l).format(format, args).toString();
}

小小 14 打游戏 采用格式化可以动态修改

package testDemo1;

public class TestThree {

	public static void main(String[] args) {
		String str = String.format("我叫%s,今年%d岁,喜欢%s","小小",14,"打游戏");
		System.out.println(str.toString());
	}
}		

在这里插入图片描述
字符串格式化详解

字符对比

package testDemo1;

public class TestOne {
	public static void main(String[] args) {
		String s1 = "abc";
		String s2 = new  String("abc");				
		String s3 = "abc";
		
		System.out.println(s1.equals(s2));	//true
		System.out.println(s1.equals(s3));	//true
		System.out.println(s2.equals(s3));  //true
	}
}

==:

基本类型比较值是否相同;
引用类型比较引用是否相同。

equals解析:

package testDemo1;

public class TestOne {
	public static void main(String[] args) {
		Dog d1 = new Dog("二狗子");
		Dog d2 = new Dog("二狗子");
		System.out.println(d1.equals(d2));		//false
		
		String s1 = "狗剩子";
		String s2 = "狗剩子";
		System.out.println(s1.equals(s2));		//true
		
		String str1 = new String("abc");
		String str2 = new String("abc");
		System.out.println(str1.equals(str2));	//true	
		
		String st1 ="AB";
		String st2 = new String("AB");
		System.out.println(st1.equals(st2));	//true	
	}
}

class Dog{	
	private String name;
	public Dog(String name){
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}	
}

出乎我们的预料,同是对堆中新建对象,为什么两个值相同的 String 对象返回true?

看看 equals 源码:

所有类的基类Object中进行定义的equals方法

public boolean equals(Object obj) {
    return (this == obj);
    }

equals 的本质是 ==

String类对equals方法

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

String 对 Object 方法进行了重写,例如 Integer 等也是重写了 equals 方法,所以像这种 equals 被重写类型所新建的对像,就变成了内容的比较。

从源码可以得出:d1 和 d2 是 Object 类中 equals 方法进行的比较,比较方式和 == 相同,d1.equals(d2) 等同于 d1==d2 比较一样,比较的是引用。(如有描述不当之处,请评论区指出)

== 和 equals的区别详解

对equals重新需要注意五点:

自反性:对任意引用值X,x.equals(x)的返回值一定为true;
对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;
传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true ;
一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;
非空性:任何非空的引用值X,x.equals(null)的返回值一定为false 。

如果忽略字符串的大小写对比值可以使用 equalsIgnoreCase() 方法:

package testDemo1;

public class TestOne {
	public static void main(String[] args) {
		String s1 = "abc";
		String s2 = "ABC";
		System.out.println(s1.equalsIgnoreCase(s2));  //true
	}
}

String、StringBuffer、StringBuilder

字符串相关类型主要有这三种:String、StringBuffer、StringBuilder,其中StringBuffer 和 StringBuilder 都是可变字符串类型。StringBuffer 在字符串拼接时使用了 synchronized 来保障线程安全,因此在多线程中字符串拼接推荐使用它。

package testDemo1;

public class TestOne {
	public static void main(String[] args) {
		String i = "abc";
		i = "ab";		
		System.out.println("i = " + i);  // i = ab
	}
}

在这里插入图片描述

由此可见:虽然 i 发生了改变,但是它是重新创建了一个对象,所以String 类是不可变对像,对其修改就是重新创建对象。

StringBuffer的使用:

package testDemo1;

public class TestOne {
	public static void main(String[] args) {

		StringBuffer sb = new StringBuffer();
		sb.append("world ");
		sb.append("world");
		System.out.println("sb = " + sb);	//	sb = world world
		
		sb.insert(0, "hello ");
		System.out.println("sb = " + sb);	//	sb = hello world world
		
		sb.setCharAt(0, 'H');
		System.out.println("sb = " + sb);	//	sb = Hello world world
	}
}

由此可见,StringBuffer 发生了改变(即进行了字符串拼接),所以说 StringBuffer 是可变字符序列。

StringBuilder 和 StringBuffer 的使用方法一样,它们都继承于AbstractStringBuilder。

JDK源码深入了解

StringBuffer类也被定义成final类,与String类相同。但是StringBuffer类对象的字符序列是可修改的,类本身写好了方法改变序列类容和长度。并且StringBuffer是线程安全的,类的各个方法都用synchronized修饰。可以这样理解StringBuffer类:可修改的String,并且线程安全。

StringBuilder类比之StringBuffer类效率更高,所以单线程采用StringBuilder。

String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String

当然这个是相对的,不一定在所有情况下都是这样。比如String str = “hello”+ "world"的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。

因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

常见面试题集

String s1 = new String(“abc”)创建了几个对象?

创建了一个或两个对象。如果字符串常量池中已有 abc 则创建了一个对象,String(“abc”) 就会创建一个引用对象 s1 指向常量池中的 abc;反之则创建了两个对象。

什么是String,它是什么数据类型

String是定义在 java.lang 包下的一个类。它不是基本数据类型。
String是不可变的,JVM使用字符串池来存储所有的字符串对象。

String, StringBuffer,StringBuilder的区别?

String是不可变类,每当我们对String进行操作的时候,总是会创建新的字符串。操作String很耗资源,所以Java提供了两个工具类来操作String - StringBuffer和StringBuilder。

StringBuffer和StringBuilder是可变类,StringBuffer是线程安全的,StringBuilder则不是线程安全的。所以在多线程对同一个字符串操作的时候,我们应该选择用StringBuffer。由于不需要处理多线程的情况,StringBuilder的效率比StringBuffer高。

String是不可变的有什么好处

String在多线程中使用是安全的,我们不需要做任何其他同步操作。
String的值也不能被改变,所以用来存储数据密码很安全。
因为java字符串是不可变的,可以在java运行时节省大量java堆空间。因为不同的字符串变量可以引用池中的相同的字符串。

什么是字符串常量池?

字符串常量池就是用来存储在 Java 堆中的字符串池,是为了防止每次新建字符串带的时间和空间消耗的一种解决方案。

感谢您的阅读。关注公众号(微微的灯光),让我们一起学习,一起交流,一起进步。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值