JavaRush /Java Blog /Random-KO /Java의 문자열(클래스 java.lang.String)
Viacheslav
레벨 3

Java의 문자열(클래스 java.lang.String)

Random-KO 그룹에 게시되었습니다

소개

프로그래머의 길은 복잡하고 긴 과정입니다. 그리고 대부분의 경우 화면에 Hello World를 표시하는 프로그램으로 시작됩니다. Java도 예외는 아닙니다(단원: "Hello World!" 애플리케이션 참조 ). 보시다시피 메시지는 System.out.println("Hello World!"); Java API를 보면 System.out.println 메소드는 String을 입력 매개변수로 사용 하여 출력됩니다 . 이러한 유형의 데이터에 대해 설명합니다.

일련의 문자로서의 문자열

실제로 영어로 번역된 String 은 문자열입니다. 맞습니다. String 유형은 텍스트 문자열을 나타냅니다. 텍스트 문자열이란 무엇입니까? 텍스트 문자열은 서로 이어지는 일종의 정렬된 문자 시퀀스입니다. 기호는 char입니다. 순서 – 순서. 그렇습니다. 절대적으로 맞습니다. String은 java.lang.CharSequence. 그리고 String 클래스 자체를 살펴보면 그 안에는 문자 배열 외에는 아무것도 없습니다. 매우 간단한 계약이 private final char value[]; 있습니다 .java.lang.CharSequence
Java의 문자열(클래스 java.lang.String) - 1
요소 수를 가져오고, 특정 요소를 가져오고, 요소 집합 + toString 메서드 자체를 가져오는 메서드가 있습니다. 이 메서드는 이를 반환합니다. Java 8에서 제공되는 메서드를 이해하는 것이 더 흥미롭습니다. : chars()그리고 Oracle의 " 원시 데이터" 유형codePoints() " 의 튜토리얼에서 char은 입니다 . 즉, 본질적으로 char는 0에서 65535까지의 숫자를 나타내는 int(32비트) 크기의 절반에 불과한 유형입니다(10진수 값 참조 ASCII 테이블 에서 ). 즉, 원한다면 char를 int로 표현할 수 있습니다. 그리고 Java 8은 이를 활용했습니다. Java 버전 8부터는 기본 int 작업을 위한 스트림인 IntStream이 있습니다. 따라서 charSequence에서는 char 또는 codePoint를 나타내는 IntStream을 얻을 수 있습니다. 계속 진행하기 전에 이 접근 방식의 편리함을 보여주는 예를 살펴보겠습니다. Tutorialspoint 온라인 Java 컴파일러를 사용 하고 코드를 실행해 보겠습니다. single 16-bit Unicode character
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" 사양)으로 표시됩니다. 나중에 유니코드 2.0이 등장했는데, 그 개념은 문자를 서로게이트 쌍(2자)으로 표현하는 것이었습니다. 이를 통해 가능한 값의 범위를 int 값으로 확장할 수 있었습니다. 자세한 내용은 stackoverflow: " 문자를 코드 포인트와 비교하시겠습니까? "를 참조하세요. UTF-16은 JavaDoc for Character 에도 언급되어 있습니다 . 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). 이것을 표준 알파벳으로 재현하는 것은 매우 어렵습니다(아마도 불가능할 수도 있습니다). 그러나 기호는 문자와 숫자로 끝나지 않습니다. 일본에서는 표의 문자와 이모티콘의 언어인 이모티콘으로 인코딩하기가 매우 어려운 것을 생각해 냈습니다. Wikipedia에 이에 대한 흥미로운 기사가 ​​있습니다: “ Emoji ”. 예를 들어 " Emoji Ghost " 와 같은 이모티콘의 예를 찾아보겠습니다 . 보시다시피 동일한 codePoint가 여기에 표시됩니다(값 = U+1F47B). 16진수 형식으로 표시됩니다. 십진수로 변환하면 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 codePoint는 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!";
즉, 위에서 언급한 것처럼 인용된 문자열은 실제로 객체입니다. 문자열을 너무 자주 사용하고 종종 동일할 수 있는 경우(예: "오류" 또는 "성공적으로"라는 텍스트) 문자열이 매번 생성되지 않도록 할 수 있는 방법이 있습니까? 그건 그렇고, 키가 문자열일 수 있는 지도가 여전히 있습니다. 그러면 동일한 문자열이 다른 객체가 될 수 없습니다. 그렇지 않으면 맵에서 객체를 가져올 수 없습니다. Java 개발자는 String Pool을 생각하고 생각하고 생각해 냈습니다. 이곳은 문자열이 저장되는 곳으로, 문자열 캐시라고 부를 수 있습니다. 모든 줄 자체가 거기에 있는 것은 아니며 코드에서 리터럴로 지정된 줄만 해당됩니다. 풀에 라인을 직접 추가할 수 있지만 이에 대한 자세한 내용은 나중에 설명합니다. 따라서 메모리 어딘가에 이 캐시가 있습니다. 공정한 질문: 이 수영장은 어디에 있습니까? 이에 대한 답은 stackoverflow에서 찾을 수 있습니다. “ Java의 문자열 상수 풀은 힙 또는 스택 중 어디에 있습니까? " 이는 특수 런타임 상수 풀 영역의 힙 메모리에 있습니다. 런타임 상수 풀은 가상 머신이 메소드 영역 (Java Virtual Machine 내부의 모든 스레드가 액세스할 수 있는 힙의 특수 영역)에서 클래스나 인터페이스를 생성할 때 할당됩니다 . 스트링 풀은 우리에게 무엇을 제공합니까? 여기에는 몇 가지 장점이 있습니다.
  • 동일한 유형의 개체는 생성되지 않습니다.
  • 참조에 의한 비교는 등호를 통한 문자별 비교보다 빠릅니다.
하지만 생성된 객체를 이 캐시에 넣으려면 어떻게 해야 할까요? 그런 다음 특별한 메소드가 있습니다: String.intern 이 메소드는 문자열 풀에 문자열을 추가합니다. 이것은 단지 배열 형태의 캐시(정수의 경우)가 아니라는 점에 주목할 가치가 있습니다. 인턴 방법은 "네이티브"로 지정됩니다. 이는 메서드 자체가 다른 언어(주로 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);
}
우리는 여전히 새로운 문자열을 만들 것이라는 점에 유의하십시오. 즉, 인턴은 캐시에서 문자열을 반환하지만 캐시에서 검색한 원래 문자열은 정리를 위해 삭제됩니다. 그에 대해 아는 사람은 아무도 없습니다. 이는 명백히 불필요한 리소스 소비입니다 =( 따라서 갑작스럽고 감지하기 어려운 오류를 최대한 피하기 위해 항상 등호를 사용하여 문자열을 비교해야 합니다.
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test.equals(test2));
}
Equals는 문자별 문자열 비교를 수행합니다.

연쇄

우리가 기억하는 것처럼 줄을 추가할 수 있습니다. 그리고 우리가 기억하는 것처럼 문자열은 불변입니다. 그렇다면 어떻게 작동합니까? 맞습니다. 추가되는 객체의 기호로 구성된 새로운 선이 생성됩니다. 더하기 연결이 작동하는 방식에는 백만 가지 버전이 있습니다. 어떤 사람들은 매번 새로운 물건이 있을 것이라고 생각하고, 다른 사람들은 다른 물건이 있을 것이라고 생각합니다. 하지만 단 한 사람만이 옳을 수도 있습니다. 그리고 그 사람은 javac 컴파일러입니다. 온라인 컴파일러 서비스를 사용하여 다음을 실행해 보겠습니다.
public class HelloWorld {

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

}
이제 이것을 zip 아카이브로 저장하고 디렉토리에 추출한 후 실행해 보겠습니다. javap –c HelloWorld 여기서 모든 것을 알아냅니다.
Java의 문자열(클래스 java.lang.String) - 3
물론 루프에서는 StringBuilder를 통해 직접 연결하는 것이 더 좋습니다. 그리고 어떤 종류의 마법 때문이 아니라 StringBuilder가 주기 전에 생성되고 주기 자체에서는 추가만 발생합니다. 그런데 여기에 또 다른 흥미로운 점이 있습니다. 훌륭한 기사가 있습니다: “ Java의 문자열 처리. 파트 I: 문자열, StringBuffer, StringBuilder ." 댓글에 유용한 정보가 많이 있습니다. new StringBuilder().append()...toString()예를 들어, 뷰를 연결할 때 기본적으로 활성화되는 -XX:+OptimizeStringConcat 옵션에 의해 규제되는 본질적인 최적화가 적용되도록 지정됩니다 . 본질적인 - "내부"로 번역됩니다. JVM은 이러한 작업을 특별한 방식으로 처리하여 JNI의 추가 비용 없이 네이티브로 처리합니다. 자세한 내용은 " HotSpot VM의 내장 메서드 "를 참조하세요.

StringBuilder 및 StringBuffer

위에서 본 것처럼 StringBuilder는 매우 유용한 도구입니다. 문자열은 불변입니다. 즉, 불변. 그리고 접고 싶어요. 따라서 우리에게는 StringBuilder와 StringBuffer라는 두 가지 클래스가 제공됩니다. 둘 사이의 주요 차이점은 StringBuffer가 JDK1.0에 도입된 반면 StringBuilder는 불필요한 메소드 동기화로 인한 오버헤드 증가를 제거하기 위해 StringBuffer의 비동기화 버전으로 Java 1.5에 도입되었다는 것입니다. 이 두 클래스는 모두 추상 클래스 AbstractStringBuilder(변경 가능한 문자 시퀀스)의 구현입니다. 참의 배열은 value.length * 2 + 2 규칙에 따라 확장된 내부에 저장됩니다. 기본적으로 StringBuilder의 크기(용량)는 16입니다.

유사한

문자열은 비교할 수 있습니다. 즉, CompareTo 메서드를 구현합니다. 이는 문자별 비교를 사용하여 수행됩니다. 흥미롭게도 두 문자열에서 최소 길이가 선택되고 이에 대해 루프가 실행됩니다. 따라서 CompareTo는 일치하지 않는 첫 번째 문자의 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