目录
前言
在Java中是没有“字符串”这一说法的,C语言中的字符串用标准库将数据和操作方法分离,这是不符合Java语言面相对象的思想,所以Java提供了String类,这更能方便程序员使用和操作字符串。
一、String类的常用方法
1.1 常用的字符串构造
跟数组的定义时差不多的
public class Test {
public static void main(String[] args) {
String str = "java";//跟数组一样,简写了
String str1 = new String("java");
char[] chars = {'j','a','v','a'};
String str2 = new String(chars);
System.out.println(str);
System.out.println(str1);
System.out.println(str2);
System.out.println(str.length());//打印长度
}
}
在Java中,String类是一个引用类型(final修饰不能被继承),但是呢内部并不存储字符串本身,我们可以同过String类的原码可以得出结果:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];//value是一个数组,没有分配空间,字符串就存储在value数组中
/** Cache the hash code for the string */
private int hash; // Default to 0
从原码中可以得出,String类在堆区中存了两个数据元素,hash和value[]:
chars在堆区中开辟了一块内存,用来存放字符串"java";str2在堆区中也创建了一块内存用于存放value[]和hash,然后拷贝chars引用的对象放入一块新的内存,将value引用 引用这块拷贝好的内存。
1.2 String类的大小比较
1.2.1 引用对象比较
public static void main(String[] args) {
//对于引用类型来说 ==比较的是引用的对象的地址
String str = "java";//跟数组一样,简写了
String str1 = new String("Python");
char[] chars = {'j','a','v','a'};
String str2 = new String(chars);
str = str1;
System.out.println(str2==str1);//false在栈区上开辟空间,比较的是引用的对象的内存地址
System.out.println(str1==str);//true
}
1.2.2 字符大小的比较
String类中用equals方法比较, equals方法属于父类Object类,默认的比较方式用“==”比较(内存);我们可以重写equals方法来根据自己的需求来比较大小(String类自己已经重写了equals方法,用来比较里面的值是否相同):
列表中的方法都有一一实现:
public static void main(String[] args) {
String str = "java";
String str1 = "JAVA";
System.out.println(str.equals(str1));//所引用的对象不同
System.out.println(str.compareTo(str1));//结果是ASCII的差值
System.out.println(str.compareToIgnoreCase(str1));//忽略大小写比较
System.out.println(str.equalsIgnoreCase(str1));//忽略大小写比较是否相同 常量池下面讲解
}
1.3 字符串查找
字符串的常用方法已经列举在下面的代码中了。
//char charAt(int index);int indexOf(int ch);
//int indexOf(int ch, int fromIndex);int indexOf(String str)
//int indexOf(String str, int fromIndex);int lastIndexOf(int ch)
//int lastIndexOf(String str, int fromIndex)
public static void main(String[] args) {//字符串查找
String str = "asd";
for (int i = 0; i < str.length(); i++) {
char ret = str.charAt(i);//注意越界,根据下标查找
System.out.print(ret+" ");
}
System.out.println("=========");
int ret = str.indexOf('s',1);//获取d第一次出现的位置,从s位置开始查找 字符对应的ASCII值
System.out.println(ret);
System.out.println("=========");
ret = str.indexOf("asd");//查找字符串,返回第一个字符的起始位置 KMP算法:最高效的算法
System.out.println(ret);
System.out.println("=========");
str = "asd.dsa";
System.out.println(str.lastIndexOf('.',2));//从后往前找,3代表在位置3前面的字符里面找
System.out.println(str.lastIndexOf("asd",3));
}
1.4 数据类型之间的转化
1.4.1 数字与字符串
public static void main(String[] args) {//其他的数据类型转化为字符串
//valueOf();有许多的重载的方法,看自己具体的实现
//数字转字符串
String str = String.valueOf(12);
String str1 = String.valueOf("dasd");
System.out.println(str1);
System.out.println(str);
//讲一个对象转换成字符串
String str2 = String.valueOf(new Student("小明"));
System.out.println(str2);
System.out.println("=========");
//字符串转数字
//将"123"转换成数字,在进行进制的转换
int str3 = Integer.valueOf("123",8);//radix:进制,将十进制的数转换成八进制
System.out.println(str3);
}
1.4.2 大小写转换
public static void main(String[] args) {//大小写转换,不会改变当前的字符串,会产生新的对象
String str = "sadsDDDa上大";
System.out.println(str.toUpperCase());
System.out.println(str.toLowerCase());
}
1.4.3 字符串转数组
public static void main(String[] args) {//字符串转化成数组
String str = "hello";
char[] chars = str.toCharArray();//拷贝
for (char x : chars) {
System.out.println(x);
}
System.out.println(str);
}
1.4.4 格式化
public static void main(String[] args) {//格式化
String str = String.format("%d-%d-%d",2022,07,27);
System.out.println(str);
}
1.4.5 字符串替换
public static void main(String[] args) {//字符串替换
String str = "asdasdasdwqeqwewe";
String ret = str.replace('a','z');
System.out.println(ret);
ret = str.replace("asd","dsa");
System.out.println(ret);
ret = str.replaceAll("asd","dsa");
System.out.println(ret);
ret = str.replaceFirst("asd","dsadasdasdas");
System.out.println(ret);
}
1.4.6 字符串拆分
public static void main(String[] args) {//字符串拆分
String str = "asd asd asd wqe wew";
String[] ret = str.split(" ",3);//最多拆分3组,中间的空格与你字符串的空格要与之对应
for (String s : ret) {
System.out.println(s);
}
System.out.println();
str = "2022.07.27";
ret = str.split("\\.");
for (String s : ret) {
System.out.println(s);
}
System.out.println();
str = "2022\\07\\27";
ret = str.split("\\\\");
for (String s : ret) {
System.out.println(s);
}
System.out.println();
str = "2022 07&27";//如果一个字符串中有多个分隔符,可以用"|"作为连字符
//ret = str.split(" |&");
ret = str.split("[ &]");//正则表达式
for (String s : ret) {
System.out.println(s);
}
System.out.println();
//多次分割
str = "2022&07&27 星期三";
ret = str.split("&");
for (String s : ret) {
//s[0] = 2022
//s[1] = 07
//s[2] = 27 星期三
String[] ss = s.split(" ");
for (String s1 : ss) {
System.out.println(s1);
}
}
}
1.4.7 字符串截取
public static void main(String[] args) {//字符串截取
String str = "asdasdasdwqewew";
System.out.println(str.substring(1));//从给出的位置开始截取
str = str.substring(1,6);//截取1位置到5位置的字符串,截取后又成了一个新的对象
System.out.println(str);
System.out.println();
str = " asdasdasdwqewew " +
" ";
System.out.println(str.trim());//去掉左右的空格 换行 制表符等
}
二、字符串常量池
常量池:为了使程序的运行速度更快,减少内存的消耗,java中提供了8中类型的常量池,这里重点讲字符串常量池(专门存放字符串)。下面我们通过一段代码来展开学习:
public static void main(String[] args) {
String s = "hello";
String s1 = "hello";//第二次存储这个字符串的时候,都回去常量池里面去寻找是否存有,创建与否,提高了效率
System.out.println(str == str1);//打印true
}
通过上面的学习我们知道,String是一个引用类型,在堆区中会有一个引用数组value[],数组引用了字符串"java"。上面的代码那就应该是创建了两个内存空间,那为什么最后会打印true呢?这就是常量池所带来的结果。简单画个图来了解常量池:
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构)。 String 类型,JVM 内部使用 HashTable 进行缓存,我们知道,HashTable 的结构是一个数组,数组中每个元素是一个链表。
字节码文件加载的时候,s和s1就会被创建,但是在创建之前他们会先去字符串常量池当中寻找是否有和s和s1相同内容的字符串,如果有就会引用常量池当中的字符串(相当于字符串引用赋值给s或s1),如果没有就在常量池当中创建并保存。所以上面的代码中,s就会先在常量池当中创建,s1和s的字符串相同,那么s1就会引用s在常量池当中创建好了的字符串。s和s1都同时指向了同一块内存空间(只要是""括起来都会存在字符串常量池当中;可以避免频繁的创建字符串,减少了空间的损耗,提高了效率)。
2.1 intern()方法详解
先看一段代码:
public static void main(String[] args) {
char[] chars = new char[]{'j','a','v','a'};
String str = new String(chars);
String str1 = "java";
System.out.println(str==str1);//打印false
}
上面中str和str1引用的是不同的对象,由第一章图变可知,如果我们加入intern()后,会有什么影响呢?下面请看代码:
public static void main(String[] args) {
char[] chars = new char[]{'j','a','v','a'};
String str = new String(chars);
str.intern();//手动入池
String str1 = "java";
System.out.println(str==str1);//打印true
}
对str使用intern()方法后,整段代码的结果就变了,下面图解:
intern()方法就是手动入池,他会检查str在常量池当中是否有的相同的数据(是否存在),不存在就把str的引用的对象保存到常量池当中;如果存在,则返回这个对象。所以当str使用了intern()方法后,常量池当中就会存在字符串"java",所以str和str1都同时引用了字符串"java"这个对象。
三、StringBuilder和StringBuffer
public static void main(String[] args) {
String str = "java";
str+="EE";//不是在原来的str上拼接的,重新开辟了一块空间
System.out.println(str);
}
对于这种情况来说,我们操作字符串的时候大多数的时候都会创建新的空间,操作的字符串过多时,就会发生占用大量的空间,降低效率的情况。StringBuilder和StringBuffffer方法就是来解决出现这样的情况的。
再看一段代码:
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("java");
stringBuilder.append("EE");
System.out.println(stringBuilder);//打印javaEE
System.out.println("=========");
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("java");
stringBuffer.append("EE");
System.out.println(stringBuffer);//打印javaEE
}
对于StringBuilder和StringBuffer方法来说,拼接字符串(不只有append方法,有许多的重载方法)的时候,他们都是在同一个对象里面操作字符串的,地址没有改变,改变的只是里面的值。两个的作用都是一样的,但是他们在处理不同的场景的时候,功能的大小有所不同。
StringBuilder:一般处理单线程
StringBuffer:一般处理多线程,我们查看原码是发现StringBuffer方法多了synchronized修饰,synchronized是对象锁的意思,多线程操作的时候,我们会将他们锁进一个封闭的房间里面,知道他们的所有操作完成,这样就对他们提供了安全保障。(开锁和关锁需要消耗资源,所以看场景使用方法)
StringBuffer 采用同步处理,属于线程安全操作;而 StringBuilder 未采用同步处理,属于线程不安全操作。