Java题集 - String类常见面试题总结

1. String 字符串常量的存放位置?

1、问题引入

问题引入:字符串常量归常量池管理,那String str = "abc"; 这段代码中的abc对象是放在堆的字符串常量池中还是堆的对象实例中?

注:本文后续的堆都指堆的对象实例内存。

abc当然在常量池中,只有new String("abc")这个对象才在堆中创建,这个答案是错误的。

先来解答一道面试题:String str1="abc"与 String str1=new String(“abc”)有何区别?

String str1 = "abc";
String str2 = new String("abc"); 

(1) String str1="abc"会在堆中创建1个"abc"对象,将"abc"对象的引用存储在字符串常量池的StringTable中,最后将这个引用值赋值给变量str。
在这里插入图片描述
(2) String str1=new String(“abc”) 会在堆中创建2个"abc"对象,一个将"abc"对象的引用存储在字符串常量池的StringTable中,一个将"abc"对象的引用赋值给str1。
在这里插入图片描述

String str1=new String(“abc”) 为什么要创建两个对象?

  • "abc"是字符串常量,会在堆中创建一个对象,并将该对应的引用存储在字符串常量池的StringTable中。

  • String str1=new String(“abc”) 会调用String类的构造方法,初始化一个新的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。

想要彻底理解底层逻辑,就继续向下看吧,我相信一定会让你醍醐灌顶的。

2、Java内存区域是如何划分的?

这个问题很重要,只有把前面的每个问题理解透了,才能更好的理解后面的每个问题,同时每个问题都可以往深了去思考,从而带出新的问题。
在这里插入图片描述
Java虚拟机在执行的时候会把管理的内存分配成不同的区域,这些区域被称为虚拟机内存,同时,对于虚拟机没有直接管理的物理内存,也有一定的利用,这些被利用却不在虚拟机内存数据区的内存,我们称它为本地内存,这两种内存有一定的区别:

JVM内存:受虚拟机内存大小的参数控制,当大小超过参数设置的大小时就会报OOM。

本地内存 :本地内存不受虚拟机内存参数的限制,只受物理内存容量的限制,虽然不受参数的限制,但是如果内存的占用超出物理内存的大小,同样也会报OOM。

Java内存区域 — 运行时数据区 :

java虚拟机在执行过程中会将所管理的内存划分为不同的区域,有的随着线程产生和消失,有的随着java进程产生和消失,根据《Java虚拟机规范》的规定,运行时数据区分为以下几个区域:

1. 程序计数器:

程序计数器就是当前线程所执行的字节码的行号指示器,通过改变计数器的值,来选取下一行指令,通过他来实现跳转、循环、恢复线程等功能。

  • 在任何时刻,一个处理器内核只能运行一个线程,多线程是通过线程轮流切换,分配时间来完成的,这就需要有一个标志来记住每个线程执行到了哪里,这里便需要到了程序计数器。
  • 所以,程序计数器是线程私有的,每个线程都已自己的程序计数器。

2. java虚拟机栈:

虚拟机栈是线程私有的,随线程生灭。虚拟机栈描述的是线程中的方法的内存模型,每个方法被执行的时候,都会在虚拟机栈中同步创建一个栈帧,每个栈帧的包含如下的内容:

  • 局部变量表 :存储着方法里的java基本数据类型(byte/boolean/char/int/long/double/float/short)以及对象的引用(注:这里的基本数据类型指的是方法内的局部变量)

  • 操作数栈

  • 动态连接

  • 方法返回地址

方法被执行时入栈,执行完后出栈,虚拟机栈可能会抛出两种异常:

  • 如果线程请求的栈深度大于虚拟机所规定的栈深度,则会抛出StackOverFlowError即栈溢出
  • 如果虚拟机的栈容量可以动态扩展,那么当虚拟机栈申请不到内存时会抛出OutOfMemoryError即OOM内存溢出

3. 本地方法栈 :

本地方法栈与虚拟机栈的作用是相似的,都会抛出OutOfMemoryError和StackOverFlowError,都是线程私有的,主要的区别在于:

  • 虚拟机栈执行的是java方法
  • 本地方法栈执行的是native方法

4. java堆 :

