2026/9/7 10:59:58

【深入了解Java String类】

【深入了解Java String类】 作者TheMyth.座右铭不走心的努力都是在敷衍自己让自己所做的选择熠熠发光。目录String类常用方法字符串的不可变性String的内存分析StringBuilder类解释可变和不可变字符串常用方法面试题StringStringBuilderStringBuffer之间的区别和联系字符串常量池String类的OJ练习String类【1】直接使用无需导包【2】形象说一下字符串【3】String str “abc”;abc就是String类下的一个具体的对象【4】字符串是不可变的【5】这个String类不可以被继承不能有子类【6】String底层是一个char类型的数组区分注意jdk9之前jdk9之后是byte类型的数组主要是为了优化内存验证注意没有用\0来标识结尾String类中的字符实际保存在内部维护的value字符数组中value被final修饰表明value自身的值不能改变即不能引用其它字符数组但是其引用空间中的内容可以修改。常用方法【1】构造器底层就是给对象底层的value数组进行赋值操作。//通过构造器创建对象 String s1 new String(); String s2 new String(abc); String s3 new String(new char[]{a, b, c});【2】常用方法String s4 abc; System.out.println(字符串的长度为s4.length()); String s5 new String(abc); System.out.println(字符串是否为空s5.isEmpty()); System.out.println(获取字符串的下标对应的字符为s5.charAt(1));【3】equals:比较两个值的内容是否相同String s6 new String(abc); String s7 new String(abc); System.out.println(s6.equals(s7)); /*String s6 new String(abc); String s7 new String(abc); System.out.println(s6.equalsIgnoreCase(s7));//忽略大小写*/equals底层【4】String类实现了Comparable接口里面有一个抽象方法叫compareTo所以String中一定要对这个方法进行重写比较两个值的内容的大小相等返回0大于返回一个整数小于返回一个负数String s8 new String(abc); String s9 new String(accdef); System.out.println(s8.compareTo(s9)); /*String s8 new String(abc); String s9 new String(Abc); System.out.println(s8.compareToIgnoreCase(s9));//忽略大小写 */compareTo底层【5】其它常用方法//字符串的截取 String s10 abcdefghijk; System.out.println(s10.substring(3)); System.out.println(s10.substring(3,6));//[3,6) //字符串的拼接/合并 System.out.println(s10.concat(pppp)); //字符串中的字符的替换 String s11 abcdeahija; System.out.println(s11.replace(a,u)); //按照指定的字符串进行分裂为数组的形式 String s12 a-b-c-d-e-f; String[] strs s12.split(-); System.out.println(Arrays.toString(strs)); //转大小写的方法 String s13 abc; System.out.println(s13.toUpperCase()); System.out.println(s13.toUpperCase().toLowerCase()); //去除首尾空格 String s14 a b c ; System.out.println(s14.trim()); //toString() String s15 abc; System.out.println(s15.toString()); //转换为String类型传入任何类型的数据都将转换为String类型 System.out.println(String.valueOf(String.valueOf(false)));【6】String类的练习public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { String str hello themyth ; System.out.println(str); String ret str.trim();//去掉的字符串开头和结尾的空白字符(空格、换行、制表符等) System.out.println(ret); String string themythws; System.out.println(string.contains(ws));//判断字符串里面是否包含了这个内容。 } //这是一个main方法是程序的入口 public static void main2(String[] args) { //字符串的拆分 String str1 hello handsome themyth; String[] strs str1.split( ); /*for (String s : strs) { System.out.println(s); }*/ for (int i 0; i strs.length; i) { System.out.println(strs[i]); //hello //handsome //themyth } String[] strs2 str1.split( , 2); for (int i 0; i strs2.length; i) { System.out.println(strs2[i]); //hello //handsome themyth } String str2 192.168.0.1; String[] strs3 str2.split(\\.); for (int i 0; i strs3.length; i) { System.out.println(strs3[i]); //192 //168 //0 //1 } System.out.println(); String str3 192\\168\\0\\1; String[] strs4 str3.split(\\\\);// \\表示一个\ for (int i 0; i strs4.length; i) { System.out.println(strs4[i]); //192 //168 //0 //1 } System.out.println(); String str4 1921681; String[] strs5 str4.split(|);// for (int i 0; i strs5.length; i) { System.out.println(strs5[i]); //192 //168 //1 } System.out.println(); //多次拆分 String message namethemythage22; String[] ms message.split(|); for (String s : ms) { System.out.println(s); //name //themyth //age //22 } System.out.println(); String message2 namethemythage22; String[] ms2 message2.split(); for (int i 0; i ms2.length; i) { String[] ms3 ms2[i].split(); for (String s : ms3) { System.out.println(s); //name //themyth //age //22 } } //字符串的截取 String str abcdefgh; String s1 str.substring(2);//cdefgh 返回从2位置开始所截去的字符 System.out.println(s1); String s2 str.substring(2,6);//cdef 截取下标区间[2,6)的字符 System.out.println(s2); } //这是一个main方法是程序的入口 public static void main1(String[] args) { String str1 themyth; char ch str1.charAt(1);//h 下标对应的字符 System.out.println(ch); for (int i 0; i str1.length(); i) { if (i ! str1.length() - 1) { System.out.print(str1.charAt(i) ); } else { System.out.println(str1.charAt(i)); } }//t h e m y t h String str2 ababcabcabc; int b str2.indexOf(b);//1 字符第一次出现位置的下标默认从下标为0开始查找 System.out.println(b); int b1 str2.indexOf(b, 5);//6 从下标为5的字符开始查找字符第一次出现位置的下标 System.out.println(b1); //在主串中 查找子串 int abc str2.indexOf(abc); System.out.println(abc);//2 返回abc首字符a第一次出现的位置 int b2 str2.lastIndexOf(b);//9 从后往前找字符第一次出现位置的下标 System.out.println(b2); int b3 str2.lastIndexOf(b, 5);//3 从下标为5的字符从后往前开始查找字符第一次出现位置的下标 System.out.println(b3); int b4 str2.lastIndexOf(abc, 7);//5 从下标为7的字符从后往前找子串如果找到了第一个能够匹配的字串返回该子串的第一个字符 System.out.println(b4); String str3 String.valueOf(123);//将...转换为字符串 System.out.println(str3); String str4 123; int data Integer.parseInt(str4); System.out.println(data); Double data2 Double.parseDouble(str4); System.out.println(data2);//123.0 String str5 TheMyth; String str6 themyth; System.out.println(str5.toLowerCase());//themyth System.out.println(str6.toUpperCase());//THEMYTH String str7 abcdef; char[] chars str7.toCharArray();//字符串---字符数组 for (int i 0; i chars.length; i) { if (i ! chars.length - 1) { System.out.print(chars[i] ); } else { System.out.println(chars[i]); } }//a b c d e f String str8 new String(chars);//字符数组-字符串 System.out.println(str8); String str9 String.format(%d-%d-%d, 2021, 6, 16);//格式化打印用的很少 System.out.println(str9); //参数不同。replace的参数是char和CharSequence即可以支持字符的替换也支持字符串的替 //换replaceAll的参数是regex即基于规则表达式的替换。 //替换方式不同。replace是全部替换即把源字符串中的某一字符或字符串全部换成指定的字符或 //字符串replaceAll也是全部替换但如果所用的参数据不是基于规则表达式的则只替换第一 //次出现的字符串。 String str10 abcabcdabcde; String replace str10.replace(a, o);//obcobcdobcde System.out.println(replace); String replace1 str10.replace(abc, x);//xxdxde System.out.println(replace1); String replace2 str10.replaceAll(abc, ooo);//oooooodooode System.out.println(replace2); String replace3 str10.replaceFirst(abc, 520);//520abcabcde System.out.println(replace3); //注意字符串是不可变的对象替换不修改当前字符串 } }字符串的不可变性所有涉及到可能修改字符串内容的操作都是创建一个新对象改变的是新对象例如substring返回的都是一个新的字符串对象public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { String str1 abc; String str2 new String(abc); System.out.println(str1 str2);//false } }public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { String str1 abc; String str2 abc; System.out.println(str1 str2);//true } }总结“”双引号里面的值存在字符串常量池中如果已经存过一次了就不会进行第二次存储直接返回字符串常量池的对象即可【纠正】网上有些人说字符串不可变是因为其内部保存字符的数组被final修饰了因此不能改变。这种说法是错误的不是因为String类自身或者其内部value被final修饰而不能被修改。final修饰类表明该类不想被继承final修饰引用类型表明该引用变量不能引用其他对象但是其引用对象中的内容是可以修改的。例如import java.util.Arrays; public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { final int[] array new int[]{1, 2, 3, 4}; array[0] 666;//指向的内容可以修改 //array new int[]{1, 2, 3, 4, 5};指向本身不能修改 System.out.println(Arrays.toString(array));//[666, 2, 3, 4] } }真实的原因是在String类中是压根拿不到value[ ]的值因为根本就没有提供设置和获取value的方法所以字符串不可变的为什么 String 要设计成不可变的?(不可变对象的好处是什么?)1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.2. 不可变对象是线程安全的.3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.String的内存分析【1】字符串拼接public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { String s1 abc; String s2 abc; String s3 abc; String s4 abc; String s5 abc; } }上面的字符串会进行编译器优化直接合并成为完整的字符串我们可以反编译验证然后在常量池中常量池的特点是第一次如果没有这个字符串就放进去如果有这个字符串就直接从常量池中取内存【2】new关键字创建对象String s6 new String(abc);内存开辟两个空间1.字符串常量池中的字符串 2.堆中的开辟的空间【3】有变量参与的字符串拼接public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { String a abc; String b a def; System.out.println(b); } }a变量在编译的时候不知道a是“abc”字符串所以不会进行编译期优化不会直接合并为“abcdef”反汇编过程为了更好的帮我分析字节码文件是如何进行解析的利用IDEA中的控制台字符串修改public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { /*String str hello; str abc; System.out.println(str); for (int i 0; i 10; i) { str abc;//不建议在循环中使用对字符进行拼接要创建很多临时的对象效率低 }*/ //在底层的代码 String str hello;//对象 StringBuilder sb new StringBuilder();//对象 sb.append(str); sb.append(abc);//对象 str sb.toString();//对象 //共创建了4个对象所以上面那种写法很不好如果还是在循环里面进行字符拼接那效率更低 System.out.println(str); } }尽量避免直接对String类型对象进行修改因为String类是不能修改的所有的修改都会创建新对象效率非常低下。public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { long start System.currentTimeMillis(); String s ; for (int i 0; i 10000; i) { s i; } long end System.currentTimeMillis(); System.out.println(end - start); start System.currentTimeMillis(); StringBuffer sbf new StringBuffer(); for (int i 0; i 10000; i) { sbf.append(i); } end System.currentTimeMillis(); System.out.println(end - start); start System.currentTimeMillis(); StringBuilder sbd new StringBuilder(); for (int i 0; i 10000; i) { sbd.append(i); } end System.currentTimeMillis(); System.out.println(end - start); } }可以看到在对String类进行修改时效率是非常慢的因此尽量避免对String的直接修改如果要修改建议尽量使用StringBuffer或StringBuilderStringBuilder类【1】字符串的分类1不可变字符串String2可变字符串StringBuilderStringBuffer【2】StringBuilder底层非常重要的两个属性【3】对应内存分析从源码分析因为value数组被使用了所以count的值要发生改变最后返回当前构建的那个对象实际上在底层就是返回一个复制之后的数组。注意count是StringBuilder底层那个value数组被使用的长度上述说的第几步到第几步不是真正程序执行的步骤是自定义方便自己理解的步骤。从源码分析新传入的7个o在底层怎样运作的public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { //创建StringBuilder的对象 StringBuilder sb3 new StringBuilder(); //表面上调用StringBuilder的空构造器实际底层是对value数组进行初始化长度为16 StringBuilder sb2 new StringBuilder(3); //表面上调用StringBuilder的有参构造器传入一个int类型的数实际底层就是对value数组进行初始化长度为你传入的数字 StringBuilder sb new StringBuilder(abc); System.out.println(sb.append(def).append(aaaaaaaa).append(bbb).append(ooooooo).toString());//链式调用方式return this } }解释可变和不可变字符串【1】String---》不可变所以不可变指的是同一个地址下字符串不可改变。每次试图改变字符串对象实际上是新产生了新的字符串对象了变量每次都是指向了新的字符串对象之前字符串对象的内容确实是没有改变的因此说String的对象是不可变的。【2】StringBuilder---》可变可变在StringBuilder这个对象的地址不变的情况下想把“abc”变成“abcdef”是可能的直接追加即可public class Test { //这是一个main方法是程序的入口 public static void main(String[] args) { StringBuilder sb new StringBuilder(); System.out.println(sb.append(abc) sb.append(def));//true同一个对象 } }常用方法【1】StringBuilder常用方法public class TestStringBuilder { //这是一个main方法是程序的入口 public static void main(String[] args) { StringBuilder sb new StringBuilder(nihaojavawodeshijie); //增 sb.append(这是梦想); System.out.println(sb);//nihaojavawodeshijie这是梦想 //删 sb.delete(3,6);//删除位置在[3,6)上的字符 System.out.println(sb);//nihavawodeshijie这是梦想 sb.deleteCharAt(16);//删除位置在16上的字符 System.out.println(sb);//nihavawodeshijie是梦想 //改--插入 StringBuilder sb1 new StringBuilder($23445980947); sb1.insert(3,,);//在下标为3的位置上插入, System.out.println(sb1);//$23,445980947 //改--替换 StringBuilder sb2 new StringBuilder($2你好吗5980947); sb2.replace(3,5,我好累);//在下标[3,5)位置上替换字符串 System.out.println(sb2);//$2你我好累5980947 sb.setCharAt(3,!); System.out.println(sb);//nih!vawodeshijie是梦想 //查 StringBuilder sb3 new StringBuilder(asdfa); for (int i 0; i sb3.length(); i) { System.out.print(sb3.charAt(i)\t);//a s d f a } System.out.println(); //截取 String str sb3.substring(2,4);//截取[2,4)返回的是一个新的String对StringBuilder没有影响 System.out.println(str);//df //反转 sb3.reverse(); System.out.println(sb3);//afdsa } }【2】StringBuffer常用方法 与StringBuilder一致他们的父类都是AbstractStringBuilderpublic class Test03 { //这是一个main方法是程序的入口 public static void main(String[] args) { StringBuffer sb new StringBuffer(nihaojavawodeshijie); //增 sb.append(这是梦想); System.out.println(sb);//nihaojavawodeshijie这是梦想 //删 sb.delete(3,6);//删除位置在[3,6)上的字符 System.out.println(sb);//nihavawodeshijie这是梦想 sb.deleteCharAt(16);//删除位置在16上的字符 System.out.println(sb);//nihavawodeshijie是梦想 //改--插入 StringBuffer sb1 new StringBuffer($23445980947); sb1.insert(3,,);//在下标为3的位置上插入 , System.out.println(sb1);//$23,445980947 //改--替换 StringBuffer sb2 new StringBuffer($2你好吗5980947); sb2.replace(3,5,我好累);//在下标[3,5)位置上插入字符串 System.out.println(sb2);//$2你我好累5980947 sb.setCharAt(3,!); System.out.println(sb);//nih!vawodeshijie是梦想 //查 StringBuffer sb3 new StringBuffer(asdfa); for (int i 0; i sb3.length(); i) { System.out.print(sb3.charAt(i)\t);//a s d f a } System.out.println(); //截取 String str sb3.substring(2,4);//截取[2,4)返回的是一个新的String对StringBuilder没有影响 System.out.println(str);//df //反转 sb3.reverse(); System.out.println(sb3);//afdsa } }String和StringBuilder最大的区别在于String的内容无法修改而StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuilder。注意String和StringBuilder类不能直接转换。如果要想互相转换可以采用如下原则:String变为StringBuilder: 利用StringBuilder的构造方法或append()方法StringBuilder变为String: 调用toString()方法。面试题StringStringBuilderStringBuffer之间的区别和联系1String、StringBuffer、StringBuilder区别与联系1. String类是不可变类即一旦一个String对象被创建后包含在这个对象中的字符序列是不可改变的直至这个对象销毁。2. StringBuffer类则代表一个字符序列可变的字符串可以通过append、insert、reverse、setChartAt、setLength等方法改变其内容。一旦生成了最终的字符串调用toString方法将其转变为String。3. JDK1.5新增了一个StringBuilder类与StringBuffer相似构造方法和方法基本相同。StringBuffer采用同步处理属于线程安全操作而StringBuilder未采用同步处理属于线程不安全操作即StringBuffer是线程安全的而StringBuilder是线程不安全的所以性能略高。通常情况下创建一个内容可变的字符串应该优先考虑使用StringBuilderStringBuilder:JDK1.5开始 效率高 线程不安全StringBuffer:JDK1.0开始 效率低 线程安全StringBuilder没有synchronized而StringBuffer有synchronized2以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】String str new String(ab); // 会创建多少个对象 String str new String(a) new String(b); // 会创建多少个对象答案1如果字符串常量池中不存在ab会创建 2 个String对象一个在字符串常量池中另一个在堆内存中。答案2常量池中的a对象如果之前不存在。常量池中的b对象如果之前不存在。堆内存中的new String(a)对象。堆内存中的new String(b)对象。StringBuilder对象。StringBuilder.toString()创建的包含ab的String对象。所以总共会创建 5 个String对象和 1 个StringBuilder对象。上面的两题情况都是字符串常量池中不存在“xxx”下面我们来看一下如果字符串常量中已经存在了xxx并且跟之前做对比从对应的字节码角度分析1、如果字符串常量池中不存在字符串对象“abc”的引用那么它会在堆上创建两个字符串对象其中一个字符串对象的引用会被保存在字符串常量池中。String s1 new String(abc);对应的字节码ldc命令用于判断字符串常量池中是否保存了对应的字符串对象的引用如果保存了的话直接返回如果没有保存的话会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。2、如果字符串常量池中已存在字符串对象“abc”的引用则只会在堆中创建 1 个字符串对象“abc”。// 字符串常量池中已存在字符串对象“abc”的引用String s1 abc;// 下面这段代码只会在堆中创建 1 个字符串对象“abc”String s2 new String(abc);对应的字节码在这段代码中我们不再对上面的字节码进行详细注释。位置 7 的ldc指令不会在堆中创建新的字符串对象“abc”因为在位置 0ldc指令已经创建了一个字符串对象“abc”。所以位置 7 的ldc指令会直接返回字符串常量池中已有的“abc”对象的引用而不是重新创建一个新的对象。字符串常量池1.创建对象的思考下面两种创建String对象的方式相同吗public static void main(String[] args) { String s1 hello; String s2 hello; String s3 new String(hello); String s4 new String(hello); System.out.println(s1 s2); // true System.out.println(s1 s3); // false System.out.println(s3 s4); // false }上述程序创建方式类似为什么s1和s2引用的是同一个对象而s3和s4不是呢在Java程序中类似于1 2 33.14“hello”等字面类型的常量经常频繁使用为了使程序的运行速度更快、更节省内存Java为8种基本数据类型和String类都提供了常量池。池是编程中的一种常见的, 重要的提升效率的方式, 未来的学习中会遇到各种 内存池, 线程池, 数据库连接池 ....比如家里给大家打生活费的方式1家里经济拮据每月定时打生活费有时可能会晚最差情况下可能需要向家里张口要速度慢2家里有矿一次性打一年的生活费放到银行卡中自己随用随取速度非常快方式2就是池化技术的一种示例钱放在卡上随用随取效率非常高。常见的池化技术比如数据库连接池、线程池等。为了节省存储空间以及程序的运行效率Java中引入了Class文件常量池每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息运行时常量池在.Class文件被加载时.Class文件中的常量池被加载到内存中称为运行时常量池运行时常量池每个类都有一份字符串常量池2. 字符串常量池(StringTable)字符串常量池在JVM中是StringTable类实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构)不同JDK版本下字符串常量池的位置以及默认大小是不同的3. 再谈String对象创建由于不同JDK版本对字符串常量池的处理方式不同此处在 Java8 HotSpot 上分析1直接使用字符串常量进行赋值public static void main(String[] args) { String s1 hello; String s2 hello; System.out.println(s1 s2); // true }2通过new创建String类对象练习题观察程序的输出结果结果good and gbc3intern方法intern 是一个native方法(Native方法指底层使用C实现的看不到其实现的源代码)该方法的作用是手动将创建的String对象添加到常量池中。public static void main(String[] args) { char[] ch new char[]{a, b, c}; String s1 new String(ch); // s1对象并不在常量池中 //s1.intern(); // s1.intern()调用之后会将s1对象的引用放入到常量池中 String s2 abc; // abc 在常量池中存在了s2创建时直接用常量池中abc的引用 System.out.println(s1 s2);// 输出false } // 将上述s1.intern()方法打开之后就会输出true结论只要是new的对象都是唯一的。通过上面例子可以看出使用常量串创建String类型对象的效率更高而且更节省空间。用户也可以将创建的字符串对象通过intern方式添加进字符串常量池中。注意在Java6 和 Java7、8中Intern的实现会有些许的差别。String类的OJ练习1字符串中的第一个唯一的字符(只包含小写字母)题目链接https://leetcode.cn/problems/first-unique-character-in-a-string/class Solution { public int firstUniqChar(String s) { //int[] count new int[256]; int[] count new int[26]; // 统计每个字符出现的次数 for (int i 0; i s.length(); i) { count[s.charAt(i) - a]; } // 找第一个只出现一次的字符 for (int i 0; i s.length(); i) { if (1 count[s.charAt(i) - a]) { return i; } } return -1; } } //通用版本 不局限字母大小写 class Solution { public int firstUniqChar(String s) { int[] count new int[256]; for(int i 0; i s.length(); i){ count[s.charAt(i)]; } for(int i 0; i s.length(); i){ if(count[s.charAt(i)] 1){ return i; } } return -1; } }2字符串中最后一个单词的长度题目链接https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId37tqId21224rp5ru/activity/ojqru/ta/huawei/question-rankingimport java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static void main(String[] args) { Scanner in new Scanner(System.in); // 注意 hasNext 和 hasNextLine 的区别 while (in.hasNextLine()) { // 注意 while 处理多个 case String s in.nextLine(); // 1. 找到最后一个空格 // 2. 获取最后一个单词从最后一个空格1位置开始一直截取到末尾 // 3. 打印最后一个单词长度 int index s.lastIndexOf( ); String ret s.substring(index1); System.out.println(ret.length()); } } }3验证回文串题目链接https://leetcode.cn/problems/valid-palindrome/class Solution { public boolean isPalindrome(String s) { s s.toLowerCase(); int left 0; int right s.length() - 1; while (left right) { //不是有效字符就遍历下个字符有可能有多个空格所以要用while还有个前提是全程一直要left right因为有可能一直都没有遇到有效字符left right while (left right !isValidChar(s.charAt(left))) { left; } //left是有效字符了 while (left right !isValidChar(s.charAt(right))) { right--; } //right是有效字符了 if (s.charAt(left) ! s.charAt(right)) { return false; } else { left; right--; } } return true; } private boolean isValidChar(char ch) { if (Character.isDigit(ch) || Character.isLetter(ch)) { return true; } return false; /*if (ch 0 ch 9 || ch a ch z) { return true; } return false;*/ } }(4)华为研发工程师编程题——字符集合题目链接字符集合__牛客网方法2中的数组空间解释图↓//方法1 import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNextLine()) { String str sc.nextLine(); StringBuilder sb new StringBuilder(); for (int i 0; i str.length(); i) { char ch str.charAt(i); if (!sb.toString().contains(ch )) {//contains里面只能是字符串序列ch是单个字符ch就变成了字符串 sb.append(ch); } } System.out.println(sb); } sc.close(); } } //方法2 import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNextLine()) { String str sc.nextLine(); // 用来标记该字符是否出现过 int[] count new int[58];//一般来不及思考数组空间就写new int[256] StringBuilder sb new StringBuilder(); for (int i 0; i str.length(); i) { char ch str.charAt(i); // 如果标记是0说明该字符是第一次出现 if (0 count[ch - A]) {//注意这里一定是-A大写因为此题目还有大小写区分跟之前题目str规定了全是小写的不同 count[ch - A] 1; sb.append(ch); } } System.out.println(sb); } sc.close(); } } //方法3 import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNextLine()) { String str sc.nextLine(); // 用来标记该字符是否出现过 char[] flag new char[256]; StringBuilder sb new StringBuilder(); for (int i 0; i str.length(); i) { char ch str.charAt(i); // 如果标记是0说明该字符是第一次出现 if (0 flag[ch]) { flag[ch] 1; sb.append(ch); } } System.out.println(sb); } sc.close(); } } //方法4 实际上就是封装了下方法 然后将判断的条件多了一个布尔数组来实现 import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNextLine()) { String str sc.nextLine(); String ret func(str); System.out.println(ret); } sc.close(); } private static String func(String str) { boolean[] flag new boolean[127];//用来标记该字符是否出现过 //char[] flag new char[127]; StringBuilder sb new StringBuilder(); for (int i 0; i str.length(); i) { char ch str.charAt(i); if (flag[ch] false) {//如果标记是false说明该字符是第一次出现 sb.append(ch); flag[ch] true; } /*if (flag[ch] 0) {如果标记是0说明该字符是第一次出现 sb.append(ch); flag[ch] 1; }*/ } return sb.toString(); } }