文章目录
1. 创建字符串
1.1 常见的构造String的方法
方法一:
String str = "hello";
创建 str 这个引用,使之指向"hello"这个字符串常量的地址
方法二:
String str = new String("hello");
new String() 出一个新的对象,让"hello"这个字符串常量值赋进这个新对象,然后让 str 这个引用指向新对象
方法三:
char[] array = {a,b,c,d};
String str = new String(array);
Java中的字符串(String)和字符数组(char[ ])之间没有关联关系
1.2 其他构造String方法
官方文档Java Platform SE 8中还支持很多构造方法
1.3 了解String与栈、堆的关系
代码如下
String str1 = "Hello";
String str2 = str1;
内存布局如下
仅仅是两个引用指向了同一个对象
当修改代码为:
String str1 = "World";
String str2 = "Hello";
即修改了 str1 这个引用指向的地址为 0x200 ,不指向"Hello",而指向"World",并不是修改了之前的字符串
2. 字符串比较相等
2.1 “==” 比较
String 通过“ == ”来比较的只是两个引用是否指向同一个对象,即地址是否相同
//局部变量---栈上
String a = "hello";
String b = "hello";
//此时的比较是直接比较a和b引用里面存的地址是否相等
System.out.println(a == b);
输出:true
//成员变量---堆上
String c = new String("hello");
String d = new String("hello");
//手动创建出来的两个不同的对象,地址不同,所存内容相同
//故通过“==”来比较是不相等的
System.out.println( c == d);
输出:false
局部变量在栈上,成员变量在堆上
2.2 .equals 比较
String 通过 a.equals(b) 来比较 a,b 内容是否相同
String a = "hello";
//方法一
System.out.println(a.equals("hello"));
//方法二
System.out.println("hello".equals(a));
推荐使用方法二,因为方法一当 a 为 null 时(即为空引用)会抛出异常,而方法二不会
3. 字符串常量池
3.1 常量池
当我们需要大量使用相同的常量时,就只需要将其一次放入常量池中,以后每次使用就在其中调用即可,不必每次使用都去创建新的对象,常量池中的内容主要来自编译器的自动识别和intern方法的手动加入。
池的目的就是为了降低开销,提高效率,本质是把频繁使用的东西提前保存好,以备用到的时候可以随时使用
3.2 String 的两种实例化操作
3.2.1 直接赋值
String str1 = "hello";
String str2 = "hello";
String str3 = "hello";
System.out.println( str1 == str2 );//true
System.out.println( str2 == str3 );//true
采用直接赋值来进行String类的对象实例化操作,该对象将直接保存在对象池中,如果下次再用直接赋值的方式声明String类对象,对象池中存在则直接引用,不存在则这次存入,下次直接引用,通过“==”比较的是两对象的地址是否相同
3.2.2 采用构造方法
String str = new String("hello");
采用 String 的构造方法会开辟出两块内存,原先的"hello"已有内存,当 new String 出来新的对象,存入"hello"而原先的"hello"内存会被JVM垃圾回收器自动回收,所以这样的构造方法较浪费空间,解决方法如下:
用 String 的 intern 方法手动将 String 对象加入到字符串常量池中 :
String a = new String("hello");
System.out.println(a == "hello");//false
a 中存的是 new String() 出来的对象的地址
String a = new String("hello").intern();
System.out.println(a == "hello");//true
new String 出一个新的对象,根据这个对象的内容调用 intern()方法在常量池中找出该内容的地址再返回给 a,语句执行完毕后,并没有引用指向new出的新对象,其将会被JVM垃圾回收机制回收。
*intern方法
会拿着new String() 出的这个字符串在字符串常量池中去查找,看是否存在,若存在,则直接返回其地址,若不存在,则将该字符串存入常量池中并返回该地址
*注意事项
String 类中两种对象实例化的区别?
直接赋值:只会开辟一块空间,将该字符串对象自动保存在字符串常量池中,以供后续使用。
构造方法:会开辟出两块空间,造成空间浪费,并且不会自动保存字符串常量,可以使用intern()这个方法来将对象手动加入到字符串常量池中。
4. 理解字符串不可变
4.1 区别常量(final修饰)和不可变对象
final 修饰的是常量
若 final 修饰的是一个引用类型,则表示的是该引用的指向(引用中存的地址)不可变
若 final 修饰的是一个类,则表示该类不能被其他类继承
//final修饰的是一个常量
final A a = new A();
//a 是一个常量,其引用指向无法改变
//此操作是不合法的
a = new A();
//此时的 a 是一个"可变对象"
a.num = 10;
不可变对象是对象本身的内容不能改变
特点:
- 无法在类的外部访问
- 没有 public 的方法可以修改内容
4.2 Java为什么要把 String 设计成不可变对象
- 方便放到池中,如果是可变的对象,当池中内容改变,就会影响到所有引用这个池的对象的结果
- 对象内容不可变,则对象的hashCode也不可变,方便和hash表这样的结构配合使用
- 对象不可变,线程安全更有保障(String s =“hello”=>“hella”,在java中只能通过构造一个新的对象来完成)
4.3 特殊方法修改字符串内容
“反射”/自省的方法 :
//通过非常规的手法来修改String里面的内容(反射)
String str = "hello";
// 1.获取String 的类对象
// 2.根据"value"这个字段名字,在类对象中拿到对应的字段(仍然是一个图纸的一部分)
Field valueField = String.class.getDeclaredField("value");//须处理抛出异常
//让value 这个 private 的成员也能被访问到
valueField.setAccessible(true);
// 3.根据图纸,把 str 这个对象给拆开,取出里面的零件
char[] value = (char[])valueField.get(str);
// 4.修改零件的内容
value[4] = 'a';
System.out.println(str);
输出:hella
反射和封装是背道而驰的
- 使用反射往往可能打破封装
- 反射的代码比较复杂,容易出错
- 反射牺牲了编译器自身的一些检查校验机制,更需要程序员人工保证代码的正确性
5. 字符、字节、与字符串
5.1 字符与字符串
5.1.1字符数组转字符串
char[] value= {'a','b','c','d'};
String str = new String("value");
5.1.2 字符串转字符数组
运用charAt()方法输出字符串内容
String s = "abcd";
System.out.println(s.charAt(0));//a
System.out.println(s.charAt(1));//b
System.out.println(s.charAt(2));//c
System.out.println(s.charAt(3));//d
//输出字符串长度,用.length()这个方法
//获取字符数组长度,用.length 这个属性
System.out.println(s.length());//4
运用toCharArray()方法得到新的字符数组
toCharArray 方法相当于在内部创建了一个新的字符数组并返回
//修改这个返回值不会影响到 s 本身的内容
char[] value = s.toCharArray();
for(char v : value){
System.out.println(v);
}//输出a b c d
value[0] = 'x';
System.out.println(value);//xbcd 修改的是新字符数组
System.out.println(s);//abcd 不修改 s 本身
5.2 字节与字符串
运用getBytes()方法
String str = "HelloWorld";
//String 转 byte[]
byte[] data = str.getBytes();
for(int i = 0; i < data.length;i++){
System.out.println(data[i]+" ");
}
//byte[] 转 String
System.out.println(new String(data));
5.3 区别字节、字符
字节=>8 bit位
字符=>2 字节
6. 字符串常见操作
6.1 字符串比较
使用compareTo()方法比较两字符串大小
String a = "hello";
String b = "Hello";
//1. compareTo()会返回一个整数
//2. a 比 b 小,返回 <0 的值
//3. a 比 b 大,返回 >0 的值
//4. a 与 b 相等,返回 0
int result = a.compareTo(b);
System.out.println(result);//32
//忽略大小写比较
int result1 = a.compareToIgnoreCase(b);
System.out.println(result1);//0
比较规则:根据unicode的值来比较大小,若第一个字符区分不开再向后挪一位比较,以此类推,即可比较出大小。
6.2字符串查找
查找对应的字符串下标起始位置
//查找字符串
String a = "hello world world";
String b = "world";
//从左至右第一次出现的下标
int result = a.indexOf(b);
System.out.println(result);//6
//指定下标开始向后查找
result = a.indexOf( b , result + 1);
//result+1是从第一个world的下个位置开始,以便查找第二个world
System.out.println(result);//12
//从右向前向后查找
System.out.println(a.lastIndexOf(b));//12
查找开头/结尾字符串
String str = "@@**hello world^^";
//判断开头字符
System.out.println(str.startsWith("@@"));//true
//判断第 2 个下标的字符是否与要求相同
System.out.println(str.startsWith("*",2));//true
System.out.println(str.startsWith("h",2));//false
//判断结尾字符
System.out.println(str.endsWith("^^"));//true
方法 startsWith()、endsWith() 的返回值是布尔类型,只有( true / false )
6.3 字符串替换
//字符串替换
String a = "hello world world";
String b = "world";
//替换所有
System.out.println(a.replaceAll( b , "java"));//hello java java
//替换第一次出现的内容
System.out.println(a.replaceFirst( b ,"java"));//hello java world
6.4 字符串拆分
普通拆分
String str = "hello java java ";
//将字符串以" "为分隔符划分开来
String[] result = str.split(" ");
System.out.println(Arrays.toString(result));//[hello, java, java]
//将字符串以" "为分隔符,划分成2部分
//limit是从头开始每一段划分一块,后面为一大部分
//limit = 2 [hello, java java ]
//limit = 3 [hello, java, java ]
String[] result1 = str.split(" ",2);
System.out.println(Arrays.toString(result1));
特殊拆分
//正则表达式里的特殊字符无法直接使用来拆分,需要转义
String str = "192.168.1.1";
String[] result = str.split(".");
System.out.println(Arrays.toString(result));//[]
//正则表达式中将 . 当做特殊字符来看待,将 \. 才能当做 . 来对待
//java又将 \ 当做转义字符,故为了表达原始的 \ 需再次转义
String[] result1 = str.split("\\.") ;
System.out.println(Arrays.toString(result1));//[192, 168, 1, 1]
6.5 字符串截取
//字符串的截取
String str = "hello beautiful world";
//[begin,end)
System.out.println(str.substring(6,15));//beautiful
//从6开始一直输出到结束
System.out.println(str.substring(6));//beautiful world
6.6其他操作方法
6.6.1 去掉字符串左右空白符,不去中间的
String str = " hello world ";
System.out.println(str);// hello world
System.out.println(str.trim());//hello world
空白符:空格、换行、回车、制表符、翻页符、垂直制表符…
6.1.2 转换大小写
String str = "HeLLo";
//全部转换为大写
System.out.println(str.toUpperCase());
//全部转换为小写
System.out.println(str.toLowerCase());
6.1.3 判断字符串是否为空字符串
String str = "";
//返回 boolean 类型
System.out.println(str.isEmpty());//true
*区别空字符串和空引用
//把字符串想象成是一个装字符的盒子
//空字符串就是空盒子
//空引用连空盒子都没有
String a = "";//空字符串
String b = null;//空引用
以上方法中能改变字符串内容的都是创建了一个新的字符串,改变的是新字符串的内容,原字符串没有改变
7. StringBuilder和StringBuffer
- String 的常量一旦声明就无法更改,如果需要更改对象内容,改变的的只是其引用的指向而已,并未改变原字符串内容
- 若需要改变字符串内容,则借用StringBuilder和StringBuffer类可方便字符串的修改
- StringBuilder和StringBuffer类主要区别:
StringBulider 线程不安全
StringBuffer 线程安全
7.1 StringBuilder使用范例
7.1.1 append对字符串进行追加
StringBuilder str = new StringBuilder("hello");
for (int i = 0; i < 10; i++) {
str.append(i);
}
System.out.println(str);//hello0123456789
区别:
- 使用 append 能够把字符串进行追加,相当于 String 里面的 +=
- String 的 += 会产生新的 String 对象,如果在循环中使用,是比较低效的
7.1.2 逆置
StringBuilder str = new StringBuilder("hello");
str.reverse();//修改字符串本身
System.out.println(str.toString());//olleh
7.1.3 删除
StringBuilder str = new StringBuilder("hello");
//[begin,end)
System.out.println(str.delete( 2 , 4 ));//heo
7.1.4 增加
str.insert(int offset,各种数据类型)
StringBuilder str = new StringBuilder("hello");
System.out.println(str.insert(5," world"));//hello world