简介
/*
* java.lang.String:字符串类,并且该类加final修饰
* 底层是char数组 private final char value[];
* 字符串中很多特性与数组一致
* 1 字符串一旦创建不可更改
* 2 为了提升字符串的访问效率,Java提出字符串常量池,相当于一个缓冲区。存在于方法区中
* 引用类型对象保存在堆内存,字符串保存在静态区的字符串常量池中
* 3 在程序执行过程中,如果用到某个字符串,如“abc”,虚拟机会先去常量池中搜索,
* 如果存在,直接指向该字符串,否则新建一个字符串对象,并指向
*
* */
public class _01_StringB {
public static void main(String[] args) {
String s1 = "abc"; // 使用常量池,相当于简写了new
String s2 = "abc";
String s3 = "Abc";
// true,==基本类型比较值,引用类型比较内存地址
// s2和s3 指向地址相同
// 只创建了两个对象“abc”和“Abc”
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println("=========");
s1 = "a2";
// 这里只是新建了一个字符串“a2”,并让s1指向“a2”
System.out.println(s1);
System.out.println("===========");
// 通过new创建字符串对象,对象地址保存在堆内存中,每使用new一次,就在堆中开辟一块空间
// 因此s4和s5分别指向堆内存保存的对象,而对于字符串对象,堆内存中保存的是常量池中"123"的地址
// 当堆内存指向常量池中“123”时,如果常量池中已存在对象“123”,则直接指向。否则在常量池中创建对象“123”,并指向
// 堆内存中地址指向常量池。好比是将常量池中"123"分贝拷贝了一份放入堆内存中
// 字符串日常使用时:不使用new创建
String s4 = new String("123");
String s5 = new String("123");
// s4、s5创建了三个对象(堆内存两个、常量池一个),占用了五块空间(栈内存两个、堆内存两个、常量池一个)
// == 比较的是地址,new了两次,地址自然就不一样
System.out.println(s4 == s5);
// 在string类中 已经重写了equals方法,重写后比较的是 值 不是地址
System.out.println(s4.equals(s5));
}
}
/*
* String一旦创建不可更改,使用时不要频繁拼接字符串,效率低,浪费空间,垃圾回收也存在问题
* */
public class _02_String {
public static void main(String[] args) {
String[] strs = {"a","b","c","d"};
String temp = "";
for (String str : strs) {
temp += str;
}
System.out.println(temp);
// a,b,c,d,"",""ab,""abc,""abcd,
}
}
构造方法
/*
* String中常用构造方法
* 创建String对象的方式
* */
public class _03_StringC {
public static void main(String[] args) {
// 1
String s1 = "123";
// 2
String s2 = new String("123");
// 3 字节数组
// 补充:数组声明
// byte[] a = {1,2,3}; 静态声明,知道具体数组值
// byte[] a = new byte[]{1,2,3}; 静态声明,多new
// byte[] a = new byte[3]; 动态声明
byte[] a = {97,98,99};
String s3 = new String(a);
// abc: 把数字转换为char 97->a 98->b 99->c
// String底层是char数组
String s4 = new String(new byte[]{97,98,99});
System.out.println(s3 + " " + s4);
// 4 字节数组,截取一部分,从下标1开始,去两个
String s5 = new String(a,1,2);
System.out.println(s5);
// 5 无参构造创建
String s6 = new String();
System.out.println("空字符串" + s6);
// 6 字符数组
char[] chars = {'a','b','c','d'};
String s7 = new String(chars);
String s77 = new String(new char[]{'a','b','c'});
System.out.println(s7);
System.out.println(s77);
// 7 字符数组截取
String s8 = new String(chars,2,2);
System.out.println(s8);
// byte[] a = new byte[]{1,2,3}; 静态声明,多new。用于方法传参
byte[] arr = {1,2,3};
test(arr); // 显示静态声明,直接传参
test(new byte[]{1,2,3}); // 匿名静态传参,不用额外声明一个数组对象,没保存,只能用一次
}
// byte[] a = new byte[]{1,2,3}; 方法传参用这种方式(应用场景)
public static void test(byte[] arr){
}
}
常用方法
/*
* String中常用方法
* 1 方法属于哪个类
* 2 什么方法,静态还是成员,如何调用
* 3 方法名,出参入参
* 4 方法功能
* */
public class _04_StringM {
public static void main(String[] args) {
// 1 int length(): 返回字符串长度
String s1 = "abcdef!";
System.out.println(s1.length());
// 2 char charAt(int index): 返回指定位置上的字符
char c1 = s1.charAt(6);
System.out.println(c1);
System.out.println("-------------");
// 3 boolean endsWith(String suffix): 判断字符串是否以指定字符串结尾
// boolean startsWith(String prefix): 判断字符串是否以指定这个词开头
System.out.println(s1.endsWith("ef!")); // true
System.out.println(s1.endsWith("ef! ")); // false 末尾有空格
System.out.println("-------------");
// 4 boolean equalsIgnoreCase(String anotherString):不区分大小写比较
String s2 = "abc";
System.out.println(s2.equalsIgnoreCase("AbC"));
System.out.println(s2.equals("AbC")); // false
// 5 byte[] getByte(): 把字符串转换为字节数组
byte[] byteArr = s2.getBytes(StandardCharsets.UTF_8);
for (byte b : byteArr) {
System.out.println(b);
}
System.out.println("-------------");
// 6 int indexOf(String str): 返回指定字符串的起始索引值,找不到返回-1,若有多个,返回第一个结束
System.out.println(s1.indexOf("de"));
// 7 int indexOf(String str, int fromIndex): 从指定位置开始找(包含),同方法6
System.out.println("abcdefgabcd".indexOf("abc",1));
System.out.println("-------------");
// 8 int lastIndex(String str): 返回最后一次出现的位置,找不到返回-1(倒着遍历,第一次出现则为最后一次)
// int lastIndexOf(String str, int fromIndex): 从指定位置开始找(包含),从指定位置反向搜索,第一次出现位置
System.out.println("abcdefgabcd".lastIndexOf("abc"));
System.out.println("abcdefgabcd".lastIndexOf("abc",5));
System.out.println("-------------");
// 9 String replaceAll(String regex, String replacement): 把一个字符替换为指定字符串
// 类似方法 replace 功能一样,不过replace不支持正则表达式
System.out.println("123321".replaceAll("1","a")); // a2332a
// 会把 . 解析为正则表达式 而 . 代表任何字符
System.out.println("1.2.3".replaceAll(".","-")); // ------
// 正则表达式中, 可以通过 \ 把 . 转义为无意义字符,Java中 \ 是转义字符,所以要写 \\ 对 \ 进行转义
System.out.println("1.2.3".replaceAll("\\.","-")); // 1-2-3
// 不知此正则表达式,所以 . 就是 . 字符
System.out.println("1.2.3".replace(".","-")); // 1-2-3
System.out.println("-------------");
// 10 String[] split(String regex): 分割字符串,通过指定分割符来分割字符串,返回分割后的新字符串数组,支持正则表达式
String myTime = "2021-04-06";
String[] myTimes = myTime.split("-");
for (String time : myTimes) {
System.out.println(time);
}
System.out.println("-------------");
// 11 String substring(int begin): 获取该字符串从某个下标开始到结尾的字符串(包含)
System.out.println("hello,world".substring(3));
// 12 String substring(int beginIndex, int endIndex):
// 获取该字符串从某个下标开始(包含),到从某个下标结束的字符串(不包含)
System.out.println("hello,world".substring(3,6));
// 13 char[] toCharArray(): 转换为字符数组
char[] c2 = "hello,world".toCharArray();
for (char c : c2) {
System.out.print(c + " ");
}
System.out.println();
System.out.println("-------------");
// 14 String toUpperCase(): 转为大写
System.out.println("dkjafbVDAb,x,xbAHDnn".toUpperCase(Locale.ROOT));
// 15 String toLowerCase(): 转为小写
System.out.println("dkjafbVDAb,x,xbAHDnn".toLowerCase(Locale.ROOT));
// 16 String trim(): 删除字符串两边空格
System.out.println(" ab cd ");
System.out.println(" ab cd ".trim());
// 17 static String valueOf(Object obj): 调用指定对象的toString方法,并且避免空指针异常
// return (obj == null) ? "null" : obj.toString();
_04_StringM s17 = null;
System.out.println(s17);
String s8 = String.valueOf(123);
System.out.println(s8);
}
}
StringBuffer和StringBuilder
/*
* java.lang.StringBuffer
* java.lang.StringBuilder
*
* 1 StringBuffer和StringBuilder是什么?
* 是一个可变长字符串缓冲区
* 2 原理
* 预先在内存中申请一块空间,可以容纳字符序列(字符数组)
* 如果预留空间不足,进行自动扩容
* 底层都是char[],默认初始化容量为16个字符
*
* 3 String、StringBuffer、StringBuilder区别
* 1 String是不可变字符序列 ,StringBuffer和StringBuilder是可变长字符序列
* 2 StringBuffer是线程安全,多线程环境下不会出现问题,因此效率低,常用于类中
* 3 StringBuilder是非线程安全,在多线程环境下可能出现问题,效率高,常用于方法中
* 4 如何选择StringBuffer和StringBuilder
* 多线程环境下,是否可能出现多个线程同时操作一个数据 的情况(增、删、改)
* */
public class _05_StringBu {
public static void main(String[] args) {
StringBuffer sbf = new StringBuffer();
String[] str = {"a","b","c","d","e"};
/*
StringBuffer append源码
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this; // 返回this(即返回当前对象)可链式调用
}
* */
for (String s : str) {
sbf.append(s).append(".");
}
// 引用类型转String,调用toString方法,执行后变为非引用类型字符串
String sb1 = sbf.toString();
System.out.println(sb1);
// StringBuffer类型也可直接在输出语句中打印,说明输出语句自动调用指定对象的toString方法
/*
println 输出 StringBuffer 引用类型 源码:
public void println(Object x) {
String s = String.valueOf(x); // 调用指定对象的toString方法
synchronized (this) {
print(s);
newLine();
}
}
* */
System.out.println(sbf);
StringBuilder sbd = new StringBuilder();
String[] strs = {"a","b","c","d","e"};
/*
StringBuilder append源码
public StringBuilder append(String str) {
super.append(str);
return this;
}
* */
for (String s : strs) {
sbd.append(s);
}
// 引用类型转String,调用toString方法
System.out.println(sbd.reverse()); //反转
String sb2 = sbd.toString();
System.out.println(sb2);
System.out.println(sbd);
}
}
字符串比较易错点
/*
* String 不可任意拼接字符串
* String s2 = "a" + "b";
* 这里,在编译阶段,会把 + 去掉
* "a"+"b" "a"和"b"都是字面量,需要在编译阶段说明临时空间,需要通过值确定类型
* 编译时看到两个字符串相加,直接省略 + 拼接符,保存"ab"
* 故 s1 == s2 为true
* String s3 = a+b;
* 这里 a和b都是变量,编译阶段不确定变量值
* 在运行阶段,两个字符串相加
* 会自动创建一个StringBuffer对象,然后把两个字符串拼接(StringBuffer的append方法)在一起
* 最终转换为String类型如下:(new)
* public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
* 所以s3是指向堆内存。故 s1 == s3 为false
* */
public class _06_StringBu {
public static void main(String[] args) {
String s1 = "ab";
String a = "a";
String b = "b";
String s2 = "a" + "b"; // 在常量池中有三个对象“a”,"b","ab"
String s3 = a+b; // 变量相加
String s4 = new String("ab");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true,值相等,在string类中 已经重写了equals方法,重写后比较的是 值 不是地址
System.out.println(s1 == s4); // false
}
}