文章目录
1. String
String类型是Java编程中最为常见的数据结构(没有之一),与之相关联的还有StringBuilder和StringBuffer。其中String类型是不可变的;后者均是可变的字符串,但是StringBuilder是线程不安全的,StringBuffer线程安全;所以三者的效率排名为:StringBuilder>String>StringBuffer。另外,为了优化字符串的使用,Java定义了两种字符串变量,一个是字符串常量,另一个就是字符串对象。
1.2.字符串的底层实现机制是什么?
不论是字符串常量还是字符串对象,其底层都是String类,而String类存储字符串的方式是通过char型数组存储的:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
从这里我们可以看到,value被声明了final不可变,所以正好证实了文章开头所说的String的不可变性!
1.3. String字符串常量和String对象的区别是什么?
上面讲到,String字符串常量和String字符串对象底层都是char型数组罢了,但是实际在内存存储还是有区别的!
因为字符串是Java中用的最多的一种数据类型,所以JVM专门开辟一个常量池空间针对String类型的数据作了特殊优化:即如果是字符串常量形式的声明首先会查看常量池中是否存在这个常量,如果存在就不会创建新的对象,否则在在常量池中创建该字符串并创建引用
,此后不论以此种方式创建多少个相同的字符串都是指向这一个地址的引用,而不再开辟新的地址空间放入相同的数据;但是字符串对象每次new都会在堆区形成一个新的内存区域并填充相应的字符串,不论堆区是否已经存在该字符串
!
这里讲一下常量池概念,常量池包含两种:
一种是class文件中的静态常量池,它只是java源码编译后形成的一类数据,不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间,是存在硬盘中的数据类型的命名方式;
另一种是运行时常量池,即上文中一直提到的常量池,它是内存中存储一类数据的内存区域命名方式,那么常见的就是字符串常量池,专门为优化字符创常量而分配的一个空间。在JDK6时,常量池存在方法区的永久代中
;
JDK7、JDK8都把常量池转移到堆区了
,而且到了JDK8的时候已经不存在永久代了,取而代之的是元空间的概念,但是元空间并不占用JVM内存而是直接共享系统内存,他本质上也是方法区的一种实现方式罢了
1.4 特点
- 字符串不变:字符串的值在创建后不能被更改。
String s1 = "abc";
s1 += "d";
System.out.println(s1); // "abcd"
// 内存中有"abc","abcd"两个对象,s1从指向"abc",改变指向,指向了"abcd"。
- 因为String对象是不可变的,所以它们可以被共享。
String s1 = "abc";
String s2 = "abc";
// 内存中只有一个"abc"对象被创建,同时被s1和s2共享。
"abc"
等效于char[] data={ 'a' , 'b' , 'c' }
,但是底层原理是字节数组( byte[] )
例如:
String str = "abc";
相当于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
// String底层是靠字符数组实现的。
1.5 构造方法
-
查看类
java.lang.String
:此类不需要导入。
-
查看构造方法
public String()
:初始化新创建的 String对象,以使其表示空字符序列。public String(char[] value)
:通过当前参数中的字符数组来构造新的String。public String(byte[] bytes)
:通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。- 直接赋值的方式创建字符串对象
- 构造举例,代码如下:
public class StringDemo01 {
public static void main(String[] args) {
//public String():创建一个空白字符串对象,不含有任何内容
String s1 = new String();
System.out.println("s1:" + s1);
//public String(char[] chs):根据字符数组的内容,来创建字符串对象
char[] chs = {'a', 'b', 'c'};
String s2 = new String(chs);
System.out.println("s2:" + s2);
//public String(byte[] bys):根据字节数组的内容,来创建字符串对象
byte[] bys = {97, 98, 99};
String s3 = new String(bys);
System.out.println("s3:" + s3);
//String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc
String s4 = "abc";
System.out.println("s4:" + s4);
}
}
1.6 常用方法
判断功能的方法
-
public boolean equals (Object anObject)
:将此字符串与指定对象进行比较。 -
public boolean equalsIgnoreCase (String anotherString)
:将此字符串与指定对象进行比较,忽略大小写。方法演示,代码如下:
/* 判断功能的方法 - public boolean equals (Object anObject) :将此字符串与指定对象进行比较内容是否相同,区分大小写。 举例:s1.equals(s2):比较s1和s2的内容是否一模一样,如果一样返回true,否则返回false - public boolean equalsIgnoreCase (String anotherString) :将此字符串与指定对象进行比较内容是否相同,忽略大小写。 举例:s1.equalsIgnoreCase(s2):比较s1和s2的内容是否相同,但是不区分大小写 - boolean contains(String str) : 当且仅当此字符串包含指定的str时,返回 true。 举例:s1.contains(s2): s1中包含s2,返回true,不包含返回false */ public class String_Demo01 { public static void main(String[] args) { // 创建字符串对象 String s1 = "hello"; String s2 = "hello"; String s3 = "HELLO"; // boolean equals(Object obj):比较字符串的内容是否相同 System.out.println(s1.equals(s2)); // true System.out.println(s1.equals(s3)); // false System.out.println("-----------"); //boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写 System.out.println(s1.equalsIgnoreCase(s2)); // true System.out.println(s1.equalsIgnoreCase(s3)); // true System.out.println("-----------"); } }
Object 是” 对象”的意思,也是一种引用类型。作为参数类型,表示任意对象都可以传递到方法中。
创建字符串对象两种方式的区别
-
通过构造方法创建
通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同
-
直接赋值方式创建
以“”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护
1.7 字符串的比较:==号和equals的作用
- ==比较基本数据类型:比较的是具体的值
- ==比较引用数据类型:比较的是对象地址值
- equals比较String类型: 比较的是对象的内容是否相同
/*
使用 == 做比较:
基本类型:比较的是数据值是否相同
引用类型:比较的是地址值是否相同
public boolean equals(Object anObject):
将此字符串与指定对象进行比较。由于我们比较的是字符串对象,所以参数直接传递一个字符串
*/
public class StringDemo02 {
public static void main(String[] args) {
//构造方法的方式得到对象
char[] chs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);
//直接赋值的方式得到对象
String s3 = "abc";
String s4 = "abc";
//比较字符串对象地址是否相同
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
System.out.println("--------");
//比较字符串内容是否相同
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s3.equals(s4));
}
}
获取功能的方法
-
public int length ()
:返回此字符串的长度。 -
public String concat (String str)
:将指定的字符串连接到该字符串的末尾。 -
public char charAt (int index)
:返回指定索引处的 char值。 -
public int indexOf (String str)
:返回指定子字符串第一次出现在该字符串内的索引。 -
public String substring (int beginIndex)
:返回一个子字符串,从beginIndex开始截取字符串到字符串结尾。 -
public String substring (int beginIndex, int endIndex)
:返回一个子字符串,从beginIndex到endIndex截取字符串。含beginIndex,不含endIndex。
方法演示,代码如下:
/*
获取功能的方法
- public int length () :返回此字符串的长度。
举例:s.length() 获取s中的字符的数量
- public String concat (String str) :将指定的字符串连接到该字符串的末尾。
举例:s1.cocat(s2) 把s2连接到s1的末尾
- public char charAt (int index) :返回指定索引处的 char值。
举例:s1.charAt(5) 获取s1中索引为5的字符
- public int indexOf (String str) :返回指定子字符串第一次出现在该字符串内的索引。
举例:s1.indexOf(s2) 查找s2在s1中第一次出现的位置,如果不存在,返回-1
- public String substring (int beginIndex) :返回一个子字符串,从beginIndex开始截取字符串到字符串结尾。
举例:s1.substring(5) 截取s1字符串从索引5开始一直到最后的内容
- public String substring (int beginIndex, int endIndex) :
返回一个子字符串,从beginIndex到endIndex截取字符串。含beginIndex,不含endIndex。
举例:s1.substring(5,10) 截取s1字符串从索引5开始一直到索引10之间的内容(包含5,不包含10)
*/
public class String_Demo02 {
public static void main(String[] args) {
//创建字符串对象
String s = "helloworld";
// int length():获取字符串的长度,其实也就是字符个数
System.out.println(s.length());
System.out.println("--------");
// String concat (String str):将将指定的字符串连接到该字符串的末尾.
String s = "helloworld";
String s2 = s.concat("**hello ");
System.out.println(s2);// helloworld**hello
// char charAt(int index):获取指定索引处的字符
System.out.println(s.charAt(0));
System.out.println(s.charAt(1));
System.out.println("--------");
// int indexOf(String str):获取str在字符串对象中第一次出现的索引,没有返回-1
System.out.println(s.indexOf("l"));
System.out.println(s.indexOf("owo"));
System.out.println(s.indexOf("ak"));
System.out.println("--------");
// String substring(int start):从start开始截取字符串到字符串结尾
System.out.println(s.substring(0));
System.out.println(s.substring(5));
System.out.println("--------");
// String substring(int start,int end):从start到end截取字符串。含start,不含end。
System.out.println(s.substring(0, s.length()));
System.out.println(s.substring(3,8));
}
}
1.8 转换功能的方法
public char[] toCharArray ()
:将此字符串转换为新的字符数组。public byte[] getBytes ()
:使用平台的默认字符集将该 String编码转换为新的字节数组。public String replace (CharSequence target, CharSequence replacement)
:将与target匹配的字符串使用replacement字符串替换。
方法演示,代码如下:
/*
转换功能的方法
- public char[] toCharArray () :把字符串变成对应的字符数组。
举例:s1.toCharArray() 把s1变成字符数组
- public byte[] getBytes () :把字符串变成对应的字节数组。
举例:s1.getBytes() 把s1变成字节数组
- public String replace (String oldStr, String newStr) :把字符串中的所有的oldStr替换成newStr。
举例:s1.replace("a","A") 把s1中的所有的"a"替换成"A"
*/
public class String_Demo03 {
public static void main(String[] args) {
//创建字符串对象
String s = "abcde";
// char[] toCharArray():把字符串转换为字符数组
char[] chs = s.toCharArray();
for(int x = 0; x < chs.length; x++) {
System.out.println(chs[x]);
}
System.out.println("-----------");
// byte[] getBytes ():把字符串转换为字节数组
byte[] bytes = s.getBytes();
for(int x = 0; x < bytes.length; x++) {
System.out.println(bytes[x]);
}
System.out.println("-----------");
// 替换字母it为大写IT
String str = "aaa ";
String replace = str.replace("it", "IT");
System.out.println(replace);
System.out.println("-----------");
}
}
CharSequence 是一个接口,也是一种引用类型。作为参数类型,可以把String对象传递到方法中。
1.9 分割功能的方法
public String[] split(String regex)
:将此字符串按照给定的regex(规则)拆分为字符串数组。
方法演示,代码如下:
/*
分割功能的方法
- public String[] split(String regex) :将此字符串按照给定的regex(规则)拆分为字符串数组
举例:String[] "a,b,c,d".split(",") 把"a,b,c,d"按照逗号切割,将切割后的多个子字符串存入String[] 中
*/
public class String_Demo03 {
public static void main(String[] args) {
//创建字符串对象
String s = "aa,bb,cc";
String[] strArray = s.split(","); // ["aa","bb","cc"]
for(int x = 0; x < strArray.length; x++) {
System.out.println(strArray[x]); // aa bb cc
}
}
}
2. StringBuilder
2.1 字符串拼接问题
由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。例如:
public class StringDemo {
public static void main(String[] args) {
String s = "Hello";
s += "World";
System.out.println(s);
}
}
在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改。
根据这句话分析我们的代码,其实总共产生了三个字符串,即"Hello"
、"World"
和"HelloWorld"
。引用变量s首先指向Hello
对象,最终指向拼接出来的新字符串对象,即HelloWord
。
由此可知,如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。为了解决这一问题,可以使用java.lang.StringBuilder
类。
2.2 StringBuilder概述
查阅java.lang.StringBuilder
的API,StringBuilder又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。
原来StringBuilder是个字符串的缓冲区,即它是一个容器,容器中可以装很多字符串。并且能够对其中的字符串进行各种操作。
它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。原理如下图所示:(默认16字符空间,超过自动扩充)
2.3 StringBuilder类和String类的区别
- String类:内容是不可变的
- StringBuilder类:内容是可变的
2.4 构造方法
根据StringBuilder的API文档,常用构造方法有2个:
-
public StringBuilder()
:构造一个空的StringBuilder容器。 -
public StringBuilder(String str)
:构造一个StringBuilder容器,并将字符串添加进去public class StringBuilderDemo01 { public static void main(String[] args) { //public StringBuilder():创建一个空白可变字符串对象,不含有任何内容 StringBuilder sb = new StringBuilder(); System.out.println("sb:" + sb); System.out.println("sb.length():" + sb.length()); //public StringBuilder(String str):根据字符串的内容,来创建可变字符串对象 StringBuilder sb2 = new StringBuilder("hello"); System.out.println("sb2:" + sb2); System.out.println("sb2.length():" + sb2.length()); } }
2.5 常用方法
StringBuilder常用的方法有3个:
public StringBuilder append(...)
:添加任意类型数据的字符串形式,并返回当前对象自身。public StringBuilder reverse()
:返回反转的字符序列public String toString()
:将当前StringBuilder对象转换为String对象。
代码简单演示
public class StringBuilderDemo01 {
public static void main(String[] args) {
//创建对象
StringBuilder sb = new StringBuilder();
//public StringBuilder append(任意类型):添加数据,并返回对象本身
// StringBuilder sb2 = sb.append("hello");
//
// System.out.println("sb:" + sb);
// System.out.println("sb2:" + sb2);
// System.out.println(sb == sb2);
// sb.append("hello");
// sb.append("world");
// sb.append("java");
// sb.append(100);
//链式编程
sb.append("hello").append("world").append("java").append(100);
System.out.println("sb:" + sb);
//public StringBuilder reverse():返回相反的字符序列
sb.reverse();
System.out.println("sb:" + sb);
String str = sb.toString();
System.out.println("str: "+str);
}
}
备注:StringBuilder已经覆盖重写了Object当中的toString方法。
2.6 StringBuilder和String相互转换
-
StringBuilder转换为String
public String toString():通过 toString() 就可以实现把 StringBuilder 转换为 String
-
String转换为StringBuilder
public StringBuilder(String s):通过构造方法就可以实现把 String 转换为 StringBuilder
-
示例代码
public class StringBuilderDemo02 {
public static void main(String[] args) {
/*
//StringBuilder 转换为 String
StringBuilder sb = new StringBuilder();
sb.append("hello");
//String s = sb; //这个是错误的做法
//public String toString():通过 toString() 就可以实现把 StringBuilder 转换为 String
String s = sb.toString();
System.out.println(s);
*/
//String 转换为 StringBuilder
String s = "hello";
//StringBuilder sb = s; //这个是错误的做法
//public StringBuilder(String s):通过构造方法就可以实现把 String 转换为 StringBuilder
StringBuilder sb = new StringBuilder(s);
System.out.println(sb);
}
}
2.7 StringBuilder练习
-
字符串拼接
定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,
并在控制台输出结果。例如,数组为int[] arr = {1,2,3}; ,执行方法后的输出结果为:[1, 2, 3]
/* 思路: 1:定义一个 int 类型的数组,用静态初始化完成数组元素的初始化 2:定义一个方法,用于把 int 数组中的数据按照指定格式拼接成一个字符串返回。 返回值类型 String,参数列表 int[] arr 3:在方法中用 StringBuilder 按照要求进行拼接,并把结果转成 String 返回 4:调用方法,用一个变量接收结果 5:输出结果 */ public class StringBuilderTest01 { public static void main(String[] args) { //定义一个 int 类型的数组,用静态初始化完成数组元素的初始化 int[] arr = {1, 2, 3}; //调用方法,用一个变量接收结果 String s = arrayToString(arr); //输出结果 System.out.println("s:" + s); } //定义一个方法,用于把 int 数组中的数据按照指定格式拼接成一个字符串返回 /* 两个明确: 返回值类型:String 参数:int[] arr */ public static String arrayToString(int[] arr) { //在方法中用 StringBuilder 按照要求进行拼接,并把结果转成 String 返回 StringBuilder sb = new StringBuilder(); sb.append("["); for(int i=0; i<arr.length; i++) { if(i == arr.length-1) { sb.append(arr[i]); } else { sb.append(arr[i]).append(", "); } } sb.append("]"); String s = sb.toString(); return s; } }
-
字符串反转
定义一个方法,实现字符串反转。键盘录入一个字符串,调用该方法后,在控制台输出结果
例如,键盘录入abc,输出结果 cba
/*
思路:
1:键盘录入一个字符串,用 Scanner 实现
2:定义一个方法,实现字符串反转。返回值类型 String,参数 String s
3:在方法中用StringBuilder实现字符串的反转,并把结果转成String返回
4:调用方法,用一个变量接收结果
5:输出结果
*/
public class StringBuilderTest02 {
public static void main(String[] args) {
//键盘录入一个字符串,用 Scanner 实现
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
//调用方法,用一个变量接收结果
String s = myReverse(line);
//输出结果
System.out.println("s:" + s);
}
//定义一个方法,实现字符串反转。返回值类型 String,参数 String s
/*
两个明确:
返回值类型:String
参数:String s
*/
public static String myReverse(String s) {
//在方法中用StringBuilder实现字符串的反转,并把结果转成String返回
//String --- StringBuilder --- reverse() --- String
// StringBuilder sb = new StringBuilder(s);
// sb.reverse();
// String ss = sb.toString();
// return ss;
return new StringBuilder(s).reverse().toString();
}
}