JavaRush /Java 博客 /Random-ZH /Java 中的字符串(类 java.lang.String)
Viacheslav
第 3 级

Java 中的字符串(类 java.lang.String)

已在 Random-ZH 群组中发布

介绍

程序员的道路是一个复杂而漫长的过程。在大多数情况下,它以一个在屏幕上显示 Hello World 的程序开始。Java 也不例外(请参阅课程:“Hello World!”应用程序)。正如我们所看到的,消息是使用以下方式输出的:System.out.println("Hello World!"); 如果您查看 Java API,则System.out.println方法将String作为输入参数。我们将讨论此类数据。

字符串作为字符序列

其实String从英文翻译过来就是字符串。没错,String类型代表的是文本字符串。什么是文本字符串?文本字符串是某种彼此跟随的有序字符序列。符号是字符。顺序——顺序。所以是的,绝对正确,String 是java.lang.CharSequence. 如果你查看 String 类本身的内部,那么在它的内部就只有一个字符数组:private final char value[]; 它有java.lang.CharSequence一个相当简单的契约:
Java 中的字符串(类 java.lang.String) - 1
我们有一个获取元素数量的方法,获取特定元素和获取一组元素+ toString 方法本身,该方法将返回 this)了解 Java 8 中提供给我们的方法更有趣,这是:chars()回想codePoints() 一下 Oracle 教程中的“原始数据”类型,char 是single 16-bit Unicode character。也就是说,本质上 char 只是 int(32 位)大小一半的类型,表示从 0 到 65535 的数字(请参阅十进制值在ASCII 表中)。也就是说,如果我们愿意,我们可以将 char 表示为 int。Java 8 利用了这一点。从 Java 版本 8 开始,我们有了IntStream——一个用于处理原始 int 的流。因此,在 charSequence 中可以获得表示 chars 或 codePoints 的 IntStream。在继续讨论之前,我们将通过一个示例来展示这种方法的便利性。让我们使用Tutorialspoint在线java编译器并执行代码:
public static void main(String []args){
        String line = "aaabccdddc";
        System.out.println( line.chars().distinct().count() );
}
现在您可以通过这种简单的方式获得许多独特的符号。

代码点

所以,我们看到了字符。现在还不清楚这些是什么类型的代码点。codePoint的概念出现是因为当Java出现时,16位(半个int)足以编码一个字符。因此,java中的char以UTF-16格式(“Unicode 88”规范)表示。后来出现了Unicode 2.0,其概念是将一个字符表示为代理对(2个字符)。这使我们能够将可能值的范围扩展到 int 值。有关更多详细信息,请参阅 stackoverflow:“将字符与代码点进行比较? ” JavaDoc for Character中也提到了 UTF-16 。在 JavaDoc 中,据说: In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogates range, (\uD800-\uDBFF), the second from the low-surrogates range (\uDC00-\uDFFF). 用标准字母表重现这一点是相当困难的(甚至可能是不可能的)。但符号并非以字母和数字结尾。在日本,他们想出了一种难以编码的东西,即表情符号——表意文字和表情符号的语言。维基百科上有一篇关于此的有趣文章:“ Emoji ”。让我们找一个表情符号的例子,例如:“ Emoji Ghost ”。正如我们所看到的,甚至在那里指示了相同的代码点(值 = U+1F47B)。它以十六进制格式表示。如果我们转换为十进制数,我们会得到 128123。这超过了允许的 16 位(即超过 65535)。我们来复制一下:
Java 中的字符串(类 java.lang.String) - 2
不幸的是,JavaRush 平台不支持文本中的此类字符。因此,在下面的示例中,您需要将一个值插入到 String 中。因此,现在我们将了解一个简单的测试:
public static void main(String []args){
	    String emojiString = "Вставте сюда эмоджи через ctrl+v";
	    //На один emojiString приходится 2 чара (т.к. не влезает в 16 бит)
	    System.out.println(emojiString.codePoints().count()); //1
	    System.out.println(emojiString.chars().count()); //2
}
正如您所看到的,在这种情况下,1 个代码点对应 2 个字符。这就是魔法。

特点

正如我们上面看到的,Java 中的字符串由 char 组成。原始类型允许您存储值,但是java.lang.Character原始类型的包装器允许您使用此符号执行许多有用的操作。例如,我们可以将字符串转换为大写:
public static void main(String[] args) {
    String line = "организация объединённых наций";
    char[] chars = line.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        if (i == 0 || chars[i - 1] == ' ') {
            chars[i] = Character.toUpperCase(chars[i]);
        }
    }
    System.out.println(new String(chars));
}
嗯,各种有趣的东西:,,,,,,isAlphabetic()isLetter()例如,括号。 ' ('有镜像')') 。isSpaceChar()isDigit()isUpperCase()isMirrored()

字符串池

Java中的字符串是不可变的,即常量。java.lang.String类本身的 JavaDoc 中也指出了这一点。其次,也是非常重要的,字符串可以指定为文字:
String literalString = "Hello, World!";
String literalString = "Hello, World!";
也就是说,如上所述,任何带引号的字符串实际上都是一个对象。这就引出了一个问题 - 如果我们如此频繁地使用字符串并且它们通常是相同的(例如,文本“Error”或“Successively”),有什么方法可以确保不会每次都创建字符串?顺便说一下,我们还有 Map,其中键可以是字符串。那么我们绝对不能让相同的字符串成为不同的对象,否则我们将无法从Map中获取对象。Java 开发人员想了又想,最后想出了字符串池。这是存储字符串的地方,你可以称之为字符串缓存。并不是所有的行本身都结束在那里,而是只有代码中通过文字指定的行才结束。您可以自己向池中添加一条线,稍后会详细介绍。所以,在内存中我们在某个地方有这个缓存。一个公平的问题:这个游泳池位于哪里?这个问题的答案可以在 stackoverflow 上找到:“ Java 的字符串常量池位于哪里,堆还是栈?” 它位于Heap内存中,一个特殊的运行时常量池区域。当虚拟机从方法区(Java 虚拟机内的所有线程都可以访问的堆中的一个特殊区域)创建类或接口时,就会分配运行时常量池。字符串池给我们带来了什么?这有几个优点:
  • 不会创建相同类型的对象
  • 通过引用进行比较比通过 equals 进行逐个字符比较更快
但是如果我们想将创建的对象放入这个缓存中怎么办?然后,我们有一个特殊的方法:String.intern 这个方法将一个字符串添加到字符串池中。值得注意的是,这不仅仅是某种数组形式的缓存(对于整数)。实习生方法被指定为“native”。这意味着该方法本身是用另一种语言(主要是 C++)实现的。对于基本 Java 方法,可以在 JVM 级别对其应用各种其他优化。一般来说,魔法会在这里发生。阅读以下关于实习生的文章很有趣:https://habr.com/post/79913/#comment_2345814 这似乎是个好主意。但这将如何影响我们呢?但确实会有影响)
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal");
    System.out.println(test == test2);
}
正如您所看到的,行是相同的,但结果将是错误的。这都是因为 == 不是按值比较,而是按引用比较。这就是它的工作原理:
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test == test2);
}
请注意,我们仍然会创建新的字符串。也就是说,intern会从缓存中返回给我们一个String,但是我们在缓存中搜索到的原始String会被扔掉进行清理,因为 没有人知道他的事。这显然是不必要的资源消耗 =( 因此,您应该始终使用 equals 来比较字符串,以尽可能避免突然且难以检测的错误。
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test.equals(test2));
}
Equals 执行逐个字符的字符串比较。

级联

Как мы помним, строки можно складывать. И How мы помним строки у нас неизменяемы. Так How же тогда это работает? Всё верно, создаётся новая строка, которая состоит из символов складываемых an objectов. Существует миллион версий о том, How работает конкатенация через плюс. Кто-то считает что будет каждый раз новый an object, кто-то считает что будет ещё что-то. Но прав может быть кто-то один. И этот кто-то – компилятор javac. Воспользуемся service онлайн компилятора и выполним:
public class HelloWorld {

    public static void main(String[] args) {
        String helloMessage = "Hello, ";
        String target = "World";
        System.out.println(helloMessage + target);
    }

}
Теперь сохраним это How zip архив, извлечём в каталог и выполним: javap –c HelloWorld И тут мы всё узнаем:
Java 中的字符串(类 java.lang.String) - 3
В цикле, конечно, лучше делать конкатенацию через StringBuilder самим. И не потому что Howая-то магия, а чтобы StringBuilder создавался до цикла, а в самом цикле происходил только append. Кстати, тут есть ещё одна интересность. Есть отличная статья: «Обработка строк в Java. Часть I: String, StringBuffer, StringBuilder». Много полезного в комментариях. Например, указано, что при конкатенации вида new StringBuilder().append()...toString() действует intrinsic оптимизация, регулируемая опцией -XX:+OptimizeStringConcat, которая по умолчанию включена. intrinsic - переводится How "внутренний". Такие вещи JVM обрабатывает особенным образом, обрабатывая их How Native, только без дополнительных затрат на JNI. Подробнее: "Intrinsic Methods in HotSpot VM".

StringBuilder и StringBuffer

Как мы выше видели, StringBuilder очень полезный инструмент. Строки являются immutable, т.е. неизменяемыми. А складывать хочется. Поэтому, нам в помощь даны 2 класса: StringBuilder и StringBuffer. Основное отличие между ними в том, что StringBuffer появился в JDK1.0, в то время How StringBuilder пришёл в java 1.5 How не синхронизированная version StringBuffer, чтобы снять повышенные затраты на ненужную синхронизацию методов. Оба эти классы являются реализацией абстрактного класса AbstractStringBuilder - A mutable sequence of characters. Внутри хранится массив чаров, который расширяется по правилу: value.length * 2 + 2. По умолчанию размер (capacity) у StringBuilder'а equals 16.

Comparable

Строки являются comparable, т.е. реализуют метод compareTo. Выполняется это при помощи посимвольного сравнения. Интересно, что из двух строк выбирается минимальная длинна и по ней выполняется цикл. Поэтому, compareTo вернёт or разницу между int значениями первых несовпавших символов в пределе наименьшей из длинн строк, либо вернёт разницу между длиннами строк, если в пределах минимальной длинны строки все символы совпадают. Такое сравнение называется «лексикографическим».

Работа со строками Java

String имеет множество полезных методов:
Java 中的字符串(类 java.lang.String) - 4
На работу со строками сущесвует множество задач. Например, на Coding Bat. Так же есть курс на coursera: "Algorithms on Strings".

Заключение

即使是对该类的简短概述也会占用大量空间。这还不是全部。我强烈建议观看 JPoint 2015 的报告:Alexey Shipilev - Catechism java.lang.String
#维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION