[Java] 字符串底层剖析与常见面试题

2年前 (2022) 程序员胖胖胖虎阿
214 0 0

 在这里插入图片描述

[Java] 字符串底层剖析与常见面试题专栏简介 :java语法

[Java] 字符串底层剖析与常见面试题创作目标:从不一样的角度,用通俗易懂的方式,总结归纳java语法知识.

[Java] 字符串底层剖析与常见面试题希望在提升自己的同时,帮助他人,与大家一起共同进步,互相成长.

[Java] 字符串底层剖析与常见面试题学历代表过去,能力代表现在,学习能力代表未来!

目录

前言

一.认识String类:

1.创建字符串:

2.字符串常量池(StringTable):

     [ intern方法]

3.理解字符串不可变:

        

4.字符串的修改与拼接

        1.字符串修改

        2.字符串拼接

 5.new String的讨论

三.字符串常见操作

1.字符串比较

2.字符串查找

3.转换

        1)数组与字符串转换

        2)大小写转换

        3)字符串转数组

        4)格式化

4.字符串替换

5.字符串拆分

        1)实现字符串拆分处理

        2)部分拆分处理

         3)拆分网址

        4)多次拆分 (开发中常用)

6.字符串截取

7.其他操作方法

四.StringBuffer与StringBuilder

五.String类笔试题:

1.第一个只出现一次的字符

2)计算字符串最后一个单词的长度 

3)验证回文串

4)字符集合

总结



前言

  [Java] 字符串底层剖析与常见面试题  Hello!大家好!今天给大家带来的是,javaSE中的字符串语法讲解,旨在从更深入更底层的视角去剖析字符串在内存中的存储结构,便于大家更深入的理解.在学习字符串底层逻辑的同时还能了解到更多关联知识,开拓视野并完善知识体系.


一.认识String类:

         观察String类的源码我们可以发现,String为一个引用类型指向一个由value和hash组成的对象(JDK1.8),value又指向一个char类型的数组.但JDK1.9中String为一个引用类型指向一个由value,hash,coder,hashIsZero组成的对象,value指向一个byte类型的数组.改动的目的是coder为byte编码更加节省空间.由于改动不影响理解,所以之后的内容都按JDK1.8来讲解.

[Java] 字符串底层剖析与常见面试题

[Java] 字符串底层剖析与常见面试题

1.创建字符串:

        常见的构造字符串的方式:

//方法一:
String str = "Hello World";

//方法二:
2.String str = new String("Hello World");

//方法三:
3.char[ ] array  = {'a','b','c'};
  String str  = new String(array);

       每种创建字符串的方式所创建的对象数量都不相同,例如方法一创建2个对象,方法二创建3个对象,方法三创建4个对象.

        注意:String为引用类型,String str = "Hello World";的内存布局如下:(以下是为了方便理解的简图,更底层的讲解在字符串常量池部分)

[Java] 字符串底层剖析与常见面试题


2.字符串常量池(StringTable):

        "池""是编程中一中常见的,提升效率的方式,在java程序中类似1,2,3,true,false,."hello" 这些常见的字面值常量会频繁的使用,为了提升程序效率节省内存,java为8种基本数据类型和String类都提供了常量池.

        例如:小美没有男朋友的时候想吃瓜子必须从瓜子池中取出一个瓜子嗑瓜子,小美觉得这样太麻烦,于是她找了一个男朋友小明,小明把嗑好的瓜子收集在一起等待小美食用,从此以后小美吃瓜子的效率大大提升了.

        JDK1.6时StringTable存放在永久代,由于永久代使用JVM内存默认空间较小且垃圾回收评率较低,于是JDK1.7时存放在堆区,JDK1.8时存放在元空间(本质还是堆区),这样做的好处是和其他对象一样需要调优应用时调整堆的大小即可.

JDK版本 字符串常量池位置 大小设置
1.6 永久代 固定大小:1009
1.7 可设置:默认60013
1.8 元空间(堆) 可设置:最小1009

        通过以下代码剖析StringTable在内存中的存储方式. 

public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = "Hello";
        String str3 = new String("world");
        String str4 = new String("world");
    }

         注意:像"hello","world"这类常见字符串,编译时期已创建好并存放于字符串池.字符串常量池会返回一个String对象引用,当创建String str1 = "hello"时,先在字符串常量池中找,找到了直接将字符串引用赋值给str1.[Java] 字符串底层剖析与常见面试题

         通过以上案例可以看出:1.只要new都会产生一个唯一的对象,2.常量串创建String对象更高效.

     [ intern方法]

[Java] 字符串底层剖析与常见面试题

        intern()方法是一个Native方法(底层由C++实现无法观察到源码),其作用是将String创建的对象收到入池.如:

char[] arr = new char[]{'h','e','l','l','o'};
String str = new String(arr).intern();
String str2 = "hello";
System.out.println(str==str2);//输出true

[Java] 字符串底层剖析与常见面试题

正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消

[Java] 字符串底层剖析与常见面试题

        调用intern()方法来创建字符串时,先到字符串常量池中寻找有没有与要创建的字符串equals相等的字符串.

  • 如果有则返回字符串常量池中的地址.
  • 如果没有则创建str到字符串常量池中并返回str的地址. 

       如果上面例子中的String str2先创建,此时字符串常量池已有"hello",那么str.intern()就不会入池,并且创建一个新的对象.最后结果一定是false.

        注意:char创建的字符数组的引用赋值给字符串时,并不会把本身引用直接赋值给字符串,因为如果这样做的话一但改变字符数组那么字符串也会跟着改变,所以需要拷贝(copyof)一份对象再赋值给字符串


3.理解字符串不可变:

        1.字符串是不可变的源码已描述的很清楚:

[Java] 字符串底层剖析与常见面试题

        该图可以清晰的看出:

  • String类中的字符实际保存在,内部维护的value数组中.
  • String被final所修饰只能表示该类不能被继承.
  • value被final所修饰表明vale自身的值不能改变,即value引用的指向不能改变,但引用空间中的值可以被修改.
  • private才是字符串真正不能被修改的原因,被private修饰且没有提供可修改的公共接口.如setvalue().

         2.所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象.

        由于博主使用JDK1.9不方便展示源码细节,感兴趣的朋友使用JDK1.8可以按住ctrl+鼠标左键在IDEAL中查看.


4.字符串的修改与拼接

        1.字符串修改

        所有涉及到字符串修改的操作和方法都会新建一个对象效率非常低下,如果要修改建议使用StringBufferStringBuilder.

public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < 10000; i++) {
            str+="w";
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);

        StringBuilder str1 = new StringBuilder("");
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            str1.append("w");
        }
        end = System.currentTimeMillis();
        System.out.println(end-start);

        StringBuffer str2 = new StringBuffer("");
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            str2.append("w");
        }
        end = System.currentTimeMillis();
        System.out.println(end-start);
}
//19
//1
//1

        2.字符串拼接

public static void test(){
        String str1 = "1";
        String str2 = "2";
        String str3 = "12";
        String str4 = str1+str2;
        System.out.println(str3==str4);//false
    }

str3在字符串常量区,str4在堆上. 二者地址必然不同. 

str1+str2的微观操作解释: 

0 ldc #14 <a>     //字符串常量池中的"a"
 2 astore_1        //存放在局部变量表索引1位置处(当前方法为非静态,0处存放的是this)
 3 ldc #15 <b>     //字符串常量池中的"b"
 5 astore_2        //存放在局部变量表索引2位置处
 6 ldc #16 <ab>    //字符串常量池中的"ab"
 8 astore_3        //存放在局部变量表索引3位置处
 9 new #9 <java/lang/StringBuilder>  //堆上创建StringBuilder
12 dup                               //将堆上对象的地址复制到局部变量表中
13 invokespecial #10 <java/lang/StringBuilder.<init>>   //init构造器初始化
16 aload_1                                              //取出局部变量表中的"a"
17 invokevirtual #11 <java/lang/StringBuilder.append>   //调用StringBuilder的append方法,将"a"添加apperd("a")   
20 aload_2                                              //取出局部变量表中的"b"
21 invokevirtual #11 <java/lang/StringBuilder.append>   //调用StringBuilder的append方法,将"b"添加apperd("b")  
24 invokevirtual #12 <java/lang/StringBuilder.toString> //toString()转为字符串,"ab"
27 astore 4                                             //"ab"存放在局部变量表索引4位置处
29 return                                               //返回

   由此可观察到字符串创建的三步骤: 

 9 new #9 <java/lang/StringBuilder>  //堆上创建StringBuilder
12 dup                               //将堆上对象的地址复制到局部变量表中
13 invokespecial #10 <java/lang/StringBuilder.<init>>   //init构造器初始化

间接说明new关键字不是原子性的. 

  1.  堆上分配空间.
  2. 局部变量表储存对象的引用.
  3. 构造器初始化.

str1+str2的宏观操作解释:

  1. 首先构建对象StringBuilder sb = new StringBuilder();
  2. sb.append("a");
  3. sb.append("b");
  4. sb.toString();//将缓冲区中的字符串对象返回.

[拓展]

字符串拼接操作不一定必须使用StringBuilder.

         如果拼接符号左右均是字符常量常量引用(final修饰),则仍然使用编译器优化而非StringBuilder.

public static void test1(){
        final String str1 = "a";
        final String str2 = "b";
        String str3 = "ab";
        String str4 = str1+str2;
        System.out.println(str3==str4);//true
    }

[Java] 字符串底层剖析与常见面试题


总得来说字符串拼接需要注意四点:

  1. 常量与常量的拼接结果在字符串常量池中,原理是编译器优化.
  2. 常量池中不会存在相同的内容.
  3. 只要拼接对象有一个是变量,结果就在堆上的非字符串常量中,原理是StringBuilder字符串缓冲区.
  4. 拼接结果调用intern()方法,将字符串常量池中没有的对象放入,返回该对象的地址.

常量或者被final修饰的变量,在编译器可以确定所以防止常量池中.

变量由于其引用地址不确定所以不能放入常量池只能在堆上创建.


[常见面试题测试]:

public static void test2() {
        String str1 = "a"+"b"+"c";
        String str2 = "abc";
        System.out.println(str1==str2);
        System.out.println(str1.equals(str2));
    }
//true
//true

 str1中拼接的都是字符串常量编译期就已确定.

public static void test3(){
        String str1 = "hello";
        String str2 = "world";

        String str3 = "helloworld";
        String str4 = "hello"+"world";
        String str5 = str1+"world";
        String str6 = "hello"+str2;
        String str7 = str1+str2;
        System.out.println(str3==str4);//true
        System.out.println(str3==str5);//false
        System.out.println(str3==str6);//false
        System.out.println(str3==str7);//false
        System.out.println(str5==str6);//false
        System.out.println(str5==str7);//false
        System.out.println(str6==str7);//false
        String str8 = str6.intern();
        System.out.println(str3==str8);//true
    }
 String str5 = str1+"world";//此时str1为一个引用变量

拼接前后出现变量则存放在堆空间中,与字符串常量池中的地址必然不一样.

String str3 = "helloworld";
String str8 = str6.intern();
System.out.println(str3==str8);//true

此时字符串常量池中已有"helloworld",调用intern()会返回字符串常量池中的地址,被str8接收那么二者地址必然一样,


 5.new String的讨论

[讨论一]

 String s = new String("ab");

new String("ab")为什么会创建两个对象?

[Java] 字符串底层剖析与常见面试题

  1. 上创建了一个.
  2.  字符串常量池中创建了一个,如果之前没有的话.
 public static void test4(){
        String s = new String("a")+new String("b");//创建了几个对象?
        System.out.println(s);
    }
  • 对象一:new StringBuilder().
  • 对象二:new String("a").
  • 对象三:字符串常量池中的"a".
  • 对象四:new String("b").
  • 对象五:字符串常量池中的"b".
  • 对象六:StringBuilder.toString().

