一, JVM 内存分配和常量池
在介绍 String 类之前, 先来简单分析一下在 JVM 中, 对内存的使用是如何进行分配的. 如下图所示(注意: 在 jdk1.8 之后便没有方法区了):
如上 JVM 将内存分为多个不同的区域, 这些区域都有各自的用途, 创建和销毁的时间, 有些区域随虚拟机进程的启动而存在, 有些区域则是依赖用户线程的启动和结束来建立和销毁.
区域名称的说明:
1.1, 方法区:
属于数据共享内存区域, 存储已被虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据.
1.2, 虚拟机栈
虚拟机栈就是我们通常说的栈, 是 Java 执行方法的内存模型, 每当执行一次方法时, 都会创建一个栈帧. 把栈帧压入栈, 当 Java 方法调用时返回正常的结果或者捕获异常时, 栈帧出栈.
栈帧: 栈帧存储方法的相关信息, 包含局部变量数表, 返回值, 操作数栈, 动态链接.
1.3, 本地方法栈
从功能上来说与虚拟机栈类似, 但是虚拟机栈执行的是字节码, 而本地方法栈调用的是 Native 方法, 并且它是线程独享的.
1.4, 程序计数器
程序计数器是线程独享的, 它是记录当前线程执行的字节码行号. 在多线程执行时, CPU 会来回在线程之间进行切换, 那么当再次回到一条线程时, 是如何得知线程的存储单元及执行指令. 而程序计数器便会进行存储下一条存储单元的地址, 执行完毕后程序计数器自动加 1 , 以此循环直到程序结束为止.
1.5, 堆
说到堆这个概念想必都不陌生, 它是内存中的重要角色. 它主要是用来存储被创建出来的对象, 通过关键字 new 实例出来的, 是所有线程共享的一块最大的区域.
== 特别注意: 在 JDK1.7 及以后, 常量池移动到堆内存中.==
堆还包括一个 == 常量池 ==, 用来存储编译期间生成的 == 字面量和符号 == 引用. 这部分内容在类被加载后, 都会存储到方法区中. 同时, 运行时产生的新常量也可以被放入常量池中, 比如 String 类中的 intern() 方法产生的常量.
常量池就是这个类型用到的常量的一个有序集合. 包括直接常量 (基本类型, String) 和对其他类型, 方法, 字段的符号引用.
二, 常量池
2.1, 什么是常量:
常量是指被 final 修饰的变量, 值一旦确定就无法改变.
final 可以修饰静态变量, 方法, 实例变量和局部变量.
常量池分为两种形式: 静态常量池和运行时常量池
2.2, 静态常量池
即 *.class 文件中的常量池, class 文件中的常量池不仅仅包含字符串 (数字) 字面量, 还包含类, 方法的信息, 占用 class 文件绝大部分空间. 这种常量池用于存放字面量和符号引用量.
2.3, 运行时常量池
指 JVM 虚拟机在完成类装载操作后, 将 class 文件中的常量池载入到内存中, 并保存在方法区中, 我们常说的常量池, 就是指方法区中的运行时常量池. 同样运行时常量池一个重要的特征就是具有动态性, 指并不需要常量只有在编译期才会产生, 在运行期也会将新的常量保存到常量池中, 如 String 类中的 intern()方法.
三,== 和 equals
3.1, 两者之间区边
==:
对于基本类型来说:== 表示数值的比较
对于引用类型来说:== 表示地址值的比较
equals:
比较的是两者之间值是否相等, 但是 Java 中的类都是直接或者间接继承 Object 类, 而 equals 不也例外. 其实在 equals 源码中也是使用 == 进行比较的, 如下源码:
![](https://img2018.cnblogs.com/blog/1655301/201909/1655301-20190902223856542-1095893842.png)
那么问题来了, 这和 == 又有什么区别呢?
上面说到 equals 也是继承自 java.lang.Object, 因此可以对 equals 进行重写来定义我们自己的比较方式.
请参看以下代码:
- String str1 = "abc";
- String str2 = "abc";
- char[] strArray = {'a','b','c'};
- String str3 = new String(strArray);
- String str4 = "abc";
- System.out.println(str1 == str2);
- System.out.println(str1 == str3);
- System.out.println(str2 == str3);
- System.out.println(str4.equals(str1));
以上运行结果为:
- true
- false
- false
- true
接下来我们依次分析上面的结果:
1,str1 与 str2 比较的是字符串对象地址, 因为它们的值是相同的, 所以地址值也是相同的.
2,str3 是 new 出来的示例对象, 在堆内存中会开辟一块新的内存地址, 它并不在常量池中. 所以返回结果为 false.
3, 同理 str2 与 str3 比较也是一样的结果.
4,equals 比较的是值是否相同, 所以返回的结果为 true.
如图所示:
四, String 常用方法
首先声明字符串:
- String str1 = "abc";
- 4.1,int length()
- int length = str1.length();
- System.out.println(length);
- 4.2,char charAt(值)
- String str= "abc";
- char c = str.charAt(1);
- System.out.println(c);
- 4.3,char toCharArray()
- String str= "abc";
- char c[] = str.toCharArray();
- for (int i = 0; i < c.length; i++) {
- System.out.println("转为数组输出:" + c[i]);
- }
- 4.4,int indexOf("字符"); int lastIndexOf("字符")
- String str="axcdefgabc";
- int a1 = str.indexOf("a");
- int a2 = str.indexOf("x", 2);
- int a3 = str.lastIndexOf("c");
- System.out.println("你的位置为:" + a1);
- System.out.println("为的位置为:" + a2);
- System.out.println("点最后出现的位置为:" + a3);
4.5, 字符串大小写转换
toUpperCase(); 转换成大写
toLowerCase(); 转换成小写
- String str = "hello world";
- String str1 = "HELLO WORD";
- System.out.println("将字符串转大写为:" + str.toUpperCase());
- System.out.println("将字符串转换成小写为:" + str1.toLowerCase());
- 4.6,String[] split("字符")
- String str = "abc,def,123";
- String[] arr1 = str.split(",");
- 4.7,boolean equals(Object anObject)
- String str = "abc";
- String str1= "123";
- if(str.equals(str1)) {
- System.out.println("相等");
- }
- else{
- System.out.println("不相等");
- }
- 4.8,String trim()
- String str = "abc";
- System.out.println("去掉左右空格后:" + str.trim());
4.9, 字符串替换
String replace(char oldChar,char newChar)
String replaceAll(String,String)将某个内容全部替换成指定内容
String repalceFirst(String,String)将第一次出现的某个内容替换成指定的内容
- String str = "abcdefgabdc";
- System.out.println("替换:" + str.replace("abc", "123"));
- System.out.println("替换全部:" + str.replaceAll("ab", "12"));
- System.out.println("替换第一次出现:" + str.repalceFirst("a", "a"));
- 4.10,String substring(int beginIndex,int endIndex)
- String str = "abcdefg";
- // 截取 0-3 个位置的内容, 不含 3
- System.out.println("截取后的字符为:" + str.substring(0, 3));
- // 从第 3 个位置开始截取, 含 2
- System.out.println("截取后字符为:" + str.substring(2));
- 4.11,boolean equalsIgnoreCase(String)
- String str = "ABC";
- String str1 = "abc";
- if(str.equalsIgnoreCase(str1)){
- System.out.println("相等");
- }
- else{
- System.out.println("不相等");
- }
- 4.12,boolean contains(String)
- String str = "ABCDEF";
- String str1 = "ABC";
- if(str.contains(str1)){
- System.out.println("str 内容中包含 ABC");
- }
- else{
- System.out.println("str 内容中不包含 ABC");
- }
五, 总结
1, 对于 JVM 内存的分配, 在 jdk6 中存在方法区, jdk8 中便没有方法区, 改成元区域.
2,jdk6 中常量池存在方法区中, jdk7 以后常量池移动到堆中.
来源: http://virtual.51cto.com/art/201909/602466.htm