JAVA 스택의 차이점
저자:Eve Cole
업데이트 시간:2009-11-30 17:08:19
-
1. 스택과 힙은 Java에서 Ram에 데이터를 저장하는 데 사용되는 장소입니다. C++와 달리 Java는 스택과 힙을 자동으로 관리하므로 프로그래머가 스택이나 힙을 직접 설정할 수 없습니다.
2. 스택의 장점은 CPU에 직접 위치한 레지스터 다음으로 액세스 속도가 힙보다 빠르다는 것입니다. 하지만 스택에 저장되는 데이터의 크기와 수명을 결정해야 하고 유연성이 부족하다는 점이 단점이다. 또한 스택 데이터를 공유할 수 있습니다. 자세한 내용은 포인트 3을 참조하세요. 힙의 장점은 메모리 크기를 동적으로 할당할 수 있고 수명을 미리 컴파일러에 알릴 필요가 없다는 것입니다. Java의 가비지 수집기는 더 이상 사용되지 않는 데이터를 자동으로 수집합니다. 하지만 런타임에 동적으로 메모리를 할당해야 하기 때문에 접근 속도가 느린 것이 단점이다.
3. Java에는 두 가지 유형의 데이터 유형이 있습니다.
하나는 기본 유형이며 총 8가지 유형, 즉 int, short, long, byte, float, double, boolean, char(기본 문자열 유형이 없음에 유의)이 있습니다. 이러한 유형의 정의는 int a = 3; long b = 255L; 과 같은 형식으로 정의되며 자동 변수라고 합니다. 자동 변수는 클래스의 인스턴스가 아닌 리터럴 값을 저장한다는 점에 주목할 가치가 있습니다. 즉, 여기에는 클래스에 대한 참조가 없습니다. 예를 들어 int a = 3입니다. 여기서 a는 리터럴 값 3을 가리키는 int 유형을 가리키는 참조입니다. 이러한 리터럴 값의 데이터는 크기와 수명으로 알려져 있습니다(이러한 리터럴 값은 특정 프로그램 블록에서 고정적으로 정의되며, 프로그램 블록이 종료되면 필드 값이 사라집니다). 스택에 존재합니다.
또한 스택에는 매우 중요한 특수 기능이 있습니다. 즉, 스택에 저장된 데이터를 공유할 수 있다는 것입니다. 다음도 정의한다고 가정해 보겠습니다.
int a = 3;
int b = 3;
컴파일러는 먼저 int a = 3을 처리합니다. 먼저 스택에서 변수 a에 대한 참조를 만든 다음 리터럴 값이 3인 주소를 검색합니다. 찾지 못하면 리터럴을 저장할 주소를 엽니다. 값은 3이고, a는 주소 3을 가리킵니다. 다음으로 int b = 3이 처리됩니다. b의 참조 변수를 생성한 후 스택에 이미 리터럴 값 3이 있으므로 b는 직접 주소 3을 가리킵니다. 이런 식으로 a와 b가 동시에 3을 가리키는 상황이 발생합니다.
이 리터럴 값의 참조는 클래스 객체의 참조와 다르다는 점에 유의하는 것이 중요합니다. 두 클래스 객체의 참조가 동시에 동일한 객체를 가리킨다고 가정합니다. 하나의 객체 참조 변수가 객체의 내부 상태를 수정하면 다른 객체 참조 변수가 즉시 변경 사항을 반영합니다. 대조적으로, 리터럴 참조의 값을 수정해도 리터럴에 대한 다른 참조의 값도 변경되지 않습니다. 위의 예에서와 같이 a와 b의 값을 정의한 후 a=4로 설정하면 b는 4와 같지 않지만 여전히 3과 같습니다. 컴파일러 내부에서 a = 4;가 발견되면 스택에 리터럴 값 4가 있는지 다시 검색합니다. 그렇지 않은 경우 주소가 이미 존재하는 경우 이를 저장하기 위해 주소를 다시 엽니다. , 이 주소를 직접 가리킵니다. 따라서 a 값의 변화는 b 값에 영향을 미치지 않습니다.
다른 하나는 Integer, String, Double 및 해당 기본 데이터 유형을 래핑하는 기타 클래스와 같은 래퍼 클래스 데이터입니다. 이러한 모든 유형의 데이터는 힙에 존재합니다. Java는 new() 문을 사용하여 런타임 시 필요에 따라 동적으로 생성된다는 점을 컴파일러에 명시적으로 알리므로 유연성이 더 높지만 시간이 더 많이 걸린다는 단점이 있습니다. 4. 문자열은 특별한 래퍼 유형 데이터입니다. 즉, String str = new String( "abc" ); 형식으로 생성할 수도 있고, String str = "abc" ; 형식으로 생성할 수도 있습니다(비교를 위해 JDK 5.0 이전에는 Integer i = 3; 문자열을 제외하고는 클래스와 리터럴 값을 서로 바꿔서 사용할 수 없기 때문에 이 표현식이 가능합니다. 배경) . 전자는 표준화된 클래스 생성 프로세스입니다. 즉, Java에서는 모든 것이 객체이고 객체는 클래스의 인스턴스이며 모두 new() 형식으로 생성됩니다. DateFormat 클래스와 같은 일부 Java 클래스는 클래스의 getInstance() 메서드를 통해 새로 생성된 클래스를 반환할 수 있는데, 이는 이 원칙을 위반하는 것으로 보입니다. 설마. 이 클래스는 클래스의 인스턴스를 반환하기 위해 싱글톤 패턴을 사용하는데, 이 인스턴스는 new()를 통해 클래스 내부에 생성되고, getInstance()는 이 세부 사항을 외부에서 숨깁니다. 그렇다면 왜 String str = "abc" ;에서 new()를 통해 인스턴스가 생성되지 않습니까? 실제로는 그렇지 않습니다.
5. String str = "abc"의 내부 작동에 대해. Java는 내부적으로 이 명령문을 다음 단계로 변환합니다.
(1) 먼저 String 클래스에 str이라는 객체 참조 변수를 정의합니다. String str;
(2) 스택을 검색하여 값이 "abc"인 주소가 있는지 확인합니다. 그렇지 않은 경우 리터럴 값이 "abc"인 주소를 연 다음 String 클래스의 새 객체를 생성하고 o의 문자 문자열 값은 이 주소를 가리키고 참조된 객체 o는 스택에서 이 주소 옆에 기록됩니다. 값이 "abc"인 주소가 이미 있으면 객체 o를 찾아 o의 주소를 반환합니다.
(3) str에 객체 o의 주소를 지정합니다.
일반적으로 String 클래스의 문자열 값이 직접 저장된다는 점은 주목할 가치가 있습니다. 하지만 이 경우 String str = "abc"; 처럼 문자열 값은 스택에 저장된 데이터에 대한 참조를 저장합니다!
이 문제를 더 잘 설명하기 위해 다음 코드를 통해 확인할 수 있습니다.
문자열 str1 = "abc" ;
문자열 str2 = "abc" ;
System.out.println(str1==str2); //참
여기서는 str1.equals(str2);를 사용하지 않습니다. 이는 두 문자열의 값이 동일한지 비교하기 때문입니다. == 기호는 JDK 지침에 따라 두 참조가 모두 동일한 객체를 가리키는 경우에만 true를 반환합니다. 여기서 보고 싶은 것은 str1과 str2가 모두 동일한 객체를 가리키는지 여부입니다.
결과는 JVM이 두 개의 참조 str1 및 str2를 생성했지만 하나의 객체만 생성했으며 두 참조 모두 이 객체를 가리킨다는 것을 보여줍니다.
한 단계 더 나아가 위의 코드를 다음과 같이 변경해 보겠습니다.
문자열 str1 = "abc" ;
문자열 str2 = "abc" ;
str1 = "bcd" ;
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false
이는 할당이 변경되면 클래스 객체 참조가 변경되고 str1은 또 다른 새 객체를 가리킨다는 것을 의미합니다! 그리고 str2는 여전히 원래 개체를 가리킵니다. 위의 예에서 str1의 값을 "bcd"로 변경했을 때 JVM은 이 값을 스택에 저장할 주소가 없다는 것을 발견하고 이 주소를 열고 문자열 값이 이 주소를 가리키는 새 객체를 생성했습니다. .
실제로 String 클래스는 변경할 수 없도록 설계되었습니다. 해당 값을 변경하려면 변경할 수 있지만 JVM은 런타임 시 새 값을 기반으로 새 객체를 자동으로 생성한 다음 이 객체의 주소를 원래 클래스에 대한 참조로 반환합니다. 이 생성 프로세스는 완전히 자동화되어 있지만 결국 시간이 더 많이 걸립니다. 시간 요구 사항에 민감한 환경에서는 특정 부작용이 발생할 수 있습니다.
원래 코드를 다시 수정합니다.
문자열 str1 = "abc" ;
문자열 str2 = "abc" ;
str1 = "bcd" ;
문자열 str3 = str1;
System.out.println(str3); //bcd
문자열 str4 = "bcd" ;
System.out.println(str1 == str4); //참
str3 객체에 대한 참조는 str1이 가리키는 객체를 직접 가리킵니다(str3은 새 객체를 생성하지 않습니다). str1이 값을 변경한 후 문자열 참조 str4를 만들고 str1이 값을 수정했기 때문에 생성된 새 개체를 가리킵니다. 이번에는 str4가 새로운 객체를 생성하지 않아 스택 내 데이터 공유를 다시 구현한 것을 알 수 있습니다.
다음 코드를 다시 살펴보겠습니다.
문자열 str1 = 새 문자열( "abc" );
문자열 str2 = "abc" ;
System.out.println(str1==str2); //false
두 개의 참조가 생성됩니다. 두 개의 객체가 생성됩니다. 두 참조는 각각 서로 다른 두 개체를 가리킵니다.
문자열 str1 = "abc" ;
String str2 = new String( "abc" );
System.out.println(str1==str2); //false
두 개의 참조가 생성됩니다. 두 개의 객체가 생성됩니다. 두 참조는 각각 서로 다른 두 개체를 가리킵니다.
위의 두 코드는 new()를 사용하여 새 개체를 생성하는 한 힙에 생성되고 해당 문자열 값이 스택의 데이터와 동일하더라도 별도로 저장된다는 것을 보여줍니다. , 스택의 데이터와 공유되지 않습니다.
6. 데이터 유형 래퍼 클래스의 값은 수정할 수 없습니다. String 클래스의 값을 수정할 수 없을 뿐만 아니라 모든 데이터 유형 래퍼 클래스는 내부 값을 변경할 수 없습니다. 7. 결론 및 제안:
(1) String str = "abc";와 같은 형식을 사용하여 클래스를 정의할 때 우리는 항상 String 클래스의 객체 str을 생성하는 것을 당연하게 여깁니다. 함정 걱정! 개체가 생성되지 않았을 수 있습니다! 확실한 것은 String 클래스에 대한 참조가 생성된다는 것입니다. 이 참조가 새 객체를 가리키는지 여부는 new() 메서드를 통해 명시적으로 새 객체를 생성하지 않는 한 컨텍스트에 따라 고려되어야 합니다. 따라서 보다 정확한 설명은 String 클래스의 객체를 가리키는 참조 변수 str을 생성하는 것입니다. 이 객체 참조 변수는 값이 "abc"인 String 클래스를 가리킵니다. 이에 대한 명확한 이해는 프로그램에서 찾기 어려운 버그를 제거하는 데 매우 도움이 됩니다.
(2) String str = "abc"를 사용하면 JVM이 스택에 있는 데이터의 실제 상황을 기반으로 새 객체를 생성해야 하는지 여부를 자동으로 결정하므로 프로그램의 실행 속도를 어느 정도 향상시킬 수 있습니다. . String str = new String("abc");의 코드는 문자열 값이 같은지, 새 객체를 생성해야 하는지 여부에 관계없이 항상 힙에 새 객체가 생성되므로 부담이 늘어납니다. 프로그램에. 이 아이디어는 플라이웨이트 모드의 아이디어여야 하는데, JDK 내부 구현에서 이 모드를 적용하는지 여부는 알 수 없다.
(3) 패키징 클래스의 값이 동일한지 비교할 때, 두 패키징 클래스의 참조가 동일한 객체를 가리키는지 테스트할 때 ==를 사용합니다.
(4) String 클래스의 불변성으로 인해 String 변수의 값을 자주 변경해야 하는 경우 StringBuffer 클래스를 사용하여 프로그램 효율성을 높이는 것을 고려해야 합니다.