技術境界的二三四

兩種能力境界

1.解決問題

在工程師中有一種人被稱為”救火隊長“。哪裡出了問題,哪裡就有他的身影,他的出現,燃眉之急就有救了。他們是解決問題的高人。但是“救火隊長”在晉陞上往往會遇到瓶頸。

對標人物:漫威-美國隊長

每天嚴陣以待,隨時準備拯救世界。無法接受鋼鐵俠防患於未然用機器來解決問題解放自己的方式。

 

2.發現問題

更高的高人會問一個問題:“為什麼每天會任務追着你跑?你為什麼沒從根源上解決所有的問題?”一個在辦公室里和下面人一起研究茶道的領導要比和大家一起加班到半夜的領導受歡迎。因為他們從更大的層面上杜絕了對救火隊長的需要。

對標人物:《罪惡黑名單》雷丁頓

雷丁頓總是運籌帷幄游刃有餘。所以在形勢危急的情況下,他總是評價哪家的什麼東西好吃,或者任何別人沒有注意到的生活瑣事,觀眾並不恨他。因為知道他早就搞定了一切。

 

 

三種需求對應境界

1.對應需求

一個兢兢業業的工程師或團隊,對產品有求必應。項目初期這樣沒有錯,時間一長,就會遇到維護和擴展性問題。

對標:下圖的狀態有木有很熟悉的趕腳?

 

2.快速對應需求

通過系統性的設計和不斷的迭代重構,一個需求來了,通過少量開發或者不開發就可以完成。每周上班五天,三天用來團建。很好,直到公司創始人完成了最初的宏圖偉業,連高層也不知道要干什麼,公司開始走下坡了。

對標:請參考《浪潮之巔》

 

3.引領需求

在線上跑着的服務就會產生數據,通過數據的分析,自己的觀察思考,推演出新的商機和需求,開拓更大的市場。

對標:請參考google的7-2-1原則。

 

 

四種技術運用境界

1.會用

很多面試者在面試中被淘汰時很不服氣,這些我會用,給我分配的活我都干出來了。為什麼不要我?答案很簡單,你這個工作別人也能幹。所以聰明的老闆寧願花4個人的錢招聘3個人干5個人的活。所以怎麼才能獲得一份收入不錯的工作?

2.知道各種優劣勢,知道怎麼用更好

公司絕對不會鼓勵重複造輪子,他們更鼓勵用好輪子。所以深入透徹的技術調研分析,根據場景選擇了合適的技術是個不錯的開始。但是現有的技術並不是為自己定製的。當自己用的足夠深,就發現很多方面,現有技術確實不能滿足自己的業務需要。

3.理解原理及技術血緣,深入運用

特別是一些新技術,由於場景覆蓋還不是很全面,需要在此基礎上做一些二次開發或者內部改造,甚至重寫。重寫重寫着,突然覺得自己有更好的想法?

4.創造技術

技術創造價值,技術引領一個時代。

 

總結

持續有聲音 

 

近期文章

代碼榮辱觀-以運用風格為榮,以隨意編碼為恥

你看不懂的spring原理是因為不知道這幾個概念

應屆生offer指南

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

高級Java工程師必備 —– 深入分析 Java IO (三)

概述

Java IO即Java 輸入輸出系統。不管我們編寫何種應用,都難免和各種輸入輸出相關的媒介打交道,其實和媒介進行IO的過程是十分複雜的,這要考慮的因素特別多,比如我們要考慮和哪種媒介進行IO(文件、控制台、網絡),我們還要考慮具體和它們的通信方式(順序、隨機、二進制、按字符、按字、按行等等)。Java類庫的設計者通過設計大量的類來攻克這些難題,這些類就位於java.io包中。

在JDK1.4之後,為了提高Java IO的效率,Java又提供了一套新的IO,Java New IO簡稱Java NIO。它在標準java代碼中提供了高速的面向塊的IO操作。本篇文章重點介紹Java IO,關於Java NIO請參考我的另兩篇文章: 

高級Java工程師必備 —– 深入分析 Java IO (一)BIO

高級Java工程師必備 —– 深入分析 Java IO (二)NIO

Java IO類庫的框架

首先看個圖:

 Java IO的類型

雖然java IO類庫龐大,但總體來說其框架還是很清楚的。從是讀媒介還是寫媒介的維度看,Java IO可以分為:

  1. 輸入流:InputStream和Reader
  2. 輸出流:OutputStream和Writer

而從其處理流的類型的維度上看,Java IO又可以分為:

  1. 字節流:InputStream和OutputStream
  2. 字符流:Reader和Writer

下面這幅圖就清晰的描述了JavaIO的分類:

字節流 字符流
輸入流 InputStream Reader
輸出流 OutputStream Writer

我們的程序需要通過InputStream或Reader從數據源讀取數據,然後用OutputStream或者Writer將數據寫入到目標媒介中。其中,InputStream和Reader與數據源相關聯,OutputStream和writer與目標媒介相關聯。

Java IO的基本用法

Java IO :字節流

通過上面的介紹我們已經知道,字節流對應的類應該是InputStream和OutputStream,而在我們實際開發中,我們應該根據不同的媒介類型選用相應的子類來處理。下面我們就用字節流來操作文件媒介:

例1,用字節流寫文件

public static void writeByteToFile() throws IOException{
    String hello= new String( "hello word!");
     byte[] byteArray= hello.getBytes();
    File file= new File( "d:/test.txt");
     //因為是用字節流來寫媒介,所以對應的是OutputStream 
     //又因為媒介對象是文件,所以用到子類是FileOutputStream
    OutputStream os= new FileOutputStream( file);
     os.write( byteArray);
     os.close();
}

例2,用字節流讀文件

public static void readByteFromFile() throws IOException{
    File file= new File( "d:/test.txt");
     byte[] byteArray= new byte[( int) file.length()];
     //因為是用字節流來讀媒介,所以對應的是InputStream
     //又因為媒介對象是文件,所以用到子類是FileInputStream
    InputStream is= new FileInputStream( file);
     int size= is.read( byteArray);
    System. out.println( "大小:"+size +";內容:" +new String(byteArray));
     is.close();
}

CopyFileDemo

package com.chenhao.io.byteIO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author ChenHao
 *
 */
public class CopyFileDemo {

    /**
     * @param args
     * @throws FileNotFoundException 
     */
    public static void main(String[] args) {
        String src ="E:/xp/test";
        String dest="e:/xp/test/4.jpg";
        try {
            copyFile(src,dest);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("文件不存在");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("拷貝文件失敗|關閉流失敗");
        }
    }
    /**
     * 文件的拷貝
     * @param  源文件路徑
     * @param  目錄文件路徑
     * @throws FileNotFoundException,IOException
     * @return 
     */
    public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException {
        //1、建立聯繫 源(存在且為文件) +目的地(文件可以不存在)  
        File src =new File(srcPath);
        File dest =new File(destPath);
        if(! src.isFile()){ //不是文件或者為null
            System.out.println("只能拷貝文件");
            throw new IOException("只能拷貝文件");
        }
        //2、選擇流
        InputStream is =new FileInputStream(src);
        OutputStream os =new FileOutputStream(dest);
        //3、文件拷貝   循環+讀取+寫出
        byte[] flush =new byte[1024];
        int len =0;
        //讀取
        while(-1!=(len=is.read(flush))){
            //寫出
            os.write(flush, 0, len);
        }
        os.flush(); //強制刷出
        
        //關閉流
        os.close();
        is.close();
    }

}

Java IO :字符流

同樣,字符流對應的類應該是Reader和Writer。下面我們就用字符流來操作文件媒介:

例3,用字符流讀文件

public static void writeCharToFile() throws IOException{
    String hello= new String( "hello word!");
    File file= new File( "d:/test.txt");
    //因為是用字符流來讀媒介,所以對應的是Writer,又因為媒介對象是文件,所以用到子類是FileWriter
    Writer os= new FileWriter( file);
    os.write( hello);
    os.close();
}

例4,用字符流寫文件

public static void readCharFromFile() throws IOException{
    File file= new File( "d:/test.txt");
    //因為是用字符流來讀媒介,所以對應的是Reader
    //又因為媒介對象是文件,所以用到子類是FileReader
    Reader reader= new FileReader( file);
    char [] byteArray= new char[( int) file.length()];
    int size= reader.read( byteArray);
    System. out.println( "大小:"+size +";內容:" +new String(byteArray));
    reader.close();
}

Java IO :字節流轉換為字符流

字節流可以轉換成字符流,java.io包中提供的InputStreamReader類就可以實現,當然從其命名上就可以看出它的作用。其實這涉及到另一個概念,IO流的組合,後面我們詳細介紹。下面看一個簡單的例子:

例5 ,字節流轉換為字符流

