Maison  >  Article  >  Java  >  Quelle est la différence entre tas, pile et pile en Java ? Introduction aux différences entre tas, pile et pile en Java

Quelle est la différence entre tas, pile et pile en Java ? Introduction aux différences entre tas, pile et pile en Java

不言
不言avant
2018-11-20 15:54:252560parcourir

Ce que cet article vous apporte, c'est la différence entre tas, pile et pile en Java ? L'introduction aux différences entre le tas, la pile et la pile en Java a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer, j'espère qu'elle vous sera utile.

Avant de commencer le contenu formel, je voudrais expliquer une chose. La pile que nous appelons souvent pile est le nom collectif du tas et de la pile est un tas et une pile est. stack. Ensemble, ils sont collectivement appelés stack ;

1. Stack et tas sont des emplacements utilisés par Java pour stocker des données dans Ram. Contrairement au C++, Java gère automatiquement la pile et le tas, et les programmeurs ne peuvent pas définir directement la pile ou le tas.

2. L'avantage de la pile est que la vitesse d'accès est plus rapide que celle du tas, juste derrière les registres directement situés dans le CPU. Mais l’inconvénient est que la taille et la durée de vie des données stockées dans la pile doivent être déterminées et qu’il y a un manque de flexibilité. De plus, les données de la pile peuvent être partagées, voir le point 3 pour plus de détails. L'avantage du tas est qu'il peut allouer dynamiquement la taille de la mémoire et qu'il n'est pas nécessaire d'indiquer la durée de vie au compilateur à l'avance. Le garbage collector de Java collectera automatiquement les données qui ne sont plus utilisées. Mais l'inconvénient est qu'en raison de la nécessité d'allouer dynamiquement de la mémoire au moment de l'exécution, la vitesse d'accès est lente.

3. Il existe deux types de données en Java.

L'un est les types primitifs, il y a 8 types au total, à savoir int, short, long, byte, float, double, boolean, char (attention, il n'y a pas de type de chaîne de base). Ce type de définition est défini sous la forme int a = 3 ; long b = 255L et est appelé variable automatique. Il convient de noter que les variables automatiques stockent des valeurs littérales, et non des instances de classes, c'est-à-dire qu'elles ne sont pas des références à des classes. Il n'y a pas de classe ici. Par exemple, int a = 3 ; où a est une référence pointant vers le type int, pointant vers la valeur littérale 3. Étant donné que les données de ces valeurs littérales peuvent être connues en taille et en durée de vie (ces valeurs littérales sont définies de manière fixe dans un certain bloc de programme et les valeurs de champ disparaissent après la sortie du bloc de programme), par souci de rapidité , ils existent sur la pile.

De plus, la pile a une particularité très importante, c'est-à-dire que les données stockées dans la pile peuvent être partagées. Supposons que nous définissions en même temps :

 int a = 3;
 int b = 3;

Le compilateur traite d'abord int a = 3, il créera d'abord une référence à la variable a sur la pile, et puis découvrez s'il existe Si l'adresse avec la valeur littérale 3 n'est pas trouvée, ouvrez une adresse pour stocker la valeur littérale 3, puis pointez a vers l'adresse de 3. Ensuite, traitez int b = 3 ; après avoir créé la variable de référence de b, puisqu'il y a déjà une valeur littérale de 3 sur la pile, b pointera directement vers l'adresse de 3. De cette façon, il existe une situation où a et b pointent tous deux vers 3 en même temps.

Il est important de noter que la référence de cette valeur littérale est différente de la référence de l'objet de classe. Supposons que les références de deux objets de classe pointent vers le même objet en même temps. Si une variable de référence d'objet modifie l'état interne de l'objet, alors l'autre variable de référence d'objet reflétera immédiatement le changement. En revanche, la modification de la valeur d’une référence littérale n’entraîne pas également la modification de la valeur d’une autre référence au littéral. Comme dans l'exemple ci-dessus, après avoir défini les valeurs de a et b, nous définissons alors a=4 ; alors, b ne sera pas égal à 4, mais toujours égal à 3. À l'intérieur du compilateur, lorsqu'il rencontre a=4;, il recherchera à nouveau s'il y a une valeur littérale de 4 sur la pile. Sinon, il rouvrira une adresse pour stocker la valeur de 4 si elle existe déjà. , il pointera directement a vers cette adresse . Par conséquent, les changements dans la valeur de a n’affecteront pas la valeur de b.

