草庐IT

带你走进Java字符串的小世界

爪哇斗罗 2023-09-30 原文

目录

一. String

1. 什么是String

2. String常用构造器

3. 字符串的判断

4. 字符串的获取

5. 字符串的转换

6. 字符串比较和替换

7. 字符串的切割

二. StringBuffer与StringBuilder

2.1 关于StringBuffer

2.1.1 定义

2.1.2 构造方法

2.2 关于StringBuffer

三. StringJoiner的使用

四. 关于常量池的面试


🐼个人主页爪哇斗罗

🐼博主介绍一名打工人

🐼签名:圣人之道,为而不争。

🐼一起交流,一起进步,一起互动。

一. String

1. 什么是String

首先,String属于引用数据类型,而不是基本数据类型。它是用来存储字符串的,使用双引号括起来的Unicode字符序列。

每个用双引号""括起来的都是属于String的一个实例。

String str = "hello"

阅读源码(Java8)会发现,String实现Serializable接口,表示字符串可以被序列化。

同时还实现Comparable接口表示字符串可以比较大小。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];// ①
}

标注①可看出Java使用char[]来存储字符串的值,Java9以后使用byte[]存储字符串。由此说明Sting底层实际上是一个字符数组

所以上述的"hello"实际上存的是['h','e','l','l','o']。

除此之外,计算机并不会存储像h,e甚至是中文这样的字符,此时就和编码表也就是字符集有关系。

指定不同的字符集,程序底层会将对应的字符按照对应的编码表进行编码存储至内存或者磁盘中。

常见的字符集编码有utf8,GBK等。utf8一个字符对应三个字节,GBK一个字符对应两个字节

2. String常用构造器

String()构造一个空的字符串
String(byte[] arr)将字节数组转换为字符串
String(byte[] arr, int offset, int lengh)将字节数组部分转换为字符串
String(char[] arr)将char字节数组转换为字符串
String(char[] arr, int offset, int length)将char字节数组部分转换为字符串
String(String original)字符串常量构建字符串
String(byte bytes[], int offset, int length, Charset charset)将字节数组部分转换为指定字符集的字符串
byte[] b = {97,98,99,100};	
String str = new String(b);
System.out.println(str);//abcd

byte数组转换为String字符串会有一个解码的过程,就是将b存入内存中然后进行解码输出对应字符串得到abcd。

public String(byte bytes[], int offset, int length) {
    checkBounds(bytes, offset, length);
    // 解码
    this.value = StringCoding.decode(bytes, offset, length);
}

char数组转String字符串方式都和上述大同小异,这里不在赘述。

3. 字符串的判断

boolean equals(Object obj)判断两个字符串内容是否相等
boolean equalsIgnorecase(String str)忽略大小写判断两个对象是否相等
boolean contains(String str)判断大字符串是否包含小字符串
​​​​​​boolean ​startsWith(String str)判断是否以指定的字符串开头
boolean endsWIth(String str)判断是否以指定的字符串结尾
boolean isEmpty()判断字符串是否为空
public static void main(String[] args) {
    String s1 = "abcde";
    String s2 = "AbCde";
    String s3 = "abcde";
    //equals(): 比较两个字符串是否相等。
    System.out.println(s1.equals(s2));//true
    System.out.println(s1.equals(s3));//false
    //equalsIgnoreCase():忽略大小写比较字符串相等。
    System.out.println(s1.equalsIgnoreCase(s2));//true
    System.out.println(s1.equalsIgnoreCase(s3));//true
    //contains():是否包含指定字符串。
    System.out.println(s1.contains("bd"));//false
    //startsWith():是否以指定字符串开头
    System.out.println(s1.startsWith("ab"));//true
    // 第二个参数表示在索引为2的字符开始比较
    System.out.println(s1.startsWith("cde",2));//true
    //是否以字符串结尾
    System.out.println(s1.endsWith(s3));//true
    //是否为空
    System.out.println(s1.isEmpty());//false
}

4. 字符串的获取

length()获取字符串的长度
charAt(inx index)获取某个索引的字符
indexOf(int ch)获取指定字符在字符串第一次出现的位置,可以写对应的ASCALL码值
indexOf(int ch, int fromIndex)获取从指定的索引开始,字符出现的位置
indexOf(String str)获取指定的字符串在原字符串的位置
indexOf(String str, int fromIndex)获取从指定的索引开始,字符串第一次出现的位置
lastIndexOf(int ch)获取指定字符最后一次出现的索引值
lastIndexOf(String str,int fromIndex)获取指定字符串最后出现的索引值
subString(int start)从指定位置开始截取字符串
subString(int start, int end)从指定位置到指定位置截取字符串
public static void main(String[] args) {
    String str = "abcdeababcdacaeca";
    //长度
    System.out.println(str.length());//17
    //获取某个索引的字符
    System.out.println(str.charAt(1));//b
    //获取最后一个元素值
    System.out.println(str.charAt(str.length() - 1));//a
    //获取'a'第一次出现的索引值
    System.out.println(str.indexOf('a'));//0
    System.out.println(str.indexOf(97));//0
    //从第二个索引值开始,'a'第一次出现的位置
    System.out.println(str.indexOf('a', 2));//5
    //获取"bc"第一次出现的索引值
    System.out.println(str.indexOf("bc"));//1
    //从2索引开始"ad"第一次出现的索引值
    System.out.println(str.indexOf("ad", 2));//-1
    //获取'a'最后一次存储的索引值
    System.out.println(str.lastIndexOf('a'));//16
    //获取'bc'最后一次出现的索引值
    System.out.println(str.lastIndexOf("bc"));//8
    //从7索引结束前获取'a'最后一次索引值
    System.out.println(str.lastIndexOf('a', 7));//7
    System.out.println(str.lastIndexOf('c', 6));//2
    //从索引5截取到末尾
    String s1 = str.substring(5);
    System.out.println(s1);//ababcdacaeca
    //"abcdeabca" ===> "deab"
    String s2 = str.substring(3, 7);
    System.out.println(s2);
}

注意:如果没有找到都会返回-1

5. 字符串的转换

byte[] getBytes()将字符串转换为字节数组
byte[] getBytes(String charset)通过指定的字符集,将字符串转成字节数组
char[] toCharArray()将字符串转成字符数组
static valueOf(char[] chs)将字符数组转成字符串
static valueOf(Object obj)将任意的引用数据转成字符串
toLowerCase()转成小写
toUpperCase()转成大写
concat(String str)字符串连接
trim()去除两边空格
public static void main(String[] args) {
    String s = "Ab三上悠亚";
    //String转为byte[]
    byte[] bys =s.getBytes();
    //[65, 98, -28, -72, -119, -28, -72, -118, -26, -126, -96, -28, -70, -102]
    System.out.println(Arrays.toString(bys));
    //String转为char[]
    char[] c = s.toCharArray();
    //[A, b, 三, 上, 悠, 亚]
    System.out.println(Arrays.toString(c));
    //其它大部分类型转String
    System.out.println(String.valueOf(100));//100
    System.out.println(String.valueOf(false));//false
    System.out.println(String.valueOf('a'));//a
    System.out.println(String.valueOf(3.23f));//3.23
    System.out.println(String.valueOf(c,0,3));//Ab三 从0索引开始截取三个字符
    char[] data = {'A','B','c'};
    System.out.println(String.valueOf(data));//ABc
    System.out.println(s.toLowerCase());//ab三上悠亚
    System.out.println(s.toUpperCase());//AB三上悠亚
}

6. 字符串比较和替换

replace(char old, char new)新的字符替换旧的字符
replace(String old, Stringnew)新的字符串替换旧的字符串
String replace(CharSequence target, CharSequence replacement)替换指定的字符序列
int compareTo(String str)字典比较字符串
int compareToIgnoreCase(String str)忽略大小写比较
public static void main(String[] args) {
    String s = "abc三上悠亚abc";
    //b替换B 字符替换
    String s1 = s.replace('b', 'B');
    System.out.println(s1);//aBc三上悠亚aBc
    //三上悠亚替换为JAPAN字符串替换
    String s2 = s.replace("三上悠亚", "JAPAN");
    System.out.println(s2);//abcJAPANabc
    //替换指定的字符序列
    String s4 = "abc";
    String s5 = "abcd";
    String s6 = s.replace(s4, s5);
    System.out.println(s6);//abcd三上悠亚abcd
}

7. 字符串的切割

String[] split(String regex, int limit)将字符串按照某种方式进行分割并指定分割多少
String[] split(String regex)将字符串按照某种方式进行分割
public static void main(String[] args) {
    String str = "1-2-3-4-5";
    // 按照规则来切割字符串
    String[] split1 = str.split("-");
    // 按照规则来切割几段字符串
    String[] split2 = str.split("-",2);
    // [1,2,3,4.5]
    System.out.println(Arrays.toString(split1));
    // [1,2-3-4-5]
    System.out.println(Arrays.toString(split2));
}

二. StringBuffer与StringBuilder

2.1 关于StringBuffer

2.1.1 定义

作为线程不安全的可变字符序列,StringBuilder类似于String的字符缓冲区,可以看做是一个高级的String。

与String的区别就是,它是一个可变的字符序列。记住:StringBuilder是线程不安全的!!!所以在多线程场景下不可使用。

2.1.2 构造方法

StringBuilder构造

  • StringBuilder():空的构造,底层默认创建容量为16的字符缓冲区对象。
  • StringBuilder(int capacity):可以指定容量创建StringBuilder对象。
  • StringBuilder(String str):创建指定字符串的StringBuilder对象。
    StringBuilder sb = new StringBuilder();
    System.out.println(sb);//空
    System.out.println(sb.length());//0
    System.out.println(sb.capacity());//16

    StringBuilder sb1 = new StringBuilder(100);
    System.out.println(sb1);//空
    System.out.println(sb1.length());//0
    System.out.println(sb1.capacity());//100

    StringBuilder sb3 = new StringBuilder("java");
    System.out.println(sb3);//java
    System.out.println(sb3.length());//4
    System.out.println(sb3.capacity());//20

StringBuilder成员方法

  • 增加功能

    • String append(Object obj):括号中可以是任意类型

    • insert(int offset, String str):任意地方添加指定类型

  • 删除功能

    • deleteCharAt(int index) 指定位置删除对应的元素
    • delete(int index, int end)删除[index,end-1]之间的元素
  • 修改功能:

    • setCharAt(int n, char ch):
    • replace(int start,int end,String str):
  • 查询功能

    • charAt(int n):
    • int Capacity():
    • int length():
  • 反转功能

    • reverse() 反转功能
  • 截取功能

    • String substring(int start):截取指定位置一直到末尾
    • String substring(int start,int end):截取[start,end-1]范围
StringBuilder sb = new StringBuilder();
//添加
sb.append('j').append("av").append(false).append(100);//javfalse100
sb.insert(1, false);//jfalseavfalse100
//删除
sb.deleteCharAt(0);//删除第一个字符 falseavfals100
sb.delete(0, 3);//删除[0-2]的字符  seavfals100
//修改
sb.setCharAt(3, 't');//seatfalse100
sb.replace(3,7,"true");//seatruelse100
//查询元素
System.out.println(sb.charAt(6));//e
System.out.println(sb.length());//12
System.out.println(sb.capacity());//初始容量16
//截取功能
System.out.println(sb.substring(3));//truelse100
System.out.println(sb.substring(4, 7));//rue 截取[4,6]        

2.2 关于StringBuffer

StringBuffer与StringBuilder的用法是一模一样的,这里不再赘述相关方法,主要对比他们两有什么区别。

常见的String,StringBuffer,StringBuilder的面试题。

  • 可变性

    • String用final修饰,不可变
    • StringBuffer与StringBuilder都是继承AbstratBuilder类,存储的char[]并未用final修饰,是可变的
  • 线程安全性

    • String对象不可变,可以视为线程安全
    • StringBuffer加了同步锁,线程安全
    • StringBuilder方法未加同步锁,线程不安全
  • 性能

    • 每次对String进行赋值时都会产生新对象,然后将指针指向新的对象。
    • StringBuffer每次都会对对象的本身进行操作,而不是产生新的对象去引用它,相同情况下,使用StringBuilder的性能会提高,但是会有线程不安全的风险。

代码比较StringBuilder与StringBuffer的性能:

    private static void demo02() {
		StringBuilder sb = new StringBuilder("abc");
        //当前时间
		long l1  = System.currentTimeMillis();
		for(int i=0; i<100000;i++) {
			sb.append("hello");
		}
        //跑完循环
		long l2  = System.currentTimeMillis();
		System.out.println("String连接耗时:"+ (l2 -l1));
	}
    private static void demo01() {
		String s = "abc";
		//当堆空间溢出会发生OutOfMemoryError
        //当前时间
		long l1  = System.currentTimeMillis();
		for(int i=0; i<100000;i++) {
			s += "hello";
		}
        //跑完循环
		long l2  = System.currentTimeMillis();
		System.out.println("String连接耗时:"+ (l2 -l1));
	}

如何使用三者?

  • 操作少量的数据:String
  • 单线程操作大量数据:StringBuilder
  • 多线程操作大量数据:StringBuffer

三. StringJoiner的使用

上面,我们总是使用SpringBuilder去拼接字符串,使用的分割符号很多。

并且代码量相对来说也是比较多的,Java1.8给我们提供了一个StringJoiner类来专门拼接字符串。

先看一个简单的例子,将集合中的元素通过逗号分割开并输出一个字符串:

public static void main(String[] args) {
	List<Integer> integers = new ArrayList<Integer>();
	integers.add(1);
	integers.add(2);
	integers.add(3);
	integers.add(4);
	integers.add(5);
	StringBuilder stringBuilder = new StringBuilder();
	for (Integer integer : integers) {
		stringBuilder.append(integer + ",");
	}
	// 1,2,3,4,5
	System.out.println(stringBuilder.toString().substring(0, stringBuilder.length() - 1));
	}

这样的代码是不是看起来繁杂,并且只能通过手动去拼接,如果使用StringJoiner去拼接字符串就会一步到位:

public static void main(String[] args) {
	List<Integer> integers = new ArrayList<Integer>();
	integers.add(1);
	integers.add(2);
	integers.add(3);
	integers.add(4);
	integers.add(5);	
	// 序号1
	StringJoiner stringJoiner = new StringJoiner(",");
	for (Integer integer : integers) {
		stringJoiner.add(String.valueOf(integer));
	}
	// 1,2,3,4,5
	System.out.println(stringJoiner.toString());
	}

以上两段代码输出的结果都是一样的,都是1,2,3,4,5,如果我们需要输出这样的字符串[1,2,3,4,5]怎么办?其实StringJoiner也提供了这样的方法,只需要将上述的序号1代码换成:

StringJoiner stringJoiner = new StringJoiner(",","[","]");

添加两个参数,分别代表的是前缀与后缀,是不是非常简单呢,这个在开发中也是很常用的。

如果获取的数据是空的,我们呢又要给其设置一个默认的值。

那么可以使用setEmptyValue()方法就可以了:

stringJoiner.setEmptyValue("1");

拼接字符串使用StringJoiner还是挺方便的,赶快用起来吧!

四. 关于常量池的面试

先看如下所示的代码:s1与s2相等吗?

String s1 = new String(abc");
String s2 = "abc";

s1与s2虽然内容都是一样,但是两者是不相等的。

因为String是引用数据类型,如果比较相等(==),两者比较的是地址与内容是否相等。这就会引出常量池的概念。

s1是引用对象,首先会在栈内存中会开辟一个s1的引用对象的空间,而abc这个字符串常量是存在方法区常量池中。通过s1这个对象引用指向abc。

s2则是s2的一个引用指向了abc。

注意:JDK1.7之前,常量池在方法区中,JDK1.7及以后常量池放在了堆里面,我们通常指的的是1.7之前的常量池

下面来看==与equals()的比较字符串是否相等的使用。

  • == 比较的地址和内容都相等才相等
  • equals()内容相等即是相等

理解上面两句,看如下代码就会清晰很多:

    String s1 = "123";①
    String s2 = "123";②
    String s3 = new String("123");③
    System.out.println(s1==s2);//正确
    System.out.println(s1==s3);//错误

只要明白了内存分布,判断不成问题。对于①,②来说,s1,s2都在栈内存中。

对于③来说,会先在栈中开辟一个内存空间存放引用对象s3。

然后会在堆内存中重新开辟空间存放new String("123")中的String匿名对象的值123所以s1==s3是错误的!!!

对于equals()就不一样了比较的是内容是否相等,三者内容都是相等的,所以equals是true

PS: null," "的区别

null代表的是空对象,并不是字符串,可以赋给任何对象,字符串中表示只是一个引用,还没有内存空间的分配

“ ”表示引用已经指向了 一块内存空间了,是一个实际的东西,可以进行操作了,表示一个长度为0的字符串

练习一:

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
String s3 = new String("hello");
String s4 = "hello";
System.out.println(s3==s4);
System.out.println(s3.equals(s4));
String s5 = "hello";
String s6 = "hello";
System.out.println(s5==s6);
System.out.println(s5.equals(s6));

答案:F T F T T T

关于字符串的拼接

  • 常量与常量的拼接还在常量池中
  • 常量池不可有相同的常量
  • 拼接的时候,只要存在变量都会存到堆中
  • 调用intern()方法返回常量池里面的常量
    String s1 = "hello";
    String s2 = "world";
    String s3 = "helloworld";
    System.out.println(s3==(s1+s2));//F 变量的连接存在堆中不相等
    System.out.println(s3==(s1+s2).intern());//T 获取的是值相等
    System.out.println(s3.equals(s1+s2));//T 获取内容相等
    System.out.println(s3=="hello" + "world");//T 常量与常量连接还在常量池中
    System.out.println(s3.equals("hello"+"world"));//T	内容相等	

有关带你走进Java字符串的小世界的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  7. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  8. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  9. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  10. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

随机推荐