public static void convertByteToChar() throws IOException{
    File file= new File( "d:/test.txt");
    //獲得一個字節流
    InputStream is= new FileInputStream( file);
    //把字節流轉換為字符流,其實就是把字符流和字節流組合的結果。
    Reader reader= new InputStreamReader( is);
    char [] byteArray= new char[( int) file.length()];
    int size= reader.read( byteArray);
    System. out.println( "大小:"+size +";內容:" +new String(byteArray));
    is.close();
    reader.close();
}

Java IO:文件媒介操作

例6 ,File操作

public class FileDemo {
  public static void main(String[] args) {
         //檢查文件是否存在
        File file = new File( "d:/test.txt");
         boolean fileExists = file.exists();
        System. out.println( fileExists);
         //創建文件目錄,若父目錄不存在則返回false
        File file2 = new File( "d:/fatherDir/subDir");
         boolean dirCreated = file2.mkdir();
        System. out.println( dirCreated);
         //創建文件目錄,若父目錄不存則連同父目錄一起創建
        File file3 = new File( "d:/fatherDir/subDir2");
         boolean dirCreated2 = file3.mkdirs();
        System. out.println( dirCreated2);
        File file4= new File( "d:/test.txt");
         //判斷長度
         long length = file4.length();
         //重命名文件
         boolean isRenamed = file4.renameTo( new File("d:/test2.txt"));
         //刪除文件
         boolean isDeleted = file4.delete();
        File file5= new File( "d:/fatherDir/subDir");
         //是否是目錄
         boolean isDirectory = file5.isDirectory();
         //列出文件名
        String[] fileNames = file5.list();
         //列出目錄
        File[]   files = file4.listFiles();
  }
}

隨機讀取File文件

通過上面的例子我們已經知道,我們可以用FileInputStream(文件字符流)或FileReader(文件字節流)來讀文件,這兩個類可以讓我們分別以字符和字節的方式來讀取文件內容,但是它們都有一個不足之處,就是只能從文件頭開始讀,然後讀到文件結束。

但是有時候我們只希望讀取文件的一部分,或者是說隨機的讀取文件,那麼我們就可以利用RandomAccessFile。RandomAccessFile提供了seek()方法,用來定位將要讀寫文件的指針位置,我們也可以通過調用getFilePointer()方法來獲取當前指針的位置,具體看下面的例子:

例7,隨機讀取文件

public static void randomAccessFileRead() throws IOException {
     // 創建一個RandomAccessFile對象
    RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
     // 通過seek方法來移動讀寫位置的指針
     file.seek(10);
     // 獲取當前指針
     long pointerBegin = file.getFilePointer();
     // 從當前指針開始讀
     byte[] contents = new byte[1024];
     file.read( contents);
     long pointerEnd = file.getFilePointer();
    System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));
     file.close();
}

例8,隨機寫入文件

public static void randomAccessFileWrite() throws IOException {
     // 創建一個RandomAccessFile對象
     RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
     // 通過seek方法來移動讀寫位置的指針
     file.seek(10);
     // 獲取當前指針
     long pointerBegin = file.getFilePointer();
     // 從當前指針位置開始寫
     file.write( "HELLO WORD".getBytes());
     long pointerEnd = file.getFilePointer();
     System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" );
     file.close();
}

Java IO:BufferedInputStream和BufferedOutputStream

BufferedInputStream顧名思義,就是在對流進行寫入時提供一個buffer來提高IO效率。在進行磁盤或網絡IO時,原始的InputStream對數據讀取的過程都是一個字節一個字節操作的,而BufferedInputStream在其內部提供了一個buffer,在讀數據時,會一次讀取一大塊數據到buffer中,這樣比單字節的操作效率要高的多,特別是進程磁盤IO和對大量數據進行讀寫的時候,能提升IO性能。

使用BufferedInputStream十分簡單,只要把普通的輸入流和BufferedInputStream組合到一起即可。我們把上面的例2改造成用BufferedInputStream進行讀文件,請看下面例子:

例10 ,用緩衝流讀文件

public static void readByBufferedInputStream() throws IOException {
     File file = new File( "d:/test.txt");
     byte[] byteArray = new byte[( int) file.length()];
     //可以在構造參數中傳入buffer大小
     InputStream is = new BufferedInputStream( new FileInputStream(file),2*1024);
     int size = is.read( byteArray);
     System. out.println( "大小:" + size + ";內容:" + new String(byteArray));
     is.close();
}

BufferedOutputStream的情況和BufferedInputStream一致,在這裏就不多做描述了。

copyFile

package com.chenhao.io.buffered;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
 * 字節流文件拷貝+緩衝流 ,提高性能
 * 緩衝流(節點流)
 * @author ChenHao
 *
 */
public class BufferedByteDemo {

    public static void main(String[] args) {
        String src ="E:/xp/test";
        String dest="e:/xp/test/4.jpg";
        try {
            copyFile(src,dest);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("文件不存在");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("拷貝文件失敗|關閉流失敗");
        }
    }
    /**
     * 文件的拷貝
     * @param  源文件路徑
     * @param  目錄文件路徑
     * @throws FileNotFoundException,IOException
     * @return 
     */
    public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException {
        //1、建立聯繫 源(存在且為文件) +目的地(文件可以不存在)  
        File src =new File(srcPath);
        File dest =new File(destPath);
        if(! src.isFile()){ //不是文件或者為null
            System.out.println("只能拷貝文件");
            throw new IOException("只能拷貝文件");
        }
        //2、選擇流
        InputStream is =new BufferedInputStream(new FileInputStream(src));
        OutputStream os =new BufferedOutputStream( new FileOutputStream(dest));
        //3、文件拷貝   循環+讀取+寫出
        byte[] flush =new byte[1024];
        int len =0;
        //讀取
        while(-1!=(len=is.read(flush))){
            //寫出
            os.write(flush, 0, len);
        }
        os.flush(); //強制刷出
        
        //關閉流
        os.close();
        is.close();
    }

}

Java IO:BufferedReader和BufferedWriter

BufferedReader、BufferedWriter 的作用基本和BufferedInputStream、BufferedOutputStream一致,具體用法和原理都差不多 ,只不過一個是面向字符流一個是面向字節流。同樣,我們將改造字符流中的例4,給其加上buffer功能,看例子:

public static void readByBufferedReader() throws IOException {
     File file = new File( "d:/test.txt");
     // 在字符流基礎上用buffer流包裝,也可以指定buffer的大小
     Reader reader = new BufferedReader( new FileReader(file),2*1024);
     char[] byteArray = new char[( int) file.length()];
     int size = reader.read( byteArray);
     System. out.println( "大小:" + size + ";內容:" + new String(byteArray));
     reader.close();
}

另外,BufferedReader提供一個readLine()可以方便地讀取一行,而FileInputStream和FileReader只能讀取一個字節或者一個字符,因此BufferedReader也被稱為行讀取器.

public static void keyIn() throws IOException {
 try (//InputStreamReader是從byte轉成char的橋樑
      InputStreamReader reader = new InputStreamReader(System.in);
      //BufferedReader(Reader in)是char類型輸入的包裝類
      BufferedReader br = new BufferedReader(reader);) {
         
         String line = null;
         while ((line = br.readLine()) != null) {
             if (line.equals("exit")) {
                 //System.exit(1);
                 break;
             }
             System.out.println(line);
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
}

Java IO: 序列化與ObjectInputStream、ObjectOutputStream

推薦博客

  程序員寫代碼之外,如何再賺一份工資?

Serializable

如果你希望類能夠序列化和反序列化,必須實現Serializable接口,就像所展示的ObjectInputStream和ObjectOutputStream例子一樣。

ObjectInputStream

ObjectInputStream能夠讓你從輸入流中讀取Java對象,而不需要每次讀取一個字節。你可以把InputStream包裝到ObjectInputStream中,然後就可以從中讀取對象了。代碼如下:

ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject(); //etc.
input.close();

在這個例子中,你讀取的對象必須是MyClass的一個實例,並且必須事先通過ObjectOutputStream序列化到“object.data”文件中。

在你序列化和反序列化一個對象之前,該對象的類必須實現了java.io.Serializable接口。

ObjectOutputStream

ObjectOutputStream能夠讓你把對象寫入到輸出流中,而不需要每次寫入一個字節。你可以把OutputStream包裝到ObjectOutputStream中,然後就可以把對象寫入到該輸出流中了。代碼如下:

ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("object.data"));
MyClass object = new MyClass();  output.writeObject(object); //etc.
output.close();

例子中序列化的對象object現在可以從ObjectInputStream中讀取了。

同樣,在你序列化和反序列化一個對象之前,該對象的類必須實現了java.io.Serializable接口。

 

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

聽說你在為天天寫業務代碼而煩惱?

寫業務代碼一般就是完成業務應用的功能,天天寫業務代碼的程序員也被戲稱為CURD程序員,CURD就是增(create)、改(update)、查(read)、刪(delete)的意思。CURD程序員每天的工作內容就是根據業務邏輯需要對數據庫數據進行增刪改查,這在很多人看來是沒有技術含量的,尤其是工作了多年的程序員,認為這無法提高他們的技術能力,寫了十年的業務代碼,卻和寫一年業務代碼的年輕人差別不大,可能對某個框架更為熟悉一點罷了,此外,少有其他方面的優勢。

實際上,每個能正常被使用的業務系統都需要CURD的工作,但僅僅是CURD也是無法完成一個比較複雜的業務系統的。一個業務系統除了需要編寫功能代碼,還要有需求分析、架構設計、詳細設計、功能編寫、測試、集成部署等等工作內容,CURD頂多是功能編寫的子集。因此,如果你的任務只是CURD,那時間長了以後確實可能會厭煩,並且覺得技能得不到提高。也因此,論壇里常有人求助於高手,問怎樣才能脫離這種CURD工作:

高手們的答案也不一致,有的說寫業務代碼同樣牛逼,CURD是核心競爭力呢,有的建議換工作,擺脫CURD,也有的說要做個有心人,多解決實際問題,自然就會提高,等你水平到一定程度了,就可以不做這類工作了。

而在我看來,不寫業務代碼的人,也不一定有多牛掰,他們也不過是根據需求實現一些東西,也需要領域知識和編程技能,只不過有些領域知識和我們常見的上層應用業務知識有比較大的區別。即使是內核開發人員,如果只是負責實現某個模塊,而且他並沒有多少進取心,每天只是讀讀文檔和協議,調調接口來實現功能,沒有深挖原理,也不關注其他方面的技術,沒有全局視角,那他其實頂多也算是一個搬磚的,離高手還是有很大的距離。

而做CURD工作的,也並不是完全學不到東西。CURD從小的方面來說,是老闆的需求,從大的方面來說,是社會需求,需要大量的人來從事這個工作。CURD程序員離業務比較近,有機會可能也必須去更多地理解業務,而業務知識也是一種領域知識,具有深刻的領域知識的人,在職場中是有競爭優勢的。因此,你可以在CURD的同時,多了解業務知識,或者多思考怎麼把CURD做得更好,比如製作一些模板工具,想辦法通過各種方式來提高工作效率,這樣面對同樣的工作時,你會更輕鬆因而也更具有競爭力。如果你真想擺脫或者基本擺脫,那麼在平時就應該注意積累其它方面的知識,能完成其他CURD程序員難以完成的任務,在工作中,你要懂得合理地越俎代庖、份內份外

越俎代庖本來是個貶義詞,指的是越權辦事、多管閑事的行為,但在這裡是褒義詞。其他同事遇到難題時,你主動幫忙解決,在你自己任務已經完成的情況下,可以研究其他人的工作內容,這樣可以在其他同事只有不太好的實現方案時,適時給出你自己的方案,這樣也不出現搶活邀功的現象。即便你的方案使用不上,你有過自己的思考研究,對自己的成長也是有利的。

可能有同學會說了,你這是站着說話不腰疼,平時加班加點才能完成任務,哪有時間去做這樣的事情。如果是這樣,那你的確難以擺脫這種境況,要不你就安心地每天CURD,要不就換個更適合自己的工作。就我自己而言,工作這麼多年,和行業里其他人相比,加班真的很少,不過我花在學習上的時間,可能會比大部分人都多。這個學習,包括工作的時候去學習其他人的任務所涉及的技能、整個項目的架構原理,以及其它自己認為有用或感興趣的技術。一般來說,工作上的事情,我工作時間就解決可能也順便理解其原理了,而要拓寬知識和技術面,一般就靠下班時間。下班時間學習的東西,有時候也是跟工作內容相關的,即便是不相關的內容,可能也會在你工作時給你帶來靈感,或者有助於你更快理解工作上的事情,這樣的話也使得你能更快速完成工作任務,於是又有更多的時間去學習和擴寬技能,形成一個良性循環。關於良性循環和惡性循環,可以參考我之前的文章:停止無謂抱怨,構建你的良性循環系統。

總之,如果你覺得自己目前就是CURD程序員並且不滿足於此,那你可以先思考如果把CURD做得更好更高效更少出bug,同時盡可能地熟悉業務,爭取在某個業務方向上比普通人更熟悉。因為你最熟悉CURD,可能在換工作時,人家還是傾向於給你提供CURD的崗位,因此如果要擺脫這種境況,就需要你在業餘時間加倍地學習、實踐新技能,然後在機會到來時,才有可能抓住它。“機會永遠只留給有準備的人”,以我的親身體會,這句話在99.9%的情況下應該是正確的,希望我們都記住它!

 

原文發表於:聽說你在為天天寫業務代碼而煩惱?

歡迎關注公眾號:

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

我的產品/競品分析鍛煉記錄(分析產品核心)

  一來,以前剛入行的時候也想學習下競品分析/產品分析,然後好提高自身的分析能力,當時看了很多文章,然後看到大多都是學生寫的,分析的思路都是從用戶、核心流程,然後一直說到交互,但是整片文章偏重的是說某個小交互怎麼設計得不好,怎麼改進怎麼。

  二來,最近面試了一次,面試我的人應該也是挺教條主義的,面試過程我說了下競品分析,了解過市場行情,當時她追問,我就說下我的分析思路,我的分析思路很特別,只是分析產品核心、整體設計的優缺、預判產品的未來發展方向等,對於細節、業務流程等,一概不分析。

  三來想起來幾年前,那時候我還非常空閑,做了個100個APP分析挑戰,結果沒做完,尷尬。

  所以想整理下這篇,關於我對產品/競品分析的一些思路及想法,以供其他人參考。

 

1、產品經理究竟是負責什麼?

  必須要理清這個點,如果沒對產品經理的職責有清晰定位,很難做出比較適合的分析。為什麼這麼說呢?因為很多剛入行的人,要不從運營/業務入行,要不就技術/畫圖入行,只是了解業務或者功能設計,而不是真正、完整的產品管理。

  產品經理是要對一個產品的從頭到尾的管理,包括用戶了解、分析、業務設計、功能設計實施、運營推廣,從產品冷啟動到產品發展到產品退出市場,產品經理都需要負起帶頭的責任。這個是我理解的產品經理該需要承擔起來的責任。(這裏不展開說,不然這文章沒完沒了)

  基於這個概念,所以我作為一個產品經理,應該關注產品核心價值,因為核心價值才是讓這個產品獨立生存在市場上面,關注市場用戶的情況,而不是將關注點局限在一個小功能、小交互。所以才會有文章一開始說的,分析思路跟那些教科書的,有差異。

2、產品分析與競品分析的差異?

  在我的概念裏面,產品分析是偏重某個產品的的分析,偏重產品的核心價值、核心業務、核心設計。而競品分析偏重是在某個市場環境裏面,同類產品的核心價值差異。

  一個相對深入點,一個相對注重分析面,並且注重市場環境。

3、我的產品分析思路,要分析什麼?

  競品分析就不怎麼寫了,以前我也沒怎麼做過真正的競品分析,產品分析我也是只是分析核心的而已。

  我的方法很原始,就是作為一個用戶、作為一個產品設計者,兩種角色交替去對一個產品進行觀察、感受,寫出它的優缺而已。

  

下面截圖一下當時做的一些內容:

 

 

 

  從上面截圖可以看到當時我做這個鍛煉的目的及原始的一些思路,這個挑戰持續一年的,所以當時在自我學習過程裏面,開始逐漸完善我自身的分析。

  當然,這個是幾年前的分析,分析重點是一個產品的核心,沒有分析細節怎麼設計是好的,怎麼不好,只是純粹從小白的角度去嘗試,從如果我是這個產品的負責人的話,市場環境大概是那樣,我該如何去突破尋求發展?

 

如果說專業一點的競品分析文章,這裏介紹一篇超級詳細的競品分析文章,分析非常深入透徹,但是個人覺得價值不大!為什麼會這麼說呢?

  1、請問要分析一個行業的競品,單靠一個人,要像這位哥們分析到這麼深入,需要多少時間?一個月?一個季度?

  2、以前做產品分析的時候就發現一個問題,當你做完產品分析,產品已經發生改變,你所做的,都是歷史記錄的(當然有很重要的參考意義,這點無可厚非),花費那麼大的時間精力,是否值得?(跟隨是無法超越對方的,因為對方的創新永遠比你快,除非你的創新比它快,這樣要求你的團隊比對方要牛逼,要更清楚用戶需要)

 

  文章鏈接:https://www.zhihu.com/question/23601989/answer/91519343

  作者:大禹

 

       在後來的工作上面,針對這種專門競品/產品分析幾乎是沒有,可能我孤陋寡聞,但是貌似,沒見過有什麼企業,會做比較專門的競品/產品分析,因為做這些實在是耗費時間精力,但是做出來的時候,市場環境可能已經發生了改變了。

       而且當分析深度不夠深的話,還不如外包給專業的市場調研機構,他們會更專業,更有效率產出相對來說更準確的報告。而產品人員通過這些分析,可以拓寬自身眼界,鍛煉思考產品的核心。如果是這樣~~~好像也沒必要畫流程圖啊、寫交互優缺~~

       以上是我個人對產品經理、產品/競品分析的一些見解,純屬個人看法,如有更好的歡迎下面評論一起討論,研究下。

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

細談unity資源管理的設計

一、概要

本文主要說說Unity是如何管理的,基於何種方式,基於這種管理方式,又該如何規劃資源管理,以及構建bundle,是後面需要詳細討論的。

二、Unity的資源管理方式

2.1 資源分類

unity項目中的資源,大體上可以分為外部導入資源和內部生成資源兩種類型。
外部導入資源: 美術生成的大部分資源,都是外部帶入資源,模型,貼圖,UI用圖,基本是美術工具生成后,導入到工程中的。
內部生成資源: 部分美術生成資源,例如材質,shader,特效,場景等,屬於基於Unity引擎來製作生成的,此外各種prefab(UI/角色等),以及代碼腳本(c#為主),也屬於Unity的內部生成資源。

2.2 資源的存儲方式

在資源導入到unity工程后,會以各種方式進行轉換存儲,主要有以下幾種:

2.2.1 腳本類資源

對於工程中的腳本類資源,主要分為plugin和非plugin兩類。

  • plugin類:在plugin中引用的dll, 屬於自己生成相關的dll,在最終構建遊戲包的時候,被打入到相關遊戲包中:      
  • 非plugin類:unity會構建成4個基本的dll, 構建的順序為:
    • Assembly-CSharp-firstpass: standard assets/Pro standard assets/plugins 中的腳本
    • Assembly-CSharp-Editor-firstpass: editor scripts in standard assets/Pro standard assets/plugins 這個dll不會被打入到遊戲包中,屬於編輯器下特有的dll
    • Assembly-CSharp: all other scripts that not inside editor 主體遊戲邏輯的dll
    • Assembly-CSharp-Editor: all remaing scripts inside editor 這個dll也不會被打入到遊戲包中,編輯器中特有的dll

所有最終構建到遊戲包中的dll,主要分為:

  • Assembly-CSharp.dll/Assembly-CSharp-firstpass.dll 這2個主要遊戲邏輯dll
  • 引擎dll和插件引用的dll

2.2.2 美術類資源

美術類資源,分為外部導入和內部生成兩個大類

  • 外部導入類: 場景/模型/貼圖 都可以外部導入
    • 繼承自AssetPostprocesser后,可以對導入的貼圖,材質,模型,場景,均執行相關的修改
  • 內部生成類: shader/材質/prefab/場景 均可以內部生成美術資源的貼圖資源和特效資源,屬於重點關注對象,後面會細談這幾個資源的管理
    • 修改操作同上

2.2.3 meta文件

工程資源劃分好后,如何對這些資源進行管理? 不同的引擎有不同的管理方式,那麼unity中是如何管理的?
這兒管理分為2個步驟:序列化和meta文件的生成

2.2.3.1 unity的序列化

工程中的資源,要存儲到本地磁盤,那麼就會通過引擎進行一步序列化的操作,序列化的實質,就是將資源對象按照一定的順序轉換成二進制文件。

2.2.3.2 meta文件的生成

在完成序列化后,unity會對應的為該文件生成一份meta文件,這份meta文件會跟隨該文件一直存在,如果刪除該資源文件,其對應的meta文件也會被引擎自動刪除。
meta文件的主要構成:

  • 文件的guid: 這個文件的全工程中的唯一索引id,基於該id,可以對應的查找到該文件。guid的生成本質,就是基於文件的路徑來進行轉換生成的,同理,如果多個工程合併的時候出現guid衝突,可以自己重新生成一份guid,相關鏈接: https://gist.github.com/ZimM-LostPolygon/7e2f8a3e5a1be183ac19

  • 文件的導入設置:

    • 對於一般的文件,導入設置都比較簡單腳本類叫MonoImporter, 資源類叫NativeFormatImporter
    • 貼圖屬於需要重點關注的類型,其導入類型叫TextureImporter,裏面詳細的列出對該貼圖的各種壓縮格式,mipmaps, 類型,uv,貼圖大小等等詳細的設置信息

2.2.4 基於meta文件和序列化的資源管理

除了meta文件的guid,unity還會為每個資源生成一份文件id,也就是fileID, 不過現在fileID已經不再保留在meta文件中了,保留到文件的序列化文件中了,對於該資源,還會有一份localID, 這個localID, 對應的就是在一個資源中包含多個子資源的時候,定位每個子資源所用:  

那麼序列化是如何與guid/fileID關聯的?
在unity工程內部,如果給資源添加其他資源的引用,例如加一個腳本,拖拽一個外部引用,那麼就會觸發一次序列化操作,序列化操作的時候,就會將引用的資源的fileID和guid都序列化下來,這樣在反序列化的時候,就會基於fileID和guid來反向找到依賴的資源,從而加載進來。   

這個過程,在Unity中,就是一個裝載的過程,多說一句,如果一個資源依賴的其他資源越多,那麼這個裝載過程就會越耗時,所以在打開一個很大的UI的時候,有一部分的時間是消耗在裝載UI上各個組件上的。

三、總結

基於前文,可以對整個unity的資源管理有一個初步的認識,基於meta文件和序列化操作,可以管理工程中的資源,同時也能管理好各個資源的互相引用,那麼基於這樣的設計,在構建bundle的時候,是可以進行相關的設計和實現的。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

手把手教你學會 基於JWT的單點登錄

  最近我們組要給負責的一個管理系統 A 集成另外一個系統 B,為了讓用戶使用更加便捷,避免多個系統重複登錄,希望能夠達到這樣的效果——用戶只需登錄一次就能夠在這兩個系統中進行操作。很明顯這就是單點登錄(Single Sign-On)達到的效果,正好可以明目張膽的學一波單點登錄知識。

本篇主要內容如下:

  • SSO 介紹
  • SSO 的幾種實現方式對比
  • 基於 JWT 的 spring boot 單點登錄實戰

注意:
  SSO 這個概念已經出現很久很久了,目前各種平台都有非常成熟的實現,比如OpenSSOOpenAMKerberosCAS等,當然很多時候成熟意味着複雜。本文不討論那些成熟方案的使用,也不考慮 SSO 在 CS 應用中的使用。

什麼是 SSO

  單點點說就是:一次登錄后可免登陸訪問其他的可信平台。比如我們登錄淘寶網后,再打開天貓首頁可以發現已經是登錄狀態了。SSO 是一種比較流行的服務於企業業務整合的一種解決方案。

如何實現 SSO

  我們都知道目前的 http 協議是無狀態的,也就是第一次請求和第二次請求是完全獨立,不相關的,但現實中我們的業務邏輯都是有狀態的,這樣就引入了 cookie-session 的機制來維護狀態,瀏覽器端存儲一個 sessionId,後台存儲跟該 sessionId 相關的數據。每次向後台發起請求時都攜帶此 sessionId 就能維持狀態了。然後就有了 cookie,瀏覽器在發送請求時自動將 cookie 中的數據放到請求中,發給服務端,無需手動設置。

  然後我們可以考慮考慮實現 SSO 的核心是什麼?答案就是如何讓一個平台 A 登錄后,其他的平台也能獲取到平台 A 的登錄信息(在 cookie-session 機制中就是 sessionId)。

方案一 共享 cookie

  基於 cookie-session 機制的系統中,登錄系統後會返回一個 sessionId 存儲在 cookie 中,如果我們能夠讓另外一個系統也能獲取到這個 cookie,不就獲取到憑證信息了,無需再次登錄。剛好瀏覽器的 cookie 可以實現這樣的效果(詳見web 跨域及 cookie 學習)。

  cookie 允許同域名(或者父子域名)的不同端口中共享 cookie,這點和 http 的同域策略不一樣(http 請求只要協議、域名、端口不完全相同便認為跨域)。因此只需將多個應用前台頁面部署到相同的域名(或者父子域名),然後共享 session 便能夠實現單點登錄。架構如下:

  上面方案顯而易見的限制就是不僅前台頁面需要共享 cookie,後台也需要共享 session(可以用jwt來幹掉 session,但是又會引入新的問題,這裏不展開).這個方案太簡單了,不作進一步說明。

方案二 基於回調實現

  通過上文可以知道,要實現單點登錄只需將用戶的身份憑證共享給各個系統,讓後台知道現在是在訪問。就能實現一次登錄,到處訪問的效果,實在是非常方便的。在 session 機制中是共享 sessionId,然後多個後台使用同一個 session 源即可。這裏我們用一種新的基於 JWT 的 token 方式來實現,不了解 JWT 的可以看這篇:java-jwt 生成與校驗,簡單來說 jwt 可以攜帶無法篡改的信息(一段篡改就會校驗失敗),所以我們可以將用戶 id 等非敏感信息直接放到 jwt 中,幹掉了後台的 session。然後我們要做的就是將 jwt 共享給各個平台頁面即可。系統架構如下:

  此架構中,業務系統 A 和業務系統 B 之間不需要有任何聯繫,他們都只和 SSO 認證平台打交道,因此可以任意部署,沒有同域的限制。你可能就要問了這樣要怎麼共享身份憑證(也就是 jwt 字符串)?這裏就要通過 url 參數來進行騷操作了。文字總結來說是這樣的:jwt 存到認證平台前端的 localStore(不一定是 localStore,cookie,sessionStore 都可以),然後業務平台攜帶自己的回調地址跳轉到認證中心的前台,認證中心的前台再將 ujwt 作為 url 參數,跳回到那個回調地址上,這樣就完成了 jwt 的共享。

  文字很可能看不懂,下面是整個過程的路程圖:

相信通過上面的流程圖你應該能大概看明白,jwt 是如何共享了的吧,還看不懂的繼續看下來,下面上一個 spring boot 實現的簡易 SSO 認證。主要有兩個系統:SSO 認證中心,系統 A(系統 A 換不同端口運行就是系統 A、B、C、D 了).

實戰

實現 SSO 認證中心

  spring boot 框架先搭起來,由於是簡易項目,除 spring boot web 基本依賴,只需要如下的額外依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.4</version>
</dependency>
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.7.0</version>
</dependency>

完整的 POM 文件,請到 github 上查看.

後台實現

  後台做的事情並不多,只有以下 5 個方法:

  • /login : 登錄成功后簽發一個 jwt token
    在 demo 中只是簡單對比用戶名密碼如果是一樣的認為登錄成功,返回 token
  • /checkJwt : 檢查 jwt 的有效性
    檢查傳來的 jwt-token 是否有效,返回失效的 jwt 列表
  • /refreshjwt : 刷新 jwt
    判斷該 jwt 是否快要過期,如果快要過期,生成一個新的 jwt 返回
  • /inValid : 讓某個 jwt 失效
    jwt 如何失效一直是一個比較麻煩的問題,各有利弊。本例中採用的是為每個 jwt 生成一個隨機的秘鑰 secret,將 jwt–secret 保存到 redis 中,想要讓某個 jwt 失效,只需將該記錄在 redis 中刪除即可(這樣在解密時便無法獲取到 secret)。但是這樣讓無狀態的認證機制變成有狀態了(記錄了 jwt 和 secret 的對應關係)。

  總結來說 SSO 後台主要只做了兩件事:驗證用戶名密碼返回 jwt;驗證 jwt 是否合法。具體代碼查看 github 上 sso 目錄下的代碼。

前台實現

  前台的邏輯較為複雜,不是那麼容易理解,不明白的多看幾遍上面的流程圖。

  再次回到 SSO 的重點:分享登錄狀態。要如何在前台將登錄狀態(在這裏就是 jwt 字符串)分享出去呢?由於瀏覽器的限制,除了 cookie 外沒有直接共享數據的辦法。既然沒有直接共享,那肯定是有間接的辦法的!

  這個辦法就是回調。系統 A 的前台在跳轉到 SSO 的前台時,將當前路徑作為 url 參數傳遞給 sso 前台,sso 前台在獲取到 jwt 后,再跳轉到系統 A 傳過來的 url 路徑上,並帶上 jwt 作為 url 參數。這就完成了 jwt 的一次共享,從 sso 共享到系統 A。

打個比方:你點了個外賣,別人要怎麼把外賣給你呢?顯然你會留下的地址,讓別人帶上飯送到這個地址,然後你就能享用美食了。這和 jwt 的傳遞非常相識了。

系統 A 說:我要 jwt,快把它送到http://localhost:8081/test1/這個地址上。

SSO 說:好嘞,這個地址是合法的可以送 jwt 過去,這就跳轉過去:http://localhost:8081/test1/?jwt=abcdefj.asdf.asdfasf

系統 A 說:不錯不錯,真香。

  要注意這裏有個坑就是:如果另外一個惡意系統 C 安裝相同的格式跳轉到 SSO,想要獲取 jwt,這顯然是不應該給它的。所以在回跳回去的時候要判斷一下這個回調地址是不是合法的,能不能給 jwt 給它,可以向後台請求判斷也可以在 sso 前台直接寫死合法的地址。在 demo 是沒有這個判斷過程的。

實現業務系統

  業務系統代碼非常簡單,主要是用了一個攔截器,攔截 http 請求,提取出 token 向 sso 認證中心驗證 token 是否有效,有效放行,否則返回錯誤給前端。太簡單也不貼代碼了,到 github 上看看就明白了。

效果

  上面說了一大串都是原理了,其實這個難也就難在原理部分,代碼實現並沒有那麼複雜。這裏就不貼代碼了,有需要直接到 github 上看。

  這裏上幾個效果圖:

  • 系統 A 首次登陸系統

可以看到首次登陸是需要跳到 sso 認證中心輸入用戶名密碼進行登陸驗證的。登陸成功回跳後接口請求成功。

  • 將 A 的啟動端口改為 8082 后再次啟動,當作系統 B

可以看到這次是無需登陸的,跳到認證中心后就馬上跳回了,如果去掉 alert 一般是看不出跳轉過程的。

最後在任意一個系統註銷,都會讓所有的系統推出登陸。

可以看到,在系統 A 登錄系統后,系統 B,系統 C 都不再需要輸入用戶名密碼進行登錄。如果速度足夠快甚至都注意不到調到 SSO 再跳回來的過程。

源碼:github

本篇原創發佈於:www.tapme.top/blog/detail/2019-03-01-18-52

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

Spring 核心技術(1)

接上篇:Spring 框架概述

version 5.1.8.RELEASE

這部分參考文檔涵蓋了 Spring Framework 所有絕對不可或缺的技術。

其中最重要的是 Spring Framework 的控制反轉(IoC)容器。在介紹完 Spring 框架的 IoC 容器之後,緊接着全面介紹 Spring 的面向切面編程(AOP)技術。Spring Framework 有自己的 AOP 框架,它在概念上易於理解,並且成功地定位了 Java 企業編程中 AOP 需求的 80% 最佳擊球點。

Spring 提供與 AspectJ (目前功能最豐富,也是Java企業領域中最成熟的 AOP 實現)的集成。

1. IoC容器

本章介紹 Spring 的控制反轉(IoC)容器。

1.1 Spring IoC 容器和 Bean 簡介

本章介紹了 Spring Framework 控制反轉(IoC)的實現原理。IoC 也稱為依賴注入(DI)。通過這個機制,對象可以通過構造方法參數、工廠方法參數以及通過工廠方法構建或返回的實例上設置的屬性來定義它們的依賴關係(即它們使用的其他對象)。然後容器在創建 bean 時注入這些依賴項。這個過程從根本上反轉了 Bean 自身通過直接調用構造方法或服務定位模式等機制來控制實例化或定位其依賴的模式,因此叫做控制反轉。

org.springframework.beansorg.springframework.context 包是 Spring 框架的 IoC 容器的基礎。BeanFactory 接口提供了一種能夠管理任何類型對象的高級配置機制。ApplicationContext 是 BeanFactory 的子類。它補充的內容有:

  • 更容易與 Spring 的 AOP 功能集成
  • 消息資源處理(用於國際化)
  • 事件發布
  • 應用層特定的上下文,例如在 Web 應用程序中使用的 WebApplicationContext

簡而言之,BeanFactory 提供了配置框架和基本功能,ApplicationContext 添加了更多針對企業級的功能。ApplicationContextBeanFactory 完整的超集,在本章中僅用它描述 Spring IoC 容器。有關使用 BeanFactory 而不是 ApplicationContext的更多信息 請參考 BeanFactory。

在 Spring 中,構成應用程序架構並由 Spring IoC 容器管理的對象稱為 beans。bean 是一個由 Spring IoC 容器實例化、組裝或管理的對象。除此之外,bean 只是應用程序中眾多對象之一。Bean 及其之間的依賴關係反映在容器使用的配置元數據中。

1.2 容器概覽

org.springframework.context.ApplicationContext 接口代表 Spring IoC 容器,負責實例化、配置和組裝 bean。容器通過讀取配置元數據獲取有關需要實例化、配置和組裝對象的指令。配置元數據以 XML、Java 註解或 Java 代碼錶示,通過它可以表示構成應用的對象以及對象之間豐富的依賴關係。

Spring 提供了多種 ApplicationContext 接口實現。在傳統單機應用中,通常會創建一個 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 的實例。雖然 XML 是定義配置元數據的傳統格式,但你也可以通過提供少量 XML 配置聲明容器啟用對其他元數據格式的支持后使用 Java 註解或代碼作為元數據格式。

在大多數應用程序方案中,不需要顯式使用代碼來實例化 Spring IoC 容器實例。例如,在 Web 應用程序場景中,應用程序文件 web.xml 中一個 8 行左右的 web 描述模板 XML 就足夠了(請參閱 Web 應用程序快速實例化 ApplicationContext)。如果你使用 Spring Tool Suite(一個基於 Eclipse 的開發環境),只需點擊幾下鼠標或按幾下鍵盤即可輕鬆創建此模板配置。

下圖是 Spring 工作原理的高級視圖。應用程序類與配置元數據相結合,在 ApplicationContext 創建並初始化之後,即可擁有完全配置且可執行的系統或應用程序。

1.2.1 配置元數據

如上圖所示,Spring IoC 容器使用一系列配置元數據。這些配置元數據描述了Spring 容器在應用程序中如何實例化,配置和組裝對象。

傳統配置元數據使用簡單直觀的 XML 格式,本章大部分內容也是用 XML 來表達 Spring IoC 容器的關鍵概念和功能。

XML 不是唯一的配置元數據格式。Spring IoC 容器與實際編寫配置元數據的格式完全解耦。目前,許多開發人員為其 Spring 應用程序選擇基於 Java 的配置。

有關在 Spring 容器中使用其他形式的元數據的信息,請參閱:

  • 基於註解的配置:Spring 2.5 引入了對基於註解的配置元數據的支持。
  • 基於Java的配置:從 Spring 3.0 開始,Spring JavaConfig 項目提供的許多功能成為 Spring Framework 核心的一部分。因此,你可以使用 Java 而不是 XML 文件來定義應用程序類外部的 bean。要使用這些新功能,請參閱 @Configuration@Bean@Import,和 @DependsOn 註解。

Spring 配置信息由至少一個(通常不止一個) 必須由容器進行管理的 bean 定義組成。基於 XML 的配置元數據將這些 bean 配置為頂級元素 <beans/> 內的 <bean/> 元素。基於 Java 的配置通常在使用 @Configuration 註解的類中使用帶有 @Bean 註解的方法。

這些 bean 定義對應構成應用程序的實際對象。我們一般會定義服務層對象,數據訪問對象(DAO),展示層對象(例如 Struts Action 實例),基礎結構對象(例如 Hibernate SessionFactories、JMS Queues 等)。一般不會在容器中配置細粒度的域對象,因為通常由 DAO 和業務邏輯負責創建和加載域對象。然而,你可以使用 Spring 集成 AspectJ 來配置非 IoC 容器創建的對象。請參閱使用 Spring 和 AspectJ 進行域對象的依賴注入。

以下示例显示了基於 XML 的配置元數據的基本結構:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">①②
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

① id 屬性是一個標識單個 bean 定義的字符串

② class 屬性使用完整的類名定義 bean 的類型

id 屬性的值表示協作對象。在此示例中未包含用於引用協作的對象的 XML。有關更多信息,請參閱依賴。

1.2.2 實例化容器

提供給 ApplicationContext 構造函數的位置路徑是一個資源字符串,它允許容器從各種外部資源加載配置元數據,例如本地文件系統、Java 環境變量等。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

在了解了 Spring 的 IoC 容器之後,你可能想要了解有關 Spring 資源抽象化(參考資源描述)的更多信息,特別是 Resource 路徑用於構建應用程序上下文(請參考應用程序上下文和資源路徑),它提供了一種便捷的機制從 URI 語句中定義的位置讀取 InputStream。。

以下示例展示了服務層對象配置文件(services.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

以下示例展示了數據訪問對象文件(daos.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

在前面的示例中,服務層由 PetStoreServiceImpl 類和兩個數據訪問對象 JpaAccountDaoJpaItemDao(基於 JPA 對象關係映射標準)組成。property 元素的 name 屬性指的是 JavaBean 屬性的名稱,ref 屬性指向另一個 bean 定義的名稱。元素 idref 之間的這種聯繫表達了協作對象之間的依賴關係。有關配置對象的依賴關係的詳細信息,請參閱依賴關係。

編寫基於XML的配置元數據

通常,每個單獨的 XML 配置文件都對應着架構中的邏輯層或模塊,讓 bean 定義在多個 XML 文件中生效會非常有用。

如上一節中所示,應用程序上下文構造函數可以使用多個 Resource 位置,它可以從這些 XML 片段中加載 bean 定義。另外也可以使用一個或多個 <import/> 元素從其他文件加載 bean 定義。以下示例展示了如何執行此操作:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

在前面的例子中,從三個文件中加載外部 Bean 定義,分別是 services.xml、messageSource.xml 和 themeSource.xml。對於執行導入的定義文件來說,所有路徑都是相對路徑,因此 services.xml 必須與執行導入的文件位於相同的目錄或環境變量, messageSource.xml 和 themeSource.xml 必須位於導入文件路徑下方的 resources 目錄中。正如你所見,前邊的斜杠會被忽略掉。鑒於提供的都是相對路徑,所以最好不要使用斜杠。這些文件中包括根據 Spring Schema 定義的正確的 XML Bean 在內的內容都會被導入,包括頂級元素 <beans/>

雖然可以使用相對路徑“../”引用父目錄中的文件,但不建議這樣使用,因為這樣做會使得當前應用依賴程序之外的文件。非常不建議使用 classpath:URL(例如,classpath:../services.xml)引用文件,因為運行時解析過程會選擇“最近”的環境變量根目錄,然後查找其父目錄。環境變量配置的更改可能導致目錄選擇不正確。

可以使用完整的資源位置替代相對路徑,例如,file:C:/config/services.xml 或 classpath:/config/services.xml。然而需要注意應用程序的配置將會與特定的絕對路徑耦合。通常最好為這些絕對路徑保持間接聯繫,例如通過在運行時通過“$ {…}”佔位符替代 JVM 系統屬性。

命名空間本身提供了導入指令的功能。Spring 提供的一系列 XML 命名空間中提供了除普通 bean 定義之外的其他配置功能,例如 context 和 util 命名空間。

Groovy Bean 定義 DSL

作為外化配置元數據的另一個示例,bean 定義也可以在 Spring 的 Groovy Bean 定義 DSL 中表示,就像 Grails 框架。通常此類配置位於“.groovy”文件中,其結構如下例所示:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

此配置樣式在很大程度上等同於 XML bean 定義,同樣支持 Spring 的 XML 配置命名空間。它還允許通過 importBeans 指令直接導入 XML bean 定義文件。

1.2.3 使用容器

ApplicationContext 是一個高級工廠接口,主要負責維護不同 bean 及其依賴項的註冊。通過使用 T getBean(String name, Class<T> requiredType) 方法可以獲得 Bean 的實例。

通過 ApplicationContext 可以讀取 bean 定義並訪問它們,如下例所示:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

使用 Groovy 配置時,初始化程序看起來非常相似。不同的是使用了支持 Groovy 的上下文實現類(也支持 XML bean 定義)。以下示例展示了 Groovy 配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最靈活的使用方式是 GenericApplicationContext 與讀取器委派結合使用,例如針對 XML 文件使用 XmlBeanDefinitionReader,如以下示例所示:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

還可以使用針對 Groovy 文件使用 GroovyBeanDefinitionReader ,如以下示例所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

你可以在相同的 ApplicationContext 中混合使用此類讀取器委託, 從不同的配置源讀取 bean 定義。

你可以使用 getBean 方法來獲取 Bean 實例。ApplicationContext 接口還有一些其他方法可以獲取 bean,但理想情況下你的應用程序不應該使用它們。實際上,你的應用程序代碼根本不應該調用 getBean()方法,也應該不依賴於 Spring API。例如,Spring 集成的 Web 框架為各種 Web 框架組件(如控制器和 JSF 託管的 bean)提供依賴注入,以便通過元數據聲明對特定 bean 的依賴關係,例如自動裝配註解。

  • 我的CSDN:https://blog.csdn.net/liweitao7610
  • 我的博客園:https://www.cnblogs.com/aotian/
  • 我的簡書:https://www.jianshu.com/u/6b6e162f1fdc

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

List集合去重方式及效率對比

List集合相信大家在開發過程中幾乎都會用到。有時候難免會遇到集合里的數據是重複的,需要進行去除。然而,去重方式有好幾種方式,你用的是哪種方式呢?去重方式效率是否是最高效、最優的呢?今天就給大家講解一下List集合去重的常見及常用的四種方式。

01

實現思路:使用兩個for循環遍歷集合所有元素,然後進行判斷是否有相同元素,如果有,則去除。這種方式是大部分最先想到的,也是最簡單的實現方式。其中,這種方式可以保證List集合原來的順序不變。

代碼實現:

/**
* notes:使用兩個for循環實現List去重
@param list
@return
*/
public static List repeatListWayOne(List<String> list){
   for(int i = 0;i < list.size();i++){
       for(int j = i+1;j < list.size();j++){
           if(list.get(i).equals(list.get(j))){
               list.remove(j);
           }
       }
   }
   return list;
}

 

02

實現思路:我們知道HashSet實現了Set接口,不允許出現重複元素。可以基於這個想法,把List集合所有元素存入HashSet對象,接着把List集合元素全部清空,最後把HashSet對象元素全部添加至List集合中,這樣就可以保證不出現重複元素。而HashSet有一個構造函數,在初始化時可以直接添加元素。其中,HashSet不能保證順序不變,所以此方式不能保證List集合原來的順序不變。

代碼實現:

/**
* notes:使用HashSet實現List去重
@param list
@return
*/
public static List repeatListWayTwo(List<String> list){
  //初始化HashSet對象,並把list對象元素賦值給HashSet對象
  HashSet set = new HashSet(list);
  //把List集合所有元素清空
  list.clear();
  //把HashSet對象添加至List集合
  list.addAll(set);
  return list;
}

 

03

實現思路:TreeSet集合也是實現Set接口,是一個有序的,並且無重複元素集合。同理,我們可以根據上面方式二的思想進行去重。其中,去重后的List集合可以保證和原來的順序一致。

代碼實現:

/**
* notes:使用TreeSet實現List去重
@param list
@return
*/
public static List repeatListWayThird(List<String> list){
   //初始化TreeSet對象,並把list對象元素賦值給TreeSet對象
   TreeSet set = new TreeSet(list);
   //把List集合所有元素清空
   list.clear();
   //把TreeSet對象添加至List集合
   list.addAll(set);
   return list;
}

 

04

實現思路:利用List集合contains方法循環遍歷,先創建新的List集合,接着循環遍歷原來的List集合,判斷新集合是否包含有舊集合,如果有,則不添加至新集合,否則添加。最後,把舊集合清空,把新集合元素賦值給舊集合。

代碼實現:

/**
* notes:利用List集合contains方法循環遍歷去重
@param list
@return
*/
public static List repeatListWayFourth(List<String> list){
   //新建新List集合,用於存放去重后的元素
   List<String> newList = new ArrayList<String>();
   //循環遍歷舊集合元素
   for(int i = 0; i < list.size(); i++ ){
       //判斷新集合是否包含有,如果不包含有,則存入新集合中
       boolean isContains = newList.contains(list.get(i));
       if(!isContains){
           newList.add(list.get(i));
       }
   }
   //把List集合所有元素清空
   list.clear();
   //把新集合元素添加至List集合
   list.addAll(newList);
   return list;
}

上面給大家介紹了四種List集合去重方式。那麼,哪種方式效率是最好的呢?下面就演示一下進行對比。

為了演示方式,隨機生成0-500之間的20000個整数字符串,並存入List集合,並在相應代碼打印相關時間進行對比。其中,隨機生成List集合代碼如下:

/**
* 隨機生成0-500之間的20000個整数字符串,並存入List集合
@return
*/
public static List<String> getRandomList(){
   List<String> list = new ArrayList<String>();
   //隨機生成20000個整数字符串
   for(int i = 1; i <= 20000; i++){
       //任意取[0,500)之間整數,其中0可以取到,500取不到
       int number = new Random().nextInt(500);
       String number_str = "geshan"+number;
       list.add(number_str);
   }
   return list;
}

為了保證List集合元素一致,創建四個List集合,分別對應List去重方式。效率對比代碼如下:

public static void main(String[] args){
   //隨機生成0-500之間的1000個整数字符串List集合
   List<String> list = getRandomList();

   //為了演示四種方式效率,創建四個List集合,保證List集合元素一致
   //方式一List集合
   List<String> oneList = new ArrayList<>();
   oneList.addAll(list);
   //方式二List集合
   List<String> twoList = new ArrayList<>();
   twoList.addAll(list);
   //方式三List集合
   List<String> thirdList = new ArrayList<>();
   thirdList.addAll(list);
   //方式四List集合
   List<String> fourthList = new ArrayList<>();
   fourthList.addAll(list);

   System.out.println("方式一:使用兩個for循環實現List去重");
   System.out.println("原來集合大小:"+oneList.size()+",集合元素>>"+oneList);
   Date oneDateBegin = new Date();
   repeatListWayOne(oneList);
   System.out.println("集合去重大小:"+oneList.size()+",集合元素>>"+oneList);
   Date oneDateEnd = new Date();
   System.out.println("去重所需時間:"+(oneDateEnd.getTime()-oneDateBegin.getTime())+"毫秒");

   System.out.println("方式二:使用HashSet實現List去重");
   System.out.println("原來集合大小:"+twoList.size()+",集合元素>>"+twoList);
   Date twoDateBegin = new Date();
   repeatListWayTwo(twoList);
   System.out.println("集合去重大小:"+twoList.size()+",集合元素>>"+twoList);
   Date twoDateEnd = new Date();
   System.out.println("去重所需時間:"+(twoDateEnd.getTime()-twoDateBegin.getTime())+"毫秒");

   System.out.println("方式三:使用TreeSet實現List去重");
   System.out.println("原來集合大小:"+thirdList.size()+",集合元素>>"+thirdList);
   Date thirdDateBegin = new Date();
   repeatListWayThird(thirdList);
   System.out.println("集合去重大小:"+thirdList.size()+",集合元素>>"+thirdList);
   Date thirdDateEnd = new Date();
   System.out.println("去重所需時間:"+(thirdDateEnd.getTime()-thirdDateBegin.getTime())+"毫秒");

   System.out.println("方式四:利用List集合contains方法循環遍歷去重");
   System.out.println("原來集合大小:"+fourthList.size()+",集合元素>>"+fourthList);
   Date fourthDateBegin = new Date();
   repeatListWayFourth(fourthList);
   System.out.println("集合去重大小:"+fourthList.size()+",集合元素>>"+fourthList);
   Date fourthDateEnd = new Date();
   System.out.println("去重所需時間:"+(fourthDateEnd.getTime()-fourthDateBegin.getTime())+"毫秒");
}

多次運行結果如下:

第一次四種方式運行時間如下:223、10、16、30;

第二次四種方式運行時間如下:164、10、17、43;

第三次四種方式運行時間如下:164、9、16、37;

綜合代碼及運行時間對比,方式二是最好的去重方式,代碼最簡潔、耗時最短。你平時List集合去重,方式用對了嗎?

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

設計模式之迭代器與組合模式(四)

因為這系列篇幅較長,所以在這裏也不進行任何鋪墊,直奔主題去啦。

利用組合設計菜單

我們要如何在菜單上應用組合模式呢?一開始,我們需要創建一個組件接口來作為菜單和菜單項的共同接口,讓我們能夠用統一的做法來處理菜單和菜單項。換句話說,我們可以針對菜單或菜單項調用相同的方法。

讓我們從頭來看看如何讓菜單能夠符合組合模式的結構:

實現菜單組件

好了,我們開始編寫菜單組件的抽象類;請記住,菜單組件的角色是為恭弘=恭弘=恭弘=叶 恭弘 恭弘 恭弘節點和組合節點提供一個共同的接口。

public abstract class MenuComponent {
   
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }
    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }
    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }
  
    public String getName() {
        throw new UnsupportedOperationException();
    }
    public String getDescription() {
        throw new UnsupportedOperationException();
    }
    public double getPrice() {
        throw new UnsupportedOperationException();
    }
    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    public abstract Iterator<MenuComponent> createIterator();
 
    public void print() {
        throw new UnsupportedOperationException();
    }
}

讓我們來看菜單類。別忘了,這是組合類圖裡的恭弘=恭弘=恭弘=叶 恭弘 恭弘 恭弘類,它實現組合內元素的行為。

public class MenuItem extends MenuComponent {
 
    String name;
    String description;
    boolean vegetarian;
    double price;
    
    public MenuItem(String name, 
                    String description, 
                    boolean vegetarian, 
                    double price) 
    { 
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }
  
    public String getName() {
        return name;
    }
  
    public String getDescription() {
        return description;
    }
  
    public double getPrice() {
        return price;
    }
  
    public boolean isVegetarian() {
        return vegetarian;
    }

    public Iterator<MenuComponent> createIterator() {
        return new NullIterator();
    }
 
    public void print() {
        System.out.print("  " + getName());
        if (isVegetarian()) {
            System.out.print("(v)");
        }
        System.out.println(", " + getPrice());
        System.out.println("     -- " + getDescription());
    }

}

我們已經有了菜單項,還需要組合類,這就是我們叫做菜單的。別忘了,此組合類可以持有菜單項或其他菜單。

public class Menu extends MenuComponent {
    Iterator<MenuComponent> iterator = null;
    ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
    String name;
    String description;
  
    public Menu(String name, String description) {
        this.name = name;
        this.description = description;
    }
 
    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }
 
    public void remove(MenuComponent menuComponent) {
        menuComponents.remove(menuComponent);
    }
 
    public MenuComponent getChild(int i) {
        return menuComponents.get(i);
    }
 
    public String getName() {
        return name;
    }
 
    public String getDescription() {
        return description;
    }

  
    public Iterator<MenuComponent> createIterator() {
        if (iterator == null) {
            iterator = new CompositeIterator(menuComponents.iterator());
        }
        return iterator;
    }
 
 
    public void print() {
        System.out.print("\n" + getName());
        System.out.println(", " + getDescription());
        System.out.println("---------------------");
    }
}

因為菜單是一個組合,包含了菜單項和其他的菜單,所以它的print()應該打印出它所包含的一切。如果它不這麼做,我們就必須遍歷整個組合的每個節點,然後將每一項打印出來。這麼一來,也就失去了使用組合結構的意義

所以,print還得進行優化,如下:

public void print() {
    System.out.print("\n" + getName());
    System.out.println(", " + getDescription());
    System.out.println("---------------------");
 
    Iterator<MenuComponent> iterator = menuComponents.iterator();
    while (iterator.hasNext()) {
        MenuComponent menuComponent = iterator.next();
        menuComponent.print();
    }
}

看到上面了沒,我們用了迭代器。用它遍歷所有菜單組件,遍歷過程中,可能遇到其他菜單,或者是遇到菜單項。由於菜單和菜單項都實現了print,那我們只要調用print即可。

開始測試數據之前,我們了解一下,在運行時菜單組合是什麼樣的:

開始運行我們的測試程序啦:

public class MenuTestDrive {
    public static void main(String args[]) {

        MenuComponent pancakeHouseMenu = 
            new Menu("PANCAKE HOUSE MENU", "Breakfast");
        MenuComponent dinerMenu = 
            new Menu("DINER MENU", "Lunch");
        MenuComponent cafeMenu = 
            new Menu("CAFE MENU", "Dinner");
        MenuComponent dessertMenu = 
            new Menu("DESSERT MENU", "Dessert of course!");
  
        MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
  
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);
  
