diff --git a/README.md b/README.md index 7fa3f92b5e07feaf93be9f7a40a20366c02b7995..3d0558d26e5b34fa3c93b6a06c1f0adb7f6d294a 100644 --- a/README.md +++ b/README.md @@ -58,15 +58,15 @@ Java的继承与实现 #### 自动拆装箱 -什么是包装类型、什么是基本类型、什么是自动拆装箱 +[什么是包装类型、什么是基本类型、什么是自动拆装箱](/basics/java-basic/boxing-unboxing.md) -Integer的缓存机制 +[Integer的缓存机制](/basics/java-basic/integer-cache.md) #### String -字符串的不可变性 +[字符串的不可变性](/basics/java-basic/final-string.md) -JDK 6和JDK 7中substring的原理及区别、 +[JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md) replaceFirst、replaceAll、replace区别、 @@ -74,7 +74,7 @@ String对“+”的重载、字符串拼接的几种方式和区别 String.valueOf和Integer.toString的区别、 -switch对String的支持 +[switch对String的支持](/basics/java-basic/switch-string.md) 字符串池、常量池(运行时常量池、Class常量池)、intern diff --git a/basics/java-basic/boxing-unboxing.md b/basics/java-basic/boxing-unboxing.md new file mode 100644 index 0000000000000000000000000000000000000000..d4f0df07461d554940efc5e92db8a39227d1815b --- /dev/null +++ b/basics/java-basic/boxing-unboxing.md @@ -0,0 +1,324 @@ +本文主要介绍Java中的自动拆箱与自动装箱的有关知识。 + +## 基本数据类型 + +基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。 + +Java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。 + +Java基本类型共有八种,基本类型可以分为三类: + +> 字符类型`char` +> +> 布尔类型`boolean` +> +> 数值类型`byte`、`short`、`int`、`long`、`float`、`double`。 + +数值类型又可以分为整数类型`byte`、`short`、`int`、`long`和浮点数类型`float`、`double`。 + +Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。 + +实际上,Java中还存在另外一种基本类型`void`,它也有对应的包装类 `java.lang.Void`,不过我们无法直接对它们进行操作。 + +### 基本数据类型有什么好处 + +我们都知道在Java语言中,`new`一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。 + +对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个Java对象的话,就会比较笨重。所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。 + +### 整型的取值范围 + +Java中的整型主要包含`byte`、`short`、`int`和`long`这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。 + +先来个简答的科普,1字节=8位(bit)。java中的整型属于有符号数。 + +先来看计算中8bit可以表示的数字: + + 最小值:10000000 (-128)(-2^7) + 最大值:01111111(127)(2^7-1) + + +整型的这几个类型中, + +* byte:byte用1个字节来存储,范围为-128(-2^7)到127(2^7-1),在变量初始化的时候,byte类型的默认值为0。 + +* short:short用2个字节存储,范围为-32,768 (-2^15)到32,767 (2^15-1),在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0。 + +* int:int用4个字节存储,范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0。 + +* long:long用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为0。 + +### 超出范围怎么办 + +上面说过了,整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出。如以下代码: + + int i = Integer.MAX_VALUE; + int j = Integer.MAX_VALUE; + + int k = i + j; + System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")"); + + +输出结果:i (2147483647) + j (2147483647) = k (-2) + +**这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。**所以,在程序中,使用同类型的数据进行运算的时候,**一定要注意数据溢出的问题。** + +## 包装类型 + +Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。 + +包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示 + +| 基本数据类型 | 包装类 | +| ------- | --------- | +| byte | Byte | +| boolean | Boolean | +| short | Short | +| char | Character | +| int | Integer | +| long | Long | +| float | Float | +| double | Double | + +在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。 + +### 为什么需要包装类 + +很多人会有疑问,既然Java中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢? + +这个问题,其实前面已经有了答案,因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。 + +为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。 + +## 拆箱与装箱 + +那么,有了基本数据类型和包装类,肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的int转换成一个包装类型的Integer对象。 + +我们认为包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于boxing,中文翻译为装箱。 + +反之,把包装类转换成基本数据类型的过程就是拆包装,英文对应于unboxing,中文翻译为拆箱。 + +在Java SE5之前,要进行装箱,可以通过以下代码: + + Integer i = new Integer(10); + + +## 自动拆箱与自动装箱 + +在Java SE5中,为了减少开发人员的工作,Java提供了自动拆箱与自动装箱功能。 + +自动装箱: 就是将基本数据类型自动转换成对应的包装类。 + +自动拆箱:就是将包装类自动转换成对应的基本数据类型。 + + Integer i =10; //自动装箱 + int b= i; //自动拆箱 + + +`Integer i=10` 可以替代 `Integer i = new Integer(10);`,这就是因为Java帮我们提供了自动装箱的功能,不需要开发者手动去new一个Integer对象。 + +## 自动装箱与自动拆箱的实现原理 + +既然Java提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java是如何实现的自动拆装箱功能。 + +我们有以下自动拆装箱的代码: + + public static void main(String[]args){ + Integer integer=1; //装箱 + int i=integer; //拆箱 + } + + +对以上代码进行反编译后可以得到以下代码: + + public static void main(String[]args){ + Integer integer=Integer.valueOf(1); + int i=integer.intValue(); + } + + +从上面反编译后的代码可以看出,int的自动装箱都是通过`Integer.valueOf()`方法来实现的,Integer的自动拆箱都是通过`integer.intValue`来实现的。如果读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律: + +> 自动装箱都是通过包装类的`valueOf()`方法来实现的.自动拆箱都是通过包装类对象的`xxxValue()`来实现的。 + +## 哪些地方会自动拆装箱 + +我们了解过原理之后,在来看一下,什么情况下,Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了,那是最简单的也最容易理解的。 + +我们主要来看一下,那些可能被忽略的场景。 + +### 场景一、将基本数据类型放入集合类 + +我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢? + + List li = new ArrayList<>(); + for (int i = 1; i < 50; i ++){ + li.add(i); + } + + +将上面代码进行反编译,可以得到以下代码: + + List li = new ArrayList<>(); + for (int i = 1; i < 50; i += 2){ + li.add(Integer.valueOf(i)); + } + + +以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。 + +### 场景二、包装类型和基本类型的大小比较 + +有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码: + + Integer a=1; + System.out.println(a==1?"等于":"不等于"); + Boolean bool=false; + System.out.println(bool?"真":"假"); + + +对以上代码进行反编译,得到以下代码: + + Integer a=1; + System.out.println(a.intValue()==1?"等于":"不等于"); + Boolean bool=false; + System.out.println(bool.booleanValue?"真":"假"); + + +可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。 + +### 场景三、包装类型的运算 + +有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢?看以下代码: + + Integer i = 10; + Integer j = 20; + + System.out.println(i+j); + + +反编译后代码如下: + + Integer i = Integer.valueOf(10); + Integer j = Integer.valueOf(20); + System.out.println(i.intValue() + j.intValue()); + + +我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。 + +### 场景四、三目运算符的使用 + +这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码: + + boolean flag = true; + Integer i = 0; + int j = 1; + int k = flag ? i : j; + + +很多人不知道,其实在`int k = flag ? i : j;`这一行,会发生自动拆箱。反编译后代码如下: + + boolean flag = true; + Integer i = Integer.valueOf(0); + int j = 1; + int k = flag ? i.intValue() : j; + System.out.println(k); + + +这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。 + +因为例子中,`flag ? i : j;`片段中,第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候i的值为`null`,那么久会发生NPE。([自动拆箱导致空指针异常][1]) + +### 场景五、函数参数与返回值 + +这个比较容易理解,直接上代码了: + + //自动拆箱 + public int getNum1(Integer num) { + return num; + } + //自动装箱 + public Integer getNum2(int num) { + return num; + } + + +## 自动拆装箱与缓存 + +Java SE的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果: + + public static void main(String... strings) { + + Integer integer1 = 3; + Integer integer2 = 3; + + if (integer1 == integer2) + System.out.println("integer1 == integer2"); + else + System.out.println("integer1 != integer2"); + + Integer integer3 = 300; + Integer integer4 = 300; + + if (integer3 == integer4) + System.out.println("integer3 == integer4"); + else + System.out.println("integer3 != integer4"); + + } + + +我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,==比较的是对象应用,而equals比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。 + +上面这段代码真正的输出结果: + + integer1 == integer2 + integer3 != integer4 + + +原因就和Integer中的缓存机制有关。在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。 + +> 适用于整数值区间-128 至 +127。 +> +> 只适用于自动装箱。使用构造函数创建对象不适用。 + +具体的代码实现可以阅读[Java中整型的缓存机制][2]一文,这里不再阐述。 + +我们只需要知道,当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。 + +其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过`-XX:AutoBoxCacheMax=size`修改。 + +实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过`java.lang.Integer.IntegerCache.high`设置最大值。 + +这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。 + +在Boxing Conversion部分的Java语言规范(JLS)规定如下: + +如果一个变量p的值是: + + -128至127之间的整数(§3.10.1) + + true 和 false的布尔值 (§3.10.3) + + ‘\u0000’至 ‘\u007f’之间的字符(§3.10.4) + + +范围内的时,将p包装成a和b两个对象时,可以直接使用a==b判断a和b的值是否相等。 + +## 自动拆装箱带来的问题 + +当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底什么时候需要拆装箱。但是,他也会引入一些问题。 + +> 包装对象的数值比较,不能简单的使用`==`,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用`equals`比较。 +> +> 前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。 +> +> 如果一个for循环中有大量拆装箱操作,会浪费很多资源。 + +## 参考资料 + +[Java的自动拆装箱][3] + + [1]: http://www.hollischuang.com/archives/435 + [2]: http://www.hollischuang.com/archives/1174 + [3]: https://www.jianshu.com/p/cc9312104876 \ No newline at end of file diff --git a/basics/java-basic/final-string.md b/basics/java-basic/final-string.md new file mode 100644 index 0000000000000000000000000000000000000000..3f7aade7ee5250d91a732798c61897108a7ffa97 --- /dev/null +++ b/basics/java-basic/final-string.md @@ -0,0 +1,39 @@ + +* * * + +## 定义一个字符串 + + String s = "abcd"; + + +![String-Immutability-1][1] + +`s`中保存了string对象的引用。下面的箭头可以理解为“存储他的引用”。 + +## 使用变量来赋值变量 + + String s2 = s; + + +![String-Immutability-2][2] + +s2保存了相同的引用值,因为他们代表同一个对象。 + +## 字符串连接 + + s = s.concat("ef"); + + +![string-immutability][3] + +`s`中保存的是一个重新创建出来的string对象的引用。 + +## 总结 + +一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。 + +如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。 + + [1]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-1.jpeg + [2]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-2.jpeg + [3]: http://www.programcreek.com/wp-content/uploads/2009/02/string-immutability-650x279.jpeg \ No newline at end of file diff --git a/basics/java-basic/integer-cache.md b/basics/java-basic/integer-cache.md new file mode 100644 index 0000000000000000000000000000000000000000..9b47ceea8d4967b7bd7891097d87e553bece601e --- /dev/null +++ b/basics/java-basic/integer-cache.md @@ -0,0 +1,164 @@ +英文原文:[Java Integer Cache][1] 翻译地址:[Java中整型的缓存机制][2] 原文作者:[Java Papers][3] 翻译作者:[Hollis][4] 转载请注明出处。 + +本文将介绍Java中Integer的缓存相关知识。这是在Java 5中引入的一个有助于节省内存、提高性能的功能。首先看一个使用Integer的示例代码,从中学习其缓存行为。接着我们将为什么这么实现以及他到底是如何实现的。你能猜出下面的Java程序的输出结果吗。如果你的结果和真正结果不一样,那么你就要好好看看本文了。 + + package com.javapapers.java; + + public class JavaIntegerCache { + public static void main(String... strings) { + + Integer integer1 = 3; + Integer integer2 = 3; + + if (integer1 == integer2) + System.out.println("integer1 == integer2"); + else + System.out.println("integer1 != integer2"); + + Integer integer3 = 300; + Integer integer4 = 300; + + if (integer3 == integer4) + System.out.println("integer3 == integer4"); + else + System.out.println("integer3 != integer4"); + + } + } + + +我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,`==`比较的是对象应用,而`equals`比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。 + +上面这段代码真正的输出结果: + + integer1 == integer2 + integer3 != integer4 + + +## Java中Integer的缓存实现 + +在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。 + +> 适用于整数值区间-128 至 +127。 +> +> 只适用于自动装箱。使用构造函数创建对象不适用。 + +Java的编译器把基本数据类型自动转换成封装类对象的过程叫做`自动装箱`,相当于使用`valueOf`方法: + + Integer a = 10; //this is autoboxing + Integer b = Integer.valueOf(10); //under the hood + + +现在我们知道了这种机制在源码中哪里使用了,那么接下来我们就看看JDK中的`valueOf`方法。下面是`JDK 1.8.0 build 25`的实现: + + /** + * Returns an {@code Integer} instance representing the specified + * {@code int} value. If a new {@code Integer} instance is not + * required, this method should generally be used in preference to + * the constructor {@link #Integer(int)}, as this method is likely + * to yield significantly better space and time performance by + * caching frequently requested values. + * + * This method will always cache values in the range -128 to 127, + * inclusive, and may cache other values outside of this range. + * + * @param i an {@code int} value. + * @return an {@code Integer} instance representing {@code i}. + * @since 1.5 + */ + public static Integer valueOf(int i) { + if (i >= IntegerCache.low && i <= IntegerCache.high) + return IntegerCache.cache[i + (-IntegerCache.low)]; + return new Integer(i); + } + + +在创建对象之前先从IntegerCache.cache中寻找。如果没找到才使用new新建对象。 + +## IntegerCache Class + +IntegerCache是Integer类中定义的一个`private static`的内部类。接下来看看他的定义。 + + /** + * Cache to support the object identity semantics of autoboxing for values between + * -128 and 127 (inclusive) as required by JLS. + * + * The cache is initialized on first usage. The size of the cache + * may be controlled by the {@code -XX:AutoBoxCacheMax=} option. + * During VM initialization, java.lang.Integer.IntegerCache.high property + * may be set and saved in the private system properties in the + * sun.misc.VM class. + */ + + private static class IntegerCache { + static final int low = -128; + static final int high; + static final Integer cache[]; + + static { + // high value may be configured by property + int h = 127; + String integerCacheHighPropValue = + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); + if (integerCacheHighPropValue != null) { + try { + int i = parseInt(integerCacheHighPropValue); + i = Math.max(i, 127); + // Maximum array size is Integer.MAX_VALUE + h = Math.min(i, Integer.MAX_VALUE - (-low) -1); + } catch( NumberFormatException nfe) { + // If the property cannot be parsed into an int, ignore it. + } + } + high = h; + + cache = new Integer[(high - low) + 1]; + int j = low; + for(int k = 0; k < cache.length; k++) + cache[k] = new Integer(j++); + + // range [-128, 127] must be interned (JLS7 5.1.7) + assert IntegerCache.high >= 127; + } + + private IntegerCache() {} + } + + +其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过`-XX:AutoBoxCacheMax=size`修改。 缓存通过一个for循环实现。从低到高并创建尽可能多的整数并存储在一个整数数组中。这个缓存会在Integer类第一次被使用的时候被初始化出来。以后,就可以使用缓存中包含的实例对象,而不是创建一个新的实例(在自动装箱的情况下)。 + +实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过`java.lang.Integer.IntegerCache.high`设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。 + +## Java语言规范中的缓存行为 + +在[Boxing Conversion][5]部分的Java语言规范(JLS)规定如下: + +> 如果一个变量p的值是: +> +> -128至127之间的整数(§3.10.1) +> +> true 和 false的布尔值 (§3.10.3) +> +> ‘\u0000’至 ‘\u007f’之间的字符(§3.10.4) +> +> 中时,将p包装成a和b两个对象时,可以直接使用a==b判断a和b的值是否相等。 + +## 其他缓存的对象 + +这种缓存行为不仅适用于Integer对象。我们针对所有的整数类型的类都有类似的缓存机制。 + +> 有ByteCache用于缓存Byte对象 +> +> 有ShortCache用于缓存Short对象 +> +> 有LongCache用于缓存Long对象 +> +> 有CharacterCache用于缓存Character对象 + +`Byte`, `Short`, `Long`有固定范围: -128 到 127。对于`Character`, 范围是 0 到 127。除了`Integer`以外,这个范围都不能改变。 + + [1]: http://javapapers.com/java/java-integer-cache/ + [2]: http://www.hollischuang.com/?p=1174 + [3]: http://javapapers.com/ + [4]: http://www.hollischuang.com + [5]: http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7 \ No newline at end of file diff --git a/basics/java-basic/substring.md b/basics/java-basic/substring.md new file mode 100644 index 0000000000000000000000000000000000000000..d8bbeb94a6033d1c2a6e5b96456dbbc316e47da6 --- /dev/null +++ b/basics/java-basic/substring.md @@ -0,0 +1,90 @@ +String是Java中一个比较基础的类,每一个开发人员都会经常接触到。而且,String也是面试中经常会考的知识点。String有很多方法,有些方法比较常用,有些方法不太常用。今天要介绍的subString就是一个比较常用的方法,而且围绕subString也有很多面试题。 + +`substring(int beginIndex, int endIndex)`方法在不同版本的JDK中的实现是不同的。了解他们的区别可以帮助你更好的使用他。为简单起见,后文中用`substring()`代表`substring(int beginIndex, int endIndex)`方法。 + +## substring() 的作用 + +`substring(int beginIndex, int endIndex)`方法截取字符串并返回其[beginIndex,endIndex-1]范围内的内容。 + + String x = "abcdef"; + x = x.substring(1,3); + System.out.println(x); + + +输出内容: + + bc + + +## 调用substring()时发生了什么? + +你可能知道,因为x是不可变的,当使用`x.substring(1,3)`对x赋值的时候,它会指向一个全新的字符串: + +![string-immutability1][1] + +然而,这个图不是完全正确的表示堆中发生的事情。因为在jdk6 和 jdk7中调用substring时发生的事情并不一样。 + +## JDK 6中的substring + +String是通过字符数组实现的。在jdk 6 中,String类包含三个成员变量:`char value[]`, `int offset`,`int count`。他们分别用来存储真正的字符数组,数组的第一个位置索引以及字符串中包含的字符个数。 + +当调用substring方法的时候,会创建一个新的string对象,但是这个string的值仍然指向堆中的同一个字符数组。这两个对象中只有count和offset 的值是不同的。 + +![string-substring-jdk6][2] + +下面是证明上说观点的Java源码中的关键代码: + + //JDK 6 + String(int offset, int count, char value[]) { + this.value = value; + this.offset = offset; + this.count = count; + } + + public String substring(int beginIndex, int endIndex) { + //check boundary + return new String(offset + beginIndex, endIndex - beginIndex, value); + } + + +## JDK 6中的substring导致的问题 + +如果你有一个很长很长的字符串,但是当你使用substring进行切割的时候你只需要很短的一段。这可能导致性能问题,因为你需要的只是一小段字符序列,但是你却引用了整个字符串(因为这个非常长的字符数组一直在被引用,所以无法被回收,就可能导致内存泄露)。在JDK 6中,一般用以下方式来解决该问题,原理其实就是生成一个新的字符串并引用他。 + + x = x.substring(x, y) + "" + + +关于JDK 6中subString的使用不当会导致内存系列已经被官方记录在Java Bug Database中: + +leak + +> 内存泄露:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 + +## JDK 7 中的substring + +上面提到的问题,在jdk 7中得到解决。在jdk 7 中,substring方法会在堆内存中创建一个新的数组。 + +![string-substring-jdk7][3] + +Java源码中关于这部分的主要代码如下: + + //JDK 7 + public String(char value[], int offset, int count) { + //check boundary + this.value = Arrays.copyOfRange(value, offset, offset + count); + } + + public String substring(int beginIndex, int endIndex) { + //check boundary + int subLen = endIndex - beginIndex; + return new String(value, beginIndex, subLen); + } + + +以上是JDK 7中的subString方法,其使用`new String`创建了一个新字符串,避免对老字符串的引用。从而解决了内存泄露问题。 + +所以,如果你的生产环境中使用的JDK版本小于1.7,当你使用String的subString方法时一定要注意,避免内存泄露。 + + [1]: http://www.programcreek.com/wp-content/uploads/2013/09/string-immutability1-650x303.jpeg + [2]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk6-650x389.jpeg + [3]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk71-650x389.jpeg \ No newline at end of file diff --git a/basics/java-basic/switch-string.md b/basics/java-basic/switch-string.md new file mode 100644 index 0000000000000000000000000000000000000000..29feeda475821d47407329b0f6af2678375864d6 --- /dev/null +++ b/basics/java-basic/switch-string.md @@ -0,0 +1,150 @@ +Java 7中,switch的参数可以是String类型了,这对我们来说是一个很方便的改进。到目前为止switch支持这样几种数据类型:`byte` `short` `int` `char` `String` 。但是,作为一个程序员我们不仅要知道他有多么好用,还要知道它是如何实现的,witch对整型的支持是怎么实现的呢?对字符型是怎么实现的呢?String类型呢?有一点Java开发经验的人这个时候都会猜测switch对String的支持是使用equals()方法和hashcode()方法。那么到底是不是这两个方法呢?接下来我们就看一下,switch到底是如何实现的。 + + + +### 一、switch对整型支持的实现 + +下面是一段很简单的Java代码,定义一个int型变量a,然后使用switch语句进行判断。执行这段代码输出内容为5,那么我们将下面这段代码反编译,看看他到底是怎么实现的。 + + public class switchDemoInt { + public static void main(String[] args) { + int a = 5; + switch (a) { + case 1: + System.out.println(1); + break; + case 5: + System.out.println(5); + break; + default: + break; + } + } + } + //output 5 + + +反编译后的代码如下: + + public class switchDemoInt + { + public switchDemoInt() + { + } + public static void main(String args[]) + { + int a = 5; + switch(a) + { + case 1: // '\001' + System.out.println(1); + break; + + case 5: // '\005' + System.out.println(5); + break; + } + } + } + + +我们发现,反编译后的代码和之前的代码比较除了多了两行注释以外没有任何区别,那么我们就知道,**switch对int的判断是直接比较整数的值**。 + +### 二、switch对字符型支持的实现 + +直接上代码: + + public class switchDemoInt { + public static void main(String[] args) { + char a = 'b'; + switch (a) { + case 'a': + System.out.println('a'); + break; + case 'b': + System.out.println('b'); + break; + default: + break; + } + } + } + + +编译后的代码如下: `public class switchDemoChar + + public class switchDemoChar + { + public switchDemoChar() + { + } + public static void main(String args[]) + { + char a = 'b'; + switch(a) + { + case 97: // 'a' + System.out.println('a'); + break; + case 98: // 'b' + System.out.println('b'); + break; + } + } + } + + +通过以上的代码作比较我们发现:对char类型进行比较的时候,实际上比较的是ascii码,编译器会把char型变量转换成对应的int型变量 + +### 三、switch对字符串支持的实现 + +还是先上代码: + + public class switchDemoString { + public static void main(String[] args) { + String str = "world"; + switch (str) { + case "hello": + System.out.println("hello"); + break; + case "world": + System.out.println("world"); + break; + default: + break; + } + } + } + + +对代码进行反编译: + + public class switchDemoString + { + public switchDemoString() + { + } + public static void main(String args[]) + { + String str = "world"; + String s; + switch((s = str).hashCode()) + { + default: + break; + case 99162322: + if(s.equals("hello")) + System.out.println("hello"); + break; + case 113318802: + if(s.equals("world")) + System.out.println("world"); + break; + } + } + } + + +看到这个代码,你知道原来字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**记住,switch中只能使用整型**,比如`byte`。`short`,`char`(ackii码是整型)以及`int`。还好`hashCode()`方法返回的是`int`,而不是`long`。通过这个很容易记住`hashCode`返回的是`int`这个事实。仔细看下可以发现,进行`switch`的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。因为Java编译器只增加了一个`equals`方法,如果你比较的是字符串字面量的话会非常快,比如”abc” ==”abc”。如果你把`hashCode()`方法的调用也考虑进来了,那么还会再多一次的调用开销,因为字符串一旦创建了,它就会把哈希值缓存起来。因此如果这个`siwtch`语句是用在一个循环里的,比如逐项处理某个值,或者游戏引擎循环地渲染屏幕,这里`hashCode()`方法的调用开销其实不会很大。 + +好,以上就是关于switch对整型、字符型、和字符串型的支持的实现方式,总结一下我们可以发现,**其实swich只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后在使用switch的。** \ No newline at end of file