java堆是JVM内存中最大的一块,由所有线程共享,是由垃圾收集器管理的内存区域,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,当然由于java虚拟机的发展,堆中也多了许多东西,现在主要有:

  • 对象实例 :类初始化生成的对象、基本数据类型的数组
  • 字符串常量池:字符串常量池原本存放于方法区,jdk7开始放置于堆中,字符串常量池存储的是string对象的直接引用,而不是直接存放的对象,是一张string table
  • 静态变量:静态变量是有static修饰的变量,jdk7时从方法区迁移至堆中
  • 线程分配缓冲区:线程私有,但是不影响java堆的共性,增加线程分配缓冲区是为了提升对象分配时的效率

java堆既可以是固定大小的,也可以是可扩展的(通过参数-Xmx和-Xms设定),如果堆无法扩展或者无法分配内存时也会报OOM。

5. 方法区:

方法区绝对是网上所有关于java内存结构文章争论的焦点,因为方法区的实现在java8做了一次大革新,现在我们来讨论一下:

方法区是所有线程共享的内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存大小参数的限制,在java8中移除了永久代的内容,方法区由元空间(Meta Space)实现,并直接放到了本地内存中,不受JVM参数的限制(当然,如果物理内存被占满了,方法区也会报OOM),并且将原来放在方法区的字符串常量池和静态变量都转移到了Java堆中,方法区与其他区域不同的地方在于,方法区在编译期间和类加载完成后的内容有少许不同,不过总的来说分为这两部分 :

类元信息:

  • 类元信息在类编译期间放入方法区,里面放置了类的基本信息,包括类的版本、字段、方法、接口以及常量池表(Constant Pool Table)
  • 常量池表存储了类在编译期间生成的字面量、符号引用,这些信息在类加载完后会被解析到运行时常量池中

运行时常量池(Runtime Constant Pool)

  • 运行时常量池主要存放在类加载后被解析的字面量与符号引用,但不止这些
  • 运行时常量池具备动态性,可以添加数据,比较多的使用就是String类的intern()方法

6. 直接内存:

直接内存位于本地内存,不属于JVM内存,但是也会在物理内存耗尽的时候报OOM,在jdk1.4中加入了NIO类,引入了一种基于通道与缓冲区的新IO方式,它可以使用native函数直接分配堆外内存,然后通过存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在一些场景下大大提高IO性能,避免了在java堆和native堆来回复制数据。

3、类常量池、运行时常量池、字符串常量池的关系?

类加载过程分为加载—>验证—>准备—>解析—>初始化这 5个阶段

1. class文件常量池:

当java文件被编译成class文件之后会生成class常量池。

class常量池是在编译后每个class文件都有的,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用, 这部分内容将在类加载后进入方法区的运行时常量池中存放。字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。 符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,一般包括下面三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

2. 字符串常量池:

字符串常量池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中。

记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的字符串)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

3. 运行时常量池:

当class文件被加载到内存时,会将字面量和符号引用解析为直接引用存储在运行时常量池。

当类加载到内存中后,jvm就会将class常量池中的大部分内容存放到运行时常量池中,主要存放在被解析的字面量与符号引用 ,解析后会把符号引用替换为直接引用,字面量的一部分是文本字符,对于文本字符来说,会在解析时查找字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

总结:

  • 全局常量池在每个JVM中只有一份,存放的是字符串常量的引用值。
  • class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
  • 运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

4、符号引用和直接引用的区别?

符号引用即用**(用字符串符号的形式)**来表示引用,其实被引用的类、方法或者变量还没有被加载到内存中。而直接引用则是有具体引用地址的指针,被引用的类、方法或者变量已经被加载到内存中。以变量举个例子:

public class Main {
    public static void main(String[] args) {
        /**
         * 符号引用
         */
        String str = "abc";
        System.out.println("str=" + str);
        /**
         * 直接引用
         */
        System.out.println("str="+"abc");
    }
}

符号引用要转换成直接引用才有效,这也说明直接引用的效率要比符号引用高。那为什么要用符号引用呢?这是因为类加载之前,javac会将源代码编译成.class文件,这个时候javac是不知道被编译的类中所引用的类、方法或者变量他们的引用地址在哪里,所以只能用符号引用来表示。

类加载过程分为加载—>验证—>准备—>解析—>初始化这 5个阶段,符号引用转换为直接引用就发生在解析阶段,解析阶段可能在初始化前,也可能在初始化之后。

5、相关面试题

回归开头问题:

public static void main(String[] args) {
    String str = "hello";
}

回到一开始说到的这句代码,可以来总结一下它的执行过程了:

(1) 字面量hello在编译期会被记录在class文件的class常量池中。

(2) 当class文件被加载到内存时,会在准备阶段之后,在堆中生成字符串对象实例"hello",然后将该字符串对象实例的引用值存到字符串常量池中。

(3) 当class文件被加载到内存时,会在解析阶段,将字面量和符号引用解析为直接引用存储在运行时常量池。

(4) 到了String str = “hello” 这步,jvm会去字符串常量池中找,如果找到了,jvm会在栈中的局部变量表里创建str变量,然后把字符串常量池中的引用复制给 str 变量。

问题 1:

public class Main {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1 == str2); // true
        System.out.println(str1.equals(str2)); // true
    }
}

在这里插入图片描述

  • String str1 = "abc" :在堆中生成1个”abc”对象,并将”abc”对象的引用值0x01存储在字符串常量池的StringTable中,最后将引用值0x01赋值给变量str1。
  • String str2 = "abc" :在字符串常量池中查找StringTable,里面有”abc”对象的引用,直接将”abc”对象对象的引用地址返回并赋值给变量str2。

问题 2:

String str1 = new String(“abc”);

在这里插入图片描述
会在堆中创建2个"abc"对象,一个将"abc"对象的引用存储在字符串常量池的StringTable中,一个将"abc"对象的引用赋值给str1。

String str1=new String(“abc”) 为什么要创建两个对象?

  • "abc"是字符串常量,会在堆中创建一个对象,并将该对应的引用存储在字符串常量池的StringTable中。

  • String str1=new String(“abc”) 会调用String类的构造方法,初始化一个新的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。

问题3:

public class Main {
    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = "abc";
        System.out.println(str1 == str2); // false
        System.out.println(str1.equals(str2)); // true
    }
}

在这里插入图片描述

  • String str1 = new String("abc"): 会在堆中创建2个"abc"对象,一个将"abc"对象的引用存储在字符串常量池的StringTable中,一个将"abc"对象的引用赋值给str1

  • String str2 = "abc" :在字符串常量池中查找StringTable,里面有”abc”对象的引用,直接将”abc”对象对象的引用地址返回并赋值给变量str2。

问题4:

public class Main {
    public static void main(String[] args) {
        String str1 = "a" + "b" + "c";
        String str2 = "abc";
        System.out.println(str1 == str2); // true
        System.out.println(str1.equals(str2)); // true
    }
}

在这里插入图片描述

  • String str1 = "a" + "b" + "c" : 会在堆中创建4个对象,分别为对象"a",对象"b",对象"c" ,对象"abc" ,同时会将他们的引用存储在字符串常量池的StringTable中。
  • String str2 = "abc" : 在字符串常量池中查找StringTable,里面有”abc”对象的引用,直接将”abc”对象对象的引用地址返回并赋值给变量str2。

问题5:

public class Main {
    public static void main(String[] args) {
        String str1 = "ab";
        String str2 = "abc";
        String str3 = str1 + "c";
        System.out.println(str2 == str3); // false
        System.out.println(str2.equals(str3)); // true
    }
}

在这里插入图片描述

JVM对于字符串引用,由于在字符串的"+“连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a” + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。

这里走的+的拼接方法,使用StringBuffer类的append方法,得到了“abc”,这个时候内存0x03表示的是一个StringBuffer对象,注意不是String对象,随后,调用了Object的toString方法把StringBuffer对象装换成了String对象。

问题6:

public class Main {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = new String("def");
        String str3 = "abc";
        String str4 = str2.intern();
        String str5 = "def";
        System.out.println(str1 == str3); //true
        System.out.println(str2 == str4); //false
        System.out.println(str4 == str5); //true
    }
}

现在就很容易解释整个程序的内存分配过程了,如图所示:
在这里插入图片描述

(1) String str1 = "abc":在堆中生成1个”abc”对象,并将”abc”对象的引用值0x01存储在字符串常量池的StringTable中,最后将引用值0x01赋值给变量str1。

(2) String str2 = new String("def") :会在堆中创建2个"def"对象,一个将"def"对象的引用存储在字符串常量池的StringTable中,一个将"def"对象的引用赋值给str2。

(3) String str3 = "abc" :查找StringTable,里面有”abc”的字符串引用,所以str3的引用地址与str1相同。

(4) String str4 = str2.intern() :str4是在运行的时候调用intern()函数,返回StringTable中”def”的引用值,如果没有就将str2的引用值添加进去,在这里,StringTable中已经有了”def”的引用值了,所以返回上面在new str2的时候添加到StringTable中的 “def”引用值。

(5) String str5 = "def":指向存在于StringTable中的”def”的引用值。

上面程序的首先经过编译之后,在该类的class常量池中存放一些符号引用,然后类加载之后,将class常量池中存放的符号引用转存到运行时常量池中,然后经过验证,准备阶段之后,在堆中生成驻留字符串的实例对象(也就是上例中str1所指向的”abc”实例对象),然后将这个对象的引用存到全局String Pool中,也就是StringTable中,最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询StringTable,保证StringTable里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。

问题7:

public class Main {
    public static void main(String[] args) {
        String str1 = "hellojava";
        String str2 = "hello"+"java";
        System.out.println(str1==str2); // true

        String str3 = "hello";
        String str4 = "java";
        String str5 = str3+str4;
        System.out.println(str1==str5); // false
    }
}

在这里插入图片描述

2. 什么是String类的不可变性?

首先需要先理解这两个知识点:

(1) 字符串常量池中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。

(2) 使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容 。

String类是不可变类,即创建String对象后,该对象中的字符串是不可改变的,直到这个对象被销毁,每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。

public class Main {
    public static void main(String[] args) {
        String str1 = "abc";
        str1 = "abcde";
    }
}

通过String类的字符串存放位置来理解这段代码:
在这里插入图片描述
(1) “abc”属于字符串常量,因此会在堆中会有一个”abc”实例,将”abc”的引用0x01存放在字符串常量池的StringTable中,将引用值0x01赋值变量str1,此时str1指向引用0x01所指向的对象"abc"。

(2) 字符串常量池中不会存放重复的字符串常量,因此执行str = "abcde";会在堆中再次创建一个"abcde"实例,然后将"abcde"的引用存放在字符串常量池的StringTable中,将引用值0x02赋值给str1,此时str1指向引用0x02所指向的对象"abcde"。

3. String类为什么不可变?

首先进入String类的底层源码:

public final class String {
    private final char value[];
}

(1) String类是final修饰的,代表这个这个类不能被继承,但是这不是主要原因,即使可以被继承,子类继承父类,也不会继承其private成员。

(2) String类底层是使用字符数组来存放字符串的,而这个字符数组value[]是被final修饰的,final虽然保证了引用类型变量所引用的地址不会改变,即一直引用同一个对象,但是这个对象的内容却可以发生改变。(即value[] 数组可以变成"dbc"),因此这也不能保证String类的不可变性。

public class Main {
    public static void main(String[] args) {
        final char[] value = {'a','b','c'};
        value[0] = 'd';
        System.out.println(value); // dbc
    }
}

在这里插入图片描述
(3) 由于value是private的,并且没有提供setValue等公共方法来修改value的值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改。此外,value变量是final的, 也就是说在String类内部,一旦这个值初始化了,value引用类型变量所引用的地址不会改变,即一直引用同一个对象。因此,可以说String对象是不可变对象。

4. String类不可变性的好处?

(1) 字符串常量池

String str1 = "abc";
String str2 = "abc";

对于字符串常量str1 = “abc”,会在堆中创建一个"abc"对象,并将这个对象的引用存储在字符串常量池中的StringTab中,最后将引用值赋值给str1,当str2 = "abc"时,会去查找String Tab,如果对象 “abc"的引用值已经存在,直接将"abc"对象的引用值赋值给str2,因此str1和str2最终都会指向"abc"对象。因此堆内存中只有一个对象"abc”。
在这里插入图片描述
设想一下,如果String可变,那么用某个引用一旦改变了字符串的值将会导致其他引用指向错误的值。比如修改str1=“abcd”,那么将会导致str2=“abcd”,但实际上str2=“abc”。

因此,当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的String.intern()方法也失效,每次创建新的 String 将在堆内开辟出新的空间,占据更多的内存。

(2) 缓存hashcode

当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性保证同一个字符串对象的hashcode总是相同的,使其比其他对象更适合当容器的键值。

String类中重写的hashcode()方法:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        // 因为value数组是不变的,因此字符串的hashcode就是不变的
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

在hashmap中使用hashcode计算key的hash值:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

(3) 安全

引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。

(4) 线程安全

保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程。

5. String 类的常用方法都有那些?

1、String的构造方法:

1)String(String original):把字符串数据封装成字符串对象
2)String(char[] value):把字符数组的数据封装成字符串对象
3)String(char[] value, int index, int count):把字符数组中的一部分数据封装成字符串对象

2、String类的获取功能:

1)length():获取字符串的长度,其实也就是字符个数
2)charAt(int index):获取指定索引处的字符
3)indexOf(String str):获取str在字符串对象中第一次出现的索引
4)substring(int start):从start开始截取字符串
5)String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end

3、String判断功能:

1)equals(Object obj):比较字符串的内容是否相同
2)equalsIgnoreCase(String anotherString):比较字符串的内容是否相同,忽略大小写
3)startsWith(String prefix):判断字符串对象是否以指定的字符开头(区分大小写)
4)startsWith(String prefix,int toffset):判断字符串对象是否以指定的字符开头,toffset为指定从哪个下标开始
5)endsWith(String str):判断字符串对象是否以指定的字符结尾
6)isEmpty():判断指定字符串是否为空
6)compareTo(String anotherString):比较字符串的大小,前者大返回整数,后者大返回负数,相等返回0

4、String类中的转化方法:

1)toCharArray():把字符串转换为字符数组
2)toLowerCase():把字符串转换为小写字符串
3)toUpperCase():把字符串转换为大写字符串

5、其他常用方法:

1)trim():去除字符串两端空格
2)split():去除字符串中指定的的字符,然后返回一个新的字符串
3)subSequence(int beginIndex,int endIndex ):截取字符串中指定位置的字符组成一个新的字符串
4)replace(char oldChar, char newChar):将指定字符替换成另一个指定的字符
5)replaceAll(String regex,String replasement):用新的内容替换全部旧内容
6)replaceFirst(String regex,String replacement):替换首个满足条件的内容
7)lastIndexOf(String str):返回指定字符出现的最后一次的下标
8)contains(CharSequence s):查看字符串中是都含有指定字符
9)concat(String str):在原有的字符串的基础上加上指定字符串

6. String 属于基础的数据类型吗?

String 不属于基础类型,基础类型有 8 种:byte、short、int、long、float、double、char、boolean,占用的字节分别为1、2、4、8、4、8、1、2,而 String 属于对象。

7. Java中操作字符串的类以及它们的区别?

操作字符串的类有:String、StringBuffer、StringBuilder。

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

8. 如何实现字符串的反转?

1、利用字符串的拼接(charAt()方法),把后遍历出来的放在前面即可实现反转:

public static String charAtReverse (String s){
   int length = s.length();
   String reverse = " ";
   for (int i = 0; i < length; i++) {
     //字符串中获取单个字符的字符的放法
     reverse = s.charAt(i)+reverse;
   }
   return reverse;
}

2、利用字符串的拼接(toCharArray()方法),把后遍历出来的放在前面即可实现反转:

public static String reverseCharArrays(String s){
   char []array = s.toCharArray();//把字符串分割成单个字符的数组
   String reverse = "";
   for(int i = array.length -1 ; i>=0 ; i--){
   //遍历数组,从后向前拼接
    reverse +=array[i];
   }
   return reverse;
}

3、利用StringBuffer的reverse()方法:

public static String reverseStringBuffer(String s){
   StringBuffer sb = new StringBuffer(s);
   String afterReverse = sb.reverse().toString();
   return afterReverse;
  }

4、利用递归的方法,类似与二分查找的折半思想:

public static String reverseRecursive(String s){
   int length = s.length();
   if(length<=1){
    return s;
   }
   String left  = s.substring(0,length/2);
   String right = s.substring(length/2 ,length);
   //递归的方法调用
   String afterReverse = reverseRecursive(right)+reverseRecursive(left);
   return  afterReverse;
}  

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我一直在流浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值