目录
- 一、创建字符串
- 二、字符串常量池
- 三、字符串比较相等
- 四、字符串不可变
- 五、字符、字节与字符串
- 5.1 字符与字符串
- 5.1.1将字符数组变为字符串
- 5.1.2取得指定索引位置的字符charAt()
- 5.1.3将字符串对象变成字符数组toCharArray()
- 5.2 字节与字符串
- 5.2.1将字节数组变为字符串
- 5.2.2字符串转字节getBytes()
- 5.2.3判断字符串中是否都是数字isDigit()
- 六、字符串常见操作
- 6.1 字符串比较
- 6.1.1比较两个字符串大小关系compareTo()
- 6.1.2判断两个字符串内容是否相等equals()
- 6.1.3忽视字符串大小写,不区分大小写比较equalsIgnoreCase()
- 6.2 字符串查找
- 6.2.1判断一个子字符串是否存在contains()
- 6.2.2 查找指定字符串的位置,查到了返回位置的开始索引,如果查不到返回-1. indexOf()
- 6.2.3由后向前查找子字符串位置lastIndexOf()
- 6.2.4判断是否以指定字符串开头startsWith()
- 6.2.5判断是否以指定字符串结尾endsWith()
- 6.3 字符串替换replace()
- 6.3.1替换所有的指定内容replaceAll ()
- 6.3.2替换首个内容replaceFirst()
- 6.4 字符串拆分split()
- 6.5 字符串截取substring()
- 七、其他操作方法
- 7.1去除字符串左右两边的空格,但不不能去除中间的空格trim()
- 7.2字符串小写转化成大写toUpperCase()
- 7.3字符串大写转小写toLowerCase()
- 7.4字符串拼接concat,拼接好的对象不会入到常量池里面
- 7.5获取字符串长度length()
- 7.6判断是否为空字符串isEmpty()
- 八、 StringBuffer 和 StringBuilder
- 8.1字符串反转reverse()
- 8.2删除指定范围的数据delete()
- 8.3插入数据insert()
- String、StringBuffer、StringBuilder的区别
一、创建字符串
常见的创建字符串的三种方式:
public class test01 {
public static void main(String[] args) {
//方式一
String s = "zbd";
System.out.println(s);
//方式二
String str = new String("hello");
System.out.println(str);
//方式三
char[] chars = {'z','b','c'};
System.out.println(chars);
}
}
在Java
中数组, String, 以及自定义的类都是引用类型。
如下面的例子:s
于s1
指向同一块对象,修改s
的指向,不会改变s1
的指向,它只是将 s 这个引用指向了一个新的 String
对象。
用双引号引起来的是字面值常量,它里面的内容是不能被修改的,我们只能修改其指向。
例:
public static void main(String[] args) {
String s = "zbd";
String s1 = s;
System.out.println(s);
System.out.println(s1);
System.out.println("===========");
s = "q";
System.out.println(s);
System.out.println(s1);
}
执行结果如下:
不是说传引用就能改变实参的值,要看引用的指向到底有没有改变。
如:
public static void func(String str, char[] chars){
str = "niHao";
chars[0] = 'u';
}
public static void main(String[] args) {
String str = "zbd";
char[] chars = {'z','b','c'};
func(str,chars);
System.out.println(str);
System.out.println(chars);
}
运行结果:
内存图:
在传参的时候,str和chars的地址都被传了过去,可是str指向了新的对象,所以其地址也就改变了,但是chars只是通过传参修改了其引用的第一个元素,其地址并未改变。
二、字符串常量池
三种常见的常量池:
- Class文件常量池: 如:int a = 10;磁盘上的
- 运行时常量池:当程序把编译好的字节码文件加载到
JVM
当中后,会生成一个运行时常量池,存放在方法区、实际上是Class文件常量池。 - 字符串常量池:主要存放字符串常量,本质上是一个哈希表又称为
StringTable
,是由双引号引起来的字符串常量。从JDK1.8
开始,字符串常量池放在了堆里面。
哈希表为一种数据结构,是描述和组织数据的一种方式。
哈希表在存储数据的时候,会根据一个映射关系进行存储,如何映射,需要设计一个函数(哈希函数)。
String
类的设计使用了共享设计模式。
在JVM
底层实际上会自动维护一个对象池(字符串常量池)。
- 如果现在采用了直接赋值的模式进行
String
类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中. - 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
- 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。
String类中两种对象实例化的区别:
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用
intern()
方法手工入池。
三、字符串比较相等
String
使用 ==
比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象。
例一:
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);//false
}
通过 String str2 = new String("hello");
这样的方式创建的 String
对象,相当于在堆上另外开辟了空间来存储"hello
" 的内容, 也就是内存中存在两份 “hello
”.
例二:
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);//true
}
str1
和 str2
是指向同一个对象的. 此时如 “hello” 这样的字符串常量是在字符串常量池 中。
像 “hello
” 这样的字符串字面值常量, 也是需要一定的内存空间来存储的. 这样的常量具有一个特点, 就是不需要修改(常量). 所以如果代码中有多个地方引用都需要使用 “hello
” 的话, 就直接引用到常量池的这个位置就行了, 而没必要把 “hello
” 在内存中存储两次.
例三:
public static void main(String[] args) {
String str1 = "hello";
String str2 = "he"+"llo";//此时 he和llo都是常量,编译的时候,就已经确定好了是"hello"
String str3 = "he";
String str4 = str3+"llo";//此时str3是一个变量,编译的时候,不知道是啥
System.out.println(str1 == str2);//true
System.out.println(str1 == str4);//false
}
可以使用 String
的 intern
方法来手动把 String
对象加入到字符串常量池中。
如:
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
System.out.println(str1.equals(str2));//true
}
如果想要比较字符串的内容, 必须采用String
类提供的equals
方法。
使用equals
方法时要注意:
public static void main10(String[] args) {
String str1 = null;
String str2 = "11";
//方式一
System.out.println(str2.equals(str1));
//方式二
//System.out.println(str1.equals(str2));
}
推荐使用方式一,使用方式二的方法,str1
为空的时候,就会抛出空指针异常。
四、字符串不可变
字符串是一种不可变对象. 它的内容不可改变。
String
类的内部实现也是基于 char[]
来实现的, 但是 String
类并没有提供 set
方法之类的来修改内部的字符数组。
例:
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);//helloworld!!!
如 +=
这样的操作, 表面上好像是修改了字符串, 其实不是. +=
之后 str
打印的结果却是变了, 但是不是 String
对象本身发生改变, 而是 str
引用到了其他的对象。
那么如果实在需要修改字符串,常见的有两种修改字符串的方式:
- 常见办法: 借助原字符串, 创建新的字符串
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);//hello
- 特殊办法: 使用 “
反射
” 这样的操作可以破坏封装, 访问一个类内部的private
成员.
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String str = "abcde";
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);//hbcde
}
反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”.指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 “认清
自己” 。
为什么 String 要不可变?(不可变对象的好处是什么?)
- 方便实现字符串对象池. 如果
String
可变, 那么对象池就需要考虑何时深拷贝字符串的问题了. - 不可变对象是线程安全的.
- 不可变对象更方便缓存
hash code
, 作为key
时可以更高效的保存到HashMap
中。
五、字符、字节与字符串
5.1 字符与字符串
字符串内部包含一个字符数组,String
可以和 char[]
相互转换。
5.1.1将字符数组变为字符串
public static void main(String[] args) {
char[] chars = {'z','b','c','d','e'};
String str1 = new String(chars);//将字符数组中的所有内容变为字符串
String str2 = new String(chars,1,3);//将部分字符数组中的内容变为字符串口
System.out.println(str1);//zbcde
System.out.println(str2);//bcd
5.1.2取得指定索引位置的字符charAt()
5.1.3将字符串对象变成字符数组toCharArray()
public static void main(String[] args) {
String str2= "hello";
char ch = str2.charAt(2);//获取到2下标的字符
System.out.println(ch);//l
char[] cha = str2.toCharArray();//把str2指向的字符串对象变成字符数组
System.out.println(cha);//hello
}
5.2 字节与字符串
字节常用于数据传输以及编码转换的处理之中,String
也能方便的和 byte[]
相互转换。
5.2.1将字节数组变为字符串
5.2.2字符串转字节getBytes()
public static void main6(String[] args) {
byte[] bytes = {97,98,99,100};
String str = new String(bytes);//将字节数组中的全部内容转换为字符串
String str1 = new String(bytes,1,3);//将部分字节数组中的内容变为字符串
System.out.println(str);//abcd
System.out.println(str1);//bcd
System.out.println("=========");
String str2 = "hello";
byte[] bytes1 = str2.getBytes();//字符串转字节 将字符串以字节数组的形式返回
System.out.println(Arrays.toString(bytes1));//[104, 101, 108, 108, 111]
}
byte[]
是把String
按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作。char[]
是吧String
按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候。
5.2.3判断字符串中是否都是数字isDigit()
public static boolean isNumChar(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;
}
/*if (c < '0' || c > '9'){
return false;
}*/
}
return true;
}
public static void main(String[] args) {
String str = "12345h7";
System.out.println(isNumChar(str));//false
}
六、字符串常见操作
6.1 字符串比较
6.1.1比较两个字符串大小关系compareTo()
String
类中compareTo()
方法返回一个整型,该数据会根据大小关系返回三类内容:
- 相等:返回0.
- 小于:返回内容小于0.
- 大于:返回内容大于0
compareTo()
是一个可以区分大小关系的方法,是String
方法里是一个非常重要的方法。
字符串的比较大小规则, 总结成三个字 “字典序” 相当于判定两个字符串在一本词典的前面还是后面. 先比较第一个字符的大小(根据 unicode
的值来判定), 如果不分胜负, 就依次比较后面的内容。
6.1.2判断两个字符串内容是否相等equals()
6.1.3忽视字符串大小写,不区分大小写比较equalsIgnoreCase()
public static void main(String[] args) {
String str1 = "abcdef";
String str2 = "ABCef";
int ret = str1.compareTo(str2);//比较两个字符串大小关系
System.out.println(ret);//32
System.out.println(str1.equals(str2));//判断两个字符串内容是否相等 false
System.out.println(str1.equalsIgnoreCase(str2));//比较适合忽视大小写,不区分大小写比较 false
}
6.2 字符串查找
6.2.1判断一个子字符串是否存在contains()
6.2.2 查找指定字符串的位置,查到了返回位置的开始索引,如果查不到返回-1. indexOf()
public static void main(String[] args) {
String str1 = "ababcdef";
String tmp = "abc";
boolean flg = str1.contains(tmp);//类似于c中的strstr 判断一个子字符串是否存在
System.out.println(flg);//true
System.out.println("=================");
//从头开始查找指定字符串的位置,查到了返回位置的开始索引,如果查不到返回-1
// int index = str1.indexOf(tmp);//类似于c中的strstr->KMP算法
int index = str1.indexOf(tmp,3);//可以从指定下标开始找
System.out.println(index); //-1
}
6.2.3由后向前查找子字符串位置lastIndexOf()
6.2.4判断是否以指定字符串开头startsWith()
6.2.5判断是否以指定字符串结尾endsWith()
public static void main9(String[] args) {
String str1 = "ababcdef";
String tmp = "abc";
System.out.println(str1.lastIndexOf(tmp,4));// 2 从指定位置从后往前找
System.out.println(str1.startsWith("a"));//true 判断是否以指定字符串开头
System.out.println(str1.endsWith("ef"));//true 判断是否以指定字符串结尾
}
6.3 字符串替换replace()
6.3.1替换所有的指定内容replaceAll ()
6.3.2替换首个内容replaceFirst()
public static void main(String[] args) {
String str1 = "ababcdefababssd";
// String tmp = str1.replace("a","o");//将字符串中所有的a替换为o
// String tmp = str1.replace("ab","pp");//将字符串中所有的ab替换为pp
// String tmp = str1.replaceAll("ab","pp");//将字符串中所有的ab替换为pp
String tmp = str1.replaceFirst("ab","pp");//将字符串中第一次出现ab的地方替换为pp
System.out.println(tmp);
}
注意: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串.
6.4 字符串拆分split()
例:
public static void main11(String[] args) {
String str1 = "name = zhang&age = 18";
String[] str = str1.split("&");//字符串分割
for (String s:str) {
// System.out.println(s);
String[] ss = s.split("=");//二次拆分
for (String tmp:ss) {
System.out.println(tmp);
}
}
}
有些特殊字符作为分割符可能无法正确切分, 需要加上转义。
public static void main12(String[] args) {
String str = "192.168.10.1";
String[] strings = str.split("\\.");
// String[] strings = str.split("\\.",2);//最多分两组
for (String s:strings) {
System.out.println(s);
}
注意:
- 字符
"|","*","+"
都得加上转义字符,前面加上"\
". - 而如果是
""
,那么就得写成"\\"
. - 如果一个字符串中有多个分隔符,可以用"
|
"作为连字符.如下所示:
public static void main(String[] args) {
String str = "java34 12&21#hello";
String[] strings = str.split(" |&|#");
for (String s:strings) {
System.out.println(s);
}
}
执行结果:
6.5 字符串截取substring()
public static void main(String[] args) {
String s= "abcrtu";
// String sub = s.substring(2);//从2号位置开始,提取子串
String sub = s.substring(2,4);//从2号位置开始,到4号位置结束,提取子串(左闭右开区间)
System.out.println(sub);
}
注意:
- 索引从0开始
- 注意前闭后开区间的写法。
七、其他操作方法
7.1去除字符串左右两边的空格,但不不能去除中间的空格trim()
public static void main(String[] args) {
String s= " abcr t ug hg ";
String ret = s.trim();//去除字符串左右两边的空格,但不不能去除中间的空格
System.out.print(ret);
System.out.println("+==============");
}
7.2字符串小写转化成大写toUpperCase()
7.3字符串大写转小写toLowerCase()
public static void main(String[] args) {
String str = "sgbthyrgnADSFD";
// String ret = str.toUpperCase();//小写转化成大写
String ret = str.toLowerCase();//转小写
System.out.println(ret);
}
7.4字符串拼接concat,拼接好的对象不会入到常量池里面
7.5获取字符串长度length()
7.6判断是否为空字符串isEmpty()
判断是否为空字符串但不是null,而是长度为0.
public static void main(String[] args) {
String str = "sgbthyrgnADSFD";
String ret = str.concat("hello");//字符串拼接,拼接好的对象不会入到常量池里面
System.out.println(ret);//sgbthyrgnADSFDhello
System.out.println(str.length());//14
System.out.println(str.isEmpty());//false
}
八、 StringBuffer 和 StringBuilder
任何的字符串常量都是String
对象,而且String
的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。通常来讲String
的操作比较简单,但是由于String
的不可更改特性,为了方便字符串的修改,提供StringBuffer
和StringBuilder
类。
StringBuffer
和 StringBuilder
大部分功能是相同的.
public static void main(String[] args) {
// StringBuilder sb= new StringBuilder("abcdef");
StringBuilder sb= new StringBuilder();
// sb.append("abcdef");//append()方法不会产生新的对象
sb.append("abcdef").append("123456");//append()可以连用
System.out.println(sb.toString());//abcdef123456
}
String
和StringBuffer
最大的区别在于:String
的内容无法修改,而StringBuffer
的内容可以修改。频繁修改字符串的情况考虑使用StingBuffer
。
注意:String
和StringBuffer
类不能直接转换。如果要想互相转换,可以采用如下原则:
String
变为StringBuffer
:利用StringBuffer
的构造方法或append()
方法StringBuffer
变为String
:调用toString()
方法
/**
* StringBuffer或者StringBuilder转换成String需要调用toString()方法
* @return
*/
public static String func(){
StringBuffer sb = new StringBuffer("helloWorld");
return sb.toString();
}
/**
* String转换成StringBuffer或者StringBuilder需要调用构造方法
* @return
*/
public static StringBuffer func2(){
String str = "abdcf";
// StringBuffer sb = new StringBuffer();
// sb.append(str);
// return sb;
return new StringBuffer(str);
}
除了append()
方法外,StringBuffer
也有一些String
类没有的方法:
8.1字符串反转reverse()
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abcdef");
sb.reverse();//字符串反转
System.out.println(sb);
StringBuffer sb2 = new StringBuffer("124");
sb2.reverse();//翻转
System.out.println(sb2);
// sb.append("abcdef").append("1234");
System.out.println(sb2);
}
8.2删除指定范围的数据delete()
8.3插入数据insert()
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("helloworld");
System.out.println(sb.delete(5, 10));//hello
StringBuffer sb1 = new StringBuffer("helloworld");
System.out.println(sb1.delete(5, 10).insert(0, "你好"));//你好hello
}
String、StringBuffer、StringBuilder的区别
String
的内容不可修改,StringBuffer
与StringBuilder
的内容可以修改.StringBuffer
与StringBuilder
大部分功能是相似的。StringBuffer
采用同步处理,属于线程安全操作;而StringBuilder
未采用同步处理,属于线程不安全操作。
以上。