L'autre consiste à empaqueter les données de classe, telles que Integer, String, Double et d'autres classes qui encapsulent les types de données de base correspondants. Tous ces types de données existent dans le tas. Java utilise l'instruction new() pour indiquer explicitement au compilateur qu'elles seront créées dynamiquement selon les besoins au moment de l'exécution, c'est donc plus flexible, mais l'inconvénient est que cela prend plus de temps.

4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java 中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单 例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

(1)先定义一个名为str的对String类的对象引用变量:String str;

(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并 将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。

(3)将str指向对象o的地址。

值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

 String str1 = "abc"; 
 String str2 = "abc"; 
 System.out.println(str1==str2); //true

 注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。 
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

我们再来更进一步,将以上代码改成:

 String str1 = "abc"; 
 String str2 = "abc"; 
 str1 = "bcd"; 
 System.out.println(str1 + "," + str2); //bcd, abc 
 System.out.println(str1==str2); //false

 这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然 后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良 影响。

再修改原来代码:

String str1 = "abc"; 
String str2 = "abc"; 
str1 = "bcd";  
String str3 = str1; 
System.out.println(str3); //bcd  
String str4 = "bcd"; 
System.out.println(str1 == str4); //true

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用 str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

我们再接着看以下的代码。

1 String str1 = new String("abc"); 
2 String str2 = "abc"; 
3 System.out.println(str1==str2); //false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

1 String str1 = "abc"; 
2 String str2 = new String("abc"); 
3 System.out.println(str1==str2); //false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 

以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

7. 结论与建议:

(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因 此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认 识到这一点对排除程序中难以发现的bug是很有帮助的。

(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是 享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

栈(stack):是一个先进后出的数据结构,通常用于保存方法(函数)中的参数,局部变量. 
在java中,所有基本类型和引用类型都在栈中存储.栈中数据的生存空间一般在当前scopes内(就是由{...}括起来的区域).

堆(heap):是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),C中的malloc语句所产生的内存空间就在堆中.

En Java, tous les objets construits à l'aide de new xxx() sont stockés dans le tas. Lorsque le garbage collector détecte qu'un objet n'est pas référencé, il détruira automatiquement l'objet. Par conséquent, en théorie, java Il y a. aucune limite à l'espace de vie d'un objet. Tant qu'il y a un type de référence pointant vers lui, il peut être utilisé n'importe où

Dans. JAVA, il existe six endroits différents où les données peuvent être stockées :

1. Il s’agit de la zone mémoire la plus rapide car elle est située à un endroit différent des autres zones mémoire : à l’intérieur du processeur. Cependant, le nombre de registres est extrêmement limité, les registres sont donc alloués par le compilateur en fonction des besoins. Vous n'avez aucun contrôle direct et vous ne ressentez aucune trace de l'existence du registre dans le programme.

2. Empiler. Situé dans la RAM à usage général, mais pris en charge par le processeur via son « pointeur de pile ». Si le pointeur de pile se déplace vers le bas, une nouvelle mémoire est allouée ; s'il se déplace vers le haut, cette mémoire est libérée. Il s’agit d’une méthode rapide et efficace d’allocation de stockage, juste derrière les registres. Lors de la création d'un programme, le compilateur Java doit connaître la taille exacte et la durée de vie de toutes les données stockées sur la pile, car il doit générer le code approprié pour déplacer le pointeur de la pile de haut en bas. Cette contrainte limite la flexibilité du programme, ainsi bien que certaines données Java soient stockées sur la pile - notamment les références d'objets, les objets Java n'y sont pas stockés.

3. Tas. Un pool de mémoire universel (également présent dans la RAM) utilisé pour stocker tous les objets JAVA. L'avantage du fait que le tas est différent de la pile est que le compilateur n'a pas besoin de savoir quelle quantité de zone de stockage allouer à partir du tas, ni combien de temps les données stockées survivront dans le tas. Par conséquent, il existe une grande flexibilité dans l’allocation du stockage sur le tas. Lorsque vous devez créer un objet, il vous suffit d'écrire une simple ligne de nouveau code. Lorsque cette ligne de code est exécutée, le stockage sera automatiquement alloué dans le tas. Bien entendu, cette flexibilité doit être payée avec le code correspondant. L'allocation de stockage à l'aide du tas prend plus de temps que le stockage à l'aide de la pile.

4. Stockage statique. « Statique » signifie ici « dans une position fixe ». Le stockage statique stocke les données qui existent pendant l'exécution du programme. Vous pouvez utiliser le mot-clé static pour identifier des éléments spécifiques d'un objet comme étant statiques, mais l'objet JAVA lui-même n'est jamais stocké dans un espace de stockage statique.

5. Stockage constant. Les valeurs constantes sont généralement stockées directement dans le code du programme, ce qui est sûr car elles ne peuvent jamais être modifiées. Parfois, dans les systèmes embarqués, les constantes elles-mêmes sont séparées des autres parties, donc dans ce cas, vous pouvez choisir de les mettre en ROM

6 . Stockage hors RAM. Si les données survivent complètement en dehors du programme, elles peuvent alors exister sans aucun contrôle du programme et peuvent exister même lorsque le programme n'est pas en cours d'exécution.
En termes de vitesse, il existe la relation suivante :

Register

『Le paragraphe ci-dessus est extrait de "Penser en Java"』

Ici, on parle principalement de la relation entre tas et pile :

Tas : Le tas est le tas, qui est la mémoire dite dynamique, dans laquelle la mémoire peut être recyclée lorsqu'elle n'est pas nécessaire, pour être allouée à de nouvelles demandes de mémoire. Les données dans la mémoire ne sont pas ordonnées, c'est-à-dire qu'il n'y a pas de relation de position nécessaire entre la mémoire allouée en premier et la mémoire. mémoire allouée ultérieurement. Il ne peut y avoir d’ordre de priorité. Généralement, il est librement alloué par l'utilisateur, Malloc alloue le tas et doit être libéré manuellement.

Stack : C'est STACK. En fait, il s'agit d'une file d'attente avec une seule entrée et sortie, c'est-à-dire First In Last Out (First In Last Out). La mémoire allouée en premier doit être libérée ultérieurement. Généralement, il est automatiquement alloué par le système pour stocker les valeurs des paramètres de fonction, les variables locales, etc., et est automatiquement effacé.

De plus, le tas est global et la pile est divisée en un petit morceau lorsque chaque fonction entre et est libérée lorsque la fonction revient. Les variables statiques et globales, ainsi que les variables obtenues par new sont placées sur le tas, et les variables locales sont placées sur la pile, donc lorsque la fonction revient, toutes les variables locales ont disparu.

En fait, dans les applications réelles, la pile est principalement utilisée pour stocker les appels de méthodes. Le tas est utilisé pour le stockage d'objets.

Les types de base en JAVA nécessitent en fait un traitement spécial. Parce que, en JAVA, les objets créés via new sont stockés dans le « tas », donc utiliser new pour créer une petite variable simple, comme un type de base, etc., n'est souvent pas très efficace. Par conséquent, en JAVA, les mêmes méthodes que C et C++ sont utilisées pour ces types. En d’autres termes, au lieu d’utiliser new pour créer, créez une variable « automatique » qui n’est pas une « référence ». Cette variable possède sa « valeur » et est placée sur la pile, ce qui la rend plus efficace.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer