目录
(二)String、StringBuffer和StringBuilder比较
一、包装类
(一)八大包装类
针对八种基本数据类型相应的引用类型—包装类,有了类的特点,就可以调用类中的方法。
byte | short | int | long | float | double | char | boolean | void |
---|---|---|---|---|---|---|---|---|
Byte | Short | Integer | Long | Float | Double | Character | Boolean | Void |
包装类结构图:
(二)装箱与拆箱
装箱:jdk5之前就是基本数据类型手动转为包装类。
拆箱:jdk5之前就是包装类手动转为基本数据类型。
自动装箱:jdk5之后就是基本数据类型自动转为包装类,底层调用的是valueOf方法,比如Integer.valueOf()。
自动拆箱:jdk5之后就是包装类自动转为基本数据类型,底层调用xxxValue()方法。
以int <—> Integer为例:
jdk5之前:手动装箱和拆箱
// 手动装箱 int->Integer
int n1 = 10;
Integer integer = new Integer(n1);
// 或者
Integer integer1 = Integer.valueOf(n1);
// 手动拆箱 Integer->int
int i = integer.intValue();
jdk5之后:自动装箱和拆箱
int n2 = 100;
// 自动装箱
Integer integer2 = n2;
// 自动装箱底层源码
public static Integer valueOf(int i) {
if (i >= Integer.IntegerCache.low && i <= Integer.IntegerCache.high)
return Integer.IntegerCache.cache[i + (-Integer.IntegerCache.low)];
return new Integer(i);
}
// 自动拆箱
int n3 = integer2;
// 自动拆箱底层源码
public int intValue() {
return value;
}
自动装箱与自动拆箱练习:
Object obj1 = true ? new Integer(1) : new Double(2.0);
System.out.println(obj1); // 1.0
// 三元运算符是一个整体,这里精度最高的是Double,会提升优先级
Object obj2;
if (true) {
obj2 = new Integer(1);
} else {
obj2 = new Double(2.0);
}
System.out.println(obj2);
(三)包装类型和String类型的相互转换
// 包装类->String
Integer i = 100;
// 方式一:这种没有改变i的类型
String str1 = i + "";
// 方式二
String str2 = i.toString();
// 方式三
String str3 = String.valueOf(i);
// String->包装类
String str4 = "12345";
// 使用自动装箱
Integer i1 = Integer.parseInt(str4);
// 构造器
Integer integer = new Integer(str4);
(四)Integer和Character类常用方法
System.out.println(Integer.MIN_VALUE);//返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写
(五)Integer类面试题
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //False
//所以,这里主要是看范围 -128 ~ 127 就是直接返回
/*
//1. 如果i在 IntegerCache.low(-128)~IntegerCache.high(127),就直接从数组返回
//2. 如果不在 -128~127,就直接 new Integer(i)
底层源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
*/
Integer m = 1; //底层 Integer.valueOf(1); -> 阅读源码
Integer n = 1;//底层 Integer.valueOf(1);
System.out.println(m == n); //T
//所以,这里主要是看范围 -128 ~ 127 就是直接返回,否则,就new Integer(xx);
Integer x = 128;//底层Integer.valueOf(1);
Integer y = 128;//底层Integer.valueOf(1);
System.out.println(x == y);//False
二、String类
(一)String类对象的理解
1.String对象用于保存字符串,也就是一组字符序列
2."jack"字符串常量, 双引号括起的字符序列
3.字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节
4.String类有很多构造器,构造器可以进行重载常用的有:
String s1 = new String(); String s2 = new String(String original); String s3 = new String(char[] a); String s4 = new String(char[] a,int startIndex,int count); String s5 = new String(byte[] b);
5.String类实现了接口Serializable【String可以串行化:在网络间传输】
接口Comparable[String对象可以比较大小]
6.String是final修饰的类,不能被其他的类继承
7.String有属性private final char value[];用于存放字符串内容
8.一定要注意:value 是一个final类型,不可以修改:即value不能指向新的地址,但是单个字符内容是可以变化
public static void main(String[] args) {
String name = "jack";
name = "tom";
final char[] value = {'a','b','c'};
char[] v2 = {'t','o','m'};
value[0] = 'H';
//value = v2; value是final修饰的,不可以修改value地址
}
(二)String类对象的创建
两种创建String对象的区别
方式一:String s1 = "hello";
方式二:String s2 = new String("hello");
方式一:先在字符串常量池查看是否有"hello"数据空间,如果有,直接指向;
如果没有则重新创建,然后指向。s1最终指向常量池的空间地址
方式二:先在堆中创建空间,里面有value属性,指向字符串常量池的"hello"空间。如果常量池没有"hello",则重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
String a = "abc";
String b = "abc";
System.out.println(a.equals(b)); // true 重写了equals方法,比较内容
System.out.println(a == b); // true 两个变量指向常量池同一个地址
(三)String测试题
1.题目1
当调用intern方法时,如果字符串常量池中,已经包含一个等于此String对象的字符串(用equals(Object)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
b.intern()方法最终返回的是常量池的地址(对象)。
public static void main(String[] args) {
String a = "hello";
String b = new String("hello");
System.out.println(a.equals(b)); // equals是被重写过的,判断内容 true
System.out.println(a == b); // 比较地址 false
System.out.println(a == b.intern()); // 比较地址,都指向常量池中同一个地址 true
System.out.println(b == b.intern()); // b指向堆的地址 b.intern()指向常量池中的地址 false
}
2.题目2
public static void main(String[] args) {
String s1 = "zhangsan";
String s2 = "java";
String s4 = "java";
String s3 = new String("java"); // 堆
System.out.println(s2 == s3); // 比较地址 false
System.out.println(s2 == s4); // 都指向常量池中的同一个地址 true
System.out.println(s2.equals(s3)); // 比较内容 true
System.out.println(s1 == s2); // 比较内容 内容不一样 false
}
3.题目3
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "zhangsan";
Person p2 = new Person();
p2.name = "zhangsan";
System.out.println(p1.name.equals(p2.name)); // 比较内容 true
System.out.println(p1.name == p2.name); // 都指向常量池同一个地址 true
System.out.println(p1.name == "zhangsan"); // 常量zhangsan在常量池中 true
String s1 = new String("lisi");
String s2 = new String("lisi");
System.out.println(s1 == s2); // 对象地址不同 false
}
内存图:
(四)String字符串特性——常量相加看池,变量相加看堆
String是一个final类,代表不可变的字符序列。
字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的。
1.题目1
String s1 = "zhangsan";
s1 = "hello";
上述代码的内存图:
首先创建"zhangsan"对象,s1指向"zhangsan",然后又来了一个值,创建新的"hello"对象,s1重新指向"hello",所以一共创建了2个对象。
2.题目2
String a = "hello" + "abc";创建了几个对象?
编译器可以对上述代码做一个优化,判断创建的常量池对象,是否有引用指向。
String a = "hello" + "abc"; => String a = "helloabc";
如果单独创建"hello"和"abc",没有任何指向,那么就浪费空间,没有意义。因此,上述代码只创建了一个对象。
3.题目3(重要)
public static void main(String[] args) {
String a = "hello"; //创建 a对象
String b = "abc";//创建 b对象
//1. 先创建一个 StringBuilder sb = new StringBuilder();
//2. 执行 sb.append("hello");
//3. sb.append("abc");
//4. String c = sb.toString();
// 最后其实是 c 指向堆中的对象(String) value[] -> 池中 "helloabc"
String c = a + b;
String d = "helloabc";
System.out.println(c == d);//真还是假? 是false
String e = "hello" + "abc";//直接看池, e指向常量池
System.out.println(d == e);//真还是假? 是true
}
上述代码debug step into源码步骤:
第一步:
第二步:
第三步:
第四步:
注意:这里要用JDK8,如果JDK8仍然无法实现上述debug效果,在JDK8的基础上进行下图操作即可。
4.题目4
String s1 = "hspedu"; //s1 指向池中的 “hspedu”
String s2 = "java"; // s2 指向池中的 “java”
String s5 = "hspedujava"; //s5 指向池中的 “hspedujava”
String s6 = (s1 + s2).intern();//s6 指向池中的 “hspedujava”
System.out.println(s5 == s6); // true
System.out.println(s5.equals(s6));// true
(五)String常用方法
// 1. equals 比较内容是否相同,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));
// 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
String username = "johN";
if ("john".equalsIgnoreCase(username)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
// 3.length 获取字符的个数,字符串的长度
System.out.println("天安门广场".length()); // 5
// 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
String s1 = "wer@terwe@g";
int index = s1.indexOf('@');
System.out.println(index);// 3
System.out.println("weIndex=" + s1.indexOf("we"));// 0
// 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
s1 = "wer@terwe@g@";
index = s1.lastIndexOf('@');
System.out.println(index);// 11
System.out.println("ter的位置=" + s1.lastIndexOf("ter"));// 4
// 6.substring 截取指定范围的子串
String name = "hello,张三";
//name.substring(6) 从索引6开始截取后面所有的内容
System.out.println(name.substring(6));// 截取后面的字符
//name.substring(0,5)表示从索引0开始截取,截取到索引 5-1=4位置 [包头不包尾]
System.out.println(name.substring(2, 5));// llo
// 7.toUpperCase转换成大写
String s = "heLLo";
System.out.println(s.toUpperCase());//HELLO
// 8.toLowerCase
System.out.println(s.toLowerCase());//hello
// 9.concat拼接字符串
String s1 = "张三";
s1 = s1.concat("李四").concat("王五").concat("赵六");
System.out.println(s1);// 张三李四王五赵六
// 10.replace 替换字符串中的字符
s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
// 在s1中,将 所有的 林黛玉 替换成薛宝钗
// s1.replace()方法执行后,返回的结果才是替换过的
// 对s1没有任何影响
String s11 = s1.replace("宝玉", "jack");
System.out.println(s1);// 宝玉 and 林黛玉 林黛玉 林黛玉
System.out.println(s11);// jack and 林黛玉 林黛玉 林黛玉
// 11.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
String[] split = poem.split(",");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
poem = "E:\\aaa\\bbb";
split = poem.split("\\\\");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
// 12.toCharArray 转换成字符数组
s = "happy";
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}
// 13.compareTo 比较两个字符串的大小
// 如果前者大,则返回正数;后者大,则返回负数;如果相等,返回0
// (1) 如果长度相同,并且每个字符也相同,就返回 0
// (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
// 就返回 if (c1 != c2) {
// return c1 - c2;
// }
// (3) 如果前面的部分都相同,就返回 str1.len - str2.len
String a = "jcc";// len = 3
String b = "jack";// len = 4
System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2的值
// 14.format 格式字符串
// 占位符有:%s 字符串 %c 字符型 %d 整型 %.2f 浮点型(保留2位小数)
String name = "john";
int age = 10;
double score = 98.5 / 3;
char gender = '男';
// 将所有的信息都拼接在一个字符串.
String info = "姓名:" + name + ",年龄:" + age + ",成绩:" + score + ",性别:" + gender;
System.out.println("info=" + info);
String info2 = String.format("姓名:%s,年龄:%d,成绩:%.2f,性别:%c", name, age, score, gender);
System.out.println("info2=" + info2);
三、StringBuffer类——多线程
(一)StringBuffer基本介绍
StringBuffer属于java.lang包,它代表可变的字符序列,可以对字符串内容进行增删。很多方法与String相同,但StringBuffer是可变长度的。StringBuffer是一个容器。
- StringBuffer 的直接父类是 AbstractStringBuilder
- StringBuffer 实现了 Serializable,即StringBuffer的对象可以串行化
- 在父类中AbstractStringBuilder 有属性 byte[] value,不是final
- 该 value 数组存放字符串内容,真正存放在堆中,而不是字符串常量池
- StringBuffer 是一个 final类,不能被继承
- 因为StringBuffer 字符内容是存在 byte[] value,所以在变化(增加/删除)
- 不用每次都更换地址(只有放不下了才会创建新对象), 所以效率高于String
- 初始化大小是16
StringBuffer的类继承关系图:
(二)StringBuffer构造器的使用
注意:StringBuffer在JDK8是char[] value;在JDK11是byte[] value
//1. 创建一个 大小为16的 byte[] ,用于存放字符内容
StringBuffer stringBuffer = new StringBuffer();
//2 通过构造器指定 byte[] 大小
StringBuffer stringBuffer1 = new StringBuffer(100);
//3. 通过 给一个String 创建 StringBuffer, byte[] 大小就是 str.length() + 16
StringBuffer hello = new StringBuffer("hello");
(三)String和StringBuffer相互转换
1.String转换为StringBuffer
// 方式一:使用StringBuffer构造器
// 注意:返回的才是StringBuffer对象,对str本身没有影响
String str = "hello";
StringBuffer stringBuffer = new StringBuffer(str);
// 方式二:使用append方法
StringBuffer stringBuffer1 = new StringBuffer();
StringBuffer stringBuffer2 = stringBuffer1.append(str);
2.StringBuffer转为String
// 方式一:使用StringBuffer的toString()方法
StringBuffer stringBuffer3 = new StringBuffer("world");
String str1 = stringBuffer3.toString();
// 方式二:使用String构造器
StringBuffer stringBuffer4 = new StringBuffer("world");
String str2 = new String(stringBuffer4);
(四)StringBuffer常用方法
StringBuffer s = new StringBuffer("hello");
//增
s.append(','); // "hello,"
s.append("张三丰"); // "hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);
System.out.println(s); // "hello,张三丰赵敏100true10.5"
//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除11~14的字符 [11, 14) 包头不包尾
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏true10.5"
// 改
// 使用 周芷若 替换 索引9-11的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰1");
System.out.println(indexOf);// -1
//插
//老韩解读,在索引为9的位置插入 "赵敏",原来索引为9的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
//长度
System.out.println(s.length());//22
System.out.println(s); // hello,张三丰赵敏周芷若true10.5
(五)StringBuffer实现动态分割价格
假如价格如下:99945176841654868.56,需要使用“,”号分割,每3位一个“,”号
public static void main(String[] args) {
String price = "99945176841654868.56";
StringBuffer sb = new StringBuffer(price);
int index = sb.lastIndexOf(".");
for (int i = index - 3; i > 0; i -= 3) {
sb.insert(i, ",");
}
System.out.println(sb);
}
四、StringBuilder类——单线程
(一)StringBuilder基本介绍
StringBuilder是一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但不保证同步(StringBuilder不是线程安全)。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,单线程的时候推荐使用StringBuilder,它比StringBuffer快。
在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。
证明如下:
- StringBuilder 继承 AbstractStringBuilder 类
- 实现了Serializable接口,说明StringBuilder对象是可以串行化(对象可以网络传输,可以保存到文件)
- StringBuilder 是final修饰的类,不能被继承
- StringBuilder对象字符序列仍然是存放在其父类 AbstractStringBuilder的 byte[] value(此处为JDK11,JDK8中是char[] value);因此,字符序列存放在堆中
- StringBuilder的方法,没有做互斥的处理,即没有synchronized关键字,因此在单线程的情况下推荐使用StringBuilder
(二)String、StringBuffer和StringBuilder比较
相同点:
1)都是对字符串进行操作
2)三者的类都被final修饰
3)StringBuffer和StringBuilder,均代表可变的字符串。都实现了Serializable和Comparable,可以序列化,实现在网络间的传输,并且都继承了AbstractStringBuilder类,二者调用的方法相同。JDK11中的value是byte[],JDK8中的value是char[]。
不同点:
1)String代表不可变字符序列,byte[] value 被final修饰,效率低,但是复用率高(如果内容相同,都指向常量池中同一个地址);
String效率低的原因:创建一个字符串,后面又对其进行拼接,原来的字符串被丢弃。如果多次执行这些改变字符串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大地影响程序性能。因此,如果我们对String做大量修改,不要使用String。
2)StringBuffer代表可变字符序列,byte[] value没有被final修饰,效率较高(增删)、线程安全
3)StringBuilder代表可变字符序列,byte[] value没有被final修饰,效率最高、线程不安全
使用原则总结:
1)如果字符串存在大量的修改操作,一般使用StringBuffer或StringBuilder
2)如果字符串存在大量的修改操作,并存在单线程的情况,使用StringBuilder
3)如果字符串存在大量的修改操作,并存在多线程的情况,使用StringBuffer
4)如果字符串很少修改,被多个对象引用,使用String,比如配置信息等
5)StringBuffer和StringBuilder方法的使用一样