一、定义字符串
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
}
}
String replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replaceAll(String regex, String replacement)
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement)
使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
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
}
}
6.7、String.join 字符串分隔符分隔
public class TestDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
System.out.printf("%d = %s\n", 6, String.join(" * ", list)); // 6 = 1 * 2 * 3
System.out.println(String.join(" * ", list)); // 1 * 2 * 3
String[] array = {"a", "b", "c"};
System.out.println(String.join(" - ", array)); // a - b - c
}
}
五、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未采用同步处理,属于线程不安全操作