YAML 技術研究

YAML預研文檔

YAML概要

YAML是”YAML Ain’t a Markup Language”(YAML不是一種置標語言)的遞歸縮寫,早先YAML的意思其實是:”Yet Another Markup Language”(另外一種置標語言),但爲了強調這種語言以數據做爲中心,而不是以置標語言爲重點,而用返璞詞重新命名,YAML的官方定義很簡單,即一種人性化的數據格式定義語言,其主要功能用途類似於XML或JSON,YAML使用空白字符和分行來分隔數據,且巧妙避開各種封閉符號,如:引號、括號等,以避免這些符號在複雜層次結構中變得難以辨認。YAML的語法與高階語言類似,可以很簡單地表述序列(java中的list)、雜湊表(java中的map)、標量(java中的基本類型等)數據結構,它重點強調可閱讀性。

YAML vs XML

與YAML相似的數據格式定義語言是XML,YAML比XML優越性表現在

優勢:

  • YAML的可讀性好
  • YAML和腳本語言的交互性好
  • YAML使用實現語言的數據類型
  • YAML有一個一致的信息模型
  • YAML易於實現

上面5條是XML不足的地方,同時,YAML也具有XML的下列優點:

  • YAML可以基於流來處理
  • YAML表達能力強,擴展性好

YAML類似於XML的數據描述語言,語法比XML簡單很多,YAML試圖用一種比XML更敏捷的方式,來完成XML所完成的任務。


YAML vs JSON

JSON的語法其實是YAML的子集,大部分的JSON文件都可以被YAML的剖析器剖析。雖然大部分的數據分層形式也可以使用類似JSON的格式,不過YAML並不建議這樣使用,除非這樣編寫能讓文件可讀性增加,更重要的是,YAML的許多擴展在JSON是找不到的,如:進階資料形態關係錨點字串不需要引號映射資料形態會儲存鍵值的順序等。

YAML用途

腳本語言

由於實現簡單,解析成本很低,YAML特別適合在腳本語言中使用。列一下現有的語言實現:Ruby,Java,Perl,Python,PHP,OCaml,JavaScript,除了Java,其他都是腳本語言。

序列化

YAML比較適合做序列化。因爲它是宿主語言數據類型直轉的。

配置文件

YAML做配置文件也不錯。寫YAML要比寫XML快得多(無需關注標籤或引號),並且比ini文檔功能更強。

調試

由於其很強的閱讀性,用於調試過程中dump出信息供分析也是一種比較方便的做法。

YAML缺陷與不足

YAML沒有自己的數據類型的定義,而是使用實現語言的數據類型。一個YAML文件,在不同語言中解析後得到的數據類型可能會不同,由於其兼容性問題,不同語言間的數據流轉不建議使用YAML。

YAML語法與範例

  • YAML使用可打印的Unicode字符,可使用UTF-8或UTF-16
  • 使用空白字符(不能使用Tab)分層,同層元素左側對齊
  • 單行註解由井字號( # )開始,可以出現在行中任何位置
  • 每個清單成員以單行表示,並用短槓+空白(- )起始
  • 每個雜湊表的成員用冒號+空白(: )分開鍵和值
  • 雜湊表的鍵值可以用問號 (?)起始,表示多個詞彙組成的鍵值
  • 字串一般不使用引號,但必要的時候可以用引號框住
  • 使用雙引號表示字串時,可用倒斜線(\)進行特殊字符轉義
  • 區塊的字串用縮排和修飾詞(非必要)來和其他資料分隔,有新行保留(使用符號|)或新行摺疊(使用符號>)兩種方式
  • 在單一檔案中,可用連續三個連字號(---)區分多個檔案
  • 可選擇性的連續三個點號(...)用來表示檔案結尾(在流式傳輸時非常有用,不需要關閉流即可知道到達結尾處)
  • 重複的內容可使從參考標記星號 (*)複製到錨點標記(&
  • 指定格式可以使用兩個驚歎號 ( !! ),後面接上名稱
receipt:     Oz-Ware Purchase Invoice
date:        2007-08-06
customer:
    given:   Dorothy
    family:  Gale
items:
    - part_no:   A4786
      descrip:   Water Bucket (Filled)
      price:     1.47
      quantity:  4
    - part_no:   E1628
      descrip:   High Heeled "Ruby" Slippers 
      price:     100.27
      quantity:  1
bill-to:  &id001
    street: | 
            123 Tornado Alley
            Suite 16
    city:   East Westville
    state:  KS
ship-to:  *id001   
specialDelivery:  >
    Follow the Yellow Brick
    Road to the Emerald City.
    Pay no attention to the 
    man behind the curtain.
...

這個文件的的頂層由七個鍵值組成:其中一個鍵值”items”,是個兩個元素構成的清單,清單中的兩個元素同時也是包含了四個鍵值的雜湊表。
文件中重複的部分處理方式:使用錨點(&)和參考(*)標籤將”bill-to”雜湊表的內容複製到”ship-to”雜湊表。也可以在文件中加入選擇性的空行,以增加可讀性。

YAML的JAVA實現

YAML已經有了多種語言不少實現,詳見YAML官網
一般YAML文件擴展名爲.yaml,比如John.yaml,其內容爲:

name: John Smith
age: 37
children:
    - name: Jimmy Smith
      age: 15
    - name: Jenny Smith
      age: 12
spouse:
    name: Jane Smith
    age: 25

由於yaml的超強可讀性,我們瞭解到:John今年37歲,兩個孩子Jimmy 和Jenny活潑可愛,妻子Jane年輕美貌,而且年僅25歲,一個幸福的四口之家。
對John.yaml進行java描述,抽象出一個Person類,如下:

public class Person {
    private String name;
    private int age;
    private Person sponse;
    private Person[] children;
    // setXXX, getXXX方法略.
}

現在我們使用java裝配一個Jone:

    Person john = new Person();
    john.setAge(37);
    john.setName("John Smith");
    Person sponse = new Person();
    sponse.setName("Jane Smith");
    sponse.setAge(25);
    john.setSponse(sponse);
    Person[] children = {new Person(), new Person()};
    children[0].setName("Jimmy Smith");
    children[0].setAge(15);
    children[1].setName("Jenny Smith");
    children[1].setAge(12);
    john.setChildren(children);

使用SnakeYAML實現

項目主頁:http://code.google.com/p/snakeyaml/
使用手冊:https://code.google.com/p/snakeyaml/wiki/Documentation
SnakeYAML是一個標準的YAML的java實現,它有以下特點:

  • 完全支持YAML 1.1,可以跑通規範中的所有示例
  • 支持YAML的所有類型
  • 支持UTF-8/UTF-16的輸入和輸出
  • 提供了本地java對象的序列化和反序列化的高層API
  • 提供相對合理的錯誤提示信息

使用SnakeYAML將john dump出來,如果有引用相同對象,則dump出到yaml文件會自動使用&*進行錨點和引用

DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);
//Yaml yaml = new Yaml();
String dump = yaml.dump(john);
System.out.println(dump);

內容如下:

!!Person
age: 37
children:
- age: 15
  children: null
  name: Jimmy Smith
  sponse: null
- age: 12
  children: null
  name: Jenny Smith
  sponse: null
name: John Smith
sponse:
  age: 25
  children: null
  name: Jane Smith
  sponse: null

現在用SnakeYAML把yaml load進來,如果yaml文件中使用了&*,則會自動對load出來的對象賦相同的值

Yaml yaml = new Yaml();
Object load = yaml.load(new FileInputStream(new File("jhon.yaml")));
System.out.println(load.getClass());
System.out.println(yaml.dump(load));

Yaml yaml = new Yaml(options);
Person person = yaml.loadAs(inputStream, Person.class);
System.out.println(person.getSponse().getChildren().length);

如果一個yaml文件中有多個文檔,由---分割,解析如下:

Yaml yaml = new Yaml();
        int counter = 0;
        for (Object data : yaml.loadAll(input)) {
            System.out.println(data);
            counter++;
        }

保存一個Map對象:

Map<String, Object> data = new HashMap<String, Object>();
        data.put("name", "Silenthand Olleander");
        data.put("race", "Human");
        data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" });
        Yaml yaml = new Yaml();
        String output = yaml.dump(data);
        System.out.println(output);
    // or
    StringWriter writer = new StringWriter();
    yaml.dump(data, writer);
    System.out.println(writer.toString());

將多個文檔dump出到同一個yaml文件中去:

List<Integer> docs = new LinkedList<Integer>();
    for (int i = 1; i < 4; i++) {
        docs.add(i);
    }
    DumperOptions options = new DumperOptions();
    //options.setCanonical(true);
    options.explicitStart(true);
    Yaml yaml = new Yaml(options);
    System.out.println(yaml.dump(docs));
    System.out.println(yaml.dumpAll(docs.iterator()));
--- [1, 2, 3]

--- 1
--- 2
--- 3

YAML與java類型對照表:

YAML JAVA
!null null
!!bool Boolean
!!int Integer, Long, BigInteger
!!float Double
!!binary String
!!timestamp java.util.Date, java.sql.Date, java.sql.Timestamp
!!omap, !!pairs List of Object[]
!!set Set
!!str String
!!seq List
!!map Map

集合的默認實現是:

  • List:  ArrayList
  • Map:  LinkedHashMap

使用JYaml實現

JYaml(最新版本是2007年的,可以考慮放棄了),使用JYaml把Jone “Dump” 出來:

    File dumpfile = new File("John_dump.yaml");
    Yaml.dump(john, dumpfile);

下面我們看看John_dump.yaml是什麼樣子:

--- !yaml.test.internal.Person
age: 37
children: !yaml.test.internal.Person[]
  - !yaml.test.internal.Person
    age: 15
    name: Jimmy Smith
  - !yaml.test.internal.Person
    age: 12
    name: Jenny Smith
name: John Smith
sponse: !yaml.test.internal.Person
  age: 25
  name: Jane Smith

其中!yaml.test.internal.Person是一些類型的信息。load的時候需要用。
現在用JYaml把Jone_dump.yaml load進來:

    Person john2 = (Person) Yaml.loadType(dumpfile, Person.class);

還可以用下面的代碼dump出沒有類型信息的John.yaml:

Yaml.dump(john,dumpfile, true);

我們再來看看JYaml對流處理的支持,爲簡便起見,我們只是把同一個john寫10次:

    YamlEncoder enc = new YamlEncoder(new FileOutputStream(dumpfile));
    for(int i=0; i<10; i++){
        john.setAge(37+i);
        enc.writeObject(john);
        enc.flush();
    }
   enc.close();

下面再把這十個對象一個一個讀出來(注意while循環退出的方式):

   YamlDecoder dec = new YamlDecoder(new FileInputStream(dumpfile));
   int age = 37;
   while(true){
       try{
           john = (Person) dec.readObject();
           assertEquals(age, john.getAge());
           age++;
       }catch(EOFException eofe){
           break;
       }
   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章