Java自我学习路线
一、String
1. String概述
- String表示字符串类型,属于引用数据类型,在Java中随便使用双引号括起来的都是String对象
- String是不可变长字符串,因为底层是final修饰的char类型数组(private final char value[];),又因为数组一旦创建长度不可变,且被final修饰的引用一旦指向某个对象之后,不可再指向其他对象
jdk1.8及以前String使用的是char数组,jdk1.9及以后使用的是byte数组。因为开发人员发现人们使用的字符串值是拉丁字符居多而之前使用的char数组每一个char占用两个字节而拉丁字符只需要一个字节就可以存储,剩下的一个字节就浪费了,造成内存的浪费,gc的更加频繁。因此在jdk9中将String底层的实现改为了byte数组 - 在JDK当中双引号括起来的字符串,例如:“abc” "def"都是直接存储在“方法区”的“字符串常量池”中(因为字符串在实际的开发中使用频繁,为了执行效率,所以把字符串放到了方法区的字符串常量池中)
String底层以及字符串相加的原理
Java一个汉字占几个字节
2. String的内存状态
public class Test {
public static void main(String[] args) {
// 以下两行代码表示底层创建了3个字符串对象,都在字符串常量池当中
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
// 使用new的方式创建的字符串对象(堆内存当中开辟空间)
// 凡是双引号括起来的都在字符串常量池中有一份
String s3 = new String("xy");
// i变量中保存的是100这个值。
int i = 100;
// s变量中保存的是字符串对象的内存地址。
// s引用中保存的不是"abc",是"abc"字符串对象在“字符串常量池”当中的内存地址。
String s = "abc";
}
}
public class Test {
public static void main(String[] args) {
String s1 = "hello";
// "hello"存储在方法区的字符串常量池中
// "hello"不会新建,因为这个对象已经存在
String s2 = "hello";
// == 双等号比较的是变量中保存的内存地址
System.out.println(s1 == s2); // true
String x = new String("xyz");
String y = new String("xyz");
System.out.println(x == y); //false
// 所以“字符串对象”之间的比较不能使用“==”,应该调用String类的equals()方法。
// String类已经重写了equals()方法,以下的equals()方法调用的是String重写之后的equals()方法
System.out.println(x.equals(y)); // true
String k = new String("testString");
//String k = null;
// "testString"是一个String字符串对象,只要是对象都能调用方法(.的形式)
System.out.println("testString".equals(k)); // 建议使用这种方式,因为此方式可以避免空指针异常。
System.out.println(k.equals("testString")); // 存在空指针异常的风险,不建议这样写
}
}
- 分析以下程序,一共创建了几个对象
public class Test {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
}
}
一共创建了3个对象:方法区字符串常量池中有1个:“hello”,堆内存当中有两个String对象s1和s2
3. String类常用的构造方法
3.1 String str = new String("");
String str = new String("abc");
3.2 String str = “”;
String str = "cba";
3.2 String(byte[] bytes)
- String str = new String(byte数组);
- 将byte数组构造为新的String字符串
public class Test {
public static void main(String[] args) {
byte[] bytes = {97, 98, 99};
String str = new String(bytes);
System.out.println(str); // abc
}
}
3.3 String(byte[] bytes, int offset, int length)
- String str = new String(byte数组,起始下标,长度)
- 将byte数组中的部分或全部转换成字符串
public class Test {
public static void main(String[] args) {
byte[] bytes = {97, 98, 99};
String str1 = new String(bytes);
// String(字节数组,数组元素下标的起始位置,长度)
String str2 = new String(bytes, 1, 2);
System.out.println(str2); // bc
}
}
3.4 String(char[] value)
- String str = new String(char数组);
- 将char数组构造为新的String字符串
public class Test {
public static void main(String[] args) {
// 将char数组全部转换成字符串
char[] chars = { '我', '是', '中', '国', '人' };
String str1 = new String(chars);
System.out.println(str1); // 我是中国人
}
}
3.5 String(char[] value, int offset, int count)
- String str = new String(char数组,起始下标,长度);
- 将char数组部分或全部转换成字符串
public class Test {
public static void main(String[] args) {
// 将char数组全部转换成字符串
char[] chars = { '我', '是', '中', '国', '人' };
String str1 = new String(chars);
System.out.println(str1); // 我是中国人
// 将char数组的一部分转换成字符串
String str2 = new String(chars, 2, 3);
System.out.println(str2); // 中国人
}
}
4. String类常用的方法
4.1 length()
- 获取字符串长度:.length()方法
public class Test {
public static void main(String[] args) {
String str1 = "abecdef";
System.out.println(str1.length()); // 7
}
}
4.2 isEmpty()
- 判断该字符串是否为空:.isEmpty()方法
public class Test {
public static void main(String[] args) {
String str1 = "abecdef";
System.out.println(str1.isEmpty()); // false
}
}
4.3 charAt()
- 根据索引找字符:.charAt()
public class Test {
public static void main(String[] args) {
String str1 = "abecdef";
System.out.println(str1.charAt(2)); // e
/*
* System.out.println(str1.charAt(99));
* 字符串下标越界异常
* StringIndexOutOfBoundsException
*/
}
}
4.4 indexOf()与lastIndexOf()
- indexOf():根据字符找索引(相同字符出现则索引第一次出现;没有字符则输出-1)
- lastIndexOf():返回最后一次出现的字符的索引
public class Test {
public static void main(String[] args) {
String str = "abecdef";
System.out.println(str.indexOf('c')); // 3
System.out.println(str.indexOf('e')); // 2
System.out.println(str.indexOf('m')); // -1
System.out.println(str.lastIndexOf('e')); // 5
}
}
4.5 equals()
- 判断两个字符串是否相等, “字符串”比较相等可以使用双等号(不保险);“字符串对象”之间的比较不能使用双等号,应该调用String类的equals方法
- 源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
- 测试:
public class Test {
public static void main(String[] args) {
String str = "acb";
String str1 = "acb";
System.out.println(str.equals(str1)); // true
System.out.println(str == str1); // true
String str2 = new String("acb");
String str3 = new String("acb");
System.out.println(str2.equals(str3)); // true
System.out.println(str2 == str3); // false
}
}
4.6 .equalsIgnoreCase()
- 判断两个字符串是否相等,并且同时忽略大小写
public class Test {
public static void main(String[] args) {
String str = "acb";
String str1 = "AcB";
System.out.println(str.equalsIgnoreCase(str1)); // true
}
}
4.7 substring()
- 截取字符串
- 从哪开始:.substring(beginIndex)
- 从哪开始,到哪:.substring(beginIndex, endIndex)
public class Test {
public static void main(String[] args) {
String str = "String类的方法";
System.out.println(str.substring(2)); // ring类的方法
System.out.println(str.substring(2, 8)); // ring类的
}
}
4.8 .split()
- 分割字符串
指定分隔符将字符串分割
只处理后面尾部的空字符串,不处理中间的空字符串和开头的空字符串
public class Test {
public static void main(String[] args) {
String str = ",aa,bb,,cc,dd,,,";
String[] array = str.split(",");
// 只处理后面尾部的空字符串,不处理中间的空字符串和开头的空字符串
System.out.println(array.length);// 6
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
4.9 startsWith()
- 判断字符串以什么开头
public class Test {
public static void main(String[] args) {
String str = "acb";
System.out.println(str.startsWith("a")); // true
System.out.println(str.startsWith("b")); // false
System.out.println(str.startsWith("ac")); // true
System.out.println(str.startsWith("acb")); // true
}
}
4.10 endsWith()
- 判断字符串以什么结尾
public class Test {
public static void main(String[] args) {
String str = "acb";
System.out.println(str.endsWith("m")); // false
System.out.println(str.endsWith("b")); // true
System.out.println(str.endsWith("cb")); // true
System.out.println(str.endsWith("acb")); // true
}
}
4.11 .contains()
- 判断前面的字符串中是否包含后面的子字符串,返回值是boolean类型
public class Test {
public static void main(String[] args) {
String str1 = "abcdefg";
String str2 = "cd";
String str3 = "mn";
System.out.println(str1.contains(str2)); // true
System.out.println(str1.contains(str3)); // false
}
}
4.12 .replace()
- 替换
public class Test {
public static void main(String[] args) {
String str = "acb";
String str1 = "AcB";
String str2 = "123456";
System.out.println(str1.replace(str1, str2)); // 123456
System.out.println(str1.replace("A", str2)); // 123456cB
System.out.println(str1.replace("A", "a")); // acB
}
}
4.13 .toLowerCase()与.toUpperCase()
- 将字符串转换为小写/大写
public class Test {
public static void main(String[] args) {
String str = "AbcD";
System.out.println(str.toLowerCase()); // abcd
System.out.println(str.toUpperCase()); // ABCD
}
}
4.14 .compareTo()
- 比较字符串之间的大小(字符串之间比较大小不能直接使用 > < ,需要使用compareTo方法
- 源码:
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
- 测试:
- 比较的字符一样,返回长度差值
- 比较的两个字符串首字母不同,返回首字母的ASCII码的差值
- 比较的两个字符串首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的ASCII码差值
- 不同字符时,只比较第一个字符,返回首字母的ASCII码的差值
public class Test {
public static void main(String[] args) {
String str1 = "a";
String str2 = "a";
String str3 = "aaaaa";
String str4 = "abcde";
String str5 = "d";
String str6 = "b";
String str7 = "abcdef";
String str8 = "abcccc";
String str9 = "ace";
String str10 = "dbf";
System.out.println(str1.compareTo(str2)); // 0
System.out.println(str1.compareTo(str3)); // -4(比较的字符'a'一样,返回长度差值)
System.out.println(str1.compareTo(str4)); // -4(比较的字符'a'一样,返回长度差值)
System.out.println(str5.compareTo(str6)); // 2(比较的两个字符串首字母不同,返回首字母的ASCII码的差值)
System.out.println(str7.compareTo(str8)); // 1(比较的两个字符串首字符相同,则比较下一个字符,直到有不同的为止('d'与'c'),返回该不同的字符的ASCII码差值)
System.out.println(str9.compareTo(str10)); // -3(不同字符时,只比较第一个字符'a'与'd',返回首字母的ASCII码的差值)
}
}
4.15 .valueOf()
- 此方法是静态的,不需要new对象,可以将“非字符串”转换成“字符串”
- 源码:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
- 测试:
public class Test {
public static void main(String[] args) {
/**
* 将“非字符串”转换成“字符串”
*/
boolean b = true;
char c = '吕';
char[] ch = { '创', '建', '日', '期' };
double d = 1.0;
float f = 2f;
int i = 3;
long l = 5;
String str1 = String.valueOf(new Foo());
String str2 = String.valueOf(b);
String str3 = String.valueOf(c);
String str4 = String.valueOf(ch);
String str5 = String.valueOf(d);
String str6 = String.valueOf(f);
String str7 = String.valueOf(i);
String str8 = String.valueOf(l);
System.out.println(str2); // true
System.out.println(str3); // 吕
System.out.println(str4); // 创建日期
System.out.println(str5); // 1.0
System.out.println(str6); // 2.0
System.out.println(str7); // 3
System.out.println(str8); // 5
System.out.println(str1); // 。。。。。。(不重写toString()方法则返回对象内存地址)
}
}
class Foo {
@Override
public String toString() {
return "。。。。。。";
}
}
4.16 .trim()
- 去除字符串前后空白
public class Test {
public static void main(String[] args) {
String str = " A bc D ";
System.out.println(str); // A bc D
System.out.println(str.trim()); // A bc D
}
}
二、StringBuffer
- 工作原理:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列,即StringBuffer 可变长,因为继承自 AbstractStringBuilder 类,而在 AbstractStringBuilder 中使用字符数组保存字符串char[]value ,但是没有用 final 关键字修饰
- 因为Java中的String字符串是不可变长的(private final char value[];),每一次拼接都会产生新字符串,所以在实际的开发中,会占用大量的方法区内存,造成内存空间的浪费
- 所以进行大量字符串的拼接操作时,建议使用JDK自带的java.lang.StringBuffer与java.lang.StringBuilder
- 拼接字符串统一调用.append()方法,.append()方法底层在进行追加的时候,如果char数组满了,会自动扩容
1. 底层源码
- StringBuffer():
public StringBuffer() {
super(16); //创建一个初始化容量为16个char[] 数组(字符串缓冲区对象)
}
StringBuffer()继承自 AbstractStringBuilder:
AbstractStringBuilder(int capacity) {
// Creates an AbstractStringBuilder of the specified capacity.
value = new char[capacity];
// JDK8中 value为char[] value; 且不同于String,没有final修饰
}
- .append():
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); // 确保容量
str.getChars(0, len, value, count);
count += len;
return this;
}
append()继承自 AbstractStringBuilder
- 确保容量:ensureCapacityInternal
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value, // 数组扩容
newCapacity(minimumCapacity));
}
}
- Arrays.copyOf调用System.arraycopy():
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength)); // 数组复制
return copy;
}
解释了.append()方法底层在进行追加的时候,如果char数组满了,会自动扩容
2. 优化StringBuffer的性能
- 在创建StringBuffer的时候尽可能给定一个初始化容量
- 预估初始化容量,减少底层数组的扩容次数,提高程序的执行效率
public class Test {
public static void main(String[] args) {
// 创建一个初始化容量为16个char[] 数组(字符串缓冲区对象)
StringBuffer stringBuffer = new StringBuffer();
// 拼接字符串,以后拼接字符串统一调用 append()方法,追加的意思
stringBuffer.append("a");
stringBuffer.append(true);
stringBuffer.append(100L);
// append方法底层在进行追加的时候,如果char数组满了,会自动扩容
System.out.println(stringBuffer); // 输出atrue100(toString可加可不加,会自动调用,转换为字符串)
// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
StringBuffer sb = new StringBuffer(100);
sb.append("Hello");
sb.append("World");
System.out.println(sb.toString()); // 输出HelloWorld(toString可加可不加,会自动调用,转换为字符串)
}
}
三、StringBuilder
- 用法同StringBuffer
- StringBuilder中的方法都没有 synchronized 关键字修饰,表示StringBuilder在多线程环境下运行是不安全的
- StringBuffer中的方法都有 synchronized 关键字修饰,表示StringBuffer在多线程环境下运行是安全的
四、String、StringBuffer、StringBuilder的区别
- 效率上:String<StringBuffer<StringBuilder
- String是不可变长字符串,因为底层是final修饰的char类型数组(private final char value[];),又因为数组一旦创建长度不可变,且被final修饰的引用一旦指向某个对象之后,不可再指向其他对象
- StringBuilder和StringBuffer底层是可变长数组,因为继承自 AbstractStringBuilder 类,而在 AbstractStringBuilder 中使用字符数组保存字符串char[]value ,但是没有用 final 关键字修饰
- StringBuilder线程非安全,效率高
- StringBuffer线程安全,加了线程锁,效率不如StringBuilder