当前位置:首页 » 《随便一记》 » 正文

【认识String类】字符串比较,常量池,字符串常见操作,StringBuilder_自然选择前进四

14 人参与  2022年05月17日 10:52  分类 : 《随便一记》  评论

点击全文阅读


一、定义字符串

1、字符串使用双引号
2、在Java中,没有字符串以 \0 结尾

  • final修饰,这个类不能被继承

在这里插入图片描述


1、常见的构造 String 的方式:

public class TestDemo {
    public static void main(String[] args) {
        String str = "abc";

        // 调用构造方法 构造对象
        String str2 = new String("hello");

        // 字符数组 --> 字符串
        char[] chars = {'a', 'b', 'c'};
        String str3 = new String(chars);
    }
}

AIT+7 查看原码:
有两个字段:valuehash

在这里插入图片描述


2、注意问题:

  • 以下代码中,str2引用指向了str2这个引用所指向的对象
    改变str1的指向,不印象str2
    不能通过str1修饰"abcdef"的内容,因为它是字符串字面值常量
public class Test {
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = str1;

        str1 = "bit";
    }
}
  • 不是传引用就能改变实参的值,要看这个引用具体做了什么
    仅仅是改变了形参s的指向,输出str还是abcdef
    而通过array修改了chars的’b’

在这里插入图片描述


  • 数组的整体赋值只有一次机会,就是在定义的时候
    final 修饰引用 表示引用的内容不能修改
public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};

        array = new int[]{6,7,8}; // ok

        final int[] array2 = {1,2,3,4,5};
        // array2 = new int[]{6,7,8}; // err
    }
}
  • str1这个引用,不指向任何对象
    str2这个引用,指向的字符串是空的
public class TestDemo {
    public static void main(String[] args) {
        String str1 = null;
        String str2 = "";
    }
}

二、字符串比较相等+字符串常量池

1、常量池:

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

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

理解 “池” (pool):
“池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据
库连接池” …

  • Class文件常量池:int a = 10;
  • 运行时常量池:当程序把编译好的字节码文件,加载到JVM中后,会生成一个运行时常量池【方法区】,实际上是Class文件常量池
  • 字符串常量池:主要存放字符串常量,本质上是一个哈希表【String Table】

池的意义:提高效率

哈希表:
数据结构,描述和组织数据的一种方式
存储数据的时候,会根据一个映射关系进行存储,如何映射:设计一个函数(哈希函数)


2、一组代码测试:

1. 以下代码结果为false,分析其原理:

public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = new String("hello");
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述


2. 找常量池里有没有"hello",此时已有str1,不再重复创建,输出true

public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述


3. str中"he",“llo” 都是常量,编译的时候,已经确定了是"hello",还是true

public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "he"+"llo";
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述

但不能这样拼接,因为str3是 变量,编译是不确定

public class Test {
    public static void main(String[] args) {
        String str1 = "hello";

        String str3 = "he";
        String str4 = str3+"llo";

        System.out.println(str1 == str4);
    }
}

5. 两个匿名对象拼接:
public class Test {
    public static void main(String[] args) {
        String str1 = "11";
        String str2 = new String("1") + new String("1");
        System.out.println(str1 == str2);
    }
}

1.拼接->StringBuilder对象
2.->调用toString->String
3.->将String地址赋给str2
输出:false

分析原理:
在这里插入图片描述
在这里插入图片描述


6. 调用intern() 手动入池:
public class Test {
    public static void main(String[] args) {
        String str2 = new String("1") + new String("1");
        str2.intern(); // 手动入池
        String str1 = "11";
        System.out.println(str1 == str2);
    }
}

把str2所指向的对象整体入池,str1检查到常量池里就会有"11",结果为true

在这里插入图片描述

调整顺序:
当字符串常量池里没有时,就会入池,
输出false

public class Test {
    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

public class Test {
    public static void main(String[] args) {
        String str1 = "11";
        String str2 = new String("1") + new String("1");
        str2.intern(); //
        System.out.println(str1.equals(str2));
    }
}

3、理解字符串不可变

字符串是一种不可变对象. 它的内容不可改变.
String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组

public class TestDemo {
    public static void main(String[] args) {
        String str = "hello" ;
        str = str + " world" ;
        str += "!!!" ;
        System.out.println(str);
    }
}

执行结果:hello world!!!
表面上好像是修改了字符串, 其实是 str 引用到了其他的对象

以如下代码验证:

public class TestDemo {
    public static void main(String[] args) {
        String str = "abcd";
        for (int i = 0; i < 10; i++) {
            str += i;
        }
        System.out.println(str); // abcd0123456789
    }
}

字符串的拼接,都会被优化为StringBuilder对象
每次循环都要new对象,开辟内存,花销时间,
而value引用是被final修饰的,也就是说,每次拼接都是拼接了新的对象

在这里插入图片描述


如果实在需要修改字符串
使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员

反射:
是面向对象编程的一种重要特性,有些编程语言也称为 “自省”
指的是程序运行过程中,获取 / 修改某个对象的详细信息(类型信息,属性信息等),相当于让一个对象更好的 “认清自己”

例:输出:hello

import java.lang.reflect.Field;

public class TestDemo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    	// 获取class对象
        String str = "Hello";
        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);
    }
}

三、字符,字节与字符串

1、字符与字符串

字符串内部包含一个字符数组,String 可以和 char[] 相互转换

1.1、字符数组 – 字符串

public class TestDemo {
    public static void main(String[] args) {
        char[] val = {'a', 'b', 'c'};
        String str = new String(val);
        System.out.println(str); // abc
    }
}

1.2、自定义范围

public class TestDemo {
    public static void main(String[] args) {
        char[] val = {'a', 'b', 'c', 'd', 'e'};
        String str = new String(val, 0, 3);
        System.out.println(str); // abc
    }
}

1.3、CharAt 获取指定字符

public class TestDemo {
    public static void main(String[] args) {
        String str = "world";
        char ch = str.charAt(2); // 获取2下标的字符
        System.out.println(ch); // r
    }
}

1.4、字符串 – 字符数组

public class TestDemo {
    public static void main(String[] args) {
        String str = "hello";
        char[] chars = str.toCharArray(); // 把str2指向的字符串对象 变成字符数组
        System.out.println(Arrays.toString(chars)); // [h, e, l, l, o]
    }
}

例:给定一个字符串, 判断是否全部由数字组成

public class TestDemo {
    public static boolean isNumberChar(String s) {
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(c < '0' || c > '9') {
                return false;
            }
        }
        return true;
    }

	public static boolean isNumberChar2(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;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        String str = "12345";
        System.out.println(isNumberChar(str)); // true
    }
}

Class Character


2、字节与字符串

字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换

2.1、字节数组 – 字符串

public class TestDemo {
    public static void main(String[] args) {
        byte[] bytes = {97, 98, 99, 100};
        String str = new String(bytes);
        System.out.println(str); // abcd
    }
}

2.2、自定义范围

public class TestDemo {
    public static void main(String[] args) {
        byte[] bytes = {97, 98, 99, 100};
        String str = new String(bytes, 1, 3);
        System.out.println(str); // bcd
    }
}

调用带两个参数的构造方法,出现:
@Deprecated 说明这个方法已经过时了

在这里插入图片描述


2.3、字符串 – 字节数组

public class TestDemo {
    public static void main(String[] args) {
        String str = "bacd";
        byte[] bytes = str.getBytes();
        System.out.println(Arrays.toString(bytes)); // [98, 97, 99, 100]
    }
}

2.4、编码转换处理

public class TestDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = "比特";
        String str2 = "abcd";

        byte[] bytes = str.getBytes("utf-8");
        System.out.println(Arrays.toString(bytes)); // [-26, -81, -108, -25, -119, -71]
        byte[] bytes2 = str2.getBytes("utf-8");
        System.out.println(Arrays.toString(bytes2)); // [97, 98, 99, 100]

        byte[] bytes3 = str.getBytes("GBK");
        System.out.println(Arrays.toString(bytes3)); // [-79, -56, -52, -40]
        byte[] bytes4 = str2.getBytes("GBK");
        System.out.println(Arrays.toString(bytes4)); // [97, 98, 99, 100]
    }
}

总结

  • byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
  • char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候

四、字符串常见操作

1、字符串比较

1.1、比较内容

真假比较
大小比较

public class TestDemo {
    public static void main(String[] args) {
        String str1 = "abcd";
        String str2 = "hello";
        System.out.println(str1.equals(str2)); // false
    }
}

在这里插入图片描述

1.2、忽视大小写

public class TestDemo {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "ABC";
        // 区分大小写的比较
        System.out.println(str1.equals(str2)); // false
        // 不区分
        System.out.println(str1.equalsIgnoreCase(str2)); // true
    }
}

1.3、比较两字符串大小

lim为较短字符串长度,返回值大于0,小于0或等于0
一个字符一个字符比较,返回差
否则返回字符串长度差

在这里插入图片描述

public class TestDemo {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "ABC";
        int ret = str1.compareTo(str2);
        System.out.println(ret); // 32

		String str3 = "abC";
        String str4 = "abcdx";
        int ret = str3.compareTo(str4);
        System.out.println(ret); // -32
    }
}

2、字符串查找

2.1、判断一个子字符串是否存在

public class TestDemo {
    public static void main(String[] args) {
        String str = "absbhaabcds";
        String tmp = "abc";
        
        boolean flg = str.contains(tmp);
        System.out.println(flg); // true
    }
}

2.2、找子串

  • 在主串中找到子串出现的位置
public class TestDemo {
    public static void main(String[] args) {
        String str = "absbhabcds";
        String tmp = "abc";
        
        int index = str.indexOf(tmp); // 5  类似C的strstr:KMP算法
        System.out.println(index);
    }
}
  • 从指定位置开始找子串的位置
public class TestDemo {
    public static void main(String[] args) {
        String str = "absbhabcds";
        String tmp = "abc";
        
        int index = str.indexOf(tmp, 3); // 5
        System.out.println(index);
    }
}
  • 从后向前找
public class TestDemo {
    public static void main(String[] args) {
        String str = "xxabcabcxx";
        String tmp = "abc";

        System.out.println(str.lastIndexOf(tmp)); // 5
    }
}
  • 从指定位置从后往前找
public class TestDemo {
    public static void main(String[] args) {
        String str = "xxabcabcxx";
        String tmp = "abc";

        System.out.println(str.lastIndexOf(tmp, 4)); // 2
    }
}

2.3、判断是否以指定字符串开头

public class TestDemo {
    public static void main(String[] args) {
        String str = "absbhabcds";
        String tmp = "abc";

        System.out.println(str.startsWith("ab")); // true
    }
}

2.4、判断指定偏移量开头是否以指定字符串开头

public class TestDemo {
    public static void main(String[] args) {
        String str = "absbhabcds";
        String tmp = "abc";

        System.out.println(str.startsWith("ab", 5)); // true
    }
}

2.5、判断是否以指定字符串结尾

public class TestDemo {
    public static void main(String[] args) {
        String str = "absbhabcds";
        String tmp = "abc";

        System.out.println(str.endsWith("ds")); // true
    }
}

3、字符串替换

在这里插入图片描述

  • 替换字符
public class TestDemo {
    public static void main(String[] args) {
        String str = "asfaabcdadf";
        String ret = str.replace('a', 'p');
        System.out.println(ret); // psfppbcdpdf
    }
}
  • 替换所有字符串,与上构成重载
public class TestDemo {
    public static void main(String[] args) {
        String str = "abfababcdab";
        String ret = str.replace("ab", "pp");
        System.out.println(ret); // ppfppppcdpp
    }
}
  • 同上效果
public class TestDemo {
    public static void main(String[] args) {
        String str = "abfababcdab";
        String ret = str.replaceAll("ab", "pp");
        System.out.println(ret); // ppfppppcdpp
    }
}
  • 替换第一次的字符串
public class TestDemo {
    public static void main(String[] args) {
        String str = "abfababcdab";
        String ret = str.replaceFirst("ab", "pp");
        System.out.println(ret); // ppfababcdab
    }
}

4、字符串拆分

4.1、按指定拆分

public class TestDemo {
    public static void main(String[] args) {
        String str = "name=zhangsan&age=22";
        String[] strings = str.split("&");
        for (String s : strings) {
            String[] ss = s.split("=");
            for (String x : ss) {
                System.out.println(x);
            }
            System.out.println(s);
        }
    }
}

4.2、需注意的问题:

  • 字符 “|”,"*","+" 都得加上转义字符,前面加上 “”
public class Test {
    public static void main(String[] args) {
        String str = "192.168.1.1";
        String[] strings = str.split("\\.");
        for (String x : strings) {
            System.out.print(x+" ");
        }
        // 192 168 1 1 
    }
}
  • 一个斜杠的分割:
public class Test {
    public static void main(String[] args) {
        String str = "192\\168\\1\\1";
        String[] strings = str.split("\\\\");
        for (String x : strings) {
            System.out.println(x);
        }
        // 192
        // 168
        // 1
        // 1
    }
}

4.3、极限次分割

不均匀

public class Test {
    public static void main(String[] args) {
        String str = "192.168.1.1";
        String[] strings = str.split("\\.", 2);
        for (String x : strings) {
            System.out.println(x);
        }
        // 192
        // 168.1.1
    }
}

4.4、多次差分

如果一个字符串中有多个分隔符,可以用 “|” 作为连字符

public class Test {
    public static void main(String[] args) {
        String str = "zhang san#he&llo";
        String[] strings = str.split(" |&|#");
        for (String x : strings) {
            System.out.println(x);
        }
        // zhang
        // san
        // he
        // llo
    }
}

5、字符串截取

5.1、从指定索引截取到结尾

public class Test {
    public static void main(String[] args) {
    
        String str = "abcddef";
        String sub = str.substring(2); // 提取子串
        System.out.println(sub); // cddef
    }
}

如果是0,还是原来的对象:

在这里插入图片描述

5.2、截取部分

public class Test {
    public static void main(String[] args) {
        String str = "abcddef";
        String sub = str.substring(2, 5); // 左闭右开
        System.out.println(sub); // cdd
    }
}

6、其他操作方法

6.1、去左右空格,保留中间空格

public class Test {
    public static void main(String[] args) {
        String str = "    abc     def     ";
        String ret = str.trim();
        System.out.print(ret);
        System.out.println("=============="); // abc     def==============
    }
}

6.2、字符串大小写转换

public class Test {
    public static void main(String[] args) {
        String str = "abcDEF123高";

        String ret1 = str.toUpperCase();
        System.out.println(ret1); // ABCDEF123高

        String ret2 = str.toLowerCase();
        System.out.println(ret2); // abcdef123高
    }
}

6.3、字符串入池intern()

见上【常量池】

6.4、字符串连接,拼接的结果不入池

public class Test {
    public static void main(String[] args) {
        String str = "abc";
        String ret = str.concat("bit");
        System.out.println(ret); // abcbit
    }
}

6.5、求字符串长度

public class Test {
    public static void main(String[] args) {
        String str = "abc";
        System.out.println(str.length()); // 需加括号

        int[] array = {1,2,3,4,5};
        System.out.println(array.length);
    }
}

6.6、判断空字符串(不是null)

public class Test {
    public static void main(String[] args) {
        String str = "";
        System.out.println(str.isEmpty()); // true
    }
}

五、StringBuffer 和 StringBuilder

StringBuilder

public class Test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("abcd");
        System.out.println(sb); // abcd
        System.out.println(sb.toString()); // 同上
    }
}

append方法返回的是当前对象,不会产生新的对象

public class Test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("");
        sb.append("abcd");
        sb.append("1234");
        System.out.println(sb.toString()); // abcd1234
    }
}

append 可以连用:

public class Test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("");
        sb.append("abcd").append("123");
        System.out.println(sb.toString()); // abcd123
    }
}

普通的String拼接,底层会被优化为StringBuilder:

public class Test {
    public static void main(String[] args) {
        //String str = "abcdef";
        StringBuilder sb = new StringBuilder();
        sb.append("abcdef");
        //str += "123";//str = str + "123"
        sb.append("123");
        //str = sb.toString();
        System.out.println(sb);
    }
}

在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法

观察一段循环拼接代码:
每次循环都new了一个对象:

public class Test {
    public static void main(String[] args) {
       String str = "abcdef";
        for (int i = 0; i < 10; i++) {
            StringBuilder sb = new StringBuilder();
            sb.append(str).append(i);
            str = sb.toString();
            // str += i;
        }
        System.out.println(str); // abcdef0123456789
    }
}

优化:放在循环外,不需要每次都new对象

public class Test {
    public static void main(String[] args) {
        String str = "abcdef";
        StringBuilder sb = new StringBuilder();
        sb.append(str);
        for (int i = 0; i < 10; i++) {
            sb.append(i);
        }
        str = sb.toString();
        System.out.println(str); // abcdef0123456789
    }
}

得出局部结论:
如果在循环内,进行字符串的拼接,尽量不要使用String,优先使用StringBufferStringBuilder

局部的问题:
StringBuffer 和 StringBuilder有什么区别?

StringBuffer多了一个synchronized关键字:保证线程的安全
所以一般来说,StringBuffer用于多线程,StringBuilder用于单线程

在这里插入图片描述


String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:

  • String变为StringBuffer:利用 StringBuffer 的构造方法append()方法
  • StringBuffer变为String:调用 toString() 方法
public class Test {
    /**
     * StringBuffer或者StringBuilder-》String
     * 调用toString方法
     * @return
     */
    public static String func3() {
        StringBuilder sb = new StringBuilder();
        return sb.toString();
    }

    /**
     * String->StringBuffer或者StringBuilder
     * 使用构造方法
     * @return
     */
    public static StringBuffer func() {
        String str = "abcdef";
        return new StringBuffer(str);
    }
    
	public static StringBuffer func2() {
		String str2 = "abcdef";
        StringBuffer sb = new StringBuffer();
        sb.append(str2);
        return sb;
    }
}

请解释String、StringBuffer、StringBuilder的区别:

  • String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  • StringBuffer与StringBuilder大部分功能是相似的
  • StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作

点击全文阅读


本文链接:http://zhangshiyu.com/post/40285.html

字符串  对象  常量  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1