Différences dans la pile JAVA
Auteur:Eve Cole
Date de mise à jour:2009-11-30 17:08:19
-
1. La pile et le 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 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 (notez qu'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. Les données de ces valeurs littérales sont 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é, elles). exister sur la pile.
De plus, la pile possède une particularité très importante, à savoir que les données stockées dans la pile peuvent être partagées. Supposons que nous définissions également :
entier a = 3 ;
int b = 3;
Le compilateur traite d'abord int a = 3 ; il crée d'abord une référence à la variable a sur la pile, puis recherche une adresse avec une valeur littérale de 3. S'il ne la trouve pas, il ouvre une adresse pour stocker le littéral. valeur de 3, puis a pointe vers l'adresse de 3. Ensuite, int b = 3 est traité ; 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 est directement pointé 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 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 concerne les données de classe wrapper, 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'elle sera créée dynamiquement selon les besoins au moment de l'exécution, elle est donc plus flexible, mais l'inconvénient est que cela prend plus de temps. 4. String est un type de données de wrapper spécial. Autrement dit, il peut être créé sous la forme String str = new String( "abc" );, ou il peut être créé sous la forme String str = "abc" ; (à titre de comparaison, avant JDK 5.0, vous n'avez jamais vu Integer i = 3; expression, car les classes et les valeurs littérales ne peuvent pas être utilisées de manière interchangeable, sauf pour String, cette expression est possible car le compilateur effectue la conversion de Integer i = new Integer(3) dans le arrière-plan) . Le premier est un processus de création de classe standardisé, c'est-à-dire qu'en Java, tout est un objet et les objets sont des instances de classes, toutes créées sous la forme de new (). Certaines classes en Java, comme la classe DateFormat, peuvent renvoyer une classe nouvellement créée via la méthode getInstance() de la classe, ce qui semble violer ce principe. Pas vraiment. Cette classe utilise le modèle singleton pour renvoyer une instance de la classe, mais cette instance est créée à l'intérieur de la classe via new(), et getInstance() masque ce détail de l'extérieur. Alors pourquoi dans String str = "abc" ;, l'instance n'est pas créée via new (). Cela viole-t-il le principe ci-dessus ? En fait non.
5. À propos du fonctionnement interne de String str = "abc". Java convertit en interne cette instruction en étapes suivantes :
(1) Définissez d’abord une variable de référence d’objet nommée str dans la classe String : String str ;
(2) Recherchez dans la pile s'il existe une adresse avec une valeur de "abc". Sinon, ouvrez une adresse avec une valeur littérale de "abc", puis créez un nouvel objet o de la classe String et ajoutez le caractères de o La valeur de chaîne pointe vers cette adresse et l'objet référencé o est enregistré à côté de cette adresse sur la pile. S'il existe déjà une adresse avec la valeur "abc", l'objet o est trouvé et l'adresse de o est renvoyée.
(3) Pointez str vers l’adresse de l’objet o.
Il convient de noter que généralement les valeurs de chaîne de la classe String sont stockées directement. Mais comme String str = "abc"; dans ce cas, la valeur de chaîne enregistre une référence aux données stockées sur la pile !
Afin de mieux illustrer ce problème, nous pouvons le vérifier à travers les codes suivants.
Chaîne str1 = "abc" ;
Chaîne str2 = "abc" ;
System.out.println(str1==str2); //true
Notez que nous n'utilisons pas str1.equals(str2); ici, car cela comparera si les valeurs des deux chaînes sont égales. Le signe ==, selon les instructions du JDK, ne renvoie vrai que lorsque les deux références pointent vers le même objet. Ce que nous voulons voir ici, c'est si str1 et str2 pointent tous deux vers le même objet.
Les résultats montrent que la JVM a créé deux références str1 et str2, mais n'a créé qu'un seul objet, et les deux références pointaient vers cet objet.
Allons plus loin et changeons le code ci-dessus en :
Chaîne str1 = "abc" ;
Chaîne str2 = "abc" ;
str1 = "bcd" ;
System.out.println(str1 + "," + str2); //bcd, abc);
System.out.println(str1==str2); //false
Cela signifie que le changement d'affectation entraîne un changement dans la référence de l'objet de classe, et str1 pointe vers un autre nouvel objet ! Et str2 pointe toujours vers l'objet d'origine. Dans l'exemple ci-dessus, lorsque nous avons modifié la valeur de str1 en "bcd", la JVM a constaté qu'il n'y avait aucune adresse pour stocker cette valeur sur la pile, elle a donc ouvert cette adresse et créé un nouvel objet dont la valeur de chaîne pointe vers cette adresse. .
En fait, la classe String est conçue pour être immuable. Si vous souhaitez modifier sa valeur, vous le pouvez, mais la JVM crée silencieusement un nouvel objet basé sur la nouvelle valeur au moment de l'exécution, puis renvoie l'adresse de cet objet à une référence à la classe d'origine. Bien que ce processus de création soit entièrement automatisé, il prend finalement plus de temps. Dans un environnement sensible aux impératifs de temps, cela aura certains effets néfastes.
Modifiez à nouveau le code d'origine :
Chaîne str1 = "abc" ;
Chaîne str2 = "abc" ;
str1 = "bcd" ;
Chaîne str3 = str1 ;
System.out.println(str3); //bcd
Chaîne str4 = "bcd" ;
System.out.println(str1 == str4); //true
La référence à l'objet str3 pointe directement vers l'objet pointé par str1 (notez que str3 ne crée pas de nouvel objet). Une fois que str1 a modifié sa valeur, créez une référence String str4 et pointez vers le nouvel objet créé car str1 a modifié la valeur. On peut constater que str4 n'a pas créé de nouvel objet cette fois, réalisant ainsi à nouveau le partage des données dans la pile.
Regardons à nouveau le code suivant.
Chaîne str1 = nouvelle chaîne( "abc" );
Chaîne str2 = "abc" ;
System.out.println(str1==str2); //false
Deux références sont créées. Deux objets sont créés. Les deux références pointent respectivement vers deux objets différents.
Chaîne str1 = "abc" ;
Chaîne str2 = nouvelle Chaîne( "abc" );
System.out.println(str1==str2); //false
Deux références sont créées. Deux objets sont créés. Les deux références pointent respectivement vers deux objets différents.
Les deux morceaux de code ci-dessus illustrent que tant que new () est utilisé pour créer un nouvel objet, il sera créé dans le tas et sa valeur de chaîne est stockée séparément même si elle est la même que les données de la pile. , il ne sera pas partagé avec les données de la stack .
6. La valeur de la classe wrapper du type de données ne peut pas être modifiée. Non seulement la valeur de la classe String ne peut pas être modifiée, mais toutes les classes wrapper de types de données ne peuvent pas modifier leurs valeurs internes. 7. Conclusion et suggestions :
(1) Lorsque nous définissons une classe en utilisant un format tel que String str = "abc";, nous tenons toujours pour acquis que nous créons un objet str de la classe String. Attention aux pièges ! L'objet n'a peut-être pas été créé ! La seule chose qui est sûre est qu'une référence à la classe String est créée. Quant à savoir si cette référence pointe vers un nouvel objet, elle doit être considérée en fonction du contexte, à moins que vous ne créiez explicitement un nouvel objet via la méthode new(). Par conséquent, une déclaration plus précise est que nous créons une variable de référence str qui pointe vers un objet de la classe String. Cette variable de référence d'objet pointe vers une classe String avec une valeur de "abc". Une compréhension claire de cela est très utile pour éliminer les bogues difficiles à trouver dans le programme.
(2) L'utilisation de String str = "abc"; peut améliorer la vitesse d'exécution du programme dans une certaine mesure, car la JVM déterminera automatiquement s'il est nécessaire de créer un nouvel objet en fonction de la situation réelle des données dans la pile. . Quant au code de String str = new String("abc");, de nouveaux objets sont toujours créés dans le tas, que leurs valeurs de chaîne soient égales ou qu'il soit nécessaire de créer de nouveaux objets, augmentant ainsi la charge au programme. Cette idée devrait être l'idée du mode poids mouche, mais on ne sait pas si l'implémentation interne du JDK applique ce mode.
(3) Lorsque vous comparez si les valeurs de la classe d'emballage sont égales, utilisez la méthode equals() ; lorsque vous testez si les références de deux classes d'emballage pointent vers le même objet, utilisez ==.
(4) En raison de la nature immuable de la classe String, lorsque la variable String doit fréquemment modifier sa valeur, vous devez envisager d'utiliser la classe StringBuffer pour améliorer l'efficacité du programme.