String类
特点
字符串是不变的,值在创建之后无法改变。因为String对象不可变,所以可以共享他们。
例如:两个字符串完全相同,则在内存中可以共享同一个内存地址。
字符串常量值
字符串的缓存是在字符串常量池里进行缓存,字符串常量池存在于方法区里,(Method Area),
方法区是加载代码的区域,又称为永久代(Permanent Generation)。
方法区是被所有线程共享,同时操作的内存。
堆内存 heap
堆内存在逻辑上分为三部分 (perm):
新生代(Young generation 简写YoungGen)
老年代(Old generation 简写OldGen)
永久代(Permanent generation 简写 PermGen)
新生代:
刚创建的内存存储的位置。刚创建的对象都在新生代中。新生代的GC回收机制不一样,会非常快的清理垃圾。如果一个对象经过15次都没有被GC回收,就会被放入老年代,说明这个内存被使用的时间较长。
老年代:
老年代中的GC回收频率低,被问询的次数少,但是也可能被退回新生代。
永久代:
不进行GC,永久存在,类,方法,常亮,尤其是static修饰的,都在永久代。
在这一部分,字符串类型的对象创建后都会被放入永久代,再下次创建字符串类型的对象时会在永久代中寻找这个值。所以这就导致两个字符串直接赋值之后用等等号进行比较会返回true的情况。然而如果在创建字符串对象时用new开辟新的内存空间,在用等等号进行比较时,值一定不会相同,因为new新开辟的内存空间地址一定不和之前的字符串地址相同——哪怕两个字符串值看起来完全一样。
字符串运算的原理
例如:
String text1 = “123”;
String text2 = “456”;
String text3 = “789”;
text1 = text1 + text2 + text3;
System.out.println(text1);
这段代码在运算时,首先会在栈内存中开辟三个text的内存地址,地址指向在堆内存中存储的具体的text的值,在运算时会一次次的进行字符串拼接,拼接后的字符串会有新的内存地址,会占用堆内存,造成了程序中内存的占用空间过大。
我们在编写程序时尽量减少字符串拼接的操作。如果无法避免这样的操作,必须要进行字符串拼接,可以使用可变字符序列例如StringBuffer或者StringBuilder。这两个类都是用来进行字符串拼接的操作,一StringBuffer为例,在使用时先用无参的方式构造一个StringBuffer(),即构造了一个字符串的缓冲区,初始容量为16个字符,在字符串满了之后会进行动态扩容,添加数据可以使用append来添加各种类型的数据,这样在内存中每次拼接产生的内存垃圾都能很好的被GC进行回收。而直接进行字符串拼接的话,拼接过程中产生的字符串垃圾都会在永久代中无法被很好的回收。
对于StringBuffer可以用toString之类的方法来调用其中的字符串。
实例:
StringBuilder sb = new StringBuilder();
sb.append(“1”);
sb.append(“2”);
sb.append(“3”);
String text = sb.toString();
在拼接字符串的时候都尽量使用Stringbuilder和StringBuffer的操作,程序员在写程序时应该尽量的节约内存空间。
在不考虑线程安全的情况下,StringBuffer和StringBulider使用方法完全一致。
关键字final
Final可以用来修饰变量、方法和类。
在修饰类的时候表示这个类不能被继承,同时这个类中的成员方法都会被指定为final方法。
修饰方法的时候可以让方法不能被子类重写,如果是private权限的final方法子类不能继承,更不能重写。
修饰变量的时候表示final成员变量是常量,被赋值后就无法再改变。如果final用来修饰一个引用类型则初始化之后就不能再指向其他对象,然而其引用对象的内容可以改变。修饰基本数据类型时该数据类型一旦初始化就不能再发生变化。当方法的参数被final修饰时,说明参数是只读类型,值无法改变。