        pancakeHouseMenu.add(new MenuItem(
            "K&B's Pancake Breakfast", 
            "Pancakes with scrambled eggs, and toast", 
            true,
            2.99));
        pancakeHouseMenu.add(new MenuItem(
            "Regular Pancake Breakfast", 
            "Pancakes with fried eggs, sausage", 
            false,
            2.99));
        pancakeHouseMenu.add(new MenuItem(
            "Blueberry Pancakes",
            "Pancakes made with fresh blueberries, and blueberry syrup",
            true,
            3.49));
        pancakeHouseMenu.add(new MenuItem(
            "Waffles",
            "Waffles, with your choice of blueberries or strawberries",
            true,
            3.59));

        dinerMenu.add(new MenuItem(
            "Vegetarian BLT",
            "(Fakin') Bacon with lettuce & tomato on whole wheat", 
            true, 
            2.99));
        dinerMenu.add(new MenuItem(
            "BLT",
            "Bacon with lettuce & tomato on whole wheat", 
            false, 
            2.99));
        dinerMenu.add(new MenuItem(
            "Soup of the day",
            "A bowl of the soup of the day, with a side of potato salad", 
            false, 
            3.29));
        dinerMenu.add(new MenuItem(
            "Hotdog",
            "A hot dog, with saurkraut, relish, onions, topped with cheese",
            false, 
            3.05));
        dinerMenu.add(new MenuItem(
            "Steamed Veggies and Brown Rice",
            "A medly of steamed vegetables over brown rice", 
            true, 
            3.99));
 
        dinerMenu.add(new MenuItem(
            "Pasta",
            "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
            true, 
            3.89));
   
        dinerMenu.add(dessertMenu);
  
        dessertMenu.add(new MenuItem(
            "Apple Pie",
            "Apple pie with a flakey crust, topped with vanilla icecream",
            true,
            1.59));
        dessertMenu.add(new MenuItem(
            "Cheesecake",
            "Creamy New York cheesecake, with a chocolate graham crust",
            true,
            1.99));
        dessertMenu.add(new MenuItem(
            "Sorbet",
            "A scoop of raspberry and a scoop of lime",
            true,
            1.89));

        cafeMenu.add(new MenuItem(
            "Veggie Burger and Air Fries",
            "Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
            true, 
            3.99));
        cafeMenu.add(new MenuItem(
            "Soup of the day",
            "A cup of the soup of the day, with a side salad",
            false, 
            3.69));
        cafeMenu.add(new MenuItem(
            "Burrito",
            "A large burrito, with whole pinto beans, salsa, guacamole",
            true, 
            4.29));
 
        Waitress waitress = new Waitress(allMenus);
   
        waitress.printVegetarianMenu();
 
    }
}

結果這裏就不附上了,請大家自行去跑代碼實現吧。相信你們又對組合模式也已經有了一個大概了吧。下一篇,還有更犀利的,組合迭代器等着我們。小編馬上回去搞起來,安排上。

愛生活,愛學習,愛感悟,愛挨踢

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

ASC19最新戰況:北航打破HPL基準測試賽會紀錄!

ASC19最新戰況:北航打破HPL基準測試賽會紀錄!

2019-04-23 23:32    原創  作者: 謝濤 編輯:
0購買

  【IT168現場報道】今天(4月23日)是全球矚目的ASC19全球總決賽第三天,經過前两天的系統搭建與調試工作后,20支隊伍進入了正式競賽階段,向總冠軍寶座發起最後衝刺。

  根據賽程,今天所有參賽隊伍需要完成的項目包括超級計算機性能基準測試HPL&HPCG、超級團隊對抗賽賽題Fluidity以及全球氣候變化模式CESM——其中,CESM是今年“e Prize計算挑戰獎”的指定賽題。

  上午十二時左右,組委會公布了第一項賽題HPL的成績,在3000瓦功耗約束下,北京航空航天大學代表隊以每秒50.21萬億次浮點運算的成績打破了賽會紀錄,在HPL賽題單項成績上排名第一。

  HPL是國際通用的超級計算機浮點性能基準測試程序,是全球超算TOP500榜單評比的重要依據,主要考察參賽隊伍對硬件平台的浮點計算性能優化能力。ASC競賽的HPL計算性能測試有嚴格的規定,各參賽隊伍需要在功耗不高於3000W的約束下,採用組委會統一提供的超算節點、高速網絡和自行配置加速卡等設備,完成超算系統搭建。

  HPL測試側重於系統的整體運行能力和峰值性能,這個項目考察參賽隊伍在搭建系統、性能優化和配置選擇上的綜合能力。為了取得更好的成績,參賽隊伍需要從超算系統體繫結構、多級存儲一致性、高速網絡、算法優化等不同角度,進行全方位的深入研究,在不同方案之間進行抉擇,通過反覆優化和測試獲得更高的運算性能。

  在這一項目上,歷屆ASC大賽總能給我們帶來驚喜,充分體現了“百尺竿頭,更進一步”的積極進取精神。ASC15,來自新加坡的南洋理工大學以11.92萬億次浮點運算/秒的成績打破上一年的紀錄;ASC16,該紀錄被浙江大學以12.03萬億次浮點運算/秒的成績刷新;ASC17,濰坊學院代表隊創下31.7萬億次浮點運算/秒的新紀錄;ASC18,也就是去年,台灣清華大學獲得42.99萬億次浮點運算/秒的成績,再次刷新紀錄。

  今年,北京航空航天大學則以50.21萬億次/秒的驚人成績又一次打破賽會紀錄,在現場引起一片呼聲。該代表隊設計了“3機12卡”的異構超算系統,共採用3台浪潮AI超算服務器NF5280M5配置12塊NVIDIA Tesla系列V100加速卡。值得一提的是,北京航空航天大學在預賽期間就曾獲得了第一名的好成績。

  根據ASC19競賽組委會的介紹,明天(4月24日)將公布正式競賽第一天其餘賽題的成績,包括HPCG、Fluidity、CESM。此外,參賽隊伍還將完成人工智能賽題人臉圖像超分辨率(Face SR)、中國科學家研發的第三代基因測序組裝軟件wtdbg以及一個神秘應用賽題。

  在這些賽題上,各位參賽隊伍又將給我們帶來哪些驚喜呢?大家一起期待明天的結果吧!

  4月24日下午,第21屆國際超算高峰論壇將在大連理工大學隆重舉行。全球超級計算機排行榜TOP500提出人、ASC競賽專家委員會主席、田納西大學傑出教授Jack Dongarra,憶阻器提出人加州大學伯克利分校教授Leon Chua(蔡少棠)等多位知名國際國內專家將出席並進行主題報告與分享,與參會嘉賓就超算話題展開思想碰撞。

  IT168將對本次大賽進行全程報道,了解大賽更多信息,請持續關注:http://zt.itpub.net/topic/ASC19/

, ,

網站內容來源http://server.it168.com/本站聲明:網站內容來源http://www.it168.com/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"