StringBuilder.toString().剖析:

该操作相当于把字符串缓冲区中的字符串对象返回,但不会返回到字符串常量池,只会在堆上创建一个对象 new String("ab").

 [Java] 字符串底层剖析与常见面试题

 我们发现没有idc指令,其实就是拼接完没有往常量池中放一份.

[讨论二]

public static void test5(){
        String str1 = new String("1");
        str1.intern();//字符串常量池中已有"1"
        String str2 = "1";
        System.out.println(str1==str2);//false
        
        String str3 = new String("1")+new String("1");
        str3.intern();//字符串常量池中没有"11"
        String str4 = "11";
        System.out.println(str3==str4);//true
    }

        str1在先在上创建一个对象,再到字符串常量池中创建一个对象,str1指向的是上的对象.因为常量池中已有"1",所以str1.intern();没有任何效果.st1与str2地址不一致.

        str3拼接的对象创建在上,且不会在字符串常量池中创建(原理上文已阐述),因此 str3.intern();这项操作会把str3所指对象置于常量池中.str3与str4地址一致.


三.字符串常见操作

1.字符串比较

        字符串比较是比较常见的操作之一,java提供了四种比较方式.

        1)==比较是否引用同一个对象

注意:对于内置类型==比较的是变量中的值,对于引用类型则比较的是地址.

 public static void test7(){
        int a = 10;
        int b = 3;
        int c = 10;
        System.out.println(a==b);//false
        System.out.println(a==c);//true

        String s1 = "hello";
        String s2 = new String("hello");
        String s3 = "hello";
        System.out.println(s1==s2);//false
        System.out.println(s2==s3);//true
    }

        2)boolean equals(Object anObject)方法:按字符大小顺序比较.

父类中的equals()方法同样是比较地址,只不过String重写了该方法.

[Java] 字符串底层剖析与常见面试题

        3)int compareTo(String s)方法:按字典序比较

与equals()不同的是equals返回的是boolean类型的值,compareTo返回的是int类型的值.

  • 先按照字典次序比较,如果出现不相等的字符,直接返回两字符大小的差值.
  • 如果前k个字符相等(k为最小字符串的长度),返回两字符串长度的差值.
 public static void test8(){
        String s1 = "abc";
        String s2 = "ac";
        String s3 = "abc";
        String s4 = "abcdef";
        System.out.println(s1.compareTo(s2));//字符差值为1
        s1.compareTo(s3);//相同0
        s1.compareTo(s4);//长度差值-3
    }

         4)int compareToIgnoreCase(String s)方法:与compareTo方法相同只是忽略大小写.

public static void test8(){
        String s1 = "abc";
        String s2 = "ac";
        String s3 = "Abc";
        String s4 = "abcdef";
        System.out.println(s1.compareTo(s2));//字符差值为1
        s1.compareTo(s3);//相同0
        s1.compareTo(s4);//长度差值-3
    }

2.字符串查找

方法 功能
charAt(char index) 返回index位置上的字符,如果index为负数或者越界,抛出异常IndexOutOfBoundsException异常.

int indexOf(char

 ch)

返回ch第一次出现的位置,没有返回-1.
int indexOf(char ch,int fromIndex) 从fromIndex位置开始找ch第一次出现的位置,没有返回-1.
int indexOf(String str) 返回str第一次出现的位置,没有返回-1.
int indexOf(String str,int fromIndex) 从fromIndex位置开始找第一次出现的str的位置,没有返回-1.
int lastIndexOf(char ch) 从后往前寻找ch第一次出现的位置,没有返回的-1.
int lastIndexOf(char ch,int fromIndex) 从fromIndex开始从后往前寻找第一次出现ch的位置,没有返回-1.
int lastIndeOf(String str) 从后往前寻找str第一次出现的位置,没有返回-1.
int lastIndexOF(String str,int fromIndex) 从Index位置开始从后往前寻找str第一次出现的位置,没有返回-1.

public static void test9(){
        String str = "aaabbbcccaabbccdd";
        System.out.println(str.charAt(3));//b
        System.out.println(str.indexOf('b'));//3
        System.out.println(str.indexOf('b',6));//11
        System.out.println(str.indexOf("bbb"));//3
        System.out.println(str.lastIndexOf('c'));//14
        System.out.println(str.lastIndexOf('b',6));//5
    }

注意:上述方法都是实例方法. 


3.转换

        1)数组与字符串转换

public static void test10(){
        String s1 = String.valueOf(123);//整形转字符串
        String s2 = String.valueOf(12.3);//浮点型转字符串
        String s3 = String.valueOf(true);//布尔型转字符串
        String s4 = String.valueOf(new Student("zhangsan",18));
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
        System.out.println("============================================");
        //字符串转数组
        int x = Integer.parseInt(s1);
        double y = Double.parseDouble(s2);
        System.out.println(x);
        System.out.println(y);
    }

 注意:以上为静态方法.

        2)大小写转换

public static void test10(){
        String str1 = "abcdef";
        String str2 = "ABCDEF";
        System.out.println(str1.toUpperCase().equals(str2));//true
        System.out.println(str2.toLowerCase().equals(str1));//true
}

        3)字符串转数组

public static void test10(){
        String s = "hello";
        //字符串转数组
        char[] ch = s.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            System.out.print(ch[i]+" ");
        }
        //数组转字符串
        String s1 = new String(ch);
        System.out.println(s1);
}

        4)格式化

public static void test10(){
        String s = String.format("%d-%d-%d",2019,8,19);
        System.out.println(s);//2019-8-19
}

4.字符串替换

方法 功能
String replaceAll(String regex,String replacement) 替换所有指定内容
String replaceFirst(String regex,String replacement) 替换首个指定内容
  public static void test10(){
        String str = "helloworld";
        System.out.println(str.replace("l","_"));//he__owor_d
        System.out.println(str.replaceFirst("l","_"));//he_loworld
}

5.字符串拆分

        可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串.

方法 功能
String[] split(String regex) 将字符串区别拆分
String[] split(String regex,int limit) 将字符串以指定形式拆分为limit组.

        1)实现字符串拆分处理

public static void test10(){
        String str = "hello world hello friend";
        String[] str2 = str.split(" ");
        for (String str1:str2) {
            System.out.print(str1+" ");
        }
}

        2)部分拆分处理

public static void test10(){
        String str = "hello world hello friend";
        String[] str2 = str.split(" ",2);
        for (String str1:str2) {
            System.out.print(str1);
        }
}

         3)拆分网址

public static void test10(){
        String str = "192.168.1.1" ;
        String[] result = str.split("\\.") ;
        for(String s: result) {
            System.out.println(s);
        }
}

注意:

  • 如果是"|","+","."等有含义字符需要加上转义字符 "\\".
  • 如果是"\"就得写成"\\\\".
  • 如果一个字符串有多个分隔符可以用"|"做连字符.
public static void test10(){
        String str = "name|hansan&age=18";
        String[] str2 = str.split("&|=|\\|");//[name=zhangsan,age=18]
        for (String str1:
             str2) {
            System.out.println(str1);
        }
}

        4)多次拆分 (开发中常用)

public static void test10(){
        String str = "name=zhansan&age=18";
        String[] str2 = str.split("&");//[name=zhangsan,age=18]
        for (int i = 0; i < str2.length; i++) {
            String[] temp = str2[i].split("=");//[name,zhangsan,age,18]
            System.out.println(temp[0]+"="+temp[1]);
        }
}

6.字符串截取

方法 功能
String subString(int beginIndex) 从指定索引截取到末尾
String subString(int beginIndex,int endIndex) 截取部分内容
public static void test10(){
        String str = "HelloWold";
        System.out.println(str.substring(5));//world
        System.out.println(str.substring(0,5));//hello
}

7.其他操作方法

方法 功能
String trim() 去掉字符串左右两边空格,保留中间空格
public static void test10(){
        String str = "  Hello Wold  ";
        System.out.println(str.trim());
}

trim()方法会去掉左右两边的空格,制表符,回车键等..... 


四.StringBuffer与StringBuilder

        由于String类的不可变性,为了方便字符串的修,java提供了两种类StringBuffer和StringBuilder.接下来介绍常见的操作方法.

方法 功能
StringBuilder append(String str) 尾部追加,相当于字符串的+=,可以追加:

boolean、char、char[]、
double、float、int、long、Object、String、StringBuilder的变量
char charAt(int index) 获取index位置的字符
int length() 获取字符串的长度
int capaticy 获取底层底层保存字符串空间的总的大小
void ensureCapaticy(int mininmumCapacity) 扩容
int indexOf(String str) 返回str第一次出现的位置
int indexOf(String str,int fromIndex) 从下标fromIndex开始返回第一次出现str的位置
int lastIndexOf(String str) 返回最后一次出现str的位置
int lastIndexOf(String str,int fromIndex) 从下标fromIndex开始返回最后一次出现strf的位置.
StringBuilder insert(int offset,String str) 在offset位置插入:八种基本数据类型,或String类型或Object类型
StringBuilder deleteCharAt(int index) 删除下标为index位置的元素
StringBuilder delete(int start,int end) 删除区间[strat,end)之间的元素
StringBuilder replace(int start,int end,String str) 将区间[start,end)之间的元素替换为str
String subString(int start) 从start开始一直到末尾的字符,以String类型返回
String subString(int start,int end) 将[start,end)区间的字符,以String类型返回.
StringBuilder reverse() 反转字符串
String toStrng() 将字符串按String的方式返回
void setCharAt(int index,char ch) 将index位置的字符替换为ch
public static void test10(){
        StringBuilder sb1 = new StringBuilder("hello");
        StringBuilder sb2 = sb1;
        sb1.append("world");//helloworld
        sb1.append(123);//hellloworld123
        System.out.println(sb1==sb2);//true
        System.out.println(sb1.charAt(0));//获取0号位上的字符
        System.out.println(sb1.length());//获取字符串的长度
        System.out.println(sb1.capacity());//
        sb1.setCharAt(0,'H');//Helloworld123
        sb1.insert(0,"Helloworld!!!");//Helloworld!!!Helloworld123
        System.out.println(sb1.indexOf("Hello"));//获取Hello第一次出现的位置
        System.out.println(sb1.lastIndexOf("Hello"));//获取Hello最后一次出现的位置
        sb1.deleteCharAt(0);//elloworld!!!Helloworld123
        sb1.delete(0,5);//orld!!!Helloworld123
        String str = sb1.substring(0,5);//orld!
        System.out.println(str);
        sb1.reverse();//321dlrowolleH!!!dlro
        str = sb1.toString();
        System.out.println(str);
}

注意:String 与 StringBuilder不能直接转换

  • String转StringBuilder:利用StringBuilder的构造方法,或append()方法.
  • StringBuilder转String:利用toString方法.

[面试题 ]

1.String,StringBuilder与StringBuffer的区别?

  • String不可修改,StringBuilder与StringBuffer可以修改且大部分功能相似.
  • StringBuffer采用同步处理线程更安全,但速度慢一点.StringBuilder未采用同步处理线程不安全,但速度更快.

2.以下共创建几个对象?[前提不考虑字符串常量池中存在]

String str1 = new String("hello");//2
String str2 = new String("world")+new String("hehe");//6

五.String类笔试题:

1.第一个只出现一次的字符

[Java] 字符串底层剖析与常见面试题

        由于只考虑小写字母,所以我们可以定义一个大小为26的整形数组,每个字符-'a'就会得到它数组中对于的位置, 该字符出现一次就给数组该位置的元素+1,最后遍历字符串,如果字符在count数组中对应的元素为一就返回该字符.

public static int firstUniqChar(String s) {//第一个只出现一次的字符
        int[] count = new int[26];
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            count[ch-'a']++;
        }
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (count[ch-'a']==1){
                return i;
            }
        }
        return -1;
    }

2)计算字符串最后一个单词的长度 

[Java] 字符串底层剖析与常见面试题 方法一:

        用字符串的内置方法String[] split()方法,以空格分隔字符串并保存与字符串数组中,返回数组最后一个元素即可. 

import java.io.InputStream;
import java.util.Scanner;
public class Main{
     public  static void  main(String [] args) throws Exception{
        Scanner scanner = new Scanner(System.in);
        String str  = scanner.nextLine();
        String[] str1 = str.split(" ");
        String str2 = str1[str1.length-1];
        System.out.println(str2.length());
     }
}

 方法二:

         更底层的做法较为推荐,将字符串转换为字符数组,从后往前遍历数组,遇到空格返回数组长度-1与空格下标的差值即可.

import java.io.InputStream;
import java.util.Scanner;
public class Main{
     public  static void  main(String [] args) throws Exception{
        Scanner scanner = new Scanner(System.in);
        String str  = scanner.nextLine();
        char[] arr = str.toCharArray();
        int index = -1;
        for(int i = arr.length-1;i>=0;i--){
           if(arr[i]==' '){
               index = i;
               break;
           }
       }
         System.out.println(arr.length-1-index);
     }
}

 3)验证回文串

[Java] 字符串底层剖析与常见面试题

        题中明确说明只考虑数字字符和字母,所以我们可以构造一个方法判断字符串是否合法,涉及到包装类Character的内置方法isDigit()isLetter().题中强调不考虑字符的大小写,所以我们首先将字符串全部转换为小写或大写,然后定义首尾指针left和right比较字符串即可,注意比较时首先要满足left<right再考虑是否合法,比较后一定要记得移动指针

 public boolean isTrue(char ch){
        if(Character.isDigit(ch)||Character.isLetter(ch)){
            return true;
        }
        return false;
    }
    public boolean isPalindrome(String s) {
        String str = s.toLowerCase();
        int left = 0;
        int rigth = str.length()-1;
        while (left<rigth){
            while (left<rigth&&!isTrue(str.charAt(left))){
                left++;
            }
            while (left<rigth&&!isTrue(str.charAt(rigth))){
                rigth--;
            }
            if (str.charAt(left)!=str.charAt(rigth)){
                return false;
            }
            left++;
            rigth--;
        }
        return true;
        
    }


 4)字符集合

[Java] 字符串底层剖析与常见面试题

         创建一个布尔数组大小为所有字母的编码之和(122-65),由于布尔数组默认初始化为false,所以我们可以判断如果字符串对应的数组下标为false,我们就将它拼接给StringBuilder对象,并将该下标对应元素置为true.最后以toString()返回StringBuilde对象并打印即可.

public static String test(String s){
        boolean[] arr = new boolean[58];
        StringBuilder str = new StringBuilder();
        for(int i = 0;i<s.length();i++){
               char ch = s.charAt(i);
               if(arr[ch-'A']==false){
                   str.append(ch);
                   arr[ch-'A'']=true;
               }
           }
        return str.toString();
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) { // 注意 while 处理多个 case
           String s = scanner.nextLine();
            System.out.println(test(s));
        }
    }

总结

        以上就是字符串深入剖析的全部内容,从字符串最底层的常量池到基本操作方法和面试题1万5千多字,码子字易如果对你有亿点点帮助和启发麻烦不要吝啬三连哦!

[Java] 字符串底层剖析与常见面试题

版权声明:程序员胖胖胖虎阿 发表于 2022年11月12日 上午2:16。
转载请注明:[Java] 字符串底层剖析与常见面试题 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...