一、定义字符串
1、字符串使用双引号
2、在Java中,没有字符串以 \0 结尾
- 被final修饰,这个类不能被继承
1、常见的构造 String 的方式:
public class TestDemo {
public static void main(String[] args) {
String str = "abc";
// 调用构造方法 构造对象
String str2 = new String("hello");
// 字符数组 --> 字符串
char[] chars = {'a', 'b', 'c'};
String str3 = new String(chars);
}
}
AIT+7 查看原码:
有两个字段:value和hash
2、注意问题:
- 以下代码中,str2引用指向了str2这个引用所指向的对象
改变str1的指向,不印象str2
不能通过str1修饰"abcdef"的内容,因为它是字符串字面值常量
public class Test {
public static void main(String[] args) {
String str1 = "abcdef";
String str2 = str1;
str1 = "bit";
}
}
- 不是传引用就能改变实参的值,要看这个引用具体做了什么
仅仅是改变了形参s的指向,输出str还是abcdef
而通过array修改了chars的’b’
- 数组的整体赋值只有一次机会,就是在定义的时候
final 修饰引用 表示引用的内容不能修改
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
array = new int[]{6,7,8}; // ok
final int[] array2 = {1,2,3,4,5};
// array2 = new int[]{6,7,8}; // err
}
}
- str1这个引用,不指向任何对象
str2这个引用,指向的字符串是空的
public class TestDemo {
public static void main(String[] args) {
String str1 = null;
String str2 = "";
}
}
二、字符串比较相等+字符串常量池
1、常量池:
String类的设计使用了共享设计模式
- 在JVM底层实际上会自动维护一个对象池(字符串常量池)
1、如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
2、如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
3、如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
理解 “池” (pool):
“池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据
库连接池” …
- Class文件常量池:int a = 10;
- 运行时常量池:当程序把编译好的字节码文件,加载到JVM中后,会生成一个运行时常量池【方法区】,实际上是Class文件常量池
- 字符串常量池:主要存放字符串常量,本质上是一个哈希表【String Table】
池的意义:提高效率
哈希表:
数据结构,描述和组织数据的一种方式
存储数据的时候,会根据一个映射关系进行存储,如何映射:设计一个函数(哈希函数)
2、一组代码测试:
1. 以下代码结果为false,分析其原理:
public class Test {
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
}
}
2. 找常量池里有没有"hello",此时已有str1,不再重复创建,输出true
public class Test {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);
}
}
3. str中"he",“llo” 都是常量,编译的时候,已经确定了是"hello",还是true
public class Test {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "he"+"llo";
System.out.println(str1 == str2);
}
}
但不能这样拼接,因为str3是 变量,编译是不确定
public class Test {
public static void main(String[] args) {
String str1 = "hello";
String str3 = "he";
String str4 = str3+"llo";
System.out.println(str1 == str4);
}
}
5. 两个匿名对象拼接:
public class Test {
public static void main(String[] args) {
String str1 = "11";
String str2 = new String("1") + new String("1");
System.out.println(str1 == str2);
}
}
1.拼接->StringBuilder对象
2.->调用toString->String
3.->将String地址赋给str2
输出:false
分析原理:
6. 调用intern() 手动入池:
public class Test {
public static void main(String[] args) {
String str2 = new String("1") + new String("1");
str2.intern(); // 手动入池
String str1 = "11";
System.out.println(str1 == str2);
}
}
把str2所指向的对象整体入池,str1检查到常量池里就会有"11",结果为true
调整顺序:
当字符串常量池里没有时,就会入池,
输出false
public class Test {
public static void main(String[] args) {
String str1 = "11";
String str2 = new String("1") + new String("1");
str2.intern(); //
System.out.println(str1 == str2);
}
}
比较引用所指向的内容是否相同:
输出为true
public class Test {
public static void main(String[] args) {
String str1 = "11";
String str2 = new String("1") + new String("1");
str2.intern(); //
System.out.println(str1.equals(str2));
}
}
3、理解字符串不可变
字符串是一种不可变对象. 它的内容不可改变.
String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组
public class TestDemo {
public static void main(String[] args) {
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
}
}
执行结果:hello world!!!
表面上好像是修改了字符串, 其实是 str 引用到了其他的对象:
以如下代码验证:
public class TestDemo {
public static void main(String[] args) {
String str = "abcd";
for (int i = 0; i < 10; i++) {
str += i;
}
System.out.println(str); // abcd0123456789
}
}
字符串的拼接,都会被优化为StringBuilder对象
每次循环都要new对象,开辟内存,花销时间,
而value引用是被final修饰的,也就是说,每次拼接都是拼接了新的对象
如果实在需要修改字符串
使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员
反射:
是面向对象编程的一种重要特性,有些编程语言也称为 “自省”
指的是程序运行过程中,获取 / 修改某个对象的详细信息(类型信息,属性信息等),相当于让一个对象更好的 “认清自己”
例:输出:hello
import java.lang.reflect.Field;
public class TestDemo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// 获取class对象
String str = "Hello";
Class<?> c1 = String.class;
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
Field valueField = c1.getDeclaredField("value");
// 将这个字段的访问属性设为 true
valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) valueField.get(str);
// 修改 value 的值
value[0] = 'h';
System.out.println(str);
}
}
三、字符,字节与字符串
1、字符与字符串
字符串内部包含一个字符数组,String 可以和 char[] 相互转换
1.1、字符数组 – 字符串
public class TestDemo {
public static void main(String[] args) {
char[] val = {'a', 'b', 'c'};
String str = new String(val);
System.out.println(str); // abc
}
}
1.2、自定义范围
public class TestDemo {
public static void main(String[] args) {
char[] val = {'a', 'b', 'c', 'd', 'e'};
String str = new String(val, 0, 3);
System.out.println(str); // abc
}
}
1.3、CharAt 获取指定字符
public class TestDemo {
public static void main(String[] args) {
String str = "world";
char ch = str.charAt(2); // 获取2下标的字符
System.out.println(ch); // r
}
}
1.4、字符串 – 字符数组
public class TestDemo {
public static void main(String[] args) {
String str = "hello";
char[] chars = str.toCharArray(); // 把str2指向的字符串对象 变成字符数组
System.out.println(Arrays.toString(chars)); // [h, e, l, l, o]
}
}
例:给定一个字符串, 判断是否全部由数字组成
public class TestDemo {
public static boolean isNumberChar(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if(c < '0' || c > '9') {
return false;
}
}
return true;
}
public static boolean isNumberChar2(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
boolean flg = Character.isDigit(c); // 判断某个字符是不是数字
if(flg == false) {
return false;
}
}
return true;
}
public static void main(String[] args) {
String str = "12345";
System.out.println(isNumberChar(str)); // true
}
}
Class Character
2、字节与字符串
字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换
2.1、字节数组 – 字符串
public class TestDemo {
public static void main(String[] args) {
byte[] bytes = {97, 98, 99, 100};
String str = new String(bytes);
System.out.println(str); // abcd
}
}
2.2、自定义范围
public class TestDemo {
public static void main(String[] args) {
byte[] bytes = {97, 98, 99, 100};
String str = new String(bytes, 1, 3);
System.out.println(str); // bcd
}
}
调用带两个参数的构造方法,出现:
@Deprecated 说明这个方法已经过时了
2.3、字符串 – 字节数组
public class TestDemo {
public static void main(String[] args) {
String str = "bacd";
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes)); // [98, 97, 99, 100]
}
}
2.4、编码转换处理
public class TestDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "比特";
String str2 = "abcd";
byte[] bytes = str.getBytes("utf-8");
System.out.println(Arrays.toString(bytes)); // [-26, -81, -108, -25, -119, -71]
byte[] bytes2 = str2.getBytes("utf-8");
System.out.println(Arrays.toString(bytes2)); // [97, 98, 99, 100]
byte[] bytes3 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes3)); // [-79, -56, -52, -40]
byte[] bytes4 = str2.getBytes("GBK");
System.out.println(Arrays.toString(bytes4)); // [97, 98, 99, 100]
}
}
总结
- byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
- char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候
四、字符串常见操作
1、字符串比较
1.1、比较内容
真假比较
大小比较
public class TestDemo {
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "hello";
System.out.println(str1.equals(str2)); // false
}
}
1.2、忽视大小写
public class TestDemo {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "ABC";
// 区分大小写的比较
System.out.println(str1.equals(str2)); // false
// 不区分
System.out.println(str1.equalsIgnoreCase(str2)); // true
}
}
1.3、比较两字符串大小
lim为较短字符串长度,返回值大于0,小于0或等于0
一个字符一个字符比较,返回差
否则返回字符串长度差
public class TestDemo {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "ABC";
int ret = str1.compareTo(str2);
System.out.println(ret); // 32
String str3 = "abC";
String str4 = "abcdx";
int ret = str3.compareTo(str4);
System.out.println(ret); // -32
}
}
2、字符串查找
2.1、判断一个子字符串是否存在
public class TestDemo {
public static void main(String[] args) {
String str = "absbhaabcds";
String tmp = "abc";
boolean flg = str.contains(tmp);
System.out.println(flg); // true
}
}
2.2、找子串
- 在主串中找到子串出现的位置
public class TestDemo {
public static void main(String[] args) {
String str = "absbhabcds";
String tmp = "abc";
int index = str.indexOf(tmp); // 5 类似C的strstr:KMP算法
System.out.println(index);
}
}
- 从指定位置开始找子串的位置
public class TestDemo {
public static void main(String[] args) {
String str = "absbhabcds";
String tmp = "abc";
int index = str.indexOf(tmp, 3); // 5
System.out.println(index);
}
}
- 从后向前找
public class TestDemo {
public static void main(String[] args) {
String str = "xxabcabcxx";
String tmp = "abc";
System.out.println(str.lastIndexOf(tmp)); // 5
}
}
- 从指定位置从后往前找
public class TestDemo {
public static void main(String[] args) {
String str = "xxabcabcxx";
String tmp = "abc";
System.out.println(str.lastIndexOf(tmp, 4)); // 2
}
}
2.3、判断是否以指定字符串开头
public class TestDemo {
public static void main(String[] args) {
String str = "absbhabcds";
String tmp = "abc";
System.out.println(str.startsWith("ab")); // true
}
}
2.4、判断指定偏移量开头是否以指定字符串开头
public class TestDemo {
public static void main(String[] args) {
String str = "absbhabcds";
String tmp = "abc";
System.out.println(str.startsWith("ab", 5)); // true
}
}
2.5、判断是否以指定字符串结尾
public class TestDemo {
public static void main(String[] args) {
String str = "absbhabcds";
String tmp = "abc";
System.out.println(str.endsWith("ds")); // true
}
}
3、字符串替换
- 替换字符
public class TestDemo {
public static void main(String[] args) {
String str = "asfaabcdadf";
String ret = str.replace('a', 'p');
System.out.println(ret); // psfppbcdpdf
}
}
- 替换所有字符串,与上构成重载
public class TestDemo {
public static void main(String[] args) {
String str = "abfababcdab";
String ret = str.replace("ab", "pp");
System.out.println(ret); // ppfppppcdpp
}
}
- 同上效果
public class TestDemo {
public static void main(String[] args) {
String str = "abfababcdab";
String ret = str.replaceAll("ab", "pp");
System.out.println(ret); // ppfppppcdpp
}
}
- 替换第一次的字符串
public class TestDemo {
public static void main(String[] args) {
String str = "abfababcdab";
String ret = str.replaceFirst("ab", "pp");
System.out.println(ret); // ppfababcdab
}
}
4、字符串拆分
4.1、按指定拆分
public class TestDemo {
public static void main(String[] args) {
String str = "name=zhangsan&age=22";
String[] strings = str.split("&");
for (String s : strings) {
String[] ss = s.split("=");
for (String x : ss) {
System.out.println(x);
}
System.out.println(s);
}
}
}
4.2、需注意的问题:
- 字符 “|”,"*","+" 都得加上转义字符,前面加上 “”
public class Test {
public static void main(String[] args) {
String str = "192.168.1.1";
String[] strings = str.split("\\.");
for (String x : strings) {
System.out.print(x+" ");
}
// 192 168 1 1
}
}
- 一个斜杠的分割:
public class Test {
public static void main(String[] args) {
String str = "192\\168\\1\\1";
String[] strings = str.split("\\\\");
for (String x : strings) {
System.out.println(x);
}
// 192
// 168
// 1
// 1
}
}
4.3、极限次分割
不均匀
public class Test {
public static void main(String[] args) {
String str = "192.168.1.1";
String[] strings = str.split("\\.", 2);
for (String x : strings) {
System.out.println(x);
}
// 192
// 168.1.1
}
}
4.4、多次差分
如果一个字符串中有多个分隔符,可以用 “|” 作为连字符
public class Test {
public static void main(String[] args) {
String str = "zhang san#he&llo";
String[] strings = str.split(" |&|#");
for (String x : strings) {
System.out.println(x);
}
// zhang
// san
// he
// llo
}
}
5、字符串截取
5.1、从指定索引截取到结尾
public class Test {
public static void main(String[] args) {
String str = "abcddef";
String sub = str.substring(2); // 提取子串
System.out.println(sub); // cddef
}
}
如果是0,还是原来的对象:
5.2、截取部分
public class Test {
public static void main(String[] args) {
String str = "abcddef";
String sub = str.substring(2, 5); // 左闭右开
System.out.println(sub); // cdd
}
}
6、其他操作方法
6.1、去左右空格,保留中间空格
public class Test {
public static void main(String[] args) {
String str = " abc def ";
String ret = str.trim();
System.out.print(ret);
System.out.println("=============="); // abc def==============
}
}
6.2、字符串大小写转换
public class Test {
public static void main(String[] args) {
String str = "abcDEF123高";
String ret1 = str.toUpperCase();
System.out.println(ret1); // ABCDEF123高
String ret2 = str.toLowerCase();
System.out.println(ret2); // abcdef123高
}
}
6.3、字符串入池intern()
见上【常量池】
6.4、字符串连接,拼接的结果不入池
public class Test {
public static void main(String[] args) {
String str = "abc";
String ret = str.concat("bit");
System.out.println(ret); // abcbit
}
}
6.5、求字符串长度
public class Test {
public static void main(String[] args) {
String str = "abc";
System.out.println(str.length()); // 需加括号
int[] array = {1,2,3,4,5};
System.out.println(array.length);
}
}
6.6、判断空字符串(不是null)
public class Test {
public static void main(String[] args) {
String str = "";
System.out.println(str.isEmpty()); // true
}
}
五、StringBuffer 和 StringBuilder
StringBuilder
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abcd");
System.out.println(sb); // abcd
System.out.println(sb.toString()); // 同上
}
}
append方法返回的是当前对象,不会产生新的对象
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("");
sb.append("abcd");
sb.append("1234");
System.out.println(sb.toString()); // abcd1234
}
}
append 可以连用:
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("");
sb.append("abcd").append("123");
System.out.println(sb.toString()); // abcd123
}
}
普通的String拼接,底层会被优化为StringBuilder:
public class Test {
public static void main(String[] args) {
//String str = "abcdef";
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
//str += "123";//str = str + "123"
sb.append("123");
//str = sb.toString();
System.out.println(sb);
}
}
在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法
观察一段循环拼接代码:
每次循环都new了一个对象:
public class Test {
public static void main(String[] args) {
String str = "abcdef";
for (int i = 0; i < 10; i++) {
StringBuilder sb = new StringBuilder();
sb.append(str).append(i);
str = sb.toString();
// str += i;
}
System.out.println(str); // abcdef0123456789
}
}
优化:放在循环外,不需要每次都new对象
public class Test {
public static void main(String[] args) {
String str = "abcdef";
StringBuilder sb = new StringBuilder();
sb.append(str);
for (int i = 0; i < 10; i++) {
sb.append(i);
}
str = sb.toString();
System.out.println(str); // abcdef0123456789
}
}
得出局部结论:
如果在循环内,进行字符串的拼接,尽量不要使用String,优先使用StringBuffer 和 StringBuilder
局部的问题:
StringBuffer 和 StringBuilder有什么区别?
StringBuffer多了一个synchronized关键字:保证线程的安全
所以一般来说,StringBuffer用于多线程,StringBuilder用于单线程
String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
- String变为StringBuffer:利用 StringBuffer 的构造方法或 append()方法
- StringBuffer变为String:调用 toString() 方法
public class Test {
/**
* StringBuffer或者StringBuilder-》String
* 调用toString方法
* @return
*/
public static String func3() {
StringBuilder sb = new StringBuilder();
return sb.toString();
}
/**
* String->StringBuffer或者StringBuilder
* 使用构造方法
* @return
*/
public static StringBuffer func() {
String str = "abcdef";
return new StringBuffer(str);
}
public static StringBuffer func2() {
String str2 = "abcdef";
StringBuffer sb = new StringBuffer();
sb.append(str2);
return sb;
}
}
请解释String、StringBuffer、StringBuilder的区别:
- String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
- StringBuffer与StringBuilder大部分功能是相似的
- StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作