关于String内存分配的深入探讨

2023-11-12

public class Test {

   

    public static final String MESSAGE="taobao";

   

    public static void main(String[] args) {

      

       String a = "tao"+"bao";

       String b = "tao";

       String c = "bao";

      

System.out.println(a==MESSAGE);    System.out.println( (b+c)==MESSAGE); 

    }

}

对于这道题,考察的是对String类型的认识以及编译器优化。Java中String不是基本类型,但是有些时候和基本类型差不多,如String b = "tao"; 可以对变量直接赋值,而不用 new 一个对象(当然也可以用 new)。所以String这个类型值得好好研究下。

Java中的变量和基本类型的值存放于栈内存,而new出来的对象本身存放于堆内存,指向对象的引用还是存放在栈内存。例如如下的代码:

int i=1;

    String s = new String("Hello World");

变量i和s以及1存放在栈内存,而s指向的对象”Hello World”存放于堆内存。

 

 

 

 

栈内存的一个特点是数据共享,这样设计是为了减小内存消耗,前面定义了i=1,i和1都在栈内存内,如果再定义一个j=1,此时将j放入栈内存,然后查找栈内存中是否有1,如果有则j指向1。如果再给j赋值2,则在栈内存中查找是否有2,如果没有就在栈内存中放一个2,然后j指向2。也就是如果常量在栈内存中,就将变量指向该常量,如果没有就在该栈内存增加一个该常量,并将变量指向该常量。

 

 

如果j++,这时指向的变量并不会改变,而是在栈内寻找新的常量(比原来的常量大1),如果栈内存有则指向它,如果没有就在栈内存中加入此常量并将j指向它。这种基本类型之间比较大小和我们逻辑上判断大小是一致的。如定义i和j是都赋值1,则i==j结果为true。==用于判断两个变量指向的地址是否一样。i==j就是判断i指向的1和j指向的1是同一个吗?当然是了。对于直接赋值的字符串常量(如String s=“Hello World”;中的Hello World)也是存放在栈内存中,而new出来的字符串对象(即String对象)是存放在堆内存中。如果定义String s=“Hello World”和String w=“Hello World”,s==w吗?肯定是true,因为他们指向的是同一个Hello World。

 

 

堆内存没有数据共享的特点,前面定义的String s = new String("Hello World");后,变量s在栈内存内,Hello World 这个String对象在堆内存内。如果定义String w =new String("Hello World");,则会在堆内存创建一个新的String对象,变量w存放在栈内存,w指向这个新的String对象。堆内存中不同对象(指同一类型的不同对象)的比较如果用==则结果肯定都是false,比如s==w?当然不等,s和w指向堆内存中不同的String对象。如果判断两个String对象相等呢?用equals方法。

 

 

 

说了这么多只是说了这道题的铺垫知识,还没进入主题,下面分析这道题。MESSAGE成员变量及其指向的字符串常量肯定都是在栈内存里的,变量a运算完也是指向一个字符串“taobao”啊?是不是同一个呢?这涉及到编译器优化问题。对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并。也就是说

String a = "tao"+"bao";和String a = "taobao";编译出的字节码是一样的。所以等到运行时,根据上面说的栈内存是数据共享原则,a和MESSAGE指向的是同一个字符串。而对于后面的(b+c)又是什么情况呢?b+c只能等到运行时才能判定是什么字符串,编译器不会优化,想想这也是有道理的,编译器怕你对b的值改变,所以编译器不会优化。运行时b+c计算出来的"taobao"和栈内存里已经有的"taobao"是一个吗?不是。b+c计算出来的"taobao"应该是放在堆内存中的String对象。这可以通过System.out.println( (b+c)==MESSAGE);的结果为false来证明这一点。如果计算出来的b+c也是在栈内存,那结果应该是true。Java对String的相加是通过StringBuffer实现的,先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”,然后将值为”taobao”的StringBuffer转化成String对象。StringBuffer对象在堆内存中,那转换成的String对象理所应当的也是在堆内存中。下面改造一下这个语句System.out.println( (b+c).intern()==MESSAGE);结果是true,intern()方法会先检查String(或者说成栈内存)中是否存在相同的字符串常量,如果有就返回。所以intern()返回的就是MESSAGE指向的"taobao"。再把变量b和c的定义改一下,

final String b = "tao";

        final String c = "bao";

            

       System.out.println( (b+c)==MESSAGE);

现在b和c不可能再次赋值了,所以编译器将b+c编译成了”taobao”。因此,这时的结果是true。

在字符串相加中,只要有一个是非final类型的变量,编译器就不会优化,因为这样的变量可能发生改变,所以编译器不可能将这样的变量替换成常量。例如将变量b的final去掉,结果又变成了false。这也就意味着会用到StringBuffer对象,计算的结果在堆内存中。

    如果对指向堆内存中的对象的String变量调用intern()会怎么样呢?实际上这个问题已经说过了,(b+c).intern(),b+c的结果就是在堆内存中。对于指向栈内存中字符串常量的变量调用intern()返回的还是它自己,没有多大意义。它会根据堆内存中对象的值,去查找String池中是否有相同的字符串,如果有就将变量指向这个string池中的变量。

String a = "tao"+"bao";

       String b = new String("taobao");

     

      System.out.println(a==MESSAGE); //true

      System.out.println(b==MESSAGE);  //false

     

      b = b.intern();

      System.out.println(b==MESSAGE); //true

System.out.println(a==a.intern()); //true

转载于:https://my.oschina.net/u/551903/blog/134000

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于String内存分配的深入探讨 的相关文章

随机推荐

  • Linux错误 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

    ERROR 1045 28000 Access denied for user root localhost using password YES 翻译 错误1045 28000 对用户 root 本地主机拒绝访问 使用密码 是 一般这个错
  • Java String总结

    文章目录 创建String 字符串比较 字符串常量池 直接赋值 构造方法 理解字符串不可变 反射 特殊手段 char 和String StringBuffer和StringBuilder String API 创建String 常见的构造方
  • 实验五_linux进程控制,实验五Linux进程管理

    实验五 Linux进程管理 一 实验目的 1 进行系统进程管理 2 周期性任务安排 二 实验内容 1 进程状态查看 2 控制系统中运行的进程 3 安排一次性和周期性自动执行的后台进程 三 实验练习 任务一 进程管理 实验内容 1 查看系统中
  • Go语言 context的源码分析与典型使用(WithCancel,WithTimeout,WithDeadline,WithValue)

    文章目录 context原理 Context基本结构 WithCancel WithTimeout WithDeadline WithValue 代码实现 context原理 context 主要用来在 goroutine 之间传递上下文信
  • C语言学习打卡11.22

    上一周学习内容 函数 初学函数笔记 返回类型 一个函数可以返回一个值 return type 是函数返回的值的数据类型 有些函数执行所需的操作而不返回值 在这种情况下 return type 是关键字 void 函数名称 这是函数的实际名称
  • Windows提权基本原理

    前言 没有多少人谈论在Windows下提权 是一件让人遗憾的事 我想 没有人这么做的理由有以下几点 在渗透测试项目中 客户需要的验证就是一个低权限shell 在演示环境 你经常就会得到管理员帐户 meterpreter使你变得懒惰 gets
  • Angular4.0_依赖注入简介

    什么是依赖注入模式及使用依赖注入的好处 依赖注入 Dependency Injection 简称DI var product new Product createShipment product 我们new一个对象实例 然后当做参数传递给c
  • C++ 实现 redis 发布订阅 --- 使用 hiredis 同步API(一)

    ubuntu18 04安装redis 方法一 使用ubuntu官方软件库安装redis 1 获取最新的软件包 sudo apt update 2 安装redis sudo apt install redis server 3 查看是否安装好
  • Web安全之SQL注入漏洞学习(七)-堆叠注入

    堆叠注入简介 堆叠注入是指注入的多条SQL语句可以一起执行 MySQL命令行中 每一条语句结尾加 表示语句结束 这样是不是可以多句一起使用 这个叫做 stacked injection 堆叠注入原理 局限性 原理 在MySQL的SQL语法中
  • 华硕电脑怎么录屏?分享实用录制经验!

    华硕电脑怎么录屏呀 刚买的笔记本电脑 是华硕的 自我感觉挺好用的 但是不知道怎么录屏 最近刚好要录一个教程 怎么都找不到在哪里录制 有人能教教我吗 随着电脑技术的不断发展 人们越来越依赖电脑进行工作和娱乐 录屏功能作为电脑的一项重要功能 已
  • 华为OD机试 C++ 【报文重排序】

    题目 你手里有一堆乱七八糟的消息片段 每个片段后面都跟着一个数字 那个数字就像是每个片段的编号 你需要按照这些数字 将消息片段整合成一个完整的消息 并把那些数字扔掉 输入 第一行告诉你有几个消息片段 记作N 0 lt N 1000 第二行就
  • Axure rp9 学习笔记-进度笔记以及问题整理

    Axure rp9 学习笔记 进度笔记以及问题整理 4 15 iphone 原型设计 遇到问题 1 苹果手机左按键画的过程中没有弧度 corner 选项为灰 没法选 2 将绘制好的iphone 原型图设置成母版以后 却无法更改颜色 我们对M
  • Rust vs Go:常用语法对比(八)

    题目来自 Golang vs Rust Which Programming Language To Choose in 2023 1 141 Iterate in sequence over two lists Iterate in seq
  • 【LeetCode 热题 HOT 100-002】两数相加(python)

    题集链接 https leetcode cn problem list 2cktkvj 题目链接 https leetcode cn problems add two numbers favorite 2cktkvj 一 题目 二 代码 解
  • C# winform Panel 获取滚轮事件

    使用 Panel 做为控件容器时 设置 Panel AutoScroll true时 在适当的时候将会出现滚动条 但是只能通过拖动滚动条来调整滚动条的位置 如果想要用鼠标中间键来控制滚动条的位置 可以通过下面几步来完成 1 在构造函数中加上
  • DCGAN、WGAN、WGAN-GP、LSGAN、BEGAN原理总结及对比

    GAN系列学习 2 前生今世 本文已投稿至微信公众号 机器学习算法工程师 欢迎关注 本文是GAN系列学习 前世今生第二篇 在第一篇中主要介绍了GAN的原理部分 在此篇文章中 主要总结了常用的GAN包括DCGAN WGAN WGAN GP L
  • 记一次Vulnstack靶场内网渗透(五)

    实验环境搭建 VMware新建网卡VMnet14 选择仅主机模式 并将网段IP设置为192 168 138 0 然后将Windows 7和Windows 2008绑在这个VMnet14上 除此之外 还需要给Windows 7 新增一个网卡
  • C#实现俄罗斯方块游戏

    还记得小时候经常拿袖珍电子游戏机或者小霸王玩过最多的就是俄罗斯方块 冒险岛 超级玛丽还有魂斗罗之类的 这些游戏由于其中简单易上上手的特点 曾经风靡了全世界 俄罗斯方块的基本规则是移动 旋转和摆放游戏自动输出的各种方块 使之排列成完整的一行或
  • Linux开机&关机

    走进Linux系统 开机登录 开机会启动许多程序 它们在Windows叫做 服务 service 在Linux就叫做 守护进程 daemon 开机成功后 它会显示一个文本登录界面 这个界面就是我们经常看到的登录界面 在这个登录界面会提示用户
  • 关于String内存分配的深入探讨

    2019独角兽企业重金招聘Python工程师标准 gt gt gt public class Test public static final String MESSAGE taobao public static void main St