1

OMG,12 个精致的 Java 字符串操作小技巧,学它

 2 years ago
source link: http://www.androidchina.net/10985.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

字符串可以说是 Java 中最具有代表性的类了,似乎没有之一哈,这就好像直播界的李佳琪,脱口秀中的李诞,一等一的大哥地位。不得不承认,最近吐槽大会刷多了,脑子里全是那些段子,写文章都有点不由自主,真的是,手不由己啊。

字符串既然最常用,那就意味着面试官好这一口,就喜欢问一些字符串方面的编码技巧,来测试应聘者是否技术过硬,底子扎实,对吧?

那这次,我就来盘点 12 个精致的 Java 字符串操作小技巧,来帮助大家提高一下下。在查看我给出的答案之前,最好自己先动手尝试一遍,写不出来答案没关系,先思考一遍,看看自己的知识库里是不是已经有解决方案,有的话,就当是温故复习了,没有的话,也不要担心,刚好学一遍。

01、如何在字符串中获取不同的字符及其数量?

这道题可以拆解为两个步骤,第一步,找出不同的字符,第二步,统计出它们的数量。好像有点废话,是不是?那我先来一个答案吧。

public class DistinctCharsCount {
    public static void main(String[] args) {
        printDistinctCharsWithCount("itwanger");
        printDistinctCharsWithCount("chenmowanger");
    }

    private static void printDistinctCharsWithCount(String input) {
        Map<Character, Integer> charsWithCountMap = new LinkedHashMap<>();

        for (char c : input.toCharArray()) {
            Integer oldValue = charsWithCountMap.get(c);

            int newValue = (oldValue == null) ? 1 :
                    Integer.sum(oldValue, 1);
            
            charsWithCountMap.put(c, newValue);
        }
        System.out.println(charsWithCountMap);
    }
}

程序输出的结果是:

{i=1, t=1, w=1, a=1, n=1, g=1, e=1, r=1}
{c=1, h=1, e=2, n=2, m=1, o=1, w=1, a=1, g=1, r=1}

说一下我的思路:

1)声明一个 LinkedHashMap,也可以用 HashMap,不过前者可以保持字符串拆分后的顺序,结果看起来更一目了然。

为什么要用 Map 呢?因为 Map 的 key 是不允许重复的,刚好可以对重复的字符进行数量的累加。

2)把字符串拆分成字符,进行遍历。

3)如果 key 为 null 的话,就表明它的数量要 +1;否则的话,就在之前的值上 +1,然后重新 put 到 Map 中,这样就覆盖了之前的字符数量。

思路很清晰,对不对?忍不住给自己鼓个掌。

那,JDK 8 之后,Map 新增了一个很厉害的方法 merge(),一次性为多个键赋值:

private static void printDistinctCharsWithCountMerge(String input) {
    Map<Character, Integer> charsWithCountMap = new LinkedHashMap<>();

    for (char c : input.toCharArray()) {
        charsWithCountMap.merge(c, 1, Integer::sum);
    }
    System.out.println(charsWithCountMap);
}

有没有很厉害?一行代码就搞定。第一个参数为键,第二个参数为值,第三个参数是一个 BiFunction,意思是,如果键已经存在了,就重新根据 BiFunction 计算新的值。

如果字符是第一次出现,就赋值为 1;否则,就把之前的值 sum 1。

02、如何反转字符串?

如果同学们对 StringBuilder 和 StringBuffer 很熟悉的话,这道题就很简单,直接 reverse() 就完事,对不对?

public class ReverseAString {
    public static void main(String[] args) {
        reverseInputString("沉默王二");
    }
    private static void reverseInputString(String input) {
        StringBuilder sb = new StringBuilder(input);
        String result = sb.reverse().toString();
        System.out.println(result);
    }
}

输出结果如下所示:

二王默沉

多说一句,StringBuffer 和 StringBuilder 很相似,前者是同步的,所有 public 方法都加了 synchronized 关键字,可以在多线程中使用;后者是不同步的,没有 synchronized 关键字,所以性能更佳,没有并发要求的话,就用 StringBuilder。

03、如何判断一个字符串是前后对称的?

什么意思呢?就好像一个字符串,前后一折,是对称的。就像你站在镜子前,看到了一个玉树临风、闭月羞花的自己。

public class PalindromeString {
    public static void main(String[] args) {

        checkPalindromeString("沉默王二");
        checkPalindromeString("沉默王二 二王默沉");
    }

    private static void checkPalindromeString(String input) {
        boolean result = true;
        int length = input.length();
        for (int i = 0; i < length / 2; i++) {
            if (input.charAt(i) != input.charAt(length - i - 1)) {
                result = false;
                break;
            }
        }
        System.out.println(input + " 对称吗? " + result);

    }
}

输出结果如下所示:

沉默王二 对称吗? false
沉默王二 二王默沉 对称吗? true

说一下我的思路:要判断字符串对折后是否对称,很简单,从中间劈开,第一个字符对照最后一个字符,一旦找到不等的那个,就返回 false。

注意三点:

1)for 循环的下标从 0 开始,到 length/2 结束。

2)下标 i 和 length-i-1 是对称的。

3)一旦 false 就 break。

04、如何删除所有出现的指定字符?

字符串类没有提供 remove() 方法,但提供了 replaceAll() 方法,通过将指定的字符替换成空白字符就可以办得到,对吧?

public class RemoveCharFromString {
    public static void main(String[] args) {
        removeCharFromString("沉默王二", '二');
        removeCharFromString("chenmowanger", 'n');

    }

    private static void removeCharFromString(String input, char c) {
        String result = input.replaceAll(String.valueOf(c), "");
        System.out.println(result);
    }
}

输出结果如下所示:

沉默王
chemowager

05、如何证明字符串是不可变的?

字符串不可变的这个事我曾写过两篇文章,写到最后我都要吐了。但是仍然会有一些同学弄不明白,隔段时间就有人私信我,我就不得不把之前的文章放到收藏夹,问的时候我就把链接发给他。

之所以造成这个混乱,有很多因素,比如说,Java 到底是值传递还是引用传递?字符串常量池是个什么玩意?

这次又不得不谈,虽然烦透了,但仍然要证明啊!

public class StringImmutabilityTest {
    public static void main(String[] args) {
        String s1 = "沉默王二";
        String s2 = s1;
        System.out.println(s1 == s2);

        s1 = "沉默王三";
        System.out.println(s1 == s2);

        System.out.println(s2);
    }
}

输出结果如下所示:

true
false
沉默王二

1)String s1 = "沉默王二",Java 在字符串常量池中创建“沉默王二”这串字符的对象,并且把地址引用赋值给 s1

2)String s2 = s1,s2 和 s1 指向了同一个地址引用——常量池中的那个“沉默王二”。

所以,此时 s1 == s2 为 true。

3) s1 = "沉默王三",Java 在字符串常量池中创建“沉默王三”这串字符的对象,并且把地址引用赋值给 s1,但 s2 仍然指向的是“沉默王二”那串字符对象的地址引用。

所以,此时 s1 == s2 为 false,s2 的输出结果为“沉默王二”就证明了字符串是不可变的。

06、如何统计字符串中的单词数?

这道题呢?主要针对的是英文字符串的情况。虽然中文字符串中也可以有空白字符,但不存在单词这一说。

public class CountNumberOfWordsInString {
    public static void main(String[] args) {
        countNumberOfWords("My name is Wanger");
        countNumberOfWords("I Love Java Programming");
        countNumberOfWords(" Java   is  very   important ");
    }

    private static void countNumberOfWords(String line) {
        String trimmedLine = line.trim();
        int count = trimmedLine.isEmpty() ? 0 : trimmedLine.split("\\s+").length;

        System.out.println(count);
    }
}

输出结果如下所示:

4
4
4

split() 方法可以对字符串进行拆分,参数不仅可以是空格,也可以使正则表达式代替的空白字符(多个空格、制表符);返回的是一个数组,通过 length 就可以获得单词的个数了。

如果对 split() 方法很感兴趣的话,可以查看我之前写的一篇文章,很饱满,很丰富。

咦,拆分个字符串都这么讲究

07、如何检查两个字符串中的字符是相同的?

如何理解这道题呢?比如说,字符串“沉默王二”和“沉王二默”就用了同样的字符,对吧?比如说,字符串“沉默王二”和“沉默王三”用的字符就不同,理解了吧?

public class CheckSameCharsInString {
    public static void main(String[] args) {
        sameCharsStrings("沉默王二", "沉王二默");
        sameCharsStrings("沉默王二", "沉默王三");
    }

    private static void sameCharsStrings(String s1, String s2) {
        Set<Character> set1 = s1.chars().mapToObj(c -> (char) c).collect(Collectors.toSet());
        System.out.println(set1);
        Set<Character> set2 = s2.chars().mapToObj(c -> (char) c).collect(Collectors.toSet());
        System.out.println(set2);
        System.out.println(set1.equals(set2));
    }
}

输出结果如下所示:

[默, 沉, 王, 二]
[默, 沉, 王, 二]
true
[默, 沉, 王, 二]
[默, 沉, 三, 王]
false

上面的代码用到了 Stream 流,看起来很陌生,但很好理解,就是把字符串拆成字符,然后收集到 Set 中,Set 是一个不允许有重复元素的集合,所以就把字符串中的不同字符收集起来了。

