效能調優讀書筆記(上篇)

2022-08-30 06:01:48

一、Amdahl定律

加速=優化前耗時/優化後耗時比
公式圖:

二、設計模式

1、單例模式

靜態內部類的方式:

/**
 * 內部類的單例模式
 */
public class StaticSingleton {
    private StaticSingleton(){
        System.out.println("aaa");
    }

    private static class StaticSingletonHolder{
       private static StaticSingleton singleton=new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return StaticSingletonHolder.singleton;
    }
}

除了反射機制強制呼叫私有建構函式,生成多個範例外,序列化和反序列化也可能會導致。

防止序列化的單例:

/**
 * 可以被序列化的單例
 */
public class SerSingleton implements Serializable {

    String name;

    private SerSingleton(){
        System.out.println("SerSingleton is create");
        name="SerSingleton";
    }

    private static SerSingleton instance=new SerSingleton();

    public static SerSingleton getInstance(){
        return instance;
    }

    public static void createString(){
        System.out.println("createString in Singleton");
    }

    //阻止生成新的範例,總是返回當前物件
    private Object readResolve(){
        return instance;
    }
}

關鍵是實現了readResolve方法

2、代理模式

1、代理模式用於延遲載入

2、動態代理
動態代理是指再執行時,動態生成代理類。

注意:動態代理使用位元組碼動態生成載入技術,在執行時生成並載入類。

常見的有jdk動態代理,cglib和javassist三種方式。

JDK方式


/**
 * JDK的動態代理
 */
public class JdkDBQueryHandler implements InvocationHandler {

    IDBQuery real=null;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(real==null){
            real=new DBQuery();
        }
        return real.request();
    }

    public static IDBQuery createJdkProxy(){
        IDBQuery jdkProxy=(IDBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{IDBQuery.class},
                new JdkDBQueryHandler());
        return jdkProxy;
    }
}

CGLIB:


/**
 * cglib方式
 */
public class CglibDBQueryInterceptor implements MethodInterceptor {

    IDBQuery real=null;

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if(real==null){
            real=new DBQuery();
        }
        return real.request();
    }

    public static IDBQuery createCglibProxy(){
        Enhancer enhancer=new Enhancer();
        enhancer.setCallback(new CglibDBQueryInterceptor());    //指定切入器
        enhancer.setInterfaces(new Class[]{IDBQuery.class});
        IDBQuery cglibQuery=(IDBQuery)enhancer.create();
        return cglibQuery;
    }
}

結論:jdk方式建立物件很快,但是呼叫方法較慢。

Hibernate中代理模式的應用:

User u=(User)HibernateSessionFactory.getSession().load(User.class,1);
System.out.print(u.getClass().getName());
System.out.print(u.getName());

以上程式碼中,load方法後,並沒有查詢資料庫,在呼叫u.getName()時才查詢的資料庫,這就是Hibernate用代理模式做了延遲載入。

3、享元模式

概念:如果在一個系統中存在多個相同的物件,那麼只需要共用一份物件的拷貝,而不必每一次使用都建立新的物件。
功能元件如圖:

注意:享元模式時為數不多的只為了提升系統效能而生的設計模式。他的主要作用就是複用大物件,節省記憶體和物件建立時間。

享元模式和物件池的最大不同在於:享元模式是不能互相替代的,他們有

/**
 * 報表介面
 */
public interface IReportManager {
    String createReport();
}
/**
 * 員工報表
 */
public class EmployeeReportManager implements IReportManager {

    //租戶ID
    protected String   tenanId=null;

    public EmployeeReportManager(String tenanId) {
        this.tenanId = tenanId;
    }

    @Override
    public String createReport() {
        return "this is a employee Report";
    }
}
/**
 * 財務報表
 */
public class FinancialReportManager implements IReportManager {

    //租戶ID
    protected String   tenanId=null;

    public FinancialReportManager(String tenanId) {
        this.tenanId = tenanId;
    }

    @Override
    public String createReport() {
        return "this is a Financial Report";
    }
}


/**
 * 享元工廠類
 * 保證同一個id獲取到的是同一個物件
 */
public class ReportManagerFactory {
    Map<String,IReportManager> financialReportManager=new HashMap<>();
    Map<String,IReportManager> employeeReportManager=new HashMap<>();
    IReportManager getFinancialReportManager(String tenantId){
        IReportManager r=financialReportManager.get(tenantId);
        if(r==null){
            r=new FinancialReportManager(tenantId);
            financialReportManager.put(tenantId,r);
        }
        return r;
    }

    IReportManager getEmployeeReportManager(String tenantId){
        IReportManager r=employeeReportManager.get(tenantId);
        if(r==null){
            r=new EmployeeReportManager(tenantId);
            employeeReportManager.put(tenantId,r);
        }
        return r;
    }
}
4、裝飾者模式

裝飾者(Decorator)和被裝飾者(ConcreteComponent)擁有相同的介面,裝飾者可以在被裝飾者的方法上加上特定的前後置處理,增強被裝飾者的功能。

/**
 * 介面
 */
public interface IPacketCreator {
    String handleContent();
}

public abstract class PacketDecorator implements IPacketCreator {

    IPacketCreator iPacketCreator;

    public PacketDecorator(IPacketCreator iPacketCreator) {
        this.iPacketCreator = iPacketCreator;
    }
}

public class PacketHTMLHeaderCreator extends PacketDecorator {

    public PacketHTMLHeaderCreator(IPacketCreator iPacketCreator) {
        super(iPacketCreator);
    }

    /**
     * 將資料封裝成html格式
     * @return
     */
    @Override
    public String handleContent() {
        StringBuffer sb=new StringBuffer();
        sb.append("<html>");
        sb.append("<body>");
        sb.append(iPacketCreator.handleContent());
        sb.append("</body>");
        sb.append("</html>\n");
        return sb.toString();
    }
}
public class PacketHTTPHeaderCreator extends PacketDecorator {

    public PacketHTTPHeaderCreator(IPacketCreator iPacketCreator) {
        super(iPacketCreator);
    }

    @Override
    public String handleContent() {
        StringBuffer sb=new StringBuffer();
        sb.append("Cache-Control:no-cache\n");
        sb.append(iPacketCreator.handleContent());
        return sb.toString();
    }
}

使用類:

@Test
    public void test3(){
        IPacketCreator iPacketCreator = new PacketHTTPHeaderCreator(new PacketHTMLHeaderCreator(new PacketBodyCreator()));
        System.out.println(iPacketCreator.handleContent());
    }
5、觀察者模式

在軟體系統中,當一個物件的行為依賴於另一個物件的狀態時,觀察者模式就非常有用。

程式碼範例如下:

package com.mmc.concurrentcystudy.design.guanchazhe;

import java.util.Observable;
import java.util.Observer;

/**
 * 讀者類,實現了觀察者介面
 */
public class Reader implements Observer {

    private String name;

    public Reader(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 關注作者
     * @param writerName
     */
    public void subscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).addObserver(this);
    }


    /**
     * 取消關注
     * @param writerName
     */
    public void unsunbscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
    }

    /**
     * 業務方法
     * @param o
     * @param arg
     */
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof Writer){
            Writer writer=(Writer)o;
            System.out.println(name+"知道"+writer.getName()+"釋出了新書《"+writer.getLastNovel()+"》");
        }
    }
}

package com.mmc.concurrentcystudy.design.guanchazhe;

import java.util.Observable;

/**
 * 作者類,要繼承自被觀察者類
 */
public class Writer extends Observable {

    private String name;      //作者的名稱

    private String lastNovel;    //記錄作者最新發布的小說

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLastNovel() {
        return lastNovel;
    }

    public void setLastNovel(String lastNovel) {
        this.lastNovel = lastNovel;
    }


    public Writer(String name) {
        this.name = name;
        WriterManager.getInstance().add(this);
    }

    //釋出新書
    public void addNovel(String novel){
        System.out.println(name+"釋出了新書:《"+novel+"》");
        lastNovel=novel;
        setChanged();
        notifyObservers();
    }
}

package com.mmc.concurrentcystudy.design.guanchazhe;

import java.util.HashMap;
import java.util.Map;

/**
 * 管理器,保持一份獨有的作者列表
 */
public class WriterManager {

    private Map<String,Writer> map=new HashMap<>();
    public void add(Writer writer){
        map.put(writer.getName(),writer);
    }

    public Writer getWriter(String name){
        return map.get(name);
    }

    //單例
    private WriterManager(){}

    public static WriterManager getInstance(){
        return WriterManagerInstance.writerManager;
    }

    private static  class WriterManagerInstance{
        private static WriterManager writerManager=new WriterManager();
    }
}

測試方法:

@Test
    public void test4(){
        Reader reader=new Reader("小明");
        Reader reader2=new Reader("小紅");
        Reader reader3=new Reader("小李");

        Writer writer=new Writer("韓寒");
        Writer writer2=new Writer("李敖");

        reader.subscribe("韓寒");
        reader2.subscribe("韓寒");
        reader3.subscribe("李敖");

        writer.addNovel("三重門");
        writer2.addNovel("不知道");
    }

三、常見的優化元件

1、緩衝

示意圖:

IO操作很容易形成效能瓶頸,所以儘可能加入緩衝元件。

2、快取

快取是為了系統效能而開闢的記憶體空間。最為簡單的快取是使用HashMap,但是這樣做會遇到很多問題,比如不知道合適清理無效的資料,如何防止資料過多而記憶體溢位。

現在有很多快取框架,如EHCache,OSCache,JBossCache等。

3、物件複用-「池」

如果一個類被頻繁的使用,那麼不必每次都生成一個範例,可以將這個範例儲存在一個池中,待需要的時候直接從池中獲取。這個池就稱為物件池。

Apache中提供了一個Jakarta Commons Pool物件池元件,可以直接使用。

API列表:

public interface ObjectPool<T> extends Closeable {
    //從物件池中獲取到一個物件
    T borrowObject() throws Exception, NoSuchElementException, IllegalStateException;

    //物件返回給物件池
    void returnObject(T var1) throws Exception;
    }

Common Pool中內建了3個物件池,分別是StackObjectPool,GenericObjectPool,SoftReferenceObjectPool。

  • StackObjectPool:利用Stack來儲存物件,可以指定初始化大小。

  • GenericObjectPool:是一個通用的物件池,可以設定物件池的容量,也可以設定無可用物件時應該怎樣,有一個複雜的建構函式來定義這些行為。

  • SoftReferenceObjectPool:使用的是ArrayList儲存,儲存的是物件的軟參照。

使用範例:

/**
 * 物件池
 */
public class PoolFactory extends BasePooledObjectFactory<Object> {

    static GenericObjectPool<Object> pool = null;

    // 取得物件池工廠範例
    public synchronized static GenericObjectPool<Object> getInstance() {
        if (pool == null) {
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            poolConfig.setMaxIdle(-1);
            poolConfig.setMaxTotal(-1);
            poolConfig.setMinIdle(100);
            poolConfig.setLifo(false);
            pool = new GenericObjectPool<Object>(new PoolFactory(), poolConfig);
        }
        return pool;
    }

    public static Object borrowObject() throws Exception{
        return (Object) PoolFactory.getInstance().borrowObject();
    }

    public static void returnObject(Object jdbcUtils) throws Exception{
        PoolFactory.getInstance().returnObject(jdbcUtils);
    }

    public static void close() throws Exception{
        PoolFactory.getInstance().close();
    }

    public static void clear() throws Exception{
        PoolFactory.getInstance().clear();
    }

    @Override
    public Object create() throws Exception {
        return new Object();
    }

    @Override
    public PooledObject<Object> wrap(Object obj) {
        return new DefaultPooledObject<Object>(obj);
    }
} 

測試程式碼:

@Test
    public void test6() throws Exception {
        Object o=PoolFactory.borrowObject();
        PoolFactory.returnObject(o);
        Object o2=PoolFactory.borrowObject();
        PoolFactory.returnObject(o2);
        System.out.println(o==o2);
    }

注意:只有對重量級物件使用物件池技術才能提高系統效能,對輕量級的物件使用反而會降低效能。

4、並行替代序列
5、負載均衡

為保證應用程式的服務質量,需要使用多臺計算機協同工作,將系統負載儘可能分配到各個計算機節點上。

6、時間換空間

一般用於嵌入式裝置或者記憶體,硬碟不足的情況下。
比如一個簡單的例子,a和b兩個變數的值的替換。最常用的方法是引入一箇中間變數。為了省去中間變數可以用這樣的方法:

 @Test
    public void test7(){
        int a=3;
        int b=5;
        a=a+b;
        b=a-b;
        a=a-b;
        System.out.println(a);
        System.out.println(b);
    }
7、空間換時間

最典型的應用就是快取了,除了緩以外,有一些排序方法也會用到。

一個空間換時間的排序範例:

/**
 * 空間換時間的排序
 */
public class SpaceSort {


    public static int arrayLen=1000000;

    public static void main(String[] args) {
        int [] a=new int[arrayLen];
        int [] old=new int[arrayLen];
        Map<Integer,Object> map=new HashMap<>();
        int count=0;
        while (count<a.length){      //初始化陣列
            int value=(int)(Math.random()*arrayLen*10);
            if(map.get(value)==null){
                map.put(value,value);
                a[count]=value;
                count++;
            }
        }

        System.arraycopy(a,0,old,0,a.length);
        long start=System.currentTimeMillis();
        Arrays.sort(a);
        System.out.println("Arrays.sort spend:"+(System.currentTimeMillis()-start));

        start=System.currentTimeMillis();
        spaceToTime(old);
        System.out.println("spaceToTime spend:"+(System.currentTimeMillis()-start));

    }

    public static void spaceToTime(int[] array){
        int i=0;
        int max=array[0];
        int l=array.length;
        //找出最大值
        for (i=0;i<l;i++){
            if(array[i]>max)
                max=array[i];
        }

        int []temp=new int[max+1];   //分配臨時空間
        for (i=0;i<l;i++){
            temp[array[i]]=array[i];    //以索引下標標識數位大小
        }

        int j=0;
        int max1=max+1;
        for (i=0;i<max1;i++){   //線性複雜度
            if (temp[i]>0){
                array[j++]=temp[i];
            }
        }
    }
}

四、Java程式優化

1、字串優化

使用StringTokenizer類分割字串

 public void test9(){
        StringBuilder sb=new StringBuilder();
        int len=1000;
        for (int i=0;i<len;i++){
            sb.append(i);
            sb.append(";");
        }
        String str=sb.toString();
        long start=System.currentTimeMillis();
        StringTokenizer st=new StringTokenizer(str,";");
        for (int i=0;i<10000;i++){
           while (st.hasMoreElements()){
               String s = st.nextToken();
           }
           st=new StringTokenizer(str,";");
        }
        System.out.println(System.currentTimeMillis()-start);

    }

目前來說這種方法也沒快多少

2、資料結構

ArrayList和LinkedList

  • 新增元素到佇列尾部 ArrayList塊
  • 新增到任意位置 LinkedList快

刪除對比:

  • 在能夠有效評估ArrayList陣列大小時,指定容量大小能對效能有提升
  • LinkedList不要用for(int i=0;i<list.size();i++)遍歷,用foreach。即如果需要通過索引下標對集合進行存取,那最好用ArrayList

TreeMap和LinkedHashMap的區別。
他們都是可排序的,LinkedHashMap基於元素進入集合或者被存取的先後順序排序,TreeMap是根據元素的固有順序(Comparator或者Comparable)

3、優化集合存取方法
  1. 分離迴圈中被重複呼叫的程式碼
    如下面的那個list.size()會多次呼叫。但實際上這個方法也很快
   for (int i=0;i<list.size();i++){
            count++;
        }
        len=list.size();
        for (int i=0;i<len;i++){
            count++;
        }
  1. 減少方法呼叫
    如果可以直接存取內部元素,就不用呼叫對應的介面。因為函數呼叫是需要消耗系統資源的。
4、NIO提升效能

在讀寫檔案上使用NIO會更快
以寫入4000000的int數位為例

--- Stream ByteBuffer MappedByteBuffer
寫耗時 295ms 123ms 32ms
讀耗時 433ms 59ms 17ms

測試範例:

/**
     * IO寫檔案
     * @throws IOException
     */
    @Test
    public void test12() throws IOException {
        long start=System.currentTimeMillis();
        DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("d://1.txt")));
        for (int i=0;i<4000000;i++){
            dos.writeInt(i);
        }
        if(dos!=null)
            dos.close();
        System.out.println(System.currentTimeMillis()-start);
    }

    /**
     * IO讀檔案
     * @throws IOException
     */
    @Test
    public void test13() throws IOException {
        long start=System.currentTimeMillis();
       DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("d://1.txt")));
       for (int i=0;i<4000000;i++){
           dis.readInt();
       }
       if(dis!=null)
           dis.close();
        System.out.println(System.currentTimeMillis()-start);
    }

    /**
     * NIO寫檔案
     * @throws IOException
     */
    @Test
    public void test14() throws IOException {
        long start=System.currentTimeMillis();
        FileOutputStream fos=new FileOutputStream("d:/1.txt");
        FileChannel fc=fos.getChannel();
        ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4);
        for (int i = 0; i <4000000 ; i++) {
            byteBuffer.put(int2byte(i));
        }
        byteBuffer.flip();
        fc.write(byteBuffer);
        System.out.println(System.currentTimeMillis()-start);
    }

    /**
     * NIO讀檔案
     * @throws IOException
     */
    @Test
    public void test15() throws IOException {
        long start=System.currentTimeMillis();
        FileInputStream fin=new FileInputStream("d://1.txt");
        FileChannel channel = fin.getChannel();
        ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4);
        channel.read(byteBuffer);
        channel.close();
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()){
            byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());
        }
        System.out.println(System.currentTimeMillis()-start);
    }

    public static byte[] int2byte(int res){
        byte[] targets=new byte[4];
        targets[3]=(byte)(res&0xff);
        targets[2]=(byte)((res>>8)&0xff);
        targets[1]=(byte)((res>>16)&0xff);
        targets[0]=(byte)(res>>24);
        return targets;
    }

    public static int byte2int(byte b1,byte b2,byte b3,byte b4){
        return ((b1&0xff)<<24)|((b2&0xff)<<16)|((b3&0xff)<<8)|(b4&0xff);
    }
    
     /**
     * NIO MappedByteBuffer寫檔案
     * @throws IOException
     */
    @Test
    public void test16() throws IOException {
        long start=System.currentTimeMillis();
       FileChannel fc=new RandomAccessFile("d://1.txt","rw").getChannel();
        IntBuffer ib=fc.map(FileChannel.MapMode.READ_WRITE,0,4000000*4).asIntBuffer();
        for (int i = 0; i < 4000000; i++) {
            ib.put(i);
        }
        if(fc!=null)
            fc.close();
        System.out.println(System.currentTimeMillis()-start);
    }

    /**
     * NIO MappedByteBuffer讀檔案
     * @throws IOException
     */
    @Test
    public void test17() throws IOException {
        long start=System.currentTimeMillis();
      FileChannel fc=new FileInputStream("d://1.txt").getChannel();
      IntBuffer ib=fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer();
      while (ib.hasRemaining()){
          ib.get();
      }
    if(fc!=null)
        fc.close();
        System.out.println(System.currentTimeMillis()-start);
    }

2、直接記憶體存取
DirectBuffer:直接可以存取系統實體記憶體的類,在對普通的ByteBuffer存取時,系統會使用一個「核心緩衝區」進行間接的操作,而DirectBuffer所處的位置就相當於這個「核心緩衝區」。因此他更接近底層,更快。

但是DirectBuffer建立銷燬都比較費時間,在需要頻繁建立和銷燬的情況下不適合用。

/**
     * 使用DirectBuffer
     * @throws IOException
     */
    @Test
    public void test18() throws IOException {
        long start=System.currentTimeMillis();
       ByteBuffer b=ByteBuffer.allocateDirect(500);
       for (int i=0;i<100000;i++){
           for (int j=0;j<99;j++)
               b.putInt(j);
          b.flip();
           for (int j=0;j<99;j++)
               b.getInt();
           b.clear();
       }
        System.out.println(System.currentTimeMillis()-start);
    }

    @Test
    public void test19() throws IOException {
        long start=System.currentTimeMillis();
        ByteBuffer b=ByteBuffer.allocate(500);
        for (int i=0;i<100000;i++){
            for (int j=0;j<99;j++)
                b.putInt(j);
            b.flip();
            for (int j=0;j<99;j++)
                b.getInt();
            b.clear();
        }
        System.out.println(System.currentTimeMillis()-start);
    }
5、參照型別
  1. 強參照
  2. 軟參照
    GC的時候,因為記憶體沒滿,沒有被回收。
@Test
    public void test20(){
        MyObject myObject=new MyObject();
        //建立參照佇列
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        //建立軟參照
        SoftReference<MyObject> softReference=new SoftReference<>(myObject,referenceQueue);
        myObject=null;
        System.gc();
        System.out.println("After Gc:soft get="+softReference.get());
        System.out.println("分配大記憶體");
        byte[] b=new byte[4*1024*925];
        System.out.println("After new byte[]:soft get="+softReference.get());
    }
  1. 弱參照
    在GC的時候一旦發現有弱參照,直接被回收
@Test
    public void test20(){
        MyObject myObject=new MyObject();
        //建立參照佇列
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        //建立軟參照
        WeakReference<MyObject> softReference=new WeakReference<>(myObject,referenceQueue);
        myObject=null;
        System.gc();
        System.out.println("After Gc:soft get="+softReference.get());
        System.out.println("分配大記憶體");
        byte[] b=new byte[4*1024*925];
        System.out.println("After new byte[]:soft get="+softReference.get());
    }
  1. 虛參照
    虛參照是參照型別最弱的一個,他的作用在於跟蹤垃圾回收。
  2. WeakHashMap
    當需要使用HashMap做一個簡單的快取時,建議使用WeakHashMap,他是弱參照的,可以在記憶體滿的情況下,GC時清除沒有被參照的表項
6、改善效能小技巧
  1. 使用區域性變數

呼叫方法時傳遞的引數和在方法在建立的臨時變數都儲存在棧中,速度較快。其他變數,如靜態變數,範例變數都在堆中。

 @Test
    public void test21() throws IOException {
        long start=System.currentTimeMillis();
        int a=0;
        for (int i=0;i<2000000000;i++){
                a++;
        }
        System.out.println(a);
        System.out.println(System.currentTimeMillis()-start);
    }

    private static int ta=0;

    @Test
    public void test22() throws IOException {
        long start=System.currentTimeMillis();
        int a=0;
        for (int i=0;i<2000000000;i++){
            ta++;
        }
        System.out.println(ta);
        System.out.println(System.currentTimeMillis()-start);
    }
  1. 位運算代替乘除法

a*2 用a<<1

a/2 用a>>1
3. 替代switch

用陣列替代switch,效率會更高

 public int sw(int a){
        switch (a){
            case 1:return 1;
            case 2:return 3;
            case 3:return 5;
            case 4:return 9;
            default:return 0;
        }
    }
    
    //用陣列來實現
    public int sw2(int a){
        int[] array=new int[]{1,3,5,9,0};
        return array[a];
    }

4、複製陣列用System.arraycopy
System.arraycopy時淺拷貝。對於非基本型別而言,他拷貝的是物件的參照,而非新建一個物件。

5、clone方法代替new

clone方法不會呼叫建構函式,所以能夠快速的創造一個範例。預設情況下是淺拷貝。但是拷貝的物件修改屬性,舊的物件的值可能是不會變的。

6、靜態方法代替實體方法

實體方法需要維護一張類似虛擬結構表的東西以支援對多型的實現。所以比靜態方法慢。