>  기사  >  Java  >  Java 문자열 개요(1부)

Java 문자열 개요(1부)

黄舟
黄舟원래의
2017-03-14 11:34:441543검색



1. Java 메모리 모델 상수

1. Java 메모리 모델

타임 슬라이스 폴링을 기반으로 CPU 시간 리소스를 확보해야 합니다. Java 문자열 개요(1부)

따라서
  • 각 스레드에는 다음에 실행할 명령을 기록하기 위한 독립적인 프로그램 카운터가 있어야 하며, 이는 스레드 전용 메모리 영역입니다.

    JAVA 메소드가 실행되면 카운터는 실행 중인 Java 바이트코드 주소를 기록합니다. 네이티브 메소드가 실행되면 카운터는 비어 있습니다.

    가상 머신 스택 Thread와 동시에 생성되는 Thread-private은 JAVA의 실행을 관리하는 메모리 모델이다. 행동 양식. 스택은 주로 몇 가지 기본 유형의 변수

    데이터(int, short, long, byte,
  • float
  • , double, boolean, char) 및

    객체

    를 저장합니다. 인용 . 각 메소드가 실행되면 메소드의 변수 테이블, 피연산자 스택, 동적 링크 메소드, 반환 값, 반환 주소 및 기타 정보를 저장하기 위해 프레임 스택이 생성됩니다. 스택 크기에 따라 도달 가능한 메서드 호출 깊이가 결정됩니다(재귀 수준 수 또는 다른 메서드에 대한 중첩 호출 수준 수, -Xss 매개변수가 가상 머신 스택 크기를 설정할 수 있음). 스택 크기는 고정되거나 동적으로 확장될 수 있습니다. 요청된 스택 깊이가 사용 가능한 최대 깊이보다 크면 stackOverflow오류가 발생합니다. 스택이 동적으로 확장 가능하지만 확장을 지원할 메모리 공간이 없으면 OutofMemoryError가 발생합니다. jclasslib 도구를 사용하여 클래스 파일의 구조를 확인하세요. 다음 그림은 스택 프레임 구조 다이어그램을 보여줍니다. 로컬 메서드 영역

    은 다음과 유사한 기능을 가지고 있습니다. 가상 머신 스택이지만 관리하는 것은 JAVA 방식이 아닌 로컬 방식이며, 로컬 방식은 C로 구현된다. Java 문자열 개요(1부)

  • JAVA 힙

     은 스레드로 공유되며 모든 객체 인스턴스와 배열

    을 저장합니다. 가비지 수집을 위한 주요 영역입니다.
  • 힙은 클래스 객체가 공간을 할당하는 런타임 데이터 영역입니다. 이러한 객체는

    new

    , new

    array, anewarray 및 multianewarray와 같은 명령을 통해 생성됩니다. 명시적으로 해제하려면 프로그램 코드가 필요하지 않습니다. 힙은 신세대와 구세대(tenured)로 나눌 수 있습니다. New Generation은 새로 생성된 개체와 젊은 개체를 저장하는 데 사용됩니다. 개체가 재활용되지 않고 충분히 오래 지속되면 오래된 개체가 Old Generation으로 이동됩니다. 새로운 세대는 eden(Garden of Eden), 생존자Space0(s0, 우주에서), 생존자Space1(s1, tospace)로 더 세분화될 수 있습니다. 새로 생성된 객체는 eden에 들어가고, s0과 s1 모두 적어도 한 번은 GC를 거쳐 살아남았습니다. 일정 기간이 지난 후에도 살아남은 객체가 여전히 존재하는 경우 Old Generation(tenured)으로 들어갑니다.    메서드 영역 

    스레드 공유, 가상 객체 저장에 사용 상수, Java 문자열 개요(1부)정적

    변수, JIT(Just-In-Time) 컴파일러로 컴파일된 코드 등 기계에 로드된 클래스의 메타데이터 정보도 영구 생성됩니다.
  • 핫스팟 가상 머신이 클래스의 정의 정보를 사용하지 않을 것이라고 판단하면 해당 클래스도 재활용됩니다. 재활용을 위한 기본 조건은 최소한 클래스의 모든 인스턴스가 재활용되고 클래스를 로드하는 ClassLoader가 재활용되는 것입니다.

    2. 상수 풀

     상수 풀은 클래스 정보의 일부이며 클래스 정보는 메소드 영역에 해당하는 JVM 메모리 모델에 반영됩니다. 즉, 상수 풀은 메소드 영역에 위치합니다. . 상수 풀은 주로 두 가지 주요 상수인 리터럴 참조와 기호 참조를 저장합니다. 그 중 리터럴에는 주로 문자열 리터럴, 정수 리터럴 및 final으로 선언된 상수 값 등이 포함되며 기호 참조는 컴파일 원칙에 속합니다. 개념에는 다음 세 가지 유형의 상수가 포함됩니다.

    • 정규화된 클래스 이름 및 인터페이스

    • 필드 이름 및 설명자

    • 메소드 이름 및 설명자


    2. 상수 및 변수

    • 우리는 일반적으로 메모리 주소가 변하지 않지만 값이 바뀔 수 있는 것을 변수로 호출합니다. 즉, 메모리 주소가 변하지 않는다는 전제 하에 메모리의 내용이 가변적입니다. 예:

      public class String_2 {  
          public static void f(){  
              Human_1 h = new Human_1(1,30);  
              Human_1 h2 = h; 
              System.out.printf("h: %s\n", h.toString());   
              System.out.printf("h2: %s\n\n", h.toString());   
      
              h.id = 3;  
              h.age = 32;  
              System.out.printf("h: %s\n", h.toString());   
              System.out.printf("h2: %s\n\n", h.toString());   
      
              System.out.println( h == h2 );   // true : 引用值不变,即对象内存底子不变,但内容改变
          }
      }
      우리는 일반적으로 메모리 주소가 변경되지 않으면 값을 변경할 수 없는 것을
    • 이라고 부릅니다. 🎜>, 일반적인 문자열은 immutable하므로 상수(constant) 이라고 합니다. 또, final 키워드를 통해 상수를 정의할 수 있는데, 엄밀히 말하면 기본형만 수정 후 상수가 된다(기본형의 경우 그 값이 불변이고, 객체변수의 경우에는 그 값이 불변이라고 한다). 참조는 더 이상 변경할 수 없습니다.) 예:

      final int i = 5;

    • 3. 문자열 정의 및 기본

    문자열 선언
    1. 동일한 기본 문자 배열

      을 공유할 수 있습니다(예: 문자열

      String s="abc"Java 문자열 개요(1부)

      s.

      substr
      ing(1)

      동일한 문자 배열을 공유하는 것:
    2. char[] c = {'a','b','c'}
      . 그 중 전자의 off
    • set

      count의 값은 각각 0과 3이고, 후자의 offset과 count의 값은 각각 1과 2이다. . 예를 들어 하위 문자열 연산을 수행할 때 두 멤버 변수 offset과 count는 중복되지 않습니다. 2. JDK의 String 설명String

    • 클래스는 문자열을 나타냅니다. "abc"와 같은 Java 프로그램의
    • 모든

    • 문자열 리터럴(문자열 리터럴)
    은 이 클래스의 인스턴스로 구현됩니다.

    문자열. 해당 값은 생성된 후에는 변경할 수 없습니다.

    문자열 버퍼【StringBuilder OR StringBuffer】는 가변 문자열을 지원합니다. 문자열 객체는 변경 불가능하므로 공유할 수 있습니다( Flyweight 모드 ).3. 문자열 클래스

      The class String includes methods for examining inpidual characters of the sequence for examining inpidual characters of the sequence,  for comparing strings  , for searching strings , for extracting substrings and for creating a copy of a string with all characters translated to uppercase or to lowercase. Case mapping is based on the Unicode Standard version specified by the java.lang.Character class.


    4、字符串串联符号(”+”)以及将其他对象转换为字符串的特殊支持

      The Java language provides special support for the string concatenation operator (+), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(JDK1.5 以后) OR StringBuffer(JDK1.5 以前) class and its append  method. String conversions(转化为字符串) are implemented through the method toString, defined by class Object and inherited by all classes in Java.


    注意:

    • String不属于八种基本数据类型,String 的实例是一个对象。因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性(String 的不可变性导致其像八种基本类型一样,比如,作为方法参数时,像基本类型的传值效果一样)。 例如,以下代码片段:

    public class StringTest {
    
        public static void changeStr(String str) {
            String s = str;
            str += "welcome";
            System.out.println(s);
        }    public static void main(String[] args) {
            String str = "1234";
            changeStr(str);
            System.out.println(str);
        }
    }/* Output: 
            1234
            1234 
    *///:~
    • new String() 和 new String(“”)都是声明一个新的空字符串,是空串不是null;


    四. String 的不可变性

    1、什么是不可变对象?

      众所周知,在Java中,String类是不可变类 (基本类型的包装类都是不可改变的) 的典型代表,也是Immutable设计模式的典型应用。String变量一旦初始化后就不能更改,禁止改变对象的状态,从而增加共享对象的坚固性、减少对象访问的错误,同时还避免了在多线程共享时进行同步的需要。那么,到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态指的是不能改变对象内的成员变量,包括:

    • 基本数据类型的值不能改变;

    • 引用类型的变量不能指向其他的对象;

    • 引用类型指向的对象的状态也不能改变;

    除此之外,还应具有以下特点:

    • 除了构造函数之外,不应该有其它任何函数(至少是任何public函数)修改任何成员变量;

    • 任何使成员变量获得新值的函数都应该将新的值保存在新的对象中,而保持原来的对象不被修改。


    2、区分引用和对象

      对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:

    String s = "ABCabc";
    System.out.println("s = " + s);    // s = ABCabc
    
    s = "123456";
    System.out.println("s = " + s);    // s = 123456

      首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s 只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个 4 字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。 也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:

                      Java 문자열 개요(1부)

      Java和C++的一个不同点是,在 Java 中,引用是访问、操纵对象的唯一方式: 我们不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。


    3、为什么String对象是不可变的?

      要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String 的成员变量有以下几个:

    public final class String
        implements java.io.Serializable, Comparable<string>, CharSequence{
        /** The value is used for character storage. */
        private final char value[];    /** The offset is the first index of the storage that is used. */
        private final int offset;    /** The count is the number of characters in the String. */
        private final int count;    /** Cache the hash code for the string */
        private int hash; // Default to 0</string>

      在JDK1.7中,String类做了一些改动,主要是改变了substring方法执行时的行为,这和本文的主题不相关。JDK1.7中String类的主要成员变量就剩下了两个:

    public final class String
        implements java.io.Serializable, Comparable<string>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];    /** Cache the hash code for the string */
        private int hash; // Default to 0</string>

      由以上的代码可以看出, 在Java中,String类其实就是对字符数组的封装JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章java中数组的特性)。 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

                     Java 문자열 개요(1부)

      value,offset和count这三个变量都是 private 的,并且没有提供setValue,setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以,可以认为String对象是不可变的了。

      那么在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代码:

    String a = "ABCabc";
    System.out.println("a = " + a);    // a = ABCabca = a.replace(&#39;A&#39;, &#39;a&#39;);
    System.out.println("a = " + a);    //a = aBCabc

      那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace(‘A’, ‘a’)时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题:

                Java 문자열 개요(1부)

      我们可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

    String ss = "123456";
    System.out.println("ss = " + ss);     // ss = 123456ss.replace(&#39;1&#39;, &#39;0&#39;);
    System.out.println("ss = " + ss);     //ss = 123456

    4、String对象真的不可变吗?

      从上文可知String的成员变量是 private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如,将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组,那么,用什么方式可以访问私有成员呢? 没错,用反射,可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

    public static void testReflection() throws Exception {    //创建字符串"Hello World", 并赋给引用s
        String s = "Hello World"; 
    
        System.out.println("s = " + s); //Hello World
    
        //获取String类中的value字段
        Field valueFieldOfString = String.class.getDeclaredField("value");    //改变value属性的访问权限
        valueFieldOfString.setAccessible(true);    //获取s对象上的value属性的值
        char[] value = (char[]) valueFieldOfString.get(s);    //改变value所引用的数组中的第5个字符
        value[5] = &#39;_&#39;;
    
        System.out.println("s = " + s);  //Hello_World}

      在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。


    五. String 对象创建方式

    1. 字面值形式: JVM会自动根据字符串常量池中字符串的实际情况来决定是否创建新对象

    JDK 中明确指出:

    String s = "abc";

    等价于:

    char data[] = {&#39;a&#39;, &#39;b&#39;, &#39;c&#39;};
    String str = new String(data);

      该种方式先在栈中创建一个对String类的对象引用变量s,然后去查找 “abc”是否被保存在字符串常量池中。若”abc”已经被保存在字符串常量池中,则在字符串常量池中找到值为”abc”的对象,然后将s 指向这个对象; 否则,中创建char数组 data,然后在中创建一个String对象object,它由 data 数组支持,紧接着这个String对象 object 被存放进字符串常量池,最后将 s 指向这个对象。

    例如:

        private static void test01(){  
        String s0 = "kvill";        // 1
        String s1 = "kvill";        // 2
        String s2 = "kv" + "ill";     // 3
    
        System.out.println(s0 == s1);       // true  
        System.out.println(s0 == s2);       // true  }

      执行第 1 行代码时,“kvill” 入池并被 s0 指向;执行第 2 行代码时,s1 从常量池查询到” kvill” 对象并直接指向它;所以,s0 和 s1 指向同一对象。 由于 ”kv” 和 ”ill” 都是字符串字面值,所以 s2 在编译期由编译器直接解析为 “kvill”,所以 s2 也是常量池中”kvill”的一个引用。 所以,我们得出 s0==s1==s2;


    2. 通过 new 创建字符串对象 : 一概在堆中创建新对象,无论字符串字面值是否相等

    String s = new String("abc");

    等价于:

    1、String original = "abc"; 
    2、String s = new String(original);

      所以,通过 new 操作产生一个字符串(“abc”)时,会先去常量池中查找是否有“abc”对象,如果没有,则创建一个此字符串对象并放入常量池中。然后,在堆中再创建“abc”对象,并返回该对象的地址。所以,对于 String str=new String(“abc”)如果常量池中原来没有”abc”,则会产生两个对象(一个在常量池中,一个在堆中);否则,产生一个对象。
     
      用 new String() 创建的字符串对象位于堆中,而不是常量池中。它们有自己独立的地址空间,例如,

        private static void test02(){  
        String s0 = "kvill";  
        String s1 = new String("kvill");  
        String s2 = "kv" + new String("ill");  
    
        String s = "ill";
        String s3 = "kv" + s;    
    
    
        System.out.println(s0 == s1);       // false  
        System.out.println(s0 == s2);       // false  
        System.out.println(s1 == s2);       // false  
        System.out.println(s0 == s3);       // false  
        System.out.println(s1 == s3);       // false  
        System.out.println(s2 == s3);       // false  }

      例子中,s0 还是常量池中”kvill”的引用,s1 指向运行时创建的新对象”kvill”,二者指向不同的对象。对于s2,因为后半部分是 new String(“ill”),所以无法在编译期确定,在运行期会 new 一个 StringBuilder 对象, 并由 StringBuilder 的 append 方法连接并调用其 toString 方法返回一个新的 “kvill” 对象。此外,s3 的情形与 s2 一样,均含有编译期无法确定的元素。因此,以上四个 “kvill” 对象互不相同。StringBuilder 的 toString 为:

     public String toString() {    
     return new String(value, 0, count);   // new 的方式创建字符串
        }

      构造函数 String(String original) 的源码为:

        /**
         * 根据源字符串的底层数组长度与该字符串本身长度是否相等决定是否共用支撑数组
         */
        public String(String original) {        
        int size = original.count;        
        char[] originalValue = original.value;        
        char[] v;        
        if (originalValue.length > size) {            
        // The array representing the String is bigger than the new
                // String itself. Perhaps this constructor is being called
                // in order to trim the baggage, so make a copy of the array.
                int off = original.offset;
                v = Arrays.copyOfRange(originalValue, off, off + size);  // 创建新数组并赋给 v
            } else {            // The array representing the String is the same
                // size as the String, so no point in making a copy.
                v = originalValue;
            }        this.offset = 0;        this.count = size;        this.value = v;
        }

      由源码可以知道,所创建的对象在大多数情形下会与源字符串 original 共享 char数组 。但是,什么情况下不会共享呢?
      
      Take a look at substring , and you’ll see how this can happen.

      Take for instance String s1 = “Abcd”; String s2 = s1.substring(3). Here s2.size() is 1, but s2.value.length is 4. This is because s1.value is the same as s2.value. This is done of performance reasons (substring is running in O(1), since it doesn’t need to copy the content of the original String).

    String s1 = "Abcd";       
    // s1 的value为Abcd的数组,offset为 0,count为 4String s2 = a.substring(3);      
    // s2 的value也为Abcd的数组,offset为 3,count为 1String c = new String(s2);      
    // s2.value.length 为 4,而 original.count = size = 1, 即 s2.value.length > size 成立

      Using substring can lead to a memory leak. Say you have a really long String, and you only want to keep a small part of it. If you just use substring, you will actually keep the original string content in memory. Doing String snippet = new String(reallyLongString.substring(x,y)) , prevents you from wasting memory backing a large char array no longer needed.

    详细可见:
    how could ‘originalValue.length > size’ happen in the String constructor?
    String构造器中 originalValue.length > size 发生的情况


위 내용은 Java 문자열 개요(1부)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.