Java的字符串String

什么是字符串

从概念上将,Java字符串就是Unicode字符序列,例如字符串 "Java\u2122"由五个Unicode字符 J,a,v,a和™组成

  • 我们的Java没有内置的字符串类型,而是再标准Java类库中提供一个预定义类,很自然的叫做了String
  • 所以我们的String不是一个基本类型,而是一个类
  • 每个用双引号括起来的字符串都是String类的实例
String s="";
String str="Hello";

String类的声明

String类在我们的java.lang包下

//我们JDK1.8下String的部分源码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

为什么我们的String是不可变的

  • 我们可以看到源码中我们的字符串底层是使用一个char数组进行存储,这个char数组是私有且用了final进行修饰
    • 成员属性是私有的,且String类并没有提供其任何对应的方法进行修改,所以在外部是不能修改我们的String的内容
    • 这个数组用final修饰,final修饰一个对象,表示的是这个对象的引用是不能改变的,所以我们一个String实例的对象中的这个存储数据的value数组的指向也是不能改变的
    • 以上两个修饰导致了我们字符串的不可变性

字符串的不可变不代表引用不可变

String str="hello";
str="hello world";
  • 我们后面字符串的常见API看上去改变字符串,其实都是这种原理
  • 我们的字符串拼接也是这种原理

为什么String类用final修饰

在这里插入图片描述

  • 我们的String经常使用且处于核心类库中,我们必须要保证我们每个程序员用的String是同一个类,所以让其被继承

String的创建

  • 1直接赋值(语法糖)String s=“hello String”;
  • 2通过构造方法产生对象 String s=new String(“hello String”);
  • 3通过字符数组产生 char []date=new char[]{1,2,3,4}; String s=new String(date);
  • 4通过String的静态方法valueOf(任意数据类型)——>转换为字符串
// 方式一
String str = "Hello Bit";
// 方式二
String str2 = new String("Hello Bit");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);
//方式四
String str4= String.valueOf(10);
  • “hello” 这样的字符串字面值常量, 类型也是 String.
  • String 也是引用类型. String str = “Hello”; str只是一个reference类型 "Hello"本身才是真正的字符串对象

字符串比较相等

int x = 10 ;
int y = 10 ;
System.out.println(x == y);
// 执行结果
true
  • 在Java中对于基本类型的数值判断是否相等,我们用==来进行比较

代码1

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
// 执行结果
true
  • 感觉好像没什么问题,好像是两个字符串的值相等用==来比较,看下一个代码
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
// 执行结果
false  
  • 怎么这个比较就变成了false,str1和str2的数值是相同的啊,这是因为两种赋值方式的不同内存分配和==在Java中的语义导致

关于Java中的==比较

  • Java引用使用 == 比较并不是在比较所指向对象的内容, 而是比较两个引用是否是指向同一个对象.
  • Java中基本类型使用==是用来比较对应的数值是否相同关于对象的比较

关于字符串不同赋值操作对应的内存分配

在这里插入图片描述

  • 对于这种字符串的直接赋值的方式,我们会将出现的字符串常量在编译期间(生成class文件)放在我们的内存中的一个常量池中,如果采用直接赋值的方式,我们就会直接去引用常量池中的字符串常量,所以str1和str2用==比较的结果是true,因为两个引用对象指向同一个对象
    • 其实对于str1和str2存储在栈帧的局部变量表中
    • 方法区在JDK1.8之后也只是一个概念上的思想,其实常量池真正的存放的地方变成了我们的堆
    • 这些知识想真正的弄清楚,需要我们去学习JVM的知识,但是这里我们就大概知道是怎么回事就行

在这里插入图片描述

  • 首先我们的new 这种方式,也出现了字面量,所以也会在编译期间在常量池存储这个字符串常量
  • 因为我们使用了new这个关键字,所以会在堆中创建对应的一个字符串对象,所以str1和str2指向我们对应的字符串对象

池的思想

也就是共享设计模式,为了节省空间(因为内存十分宝贵),字符串产生之后大部分情况都是用来进行输出处理,只打印,一个对象就够了,数据库的连接池和线程池都是这样的思想

如 “Hello” 这样的字符串字面值常量, 也是需要一定的内存空间来存储的. 这样的常量具有一个特点, 就是不需要
修改(常量嘛). 所以如果代码中有多个地方引用都需要使用 “Hello” 的话, 就直接引用到常量池的这个位置就行
了, 而没必要把 “Hello” 在内存中存储两次.

那对象如何进行比较内容

Java 中要想比较字符串的内容, 必须采用String类提供的equals方法.
equals 使用注意事项

  • 我们使用的这个equals,这个方法其实在Object中,我们的String类重写了这个方法,所以可以起到比较字符串内容的功能

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

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者这样写也行
// 执行结果
true
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));
String str = null;
// 方式一
System.out.println(str.equals("Hello")); // 执行结果 抛出 java.lang.NullPointerException 异// 方式二
System.out.println("Hello".equals(str)); // 执行结果 false
  • 所以我们第二种方法是更好的一种写法

字符串常量池

根据上面的比较操作,我们对常量池有个认识,现在就介绍一个入池intern()操作

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";// javac 在编译期间的优化,结果已经在编译期确定为ab
String s4 = s1 + s2;// new StringBuilder().append("a").append("b").toString()  new String("ab")
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4); //false
System.out.println(s3 == s5);//true
System.out.println(s3 == s6);//true
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
System.out.println(x1 == x2); //1.6false  1.8false

String x2 = new String("c") + new String("d");
x2.intern();
String x1 = "cd";
System.out.println(x1 == x2); //1.6false  1.8true

  • 常量池中的字符串仅是符号,第一次用到时才变为对象利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,方法的返回值是在池中字符串对象的位置.如果没有则放入串池, 会把串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,方法的返回值是在池中字符串对象的位置.如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

在这里插入图片描述

当字符串常量池有对应的字符串常量

在这里插入图片描述

当字符串常量池没有对应的字符串

在这里插入图片描述

StringTalbe的位置

jdk1.6 StringTable 位置是在永久代中,1.8 StringTable 位置是在堆中。
为什么要这样
因为我们永久代的回收效率很低,只有FULL GC才会触发永久代的回收(FULL GC在老年代空间不足才会触发)
StringTable存储着我们的字符串常量,我们的字符串常量在程序中应用的次数很多,所以要及时对StringTable进行回收

字符串常见的操作

String提供的API都不是在原本的字符串进行操作,而是返回一个新的字符串对象

拼接操作

Java跟许多程序设计语言一样,也是支持+来拼接两个字符串

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";// javac 在编译期间的优化,结果已经在编译期确定为ab
String s4 = s1 + s2;// new StringBuilder().append("a").append("b").toString()  new String("ab")
  • 我们的字符串+,其实在底层调用的我们的StringBuilder的append方法(JDK1.8),常量拼接在编译器间优化成确定的字符串常量
  • 而且任何数据跟一个字符串进行+拼接就变成了一个字符串
int age=13;
String rating="PG"+13;

如果需要将多个字符串放在一起,用一个界定符分隔,可以使用静态方法join

String all=String.join(" / ","s","m","l");
//all is the String "s / m / l"

Java11还提供了一个repeat方法

String repeated="Java".repeat(3);// repeated is "JavaJavaJava"

获得字符串的子串

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

  • 索引从0开始
  • 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
  • 很多方法都是前闭后开的规则
String str = "helloworld" ;
System.out.println(str.substring(5));//hello
System.out.println(str.substring(0, 5));//hello

字符串的比较

我们上面知道有equals是用来比较两个字符串的内容是否相同

不区分大小写比较 equalsIgnoreCase()

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

在String类中compareTo()方法是一个非常重要的方法,该方法返回一个整型,该数据会根据大小关系返回三类内容:

  • 相等:返回0.
  • 小于:返回内容小于0.
  • 大于:返回内容大于0。
System.out.println("A".compareTo("a")); // -32
System.out.println("a".compareTo("A")); // 32
System.out.println("A".compareTo("A")); // 0
System.out.println("AB".compareTo("AC")); // -1
System.out.println("刘".compareTo("杨"));//比较刘和杨这两个字符对应的unicode值,返回刘-杨的unicode的差值

compareTo()是一个可以区分大小关系的方法,是String方法里是一个非常重要的方法。
字符串的比较大小规则, 总结成三个字 “字典序” 相当于判定两个字符串在一本词典的前面还是后面. 先比较第一
个字符的大小(根据 unicode 的值来判定), 如果不分胜负, 就依次比较后面的内容

字符串的查找操作

最好用的就是contains()

String str = "helloworld" ;
System.out.println(str.contains("world")); // true

该判断形式是从JDK1.5之后开始追加的,在JDK1.5以前要想实现与之类似的功能,就必须借助、indexOf()方法完
成。

使用indexOf()

String str = "helloworld" ;
System.out.println(str.indexOf("world")); // 5,w开始的索引
System.out.println(str.indexOf("bit")); // -1,没有查到
if (str.indexOf("hello") != -1) {
	System.out.println("可以查到指定字符串!");
}  
  • 使用indexOf()需要注意的是,如果内容重复,它只能返回查找的第一个位置

在进行查找的时候往往会判断开头或结尾endWith和startWith

String str = "**@@helloworld!!" ;
System.out.println(str.startsWith("**")); // true
System.out.println(str.startsWith("@@",2)); // ture 后面一个参数决定从那个位置开始计算
System.out.println(str.endsWith("!!")); // true  

字符串拆分操作

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

String str = "hello world hello bit" ;
String[] result = str.split(" ") ; // 按照空格拆分
for(String s: result) {  //返回的是一个字符串数组
	System.out.println(s);
}
String str = "hello world hello bit" ;
String[] result = str.split(" ",2) ;//说明最多只能分为两个子字符串
for(String s: result) {
	System.out.println(s);
}
//hello
//world hello bit

拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义.
拆分IP地址

String str = "192.168.1.1" ;
String[] result = str.split("\\.") ;
for(String s: result) {
	System.out.println(s);
}  
  • 字符"|“, “*” , “+” 都得加上转义字符,前面加上”\".
  • 而如果是"“,那么就得写成”\".
  • 如果一个字符串中有多个分隔符,可以用"|"作为连字符

字符串的替换操作

字符串的替换处理

String str = "helloworld" ;
System.out.println(str.replaceAll("l", "_"));//替换全部的_
System.out.println(str.replaceFirst("l", "_"));//替换第一个_

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

其他操作

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

String str = " hello world " ;
System.out.println("["+str+"]");
System.out.println("["+str.trim()+"]");  
//[ hello world ]
//[hello world]

大小写转换

String str = " hello%$$%@#$%world 哈哈哈 " ;
System.out.println(str.toUpperCase());
System.out.println(str.toLowerCase());

这两个函数只转换字母。
字符串length()

String str = " hello%$$%@#$%world 哈哈哈 " ;
System.out.println(str.length());

注意:数组长度使用数组名称.length属性,而String中使用的是length()方法,而且我们的length()返回的是字符串代码单元的个数

isEmpty()方法 判断一个字符串对象是不是空串

System.out.println("hello".isEmpty());
System.out.println("".isEmpty());
System.out.println(new String().isEmpty());
  • 空串与Null串
    • 空串是一个长度为0的字符串
    • Null串表示目前没有任何对象和该变量进行关联
//判断是不是空串
if(str.length()==0)
if(str.equals(""))
//判断是不是null串
if(str==null)

字符串与字符和字节的关系

字符与字符串

我们String提供了将字符转换成字符串的构造方法,和将字符串转换为字符数组的toCharArray()

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)); // 部分转换

查找指定字符串指定位置的字符 charAt()

String str = "hello" ;
System.out.println(str.charAt(0)); // 下标从 0 开始
// 执行结果
System.out.println(str.charAt(10));
// 执行结果
产生 StringIndexOutOfBoundsException 异常
  • 返回的还是指定位置的代码单元,这个还是不要轻易使用,因为比较偏底层

字节与字符串

我们String提供了将字节转换成字符串的构造方法,和将字符串转换为字节数组的getBytes()

String str = "helloworld" ;
// String 转 byte[]
byte[] data = str.getBytes() ;
for (int i = 0; i < data.length; i++) {
	System.out.print(data[i]+" ");
}
// byte[] 转 String
System.out.println(new String(data));  

那么何时使用 byte[], 何时使用 char[] 呢?

  • byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
  • char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候

StringBuilder和StringBuffer

任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。
通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,而且因为如果频繁的对字符串进行拼接,会不断产生新的对象,比较消耗内存,所以提供StringBuffer和StringBuilder类。

如何让同一个字符串进行字符串拼接

StringBuffer 和 StringBuilder 大部分功能是相同的,主要介绍 StringBuffer
在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法:

public synchronized StringBuffer append(各种数据类型  )

使用提供的append方法来增加字符串的内容,这里不会产生新的对象,一种指向着一个对象

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

String和StringBuffer的相互转换

  • String变为StringBuffer:利用StringBuffer的构造方法或append()方法
  • StringBuffer变为String:调用toString()方法
String str1 = " hello world " ;
StringBuffer sb=new StringBuffer(str);
sb=sb.append(123);
String str2=sb.toString();

StringBuffer额外的方法

字符串反转 reverse

字符串反转
StringBuffer sb = new StringBuffer("helloworld");
System.out.println(sb.reverse());

删除操作 delete

StringBuffer sb = new StringBuffer("helloworld");
System.out.println(sb.delete(5, 10));  //hello

插入操作insert

StringBuffer sb = new StringBuffer("helloworld");
System.out.println(sb.delete(5, 10).insert(0, "你好"));  // 你好hello

请解释String、StringBuffer、StringBuilder的区别

  • String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  • StringBuffer与StringBuilder大部分功能是相似的
  • StringBuffer采用同步处理(在每个方法用synchronized修饰),属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

库里不会投三分

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

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

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

打赏作者

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

抵扣说明:

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

余额充值