惠州网站建设企业,上海网络优化seo,应用下载,怎样做网络推广在哪济南兴田德润什么活动String
字符型常量和字符串常量的区别#xff1f; 形式上: 字符常量是单引号引起的一个字符#xff0c;字符串常量是双引号引起的若干个字符#xff1b; 含义上: 字符常量相当于一个整型值( ASCII 值)#xff0c;可以参加表达式运算#xff1b;字符串常量代表一个地址值…String
字符型常量和字符串常量的区别 形式上: 字符常量是单引号引起的一个字符字符串常量是双引号引起的若干个字符 含义上: 字符常量相当于一个整型值( ASCII 值)可以参加表达式运算字符串常量代表一个地址值(该字符串在内存中存放位置相当于对象 占内存大小字符常量只占2个字节字符串常量占若干个字节(至少一个字符结束标志) (注意: char 在Java中占两个字节)。
String、StringBuffer、StringBuilder 的区别
可变性
String 是不可变的后面会详细分析原因。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类在 AbstractStringBuilder 中也是使用字符数组保存字符串不过没有使用 final 和 private 关键字修饰最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
abstract class AbstractStringBuilder implements Appendable, CharSequence {char[] value;public AbstractStringBuilder append(String str) {if (str null)return appendNull();int len str.length();ensureCapacityInternal(count len);str.getChars(0, len, value, count);count len;return this;}//...
}线程安全性
String 中的对象是不可变的也就可以理解为常量线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类定义了一些字符串的基本操作如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁所以是线程安全的。StringBuilder 并没有对方法进行加同步锁所以是非线程安全的。
性能
每次对 String 类型进行改变的时候都会生成一个新的 String 对象然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升但却要冒多线程不安全的风险。
对于三者使用的总结
操作少量的数据: 适用 String单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer。
String 为什么是不可变的?
public final class String implements java.io.Serializable, ComparableString, CharSequence {private final char value[];//...
}我们知道被 final 关键字修饰的类不能被继承修饰的方法不能被重写修饰的变量是基本数据类型则值不能改变修饰的变量是引用类型则不能再指向其他对象。因此final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因因为这个数组保存的字符串是可变的final 修饰引用类型变量的情况。String 真正不可变有下面几点原因 保存字符串的数组被 final 修饰且为私有的并且String 类没有提供/暴露修改这个字符串的方法。 String 类被 final 修饰导致其不能被继承进而避免了子类破坏 String 不可变。
如果想要详细了解这个问题可以参考这篇文章——如何理解 String 类型值的不可变
在 Java 9 之后String 、StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串。
public final class String implements java.io.Serializable,ComparableString, CharSequence {// Stable 注解表示变量最多被修改一次称为“稳定的”。Stableprivate final byte[] value;
}abstract class AbstractStringBuilder implements Appendable, CharSequence {byte[] value;} Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?
新版的 String 其实支持两个编码方案 Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下byte 占一个字节(8 位)char 占用 2 个字节16byte 相较 char 节省一半的内存空间。
JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。
如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符byte 和 char 所占用的空间是一样的。
String为什么要设计成不可变的
设计考虑
只有当字符串是不可变的字符串池才有可能实现。
字符串池的实现可以在运行时节约很多heap空间因为不同的字符串变量都指向池中的同一个字符串。
但如果字符串是可变的那么String interning将不能实现
(译者注String interning是指对不同的字符串仅仅只保存一个即不会保存多个相同的字符串)
因为这样的话如果变量改变了它的值那么其它指向这个值的变量的值也会一起改变。设计考虑安全考虑
如果字符串是可变的那么会引起很严重的安全问题。
譬如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接
或者在socket编程中主机名和端口都是以字符串的形式传入。
因为字符串是不可变的所以它的值是不可改变的否则黑客们可以钻到空子改变字符串指向的对象的值造成安全漏洞。安全性因为字符串是不可变的所以是多线程安全的同一个字符串实例可以被多个线程共享。
这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。安全性类加载器要用到字符串不可变性提供了安全性以便正确的类被加载。
譬如你想加载java.sql.Connection类而这个值被改成了myhacked.Connection那么会对你的数据库造成不可知的破坏。安全性效率优化
因为字符串是不可变的所以在它创建的时候hashcode就被缓存了不需要重新计算。
这就使得字符串很适合作为Map中的键字符串的处理速度要快过其它的键对象。
这就是HashMap中的键往往都使用字符串。效率优化总体来说String不可变的原因要包括 设计考虑效率优化以及安全性这三大方面。
字符串常量池
如果想要了解字符串常量池可以参考我的这篇文章——JVM面试题详解系列——Java中几种常量池的区分 。
String 是最基本的数据类型吗?
不是。Java 中的基本数据类型只有 8 个 byte、short、int、long、float、double、char、boolean除了基本类型primitive type剩下的都是引用类型referencetypeJava 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
String有哪些特性? 不变性String 是只读字符串是一个典型的 immutable 对象对它进行任何操作其实都是创建一个新的对象再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时可以保证数据的一致性 常量池优化String 对象创建之后会在字符串常量池中进行缓存如果下次创建同样的对象时会直接返回缓存的引用 final使用 final 来定义 String 类表示 String 类不能被继承提高了系统的安全性。
在使用 HashMap 的时候用 String 做 key 有什么好处
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置因为字符串是不可变的所以当创建字符串时它的 hashcode 被缓存下来不需要再次计算所以相比于其他对象更快。
字符串拼接用“” 还是 StringBuilder?
Java 语言本身并不支持运算符重载“”和“”是专门为 String 类重载过的运算符也是 Java 中仅有的两个重载过的运算符。
String str1 he;
String str2 llo;
String str3 world;
String str4 str1 str2 str3;上面的代码对应的字节码如下 可以看出字符串对象通过“”的字符串拼接方式实际上是通过 StringBuilder 调用 append() 方法实现的拼接完成之后调用 toString() 得到一个 String 对象 。
不过在循环内使用“”进行字符串的拼接的话存在比较明显的缺陷编译器不会创建单个 StringBuilder 以复用会导致创建过多的 StringBuilder 对象。
String[] arr {he, llo, world};
String s ;
for (int i 0; i arr.length; i) {s arr[i];
}
System.out.println(s);StringBuilder 对象是在循环内部被创建的这意味着每循环一次就会创建一个 StringBuilder 对象。 如果直接使用 StringBuilder 对象进行字符串拼接的话就不会存在这个问题了。
String[] arr {he, llo, world};
StringBuilder s new StringBuilder();
for (String value : arr) {s.append(value);
}
System.out.println(s);String#equals() 和 Object#equals() 有何区别
String 中的 equals 方法是被重写过的比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。
String straaa与 String strnew String(“aaa”)一样吗new String(“aaa”);创建了几个字符串对象?
1.使用 String str “aaa” 程序运行时会在常量池中查找”aaa”字符串若没有会将”aaa”字符串放进常量池再将其地址赋给a若有将找到的”aaa”字符串的地址赋给a。
2.使用 String strnew String(“aaa”) 会创建 1 或 2 个字符串对象。
如果字符串常量池中不存在字符串对象“abc”的引用那么会在堆中创建 2 个字符串对象“abc”。
示例代码JDK 1.8
String s1 new String(abc);对应的字节码 ldc 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用如果保存了的话直接返回如果没有保存的话会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。
如果字符串常量池中已存在字符串对象“abc”的引用则只会在堆中创建 1 个字符串对象“abc”。
示例代码JDK 1.8
// 字符串常量池中已存在字符串对象“abc”的引用
String s1 abc;
// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 new String(abc);对应的字节码 这里就不对上面的字节码进行详细注释了7 这个位置的 ldc 命令不会在堆中创建新的字符串对象“abc”这是因为 0 这个位置已经执行了一次 ldc 命令已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 ldc 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。
intern 方法有什么作用?
String.intern() 是一个 native本地方法其作用是将指定的字符串对象的引用保存在字符串常量池中可以简单分为两种情况
如果字符串常量池中保存了对应的字符串对象的引用就直接返回该引用。如果字符串常量池中没有保存了对应的字符串对象的引用那就在常量池中创建一个指向该字符串对象的引用并返回。
示例代码JDK 1.8 :
// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 Java;
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 new String(Java);
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 s4); //trueString 类型的变量和常量做“”运算时发生了什么
先来看字符串不加 final 关键字拼接的情况JDK1.8
String str1 str;
String str2 ing;
String str3 str ing;
String str4 str1 str2;
String str5 string;
System.out.println(str3 str4);//false
System.out.println(str3 str5);//true
System.out.println(str4 str5);//false注意 比较 String 字符串的值是否相等可以使用 equals() 方法。 String 中的 equals 方法是被重写过的。
Object 的 equals 方法是比较的对象的内存地址而 String 的 equals 方法比较的是字符串的值是否相等。
如果你使用 比较两个字符串是否相等的话IDEA 还是提示你使用 equals() 方法替换。对于编译期可以确定值的字符串也就是常量字符串 jvm 会将其存入字符串常量池。并且字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池这个得益于编译器的优化。
在编译过程中Javac 编译器下文中统称为编译器会进行一个叫做 常量折叠(Constant Folding) 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到 常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。
对于 String str3 “str” “ing” 编译器会优化成 String str3 “string”。并不是所有的常量都会进行折叠只有编译器在程序编译期就可以确定值的常量才可以 基本数据类型( byte、boolean、short、char、int、float、long、double)以及字符串常量。 final 修饰的基本数据类型和字符串变量 字符串通过 “”拼接得到的字符串、基本数据类型之间算数运算加减乘除、基本数据类型的位运算、、
引用的值在程序编译期是无法确定的编译器无法对其进行优化。
对象引用和“”的字符串拼接方式实际上是通过 StringBuilder 调用 append() 方法实现的拼接完成之后调用 toString() 得到一个 String 对象 。
String str4 new StringBuilder().append(str1).append(str2).toString();我们在平时写代码的时候尽量避免多个字符串对象拼接因为这样会重新创建对象。如果需要改变字符串的话可以使用 StringBuilder 或者 StringBuffer。不过字符串使用 final 关键字声明之后可以让编译器当做常量来处理。
示例代码
final String str1 str;
final String str2 ing;
// 下面两个表达式其实是等价的
String c str ing;// 常量池中的对象
String d str1 str2; // 常量池中的对象
System.out.println(c d);// true被 final 关键字修改之后的 String 会被编译器当做常量来处理编译器在程序编译期就可以确定它的值其效果就相当于访问常量。
如果 编译器在运行时才能知道其确切值的话就无法对其优化。
示例代码str2 在运行时才能确定其值
final String str1 str;
final String str2 getStr();
String c str ing;// 常量池中的对象
String d str1 str2; // 在堆上创建的新的对象
System.out.println(c d);// false
public static String getStr() {return ing;
}