08、如何判断一个字符串包含了另外一个字符串?

这道题有点简单,对吧?上一道还用 Stream 流,这道题就直接送分了?不用怀疑自己,就用字符串类的 contains() 方法。

public class StringContainsSubstring {
    public static void main(String[] args) {
        String s1 = "沉默王二";
        String s2 = "沉默";

        System.out.println(s1.contains(s2));
    }
}

输出结果如下所示:

true

contains() 方法内部其实调用的是 indexOf() 方法:

public boolean contains(CharSequence s) {
    return indexOf(s.toString()) >= 0;
}

09、如何在不用第三个变量的情况下交换两个字符串?

这道题就有点意思了,对吧?尤其是前提条件,不使用第三个变量。

public class SwapTwoStrings {
    public static void main(String[] args) {
        String s1 = "沉默";
        String s2 = "王二";

        s1 = s1.concat(s2);
        s2 = s1.substring(0,s1.length()-s2.length());
        s1 = s1.substring(s2.length());

        System.out.println(s1);
        System.out.println(s2);
    }
}

输出结果如下所示:

王二
沉默

说一下我的思路:

1)通过 concat() 方法把两个字符串拼接到一块。

2)然后通过 substring() 方法分别取出第二个字符串和第一个字符串。

10、如何从字符串中找出第一个不重复的字符?

来,上个例子来理解一下这道题。比如说字符串“沉默王沉沉默二”,第一个不重复的字符是“王”,对吧?因为“沉”重复了,“默”重复了。

public class FindNonRepeatingChar {
    public static void main(String[] args) {
        System.out.println(printFirstNonRepeatingChar("沉默王沉沉默二"));
        System.out.println(printFirstNonRepeatingChar("沉默王沉"));
        System.out.println(printFirstNonRepeatingChar("沉沉沉"));
    }

    private static Character printFirstNonRepeatingChar(String string) {
        char[] chars = string.toCharArray();

        List<Character> discardedChars = new ArrayList<>();

        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];

            if (discardedChars.contains(c))
                continue;

            for (int j = i + 1; j < chars.length; j++) {
                if (c == chars[j]) {
                    discardedChars.add(c);
                    break;
                } else if (j == chars.length - 1) {
                    return c;
                }
            }
        }
        return null;
    }
}

输出结果如下所示:

王
默
null

说一下我的思路:

1)把字符串拆分成字符数组。

2)声明一个 List,把重复的字符放进去。

3)外层的 for 循环,从第一个字符开始,如果已经在 List 中,继续下一轮。

4)嵌套的 for 循环,从第一个字符的下一个字符(j = i + 1)开始遍历,如果找到和之前字符重复的,就加入到 List 中,跳出内层的循环;如果找到最后(j == chars.length - 1)也没有找到,就是第一个不重复的字符,对吧?

11、如何检查字符串中只包含数字?

有一种很傻的解法,就是用 Long.parseLong(string) 对字符串强转,如果转不成整形,那肯定不是只包含数字,对吧?

但这种方法也太不可取了,所以还得换一种巧妙的,就是使用正则表达式。

public class CheckIfStringContainsDigitsOnly {
    public static void main(String[] args) {
        digitsOnlyString("123 沉默王二");
        digitsOnlyString("123");

    }

    private static void digitsOnlyString(String string) {
        if (string.matches("\\d+")) {
            System.out.println("只包含数字的字符串:" + string);
        }
    }
}

输出结果如下所示:

只包含数字:123

12、如何实现字符串的深度拷贝?

由于字符串是不可变的,所以可以直接使用“=”操作符将一个字符串拷贝到另外一个字符串,并且互不影响。

public class JavaStringCopy {
    public static void main(String args[]) {
        String str = "沉默王二";
        String strCopy = str;

        str = "沉默王三";
        System.out.println(strCopy);
    }
}

输出结果如下所示:

沉默王二

这个例子和之前证明字符串是不可变的例子几乎没什么差别,对吧?这的确是因为字符串是不可变的,如果是可变对象的话,深度拷贝就要注意了,最好使用 new 关键字返回新的对象。

public Book getBook() {
    Book clone = new Book();
    clone.setPrice(this.book.getPrice());
    clone.setName(this.book.getName());
    return clone;
}

关于不可变对象,请点击下面的链接查看我之前写了一篇文章。

这次要说不明白immutable类,我就怎么地

希望这 12 个精致的字符串操作小技巧可以帮助大家巩固一波基础,反正我自己已经重新巩固了一波,很有收获的样子,感觉就像是“一群小精灵在我脑子里跳舞一样”,学它就对了!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK