2. String字符串详解
前言
字符串是复合数据类型。在程序中经常会用到字符串及对字符串的各种操作,如字符串的连接、比较、截取、查找和替换等。Java提供了Java.lang.String类来对字符串进行这一系列的操作,以及StringBuffer类
字符
字符是用单引号括起来的单个字母,在Java中,表示字符的数据类型为char。一个字符在内存中占16位大小的空间(2个字节)。在编写程序的多数时候,使用字符值,通常会使用原始的char类型。例如:
char ch ='a'; //创建一个名为ch的字符并赋值
char uniChar='\u039a'; //创建大写的希腊omega字符的Unicode编码
char [] charArray={'h','e','l','l','o'}; //创建一个字符的数组charArray并赋值
char sex='男'; //创建一个名为sex的字符并赋值
Character类
在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情况。为了解决这个问题,Java语言为内置数据类型char提供了包装类Character类,提供了一个“包装(wrapper)器”类,用来将char类型的字符“包装”为一个Character对象。可以使用Character构造器创建一个Character对象.
例如:
Character sex=new Character(‘男’);
Java编译器会根据需要自动创建一个Character对象。将一个char类型的参数传递给需要一个Character类型参数的方法时,那么编译器会自动地将char类型参数转换为Character对象。 这种特征称为装箱,反过来称为拆箱。
Character ch = ‘a’; // 原始字符 ‘a’ 装箱到 Character 对象 ch 中
char c= test(‘x’); //原始的’x’被装箱用于方法test,返回值被拆箱为字符’c’
**注意:**Character类是不可变的,所以一旦一个Character对象被创建,就不能被改变,下面列出了Character类中最有用的一些方法。
方法 | 描述 |
---|---|
isLetter() | 是否是一个字母 |
isDigit() | 是否是一个空白字符 |
isWhitespace() | 是否是一个空白字符 |
isUpperCase() | 是否是大写字母 |
isLowerCase() | 是否是小写字母 |
toUpperCase() | 指定字母的大写形式 |
toLowerCase() | 指定字母的小写形式 |
toString() | 返回字符的字符串形式,字符串的长度仅为1 |
转义字符
在字符前带一个反斜杠符号“\”,是一个转义字符,每一个转义字符都有特定的含义。例如下表:
转义字符 | 描述 |
---|---|
\t | 在文本当前位置插入一个制表位 |
\b | 在文本当前位置插入一个退格 |
\n | 在文本当前位置换行 |
\r | 在文本当前位置插入一个回车 |
\f | 在文本当前位置插入换页符 |
\’ | 在文本当前位置插入单引号 |
\” | 在文本当前位置插入双引号 |
\ | 在文本当前位置插入反斜杠 |
字符串
字符串(String)是用一对双引号括起来的零个或多个字符组成的有限序列。在Java中,字符串被当作对象来处理。
程序中需要用到的字符串可以分为两大类:
- String类:创建之后不会再做修改和变动的字符串常量;
- StringBuffer类:创建之后允许再做更改和变化的字符串变量。
串的两种最基本的存储方式是顺序存储方式和链接存储方式。
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 内部使用 char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder
来标识使用了哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
String类字符串
字符串变量必须赋值后才可以使用,这称为字符串对象初始化。
字符串的创建和赋值的方式有三种:
String 字符串变量名;
字符串变量名="字符串常量";
String username="";
username="Tom"
或者
String 字符串变量名="字符串常量";
String SayHello="Hello world";
或者
String studentName=new String("周杰杰");
不可变的好处
1. 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2. String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String, StringBuffer和StringBuilder
1. 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
2. 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
字符串常量池
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 和 s2.intern() 方法取得同一个字符串引用。intern() 首先把 "aaa" 放到 String Pool 中,然后返回这个字符串引用,因此 s3 和 s4 引用的是同一个字符串。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4); // true
如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
new String("abc")
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。
- "abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量;
- 而使用 new 的方式会在堆中创建一个字符串对象。
创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。
public class NewStringTest {
public static void main(String[] args) {
String s = new String("abc");
}
}
使用 javap -verbose 进行反编译,得到以下内容:
// ...
Constant pool:
// ...
#2 = Class #18 // java/lang/String
#3 = String #19 // abc
// ...
#18 = Utf8 java/lang/String
#19 = Utf8 abc
// ...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
// ...
在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。
以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
获取字符串的长度
length()方法是用来获取字符串长度的。它会返回字符串对象中所包含的字符的个数,例如:
public class myfirst {
public static void main(String[] args) {
String sayHello="Hello,Welcome to Java!!!"; //创建一个sayHello字符串并赋值
int length=sayHello.length(); //创建一个变量length存放所获取的字符串长度
System.out.println(length);
}
}
运行结果为:24
注意:字符串中的标点或空格在计算字符串长度时,也要包括在内。
字符串基本操作
Java语言提供了几种对字符串的操作函数或方法
字符串连接
最常用对字符串进行的操作之一就是将两个字符串连接起来,合并为一个字符串。String类提供连接两个字符串的方法concat(),语法格式为:
string1.concat(string2);
concat()方法返回一个字符串,是将字符串string2添加到string1后面之后形成新字符串,例如:
public class myfirst {
public static void main(String[] args) {
String sayHello1="Hello,"; //创建字符串sayHello1、sayHello2并赋值
String sayHello2="Welcome to Java!!!";
String sayHello3=sayHello1.concat(sayHello2); //将合并后的字符串赋值给sayHello3
System.out.println(sayHello3);
}
}
**运行结果为:**Hello,Welcome to Java!!!
也可以直接使用字符串字面量来调用concat()方法。例如:
String sayHello3=“Hello,”.concat(“welcome to Java!!!”)
连接字符串还可以使用加号“+”运算符。这是一个重载了的运算符,用来直观地连接两个字符串,它使用起来比concat()方法更加灵活。例如:
String 字符串3=字符串1+字符串2;
注意:
- 当表达式中包含多个加号,并且存在各种数据类型参与运算时,则按照加号运算符从左到右进行运算;
- Java会根据加号运算符两边的操作数类型来决定是进行算术运算还是字符串连接的运算。
字符串比较
equals()方法,它是比较两个字符串是否相等,返回值boolean值;equalsIgnoreCase()方法,同样返回值为boolean值,使用格式为:
字符串1.equals(字符串2); //严格比较字符串是否相同
字符串1.equalsIgnoreCase(字符串2); //忽略字母大小写来进行比较
例如:
public class myfirst {
public static void main(String[] args) {
String sayHello1="HELLO"; //创建两个字符串
String sayHello2="hello";
System.out.println(sayHello1.equals(sayHello2)); //使用equals方法进行严格比较
System.out.println(sayHello1.equalsIgnoreCase(sayHello2)); //使用equalsIgnoreCase方法忽略大小写进行比较
}
}
运行结果为:
false
true
比较两个字符串的大小
我们可以使用compareTo方法比较两个字符串的大小,
- 若调用方法的字符串比较参数字符串大,返回正整数;
- 若比参数字符串小,则返回负整数;
- 若两字符串相等,则返回0;
- 若两个字符串各个位置的字符都相同,仅长度不同,则返回值为两者长度之差。
public class myfirst {
public static void main(String[] args) {
String sayHello1="HELLO";
String sayHello2="hello";
System.out.println(sayHello1.compareTo(sayHello2)); //比较两个字符串的大小
}
}
字符串的查找
indexOf() 判断传入字符串在原字符串中第一次出现的位置
lasetIndexOf() 判断传入字符串在原字符串中最后一次出现的位置
startsWith() 判断原字符串中是否以传入字符串开头
endsWith() 判断原字符串中是否以传入字符串结尾
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入QQ邮箱");
String a = sc.next();
}
public static boolean verifyEmail(String email) {
// 必须包含“@”和“.”;“@”必须在“.”的前面;“@”只能出现一次;不能以“@”开头;不能 以“.”结尾。
if (email.indexOf("@") >= 0 && email.indexOf(".") >= 0
// 必须包含“@”和“.”
&& email.indexOf('@') < email.indexOf('.')
// “@”必须在“.”的前面
&& email.indexOf('@') == email.lastIndexOf('@')
// “@”只能出现一次
&& !email.startsWith("@") // 不能以“@”开头
&& !email.endsWith(".")) { // 不能 以“.”结尾
return true;
}
return false;
}
chartAt()//方法用于返回指定索引处的字符
String s = "晚来天欲雪,能饮一杯无?";
char result = s.charAt(6);//索引下标为6的字符串
System.out.println(result);
字符串截取
- String substring(int index):提取从位置索引开始的字符串部分;
- String substring(int beginindex,int endindex):提取beginindex到endindex-1为止的字符串部分。
例如:
public class myfirst {
public static void main(String[] args) {
String s1="abcdefg";
System.out.println(s1.substring(3)); //截取开始位置为3的字符串
System.out.println(s1.substring(2, 5)); //截取开始索引为2,结束索引为5但不包含5的字符串
}
}
运行结果为:
defg
cde
字符串替换
字符串替换是用新字符去替代字符串中指定的所有字符,String类提供的replace方法可以实现这种替换。语法格式为:
string1.replace(char oldchar,char newchar)
例如:
public class myfirst {
public static void main(String[] args) {
String s1="abcdeag";
char oldchar='a'; //被替换字符
char newchar='A'; //替换字符
String s2=s1.replace(oldchar, newchar); //进行替换
System.out.println("替换后的字符串为:"+s2); //输出新字符串
}
}
运行结果为:
替换后的字符串为:AbcdeAg
字符数组转换为字符串
我们可以将字符数组转换为字符串,然后利用字符串对象的属性和方法,进一步对字符串进行处理,例如:
public class myfirst {
public static void main(String[] args) {
char []helloArray= {'h','e','l','l','o'}; //声明一个字符数组
String helloString=new String(helloArray); //将字符数组作为构造函数的参数
System.out.println(helloString);
}
}
运行结果为:
hello
字符串转换为字符数组
我们也可以将字符串转换为字符数组,这需要使用字符串对象的一个方法toCharArray()。它返回一个字符串对象转换过来的字符数组。例如:
public class myfirst {
public static void main(String[] args) {
String helloString ="hello"; //声明一个字符串变量并赋值
char []helloArray=helloString.toCharArray(); //进行字符串和字符数组的转换
for(int i=0;i<helloArray.length;i++) { //for循环输出字符数组
System.out.print(helloArray[i]+" ");
}
}
}
运行结果为:
h e l l o
最后
好了,关于Java字符串的知识学到这里了,谢谢观看!!!
我们下篇文章再见!!!
成功不是将来才有的,而是从决定去做的那一刻起,持续累积而成。