Hallo zusammen, heute werde ich die Grundkenntnisse von Java - String mit euch teilen.
Die Bedeutung der String-Klasse ist selbstverständlich die am häufigsten verwendete Klasse in unserer Back-End-Entwicklung, daher ist es notwendig, darüber zu sprechen.
Der Hauptinhalt dieses Artikels lautet wie folgt:
Lassen Sie uns zunächst über die acht wichtigsten Datentypen in Java und dann über String sprechen.
Byte: 8 Bit, die maximal gespeicherte Datenmenge beträgt 255 und der gespeicherte Datenbereich liegt zwischen -128 und 127.
kurz: 16 Bit, die maximale Datenspeicherkapazität beträgt 65536 und der Datenbereich beträgt -32768~32767.
int: 32 Bit, die maximale Datenspeicherkapazität beträgt 2 hoch 32 minus 1, der Datenbereich ist negativ 2 hoch 31 plus plus 2 hoch 31 minus 1.
lang: 64 Bit. Die maximale Datenspeicherkapazität beträgt 2 hoch 64 minus 1. Der Datenbereich ist negativ 2 hoch 63 hoch bis positiv 2 hoch 63 minus 1.
float: 32 Bit, Datenbereich ist 3,4e-45~1,4e38, bei direkter Zuweisung muss f oder F nach der Zahl hinzugefügt werden.
double: 64 Bit, Datenbereich ist 4,9e-324~1,8e308, d oder D können bei der Wertzuweisung hinzugefügt oder nicht hinzugefügt werden.
boolean: Es gibt nur zwei Werte: wahr und falsch.
char: 16 Bit, speichert Unicode-Code, weist Werte in einfachen Anführungszeichen zu.
Zusätzlich zu diesen acht Hauptdatentypen (die acht Hauptdatentypen haben auch entsprechende Kapselungstypen, ich glaube, Sie wissen es) gibt es in Java auch einen speziellen Typ: String, was wörtlich „String“ bedeutet.
Adresse: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html
看不懂吗?没事,我们可以借用翻译工具,浏览器自带的,更希望的是你能看懂原版英文。
String 存在于咱们安装的JDK
目录下rt.ar
包中,全路径名为:java.lang.String
rt.ar
package, all Der Pfad Name ist: java.lang.String
. String wird verwendet, um Zeichenfolgen in unserem Java-Code darzustellen, wie zum Beispiel: String str = "中国梦,我的梦"; String name = "zhangsan";
public class User{ private Long id; private String userName; private String address; private String password; .... }
//案例代码,来源于网络 public class StringDemo { public static void main(String[] args) throws Exception { String str1 = "Hello World"; String str2 = "Hello World"; String str3 = "hello world"; String str4 = " hello world "; //返回字符串的长度 System.out.println("r1: " + str1.length()); //比较两个字符串的大小compareTo(返回的是int),0相等,复数小于,正数大于 System.out.println("r2 : " + str1.compareTo(str2)); //比较两个字符串的大小compareTo(返回的是int),0相等,复数小于,正数大于 System.out.println("r3 : " + str1.compareTo(str3)); //字符串比较compareToIgnoreCase,忽略大小写。0相等,复数小于,正数大于 System.out.println("r4 : " + str1.compareToIgnoreCase(str3)); //字符串查找indexOf,返回的是找到的第一个的位置,没找到返回-1。从0开始 System.out.println("r5 : " + str1.indexOf("o")); //查找字符串最后一次出现的位置lastIndexOf System.out.println("r6 : " + str1.lastIndexOf("o")); //删除字符串中的一个字符,字符串从0开始的 substring(a, b) //返回指定起始位置(含)到结束位置(不含)之间的字符串 System.out.println("r7 : " + str1.substring(0, 5) + str1.substring(6)); //字符串替换,替换所有 System.out.println("r8 : " + str1.replace("o", "h")); //字符串替换,替换所有 System.out.println("r9 : " + str1.replaceAll("o", "h")); //字符串替换,替换第一个 System.out.println("r10 : " + str1.replaceFirst("o", "h")); //字符串反转 System.out.println("r11 : " + new StringBuffer(str1).reverse()); //字符串反转 System.out.println("r11’: " + new StringBuilder(str1).reverse()); //字符串分割 String[] temp = str1.split("\\ "); for (String str : temp) { System.out.println("r12 : " + str); } //字符串转大写 System.out.println("r13 : " + str1.toUpperCase()); //字符串转小写 System.out.println("r14 : " + str1.toLowerCase()); //去掉首尾空格 System.out.println("r15 : " + str4.trim()); //是否包含,大小写区分 System.out.println("r16 : " + str1.contains("World")); //返回指定位置字符 System.out.println("r17 : " + str1.charAt(4)); //测试此字符串是否以指定的后缀结束 System.out.println("r18 : " + str1.endsWith("d")); //测试此字符串是否以指定的前缀开始 System.out.println("r19 : " + str1.startsWith("H")); //测试此字符串从指定索引开始的子字符串是否以指定前缀开始 System.out.println("r20 : " + str1.startsWith("ll", 2)); //将指定字符串连接到此字符串的结尾。等价于用“+” System.out.println("r21 : " + str1.concat("haha")); //比较字符串的内容是否相同 System.out.println("r22 : " + str1.equals(str2)); //与equals方法类似,忽略大小写 System.out.println("r23 : " + str1.equalsIgnoreCase(str2)); //判断是否是空字符串 System.out.println("r24: " + str1.isEmpty()); } }
我们开发中差不多也就是这么使用了,但是如果你仅仅是使用很牛了,貌似遇到面试照样会挂。所以,学知识,不能停留在使用层面,需要更深层次的学习。
下面我们就来深层次的学习String,希望大家带着一颗平常的心学习,不要害怕什么,灯笼是张纸,捅破不值钱。
备注:JDK版本为1.8+,因为JDK9版本中和旧版本有细微差别。
/** * The {@code String} class represents character strings. All * string literals in Java programs, such as {@code "abc"}, are * implemented as instances of this class. * 这个String类代表字符串。java编程中的所有字符串常量。 * 比如说:"abc"就是这个String类的实例 * <p> * Strings are constant; their values cannot be changed after they * are created. * 字符串是常量,他们一旦被创建后,他们的值是不能被修改。(重点) * String buffers support mutable strings. * String缓存池支持可变的字符串, * Because String objects are immutable they can be shared. For example: * 因为String字符串不可变,但他们可以被共享。比如: * <blockquote><pre class="brush:php;toolbar:false"> * String str = "abc"; *
* is equivalent to: *
* char data[] = {'a', 'b', 'c'}; * String str = new String(data); *
* Here are some more examples of how strings can be used: * String使用案例 * System.out.println("abc"); * String cde = "cde"; * System.out.println("abc" + cde); * String c = "abc".substring(2,3); * String d = cde.substring(1, 2); *
* The class {@code String} includes methods for examining * individual characters of the sequence, for comparing strings, for * searching strings, for extracting substrings, and for creating a * copy of a string with all characters translated to uppercase or to * lowercase. Case mapping is based on the Unicode Standard version * specified by the {@link java.lang.Character Character} class. * 这个String类包含了一些测评单个字符序列的方法,比如字符串比较,查找字符串, * 提取字符串,和拷贝一个字符串的大小写副本。 * 大小写映射的是基于Character类支持的Unicode的字符集标准版本。 *
* The Java language provides special support for the string * concatenation operator ( + ), and for conversion of * other objects to strings. * java语言提供了对字符串的特殊支持,如:可以通过"+"号来进行字符串的拼接操作, * 为其他类提供了与字符串转换的操作 * String concatenation is implemented * through the {@code StringBuilder}(or {@code StringBuffer}) * class and its {@code append} method. * 字符串的+号拼接操作是通过StringBuilder或者StringBuffer类的append()方法 * 来实现的 * String conversions are implemented through the method * {@code toString}, defined by {@code Object} and * inherited by all classes in Java. * 对象与字符串的转换操作是通过所有类的父类Object中定义的toString()方法来实现的 * For additional information on * string concatenation and conversion, see Gosling, Joy, and Steele, * The Java Language Specification. * *
Unless otherwise noted, passing a null argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * 除非有特殊说明,否则传一个null给String的构造方法或者put方法,会报空指针异常的 *
A {@code String} represents a string in the UTF-16 format * in which supplementary characters are represented by surrogate * pairs (see the section Unicode * Character Representations in the {@code Character} class for * more information). * 一个String 对象代表了一个UTF-16编码语法组成的字符串 * Index values refer to {@code char} code units, so a supplementary * character uses two positions in a {@code String}. *
The {@code String} class provides methods for dealing with * Unicode code points (i.e., characters), in addition to those for * dealing with Unicode code units (i.e., {@code char} values). * 索引值指向字符码单元,所以一个字符在一个字符串中使用两个位置, * String 类提供了一些方法区处理单个Unicode编码,除了那些处理Unicode代码单元。 * @since JDK1.0 */
以上便是String类注释的整个片段,后面剩下的就是作者、相关类、相关方法以及从JDK哪个版本开始有的。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { .... }
类图
String类被final修饰,表示String不可以被继承。下面我们来说说String实现三个接口有什么用处:
简单介绍final
修饰类:类不可被继承,也就是说,String类不可被继承了
修饰方法:把方法锁定,以访任何继承类修改它的涵义
修饰遍历:初始化后不可更改
/** The value is used for character storage. */ // 来用存储String内容的 private final char value[]; // 存储字符串哈希值,默认值为0 private int hash; // Default to 0 // 实现序列化的标识 private static final long serialVersionUID = -6849794470754667710L;
char value[]
被final修饰,说明value[]数组是不可变的。
/** * Initializes a newly created {@code String} object so that it represents * an empty character sequence. Note that use of this constructor is * unnecessary since Strings are immutable. * 初始化新创建的String对象,时期表示空字符串序列。 * 注意:这个构造方法的用法是没必要的,因为字符串是不可变的 */ public String() { this.value = "".value; }
无参构造方法中是将一个空字符串的value值赋给当前value。
/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * 初始化创建的String对象,时期表示与参数相同的字符串序列。 * 换句话说:新创建的字符串是参数自粗糙的副本。 * 除非,如果需要original的显示副本,否则也是没有必要使用此构造方法的 * 因为字符串是不可变的 * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; } //案例: String str=new String("abc");
把original的value赋给当前的value,并把original的hash赋给当前的hash。
/** * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * 分配一个新的{@code String},以便它表示字符数组参数中当前包含的字符。这个 * 复制字符数组的内容;随后修改字符数组不影响新创建的字符串。 * @param value * The initial value of the string */ public String(char value[]) { //注:将传过来的char数组copy到value数组里 this.value = Arrays.copyOf(value, value.length); } //Arrays类中的copyOf方法 public static char[] copyOf(char[] original, int newLength) { //创建一个新的char数组 char[] copy = new char[newLength]; //把original数组中内容拷贝到新建的char数组中 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); //返回新建的char数组 return copy; }
使用Arrays类的copyOf方法,新建一个char数组,将original的内容放到新建的char数组中。
然后,把新建的char数组赋给当前的vlaue。
public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } }
因为StringBuffer是线程安全类,所以,这里加了同步锁,保证线程安全。
public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
StringBuilder是非线程安全的,这里也就没有做线程安全处理,其他内容和前面一样。
注:很多时候我们不会这么去构造,因为StringBuilder跟StringBuffer有toString方法如果不考虑线程安全,优先选择StringBuilder
这里就讲这么多构造方法,其他很复杂,也基本不用,所以,了解这些就够了。如果对其他感兴趣的,可以自行去研究研究。
前面的使用案例中,我们已经对String的大部分方法进行演示一波,这里我们就挑几个相对重要的方法进行深度解析。
hashCode()方法是在Object类中定义的,String对其进行了重写。
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; //hash算法,s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] //使用{@codeint}算法,其中{@codes[i]}是<i> i</i>字符串的第个字符, //{@code n}是字符串,{@code^}表示指数运算。 for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
hashCode的一个具体实现,由于java体系中每个对象都可以转换成String,因此他们应该都是通过这个hash来实现的
接着,我们看看equals()方法;
equals()方法也是Object类中定义的,String类对其进行了重写。
public boolean equals(Object anObject) { //首先会判断是否是同一个对象 if (this == anObject) { return true; } //判断是否为String类型 if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; //长度是否相同 if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; //逐个遍历判断是否相等 //从后往前单个字符判断,如果有不相等,返回假 while (n-- != 0) { //不相等,直接返回false if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
补充:==比较
==比较基本数据类型,比较的是值 ==比较引用数据类型,比较的是地址值
substring方法在工作使用的也是相当的多,作用就是截取一段字符串。
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } //如果beginIndex==0,返回的是当前对象, //否则这里是new的一个新对象,其实String中的很多函数都是这样的操作 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
intern()
方法是native
修饰的方法,表示该方法为本地方法。
/* * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. */ public native String intern();
方法注释会有写到,意思就是调用方法时,如果常量池有当前String
的值,就返回这个值,没有就加进去,返回这个值的引用。
案例如下
public class StringDemo { public static void main(String[] args) throws Exception { String str1 = "a"; String str2 = "b"; String str3 = "ab"; String str4 = str1 + str2; String str5 = new String("ab"); System.out.println(str5 == str3);//堆内存比较字符串池 //intern如果常量池有当前String的值,就返回这个值,没有就加进去,返回这个值的引用 System.out.println(str5.intern() == str3);//引用的是同一个字符串池里的 System.out.println(str5.intern() == str4);//变量相加给一个新值,所以str4引用的是个新的 System.out.println(str4 == str3);//变量相加给一个新值,所以str4引用的是个新的 } }
运行结果
false true false false
获取字符串长度,实际上是获取字符数组长度 ,源码就非常简单了,没什么好说的。
public int length() { return value.length; }
判断字符串是否为空,实际上是盼复字符数组长度是否为0
,源码也是非常简单,没什么好说的。
public boolean isEmpty() { return value.length == 0; }
根据索引参数获取字符 。
public char charAt(int index) { //索引小于0或者索引大于字符数组长度,则抛出越界异常 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } //返回字符数组指定位置字符 return value[index]; }
获取字符串的字节数组,按照系统默认字符编码将字符串解码为字节数组 。
public byte[] getBytes() { return StringCoding.encode(value, 0, value.length); }
这个方法写的很巧妙,先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。
public int compareTo(String anotherString) { //自身对象字符串长度len1 int len1 = value.length; //被比较对象字符串长度len2 int len2 = anotherString.value.length; //取两个字符串长度的最小值lim int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; //从value的第一个字符开始到最小长度lim处为止,如果字符不相等, //返回自身(对象不相等处字符-被比较对象不相等字符) while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } //如果前面都相等,则返回(自身长度-被比较对象长度) return len1 - len2; }
public boolean startsWith(String prefix, int toffset) { char ta[] = value; int to = toffset; char pa[] = prefix.value; int po = 0; int pc = prefix.value.length; // Note: toffset might be near -1>>>1. //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假 if ((toffset < 0) || (toffset > value.length - pc)) { return false; } //从所比较对象的末尾开始比较 while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; } public boolean startsWith(String prefix) { return startsWith(prefix, 0); } public boolean endsWith(String suffix) { return startsWith(suffix, value.length - suffix.value.length); }
起始比较和末尾比较都是比较经常用得到的方法,例如:在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较。
public String concat(String str) { int otherLen = str.length(); //如果被添加的字符串为空,返回对象本身 if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。
public String replace(char oldChar, char newChar) { //新旧值先对比 if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; //找到旧值最开始出现的位置 while (++i < len) { if (val[i] == oldChar) { break; } } //从那个位置开始,直到末尾,用新值代替出现的旧值 if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。
public String trim() { int len = value.length; int st = 0; char[] val = value; /* avoid getfield opcode */ //找到字符串前段没有空格的位置 while ((st < len) && (val[st] <= ' ')) { st++; } //找到字符串末尾没有空格的位置 while ((st < len) && (val[len - 1] <= ' ')) { len--; } //如果前后都没有出现空格,返回字符串本身 return ((st > 0) || (len < value.length)) ? substring(st, len) : this; }
trim方法就是将字符串中的空白字符串删掉。
public static String valueOf(boolean b) { //如果b为true就返回"true"否则返回"false" return b ? "true" : "false"; } public static String valueOf(char c) { //创建data[]数组 并把c添加进去 char data[] = {c}; //创建一个新的String对象并进行返回 return new String(data, true); } public static String valueOf(int i) { //调用Integer对象的toString()方法并进行返回 return Integer.toString(i); } //Integer类中的toString(i)方法 public static String toString(int i) { //是否为Integer最小数,是直接返回 if (i == Integer.MIN_VALUE) return "-2147483648"; //这个i有多少位 int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); //创建一个char数组 char[] buf = new char[size]; //把i内容方法char数组中区 getChars(i, size, buf); //返回一个String对象 return new String(buf, true); }
public String[] split(String regex) { return split(regex, 0); } //使用到了正则表达式 public String[] split(String regex, int limit) { //.... //源码有点多了,反正就是里面使用到了正则表达式,进行切分 }
split()
方法用于把一个字符串分割成字符串数组,返回一个字符串数组返回的数组中的字串不包括 regex
自身。可选的“limit
”是一个整数,第一个方法中默认是0,允许各位指定要返回的最大数组的元素个数。
常见方法源码分析就这么多了,下面我们再回顾到使用场景中来,尤其是面试中。
在java中比较对象是否相同,通常有两种方法:
==
equals
方法注意==
用于基本数据类型的比较和用于引用类型的比较的区别。
==比较基本数据类型,比较的是值
==比较引用数据类型,比较的是地址值
另外,String
对equals
方法进行了重写,所以比较字符串咱们还是要使用equals
方法来比较。主要是String
的equals
方法里包含了==
的判断(请看前面源码分析部分)。
案例
public class StringDemo { public static void main(String[] args) { String st1 = "abc"; String st2 = "abc"; System.out.println(st1 == st2); System.out.println(st1.equals(st2)); } }
输出
true true
看下面这段代码:
String str1 = "abc"; // 在常量池中 String str2 = new String("abc"); // 在堆上
关于这段代码,创建了几个对象,网上答案有多重,1个,2个还有3个的。下面我们就来聊聊到底是几个?
Zunächst müssen wir klarstellen, dass es sich bei allen um String-Variablen und nicht um Objekte handelt, aber das dient nur dem besseren Verständnis und str1 sind keine Objekte.
Zweitens, String str="abc";</ Code>, die Zeichenfolge „abc“ wird im Zeichenfolgenkonstantenpool mit nur 1 Kopie gespeichert. Der Zuweisungsvorgang entspricht zu diesem Zeitpunkt dem Erstellen von 0 oder 1 Objekten. Wenn „abc“ bereits im Konstantenpool vorhanden ist, wird kein Objekt erstellt und die Referenz direkt str1 zugewiesen. Wenn „abc“ nicht im Konstantenpool vorhanden ist, wird ein Objekt erstellt und die Referenz zugewiesen str1. <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">String str="abc";
的时候,字符串“abc”会被存储在字符串常量池中,只有1份,此时的赋值操作等于是创建0个或1个对象。如果常量池中已经存在了“abc”,那么不会再创建对象,直接将引用赋值给str1;如果常量池中没有“abc”,那么创建一个对象,并将引用赋值给str1。
那么,通过new String("abc");的形式又是如何呢?
答案是1个或2个。
当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个一个字符串。然后再执行new操作,会在堆内存中创建一个存储“abc”的String对象,对象的引用赋值给str2。此过程创建了2个对象。
当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。
最后,如果单独问String str=new String("abc");
Die Antwort ist 1 oder 2.
Wenn die JVM auf den obigen Code stößt, sucht sie zunächst, ob „abc“ im Konstantenpool vorhanden ist. Wenn die Zeichenfolge „abc“ nicht vorhanden ist, erstellt sie zunächst diese Zeichenfolge im Konstantenpool. Führen Sie dann die neue Operation aus. Im Heapspeicher wird ein String-Objekt erstellt, das „abc“ speichert, und die Referenz des Objekts wird str2 zugewiesen. Dieser Prozess erstellt 2 Objekte. Wenn beim Abrufen des Konstantenpools festgestellt wird, dass die entsprechende Zeichenfolge bereits vorhanden ist, wird natürlich nur ein neues String-Objekt im Heap erstellt, und in diesem Prozess wird nur 1 Objekt erstellt.
Abschließend, wenn Sie separat fragenDer Unterschied zwischen String und StringBuilder und StringBuffer
Objekte in String sind unveränderlich, was als Konstante und Thread-sicher verstanden werden kann. AbstractStringBuilder ist die gemeinsame übergeordnete Klasse von StringBuilder und StringBuffer und definiert einige grundlegende String-Operationen wie expandCapacity, append, insert, indexOf und andere öffentliche Methoden. StringBuffer fügt der Methode eine Synchronisationssperre hinzu oder fügt der aufrufenden Methode eine Synchronisationssperre hinzu, sodass es threadsicher ist. StringBuilder fügt der Methode keine Synchronisationssperren hinzu und ist daher nicht threadsicher.
🎜🎜Leistung🎜🎜🎜Jedes Mal, wenn der String-Typ geändert wird, wird ein neues String-Objekt generiert, und der Zeiger zeigt dann auf das neue String-Objekt. StringBuffer bearbeitet jedes Mal das StringBuffer-Objekt selbst, anstatt neue Objekte zu generieren und Objektreferenzen zu ändern. Unter den gleichen Umständen kann mit StringBuilder im Vergleich zur Verwendung von StringBuffer nur eine Leistungsverbesserung von etwa 10 bis 15 % erzielt werden, es besteht jedoch das Risiko einer Multithreading-Unsicherheit. 🎜🎜Zusammenfassung der Verwendung der drei:🎜String
StringBuilder
StringBuffer
String 常见的创建方式有两种,new String() 的方式和直接赋值的方式,直接赋值的方式会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值;而 new String() 的方式一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串。
JVM中的常量池
字面量—文本字符串,也就是我们举例中的 public String s = " abc ";
中的 "abc"。
用 final 修饰的成员变量,包括静态变量、实例变量和局部变量。
请看下面这段代码:
String s1 = new String("Java"); String s2 = s1.intern(); String s3 = "Java"; System.out.println(s1 == s2); // false System.out.println(s2 == s3); // true
它们在 JVM 存储的位置,如下图所示:
注意:JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。
除此之外编译器还会对 String 字符串做一些优化,例如以下代码:
String s1 = "Ja" + "va"; String s2 = "Java"; System.out.println(s1 == s2);
虽然 s1 拼接了多个字符串,但对比的结果却是 true,我们使用反编译工具,看到的结果如下:
Compiled from "StringExample.java" public class com.lagou.interview.StringExample { public com.lagou.interview.StringExample(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 public static void main(java.lang.String[]); Code: 0: ldc #2 // String Java 2: astore_1 3: ldc #2 // String Java 5: astore_2 6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 9: aload_1 10: aload_2 11: if_acmpne 18 14: iconst_1 15: goto 19 18: iconst_0 19: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V 22: return LineNumberTable: line 5: 0 line 6: 3 line 7: 6 line 8: 22 }
从编译代码 #2 可以看出,代码 "Ja"+"va" 被直接编译成了 "Java" ,因此 s1==s2 的结果才是 true,这就是编译器对字符串优化的结果。
下面先看看length方法源码:
private final char value[]; public int length() { return value.length; }
length()方法返回的是int类型,那可以得知String类型的长度肯定不能超过Integer.MAX_VALUE
的。
答:首先字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数,且String类中返回字符串长度的方法length() 的返回值也是int ,所以通过查看java源码中的类Integer我们可以看到Integer的最大范围是2^31 -1,由于数组是从0开始的,所以**数组的最大长度可以使【0~2^31】**通过计算是大概4GB。
但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义我们可以知道对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535。
但是由于JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错的,但是运行时拼接或者赋值的话范围是在整形的最大范围。
从JDK7
开始的话,我们就可以在switch条件表达式中使用字符串了,也就是说7之前的版本是不可以的。
switch (str.toLowerCase()) { case "tian": value = 1; break; case "jiang": value = 2; break; }
在JDK7
之前的版本,调用这个方法的时候,会去常量池中查看是否已经存在这个常量了,如果已经存在,那么直接返回这个常量在常量池中的地址值,如果不存在,则在常量池中创建一个,并返回其地址值。
但是在JDK7
以及之后的版本中,常量池从perm区搬到了heap区。intern检测到这个常量在常量池中不存在的时候,不会直接在常量池中创建该对象了,而是将堆中的这个对象的引用直接存到常量池中,减少内存开销。
下面的案例
public class InternTest { public static void main(String[] args) { String str1 = new String("hello") + new String("world"); str1.intern(); String str2 = "helloworld"; System.out.println(str1 == str2);//true System.out.println(str1.intern() == str2);//true } }
Das obige ist der detaillierte Inhalt von2w Wörter detaillierte Erklärung String, yyds. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!