主页

String

2020-09-25
java

String的基本特性 #

  • String类的声明为final。

  • String实现了Serializable接口:表明字符串是支持序列化的。实现了Comparable接口:表面字符串是可以比较大小。

  • String再jdk8及以前使用 final char[] value存储字符串数据,jdk9改为byte[]。

  • String的字符串常量池是一个固定大小的hashtable,在jdk7中默认大小是60013,(jdk8中1009是可以设置的最小值),使用-XX:StringTableSize=可以设置StringTable的长度。如果字符串常量池中的字符串非常多,就可能会造成hash冲突,从而导致链表变得很长(链表长度大于8时会转化成红黑树),但还是会导致性能下降(比如在调用intern时)。

  • 字符串的拼接操作

    • 常量与常量的拼接是放在String Pool中,原因是编译期优化。
    • 只要其中有一个是变量,结果就放在堆中。变量拼接的原理是StringBuilder。
    • 如果拼接的结果调用intern方法,则主动将常量池中还没有的字符串放入池中,并返回其地址(如果String Pool中有,则直接返回其地址),下面还要对intern进行讨论。

字符串的拼接操作 #

String s1="a";
String s2="b";
String s3="ab";
String s4=s1+s2;
/*
如果被拼接的字符串中有变量,执行字符串拼接操作会进行如下几个步骤
①StringBuilder s=new StringBuilder();
②s.append(s1); s.append(s2);
③s.toString();
如果要被拼接的字符串中全是常量或者常量引用,则仍然使用编译器优化,不会涉及到上面三步。
*/
System.out.println(s4==s3);//false

部分源码分析 #

//下面这两行代码输出的结果为什么是nullabc呢?一起来分析一下源码吧
String s=null+"abc";
System.out.println(s);//nullabc

//StringBuilder类,当传入一个对象时会将该对象转化成一个字符串
public StringBuilder append(Object obj) {
	return append(String.valueOf(obj));
}
//String类,当该对象为null时,会返回一个"null"字符串,否则返回该对象的toString方法
public static String valueOf(Object obj) {
	return (obj == null) ? "null" : obj.toString();
}

部分考点分析 #

String s=new String("ab");
/*
创建了两个对象
对象1:new String("ab");
对象2:常量池中的"ab";
*/

String s1=new String("a")+new String("b");
/*
创建了6个对象
对象1:new StringBuilder();
对象2:new String("a");
对象3:常量池中的"a";
对象4:new String("b");
对象5:常量池中的"b";
对象6:当append操作结束后,会调用StringBuilder的toString方法,将StringBuilder对象转化为String对象,
	此时又发生了一次new的操作:new String(value, 0, count);
tips:这里虽然创建了6个对象,但实际上在常量池中并没有创建"ab";
*/

intern #

从jdk7开始,当我们调用String对象的intern()方法:

  • 如果常量池中有这个字符串,则返回常量池中该串的地址。
  • 如果常量池中没有该串,则会把对象的引用地址复制一份,放入常量池,并返回常量池中的引用地址。
String s1=new String("ab");
s1.intern();
String s2="ab";
System.out.println(s1==s2);//false;
/*
这是因为一个是堆中的对象,一个是常量池中的对象
*/

String s3=new String("a")+new String("b");
s3.intern();
String s4="ab";
System.out.println(s3==s4);//true(jdk7及以上版本的结果);
/*
这个是不是感觉很匪夷所思?
原因是在创建了s3之后,常量池中并没有"ab"这个对象,
而在执行s3.intern()后,常量池中多了一个指向堆中的对象的指针,
所以当执行s4="ab"时,s4实际上也是指向了堆中创建的那个对象。
为什么这么做呢?指针才占4个字节,用指针省空间。
*/

对象实例化及内存布局

2020-09-25
java

创建对象的步骤 #

  1. 当遇到一条new指令时,首先判断能否在常量池中定位到一个类的符号应用,并检查这个符号引用代表的类是否加载、解析和初始化

  2. 为对象分配内存,对象所需的内存大小在类加载完后就可完全确定

    • 如果内存规整,指针碰撞。
    • 如果内存不规整,虚拟机需要维护一个列表,空闲列表分配。
  3. 处理并发安全问题

    • 采用cas失败重试
    • 每个线程预先分配一块TLAB,通过-XX:+/-UseTLAB参数来设置。
  4. 对象属性初始化,即所有属性设置0值。

  5. 设置对象的对象头。

  6. 属性的显式初始化、代码块中初始化、执行init方法进行初始化。

对象的内存布局 #

  1. 对象头,包括如下信息:

    • 运行时元数据:哈希值,gc分代年龄,锁状态标志,线程持有的锁,偏向线程id,偏向时间戳。
    • 类型指针:指向类元数据的指针,确定该对象是哪个类的实例。
    • 如果是数组,还需记录数组的长度。
  2. 实例数据:它是真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)。

    • 规则:相同宽度的字段总是被分配在一起;父类定义的变量会出现在子类之前;如果compactFields参数为true(默认为true),子类的窄变量可能插入到父类变量的空隙。
  3. 对齐填充

    • 仅仅起着占位符的作用,hotspot虚拟机要求任何对象的大小都必须是8字节的整数倍。

内存布局

访问定位 #

  • 直接指针
  • 句柄访问
    • java堆中会划分出一块内存来作为句柄池,引用中存放的就是对象的句柄地址,句柄中包含了对象实例数据的指针和对象类型的指针。

观察者模式

2020-09-25
设计模式

  • 定义:对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,能够自动通知其他关联对象,自动刷新对象状态。
  • Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。

类图 #

观察者模式

  • 简单解释一下:在Subject类中维护了一个Observer对象列表,通过调用Subject的Attach方法将对象加入该列表,调用Detach将对象从该列表中移除。Subject中某些事件发生改变时,就会调用Notify方法。Notify方法中有一个循环,它会调用所有在列表中的对象的Update方法,达到通知的目的。

抽象工厂

2020-09-22
设计模式

定义 #

提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无序指定他们具体的类。

理解 #

  • 一系列相互依赖对象的创建。比如servlet使用 mysql对数据库处理的对象 或者 sqlserver对数据库处理的对象 ,由于mysql对应的类是不能使用sqlserver对应的类的,所以在这里我们就可以使用抽象工厂创建对象。
  • 主要在于应对对“新系列”的需求变动。缺点在于难以应对“新对象”的需求变动。

类图 #

抽象工厂类图

工厂方法

2020-09-22
设计模式

定义 #

定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使得一个类的实例化延迟。

理解 #

  • 使用new违背了依赖倒置原则,导致程序间的紧耦合。
  • 该模式隔离了对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合(new)会导致软件的脆弱。
  • 通过面向对象的手法,将所要创建对象的工作延迟到了子类。从而实现了一种扩展的策略(而非更改),较好的解决了这种紧耦合的关系。

代码 #

//代表所有车
interface Car{
    public void run();
}
//比亚迪
class Biyadi implements Car{
    public void run(){
        System.out.println("比亚迪run");
    }
}
//奥迪
class Aodi implements Car{
    public void run(){
        System.out.println("奥迪run");
    }
}
//工厂
interface CarFactory{
    public void createCar();
}
//具体的实现
class BiyadiCarFactory implements CarFactory{
    public void createCar(){
        return new Biyadi();
    }
}

class AodiCarFactory implements CarFactory{
    public void createCar(){
        return new Aodi();
    }
}

场景 #

//使用工厂方法
public void travel(CarFactory carFactory){
	//我开什么车去旅游全凭其他人给我什么车,假如公司有钱了,可能给我传递了更好的车
    Car car=carFactory.createCar();
    car.run();
}
/*虽然可能还是会有依赖,但是在本实现中依赖基本上已经消失了
其实依赖不可能完全消失,我们使用模式的时候可能只是将散落在程序各个部分的依赖都集中起来*/
//不使用工厂方法
public void travel(){
	//我只能开比亚迪去旅游,不能被更改
    Car car=new BiyadiCar();
    car.run();
}

装饰模式

2020-09-21
设计模式

定义 #

动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。

理解 #

  • java中的各种包装流就是用的装饰模式。
  • 使用组合代替了不好的继承,使类的数量大大大的减少。
  • 解决主体类在多个方向上的扩展功能。

案例 #

有一个类叫OutputStream,其完成一个打印的功能。

现在A想对该类进行扩充,使其有缓冲的功能。

B也想对该类进行扩充,使其输出更加安全。

C也想对该类进行扩充,使其输出到文件中。

这我们都可以通过类继承来完成,并且看上去似乎也没有什么问题。但是假如D也想对该类进行缓冲,使其既有缓冲,又更安全,这时候再使用类继承就不合适了。

此时我们可以使用装饰模式来解决该问题。

类图

interface OutputStream{
    public void print();
}

class ConcreteOutputStream implements OutputStream{
    public void print(){}
}
//装饰器
abstract class Decorator implements OutputStream{
    protected OutputStream  outputStream;
    public Decorator(OutputStream outputStream){
        this.outputStream=outputStream;
    }

    public  void print(){
        outputStream.print();
    }
}
//缓冲
class BufferedOutputStream extends Decorator{

    public BufferedOutputStream(OutputStream outputStream){
        super(outputStream);
    }

    public void print(){
        System.out.println("I am BufferedOutputStream");
        super.print();
    }
}
//安全
class SecureOutputStream extends Decorator{

    public SecureOutputStream(OutputStream outputStream){
        super(outputStream);
    }

    public void print(){
        System.out.println("I am SecureOutputStream");
        super.print();
    }
}
//输出到文件
class FileOutputStream extends Decorator{

    public FileOutputStream(OutputStream outputStream){
        super(outputStream);
    }

    public void print(){
        System.out.println("I am FileOutputStream");
        super.print();
    }
}

策略模式

2020-09-21
设计模式

  • 定义:定义一系列算法,把他们一个个封装起来,并且使它们可以互相替换(变化),该模式使得算法可独立于使用他的客户程序而变化。
  • 在软件构建过程中,某些对象使用的算法可能多种多样,策略模式做到了运行时根据需要透明的更改对象的算法,将对象与自身解耦。
  • 比如税率,如果要求一个程序支持多国税率,可以使用if-else语句。
class Sale{
    public double buySomething(){
        double d=xxx;
        //...
        if(tax==ChineseTax){
            //...
        }else if(tax==AmericeTax){
            //...
        }elseif(tax==JapanTax){
            //...
        }
        //...
    }
}
  • 但是如果后期需要加上其他国家的税率,就要修改程序,违背了开闭原则。此时可以使用策略模式。实现如下:
interface Tax{
    public double calculate(double money);
}

class ChineseTax implements Tax{
    public double calculate(double money){
        //...
    }
}
class AmericaTax implements Tax{
    public double calculate(double money){
        //...
    }
}

class Sale{
    private Tax tax;
    public Sale(Tax tax){this.tax=tax;}
    
    public double buySomething(){
        double d=xxx;
        //...
        tax.calculate(d);//替换掉先前的if-else
        //...
    }
}

模板方法

2020-09-21
设计模式

定义 #

定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类,template method使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤。