Java中关于String类以及字符串拼接的问题

Posted 武帅祺的官方网站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中关于String类以及字符串拼接的问题相关的知识,希望对你有一定的参考价值。

String类部分源码

//被final修饰不可被继承
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence 
    //String维护char[] 所以不可修改
    private final char value[];

创建String对象的方式

  • str1它首先会去方法区的常量池中找,有“hello”这个对象就直接引用,没有就创建一个新的对象“hello”,存放在常量池中。
  • 而new的方式首先会在堆中创建一个对象,然后再去常量池中找,有直接引用,没有就创建一个新的对象“hello”。

两者区别

  • 直接赋值创建0或1个对象,而new的方式创建1或2个对象。
  • 为了提升Java虚拟机的性能和减少内存的开销,避免字符串的重复创建,尽量少使用new的方式创建String对象

代码分析

public static void main(String[] args) 
        String str1 = "hello";//指向常量池中的引用
        String str2 = new String("hello");//指向堆中的引用
        String str3 = str2;//str3==str2 true
        String str4 = "hello";//str1==str4 true
    

字符串拼接

String a="A";
String b="B";
String c=new String("C");
a="A"+b;//变量拼接 指向变为堆中地址
a="A"+c;//变量拼接 指向变为堆中地址!
a="A"+"A";//常量拼接依旧指向常量池中的地址 a=="AA" true
final String d="D";//这是常量
a="A"+d//a=="AD" true

+号变量拼接底层原理 StringBuidlerd构造初始化容量为16的char[] 然后调用append()进行拼接 最后将结果new String()的形式赋给左边变量

Java中关于String类详解

目录

一、创建字符串

常见的创建字符串的三种方式:

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, 以及自定义的类都是引用类型
如下面的例子:ss1指向同一块对象,修改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只是通过传参修改了其引用的第一个元素,其地址并未改变。

二、字符串常量池

三种常见的常量池:

  1. Class文件常量池: 如:int a = 10;磁盘上的
  2. 运行时常量池:当程序把编译好的字节码文件加载到JVM当中后,会生成一个运行时常量池,存放在方法区、实际上是Class文件常量池。
  3. 字符串常量池:主要存放字符串常量,本质上是一个哈希表又称为StringTable,是由双引号引起来的字符串常量。从JDK1.8开始,字符串常量池放在了堆里面。
    哈希表为一种数据结构,是描述和组织数据的一种方式。
    哈希表在存储数据的时候,会根据一个映射关系进行存储,如何映射,需要设计一个函数(哈希函数)。

String类的设计使用了共享设计模式。

JVM底层实际上会自动维护一个对象池(字符串常量池)。

  • 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
  • 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
  • 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。

String类中两种对象实例化的区别:

  1. 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
  2. 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用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
    

str1str2 是指向同一个对象的. 此时如 “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
    

可以使用 Stringintern 方法来手动把 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 引用到了其他的对象

那么如果实在需要修改字符串,常见的有两种修改字符串的方式:

  1. 常见办法: 借助原字符串, 创建新的字符串
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);//hello
  1. 特殊办法: 使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 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 要不可变?(不可变对象的好处是什么?)

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 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()方法返回一个整型,该数据会根据大小关系返回三类内容:

  1. 相等:返回0.
  2. 小于:返回内容小于0.
  3. 大于:返回内容大于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) 
            Java中关于string的些许问题及解析

java中关于String和StringBuffer的问题与解析

Java中关于String类详解

java中关于Pattern的一个方法

String类中关于"=="号和equals区别的问题

数据运算中关于字符串""的拼接问题