2015-02-27

在 Eclipse 為 Tomcat Library 增加原始碼附件

在專案目錄下選擇 [Java Resources]->[Libraries]->[Apache Tomcat]->[xxx.jar]->[右鍵內容]


Java 程式碼附件 就可以連接程式原始碼


Javadoc 位置 則可以連接 Javadoc



其實剛剛的設定都記錄在:
{WorkSpace}/.metadata/.plugins/org.eclipse.jst.server.core/
org.eclipse.jst.server.tomcat.runtimeTarget.xml


內容如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<classpath>
  <source-attachment entry="D:/apache-tomcat-7.0.53/lib/commons-io-2.4.jar" runtime-id="Apache Tomcat v7.0" source-attachment-path="D:/apache-tomcat-7.0.53/src/commons-io-2.4-sources.jar">
    <attribute name="javadoc_location" value="jar:file:/D:/apache-tomcat-7.0.53/src/commons-io-2.4-javadoc.jar!/"/>
  </source-attachment>
  <source-attachment entry="D:/apache-tomcat-7.0.53/lib/commons-logging-1.1.3.jar" runtime-id="Apache Tomcat v7.0" source-attachment-path="D:/apache-tomcat-7.0.53/src/commons-logging-1.1.3-sources.jar">
    <attribute name="javadoc_location" value="jar:file:/D:/apache-tomcat-7.0.53/src/commons-logging-1.1.3-javadoc.jar!/"/>
  </source-attachment>
</classpath>


所以只要編寫 org.eclipse.jst.server.tomcat.runtimeTarget.xml 的內容,重新啟動 Eclipse 就會連結原始碼附件,當然這麼麻煩的事還是寫程式自動處理比較快樂,既然是用 Eclipse 寫 Java 當然是用 Ant 來處理是最好的。

首先幾個環境定義:
  • Server Src 路徑:D:/apache-tomcat-7.0.53/src
  • Server Lib 路徑:D:/apache-tomcat-7.0.53/lib
  • Runtime Id:Apache Tomcat v7.0
  • Jar 名稱:xxxxx.jar
  • Source 名稱:xxxxx-sources.jar
  • Javadoc 名稱:xxxxx-javadoc.jar


tomcat-xml-build.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE project>
<project name="tomcat-xml-build" default="deploy-output-xml" basedir=".">

    <!-- 載入額外 ant-contrib 定義  [if,not,or,then,equals] -->
    <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>

    <property name="tomcat.id" value="Apache Tomcat v7.0" />
    <property name="tomcat.home" value="D:/apache-tomcat-7.0.53" />
    <property name="output.file" value="org.eclipse.jst.server.tomcat.runtimeTarget.xml" />
    <basename property="ant.filename" file="${ant.file}" />


    <path id="tomcat.lib">
        <fileset dir="${tomcat.home}/lib" includes="*.jar" />
    </path>


    <target name="build-xml">
        <echo>====================================================================</echo>
        <echo>開始建立 ${output.file}</echo>

        <echo file="${output.file}"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
<classpath>
<!-- Code Generate from "${ant.filename}" --></echo>

        <foreach target="loop-jar-file" param="jar-file">
            <path refid="tomcat.lib"/>
        </foreach>

        <echo file="${output.file}" append="true">
</classpath></echo>

        <echo>====================================================================</echo>
        <echo>完成產生 ${output.file}</echo>
    </target>


    <target name="loop-jar-file" if="jar-file">
        <echo level="info">${jar-file}</echo>

        <basename property="jar-name" file="${jar-file}" suffix=".jar"/>
        <if>
            <or>
                <available file="${tomcat.home}/src/${jar-name}-sources.jar"/>
                <available file="${tomcat.home}/src/${jar-name}-javadoc.jar"/>
            </or>
            <then>
                <echo>輸出  ${jar-name}</echo>
                <echo file="${output.file}" append="true">
  <source-attachment entry="${tomcat.home}/lib/${jar-name}.jar" runtime-id="${tomcat.id}" source-attachment-path="${tomcat.home}/src/${jar-name}-sources.jar">
    <attribute name="javadoc_location" value="jar:file://${tomcat.home}/src/${jar-name}-javadoc.jar!/"/>
  </source-attachment></echo>
            </then>
        </if>
    </target>



    <target name="deploy-output-xml" depends="build-xml">
        <echo>====================================================================</echo>
        <echo>部署 ${output.file} 到 .metadata</echo>

        <eclipse.convertPath resourcepath="workspace_loc:/" property="workspace_loc"/>
        <copy file="${output.file}" todir="${workspace_loc}/.metadata/.plugins/org.eclipse.jst.server.core" />
    </target>


    <target name="show-properties">
        <echoproperties />
    </target>

</project>


執行前需要 ant-contrib,然後啟動配置必須是[在工作區的同一個 JRE 中執行],這是因為用到 Eclipse Ant 所以提供的環境變數,執行完後重新啟動 Eclipse 就可以看到連結的附件。

在 Eclipse 安裝 ant-contrib

先下載 ant-contrib,然後放置到
{eclipse_home}/plugins/org.apache.ant_XXXXXXX/lib

接者在 [視窗]->[喜好設定]->[Ant 執行時期]->[類別路徑]->[Ant 起始目錄項目]->[新增外部 JAR]->[選擇 ant-contrib.jar]


然後在要使用 ant-contrib 的 build.xml 中加入以下宣告:
<taskdef resource="net/sf/antcontrib/antcontrib.properties"/>

2015-02-26

[轉載] Java 內部類別

轉載自:Java Gossip: 內部類別

可以在類別中再定義類別,稱之為內部類別(Inner class),初學者暫時不會使用到內部類別,在這邊先簡單介紹語法,雖然會無聊一些,不過之後章節就會看到相關應用。

內部類別可以定義在類別區塊之中。例如以下程式片段建立了非靜態的內部類別:

class Some {
    class Other {
    }
}

雖然實務上很少看到接下來的寫法,不過要使用 Some 中的 Other 類別,必須先建立實例 Some 實例。例如:

Some s = new Some();
Some.Other o =  s.new Other();

內部類別也可以使用 publicprotectedprivate 宣告。例如:

class Some {
    private class Other {
    }
}

內部類別本身可以存取外部類別的成員,通常非靜態內部類別會宣告為 private,這類內部類別是輔助類別中某些操作而設計,外部不用知道內部類別的存在。

內部類別也可以宣告為 static。例如:

class Some {
    static class Other {
    }
}

一個被宣告為 static 的內部類別,通常是將外部類別當作名稱空間。你可以如下建立類別實例:

Some.Other o = new Some.Other();

被宣告為 static 的內部類別,雖然將外部類別當作名稱空間,但算是個獨立類別,它可以存取外部類別 static 成員,但不可存取外部類別非 static 成員。例如:


方法中也可以宣告類別,這通常是輔助方法中演算之用,方法外無法使用。例如:

class Some {
    public void doSome() {
        class Other {
        }
    }
}

實務上比較少看到在方法中定義具名的內部類別,倒很常看到方法中定義匿名內部類別(Anonymous inner class)並直接實例化,這跟類別繼承或介面實作有關,以下先看一下語法,細節留到談到繼承與介面時再作討論:

Object o = new Object() {
    public String toString() {
        return "無聊的語法示範而已";
    }
};

如果要稍微解釋一下,這個語法定義了一個沒有名稱的類別,它繼承 Object 類別,並重新定義(Override)了 toString() 方法,new 表示實例化這個沒有名稱的類別。匿名類別語法本身,在某些場合有時有些囉嗦,JDK 8 提出了 Lambda,有一部份目的是用來解決匿名類別語法囉嗦的問題,之後會再討論。

[轉載] Java 動態代理

轉載自:Java Essence: 動態代理

來看一個最簡單的例子,當您需要在執行某些方法時留下日誌訊息,直覺的,您可能會如下撰寫:
package cc.openhome;

import java.util.logging.*;

public class HelloSpeaker {
    private Logger logger =
            Logger.getLogger(this.getClass().getName());

    public void hello(String name) {
        // 方法執行開始時留下日誌
        logger.log(Level.INFO, "hello method starts....");
        // 程式主要功能
        System.out.println("Hello, " + name);
        // 方法執行完畢前留下日誌
        logger.log(Level.INFO, "hello method ends....");
    }
}


HelloSpeaker 類別中,當執行 hello() 方法時,你希望該方法執行開始與執行完畢時都能留下日誌,最簡單的作法就是如以上的程式設計,在 方法執行的前後加上日誌動作,然而記錄的這幾行程式碼橫切入(Cross-cutting)HelloSpeaker 類別中,對於 HelloSpeaker 來說,日誌的這幾個動作並不屬於 HelloSpeaker 商務邏輯(顯示"Hello"等文字),這使得 HelloSpeaker 增加了額外的職責。

想想如果程式中這種日誌的動作到處都有需求,以上的寫法勢必造成你必須到處撰寫這些日誌動作的程式碼,這將使得維護日誌程式碼的困難度加大。如果需要的服 務(Service)不只有日誌動作,有一些非物件本身職責的相關動作也混入了物件之中(例如權限檢查、交易管理等等),會使得物件的負擔更形加重,甚至 混淆了物件本身該負有的職責,物件本身的職責所佔的程式碼,或許還小於這些與物件職責不相關的動作或服務的程式碼。

另一方面,使用以上的寫法,若你有一日不再需要日誌(或權限檢查、交易管理等)的服務,那麼你將需要修改所有留下日誌動作的程式碼,你無法簡單的就將這些相關服務從即有的程式中移去。

可以使用代理(Proxy)機制來解決這個問題,在這邊討論兩種代理方式:
  • 靜態代理(Static proxy)
  • 動態代理(Dynamic proxy)

在靜態代理的實現中,代理物件與被代理的物件都必須實現同一個介面,在代理物件中可以實現記錄等相關服務,並在需要的時候再呼叫被代理的物件,如此被代理物件當中就可以僅保留商務相關職責。

舉個實際的例子來說,首先定義一個 IHello 介面:
IHello.java
package cc.openhome;

public interface IHello {
    public void hello(String name);
}


然後讓實現商務邏輯的 HelloSpeaker 類別要實現 IHello 介面,例如:
HelloSpeaker.java
package cc.openhome;

public class HelloSpeaker implements IHello {
    public void hello(String name) {
        System.out.println("Hello, " + name);
    }
}


可以看到,在 HelloSpeaker 類別中現在沒有任何日誌的程式碼插入其中,日誌服務的實現將被放至代理物件之中,代理物件同樣也要實現 IHello 介面,例如:
HelloProxy.java
package cc.openhome;

import java.util.logging.*;

public class HelloProxy implements IHello {
    private Logger logger =
            Logger.getLogger(this.getClass().getName());

    private IHello helloObject;

    public HelloProxy(IHello helloObject) {
        this.helloObject = helloObject;
    }

    public void hello(String name) {
        // 日誌服務
        log("hello method starts....");

        // 執行商務邏輯
        helloObject.hello(name);

        // 日誌服務
        log("hello method ends....");
    }

    private void log(String msg) {
        logger.log(Level.INFO, msg);
    }
}


HelloProxy 類別的 hello() 方法中,真正實現商務邏輯前後可以安排記錄服務,可以實際撰寫一個測試程式來看看如何使用代理物件。
ProxyDemo.java
package cc.openhome;

public class ProxyDemo {
    public static void main(String[] args) {
        IHello proxy =
            new HelloProxy(new HelloSpeaker());
        proxy.hello("Justin");
    }
}


程式中呼叫執行的是代理物件,建構代理物件時必須給它一個被代理物件,記得在操作取回的代理物件時,必須轉換操作介面為 IHello 介面。

代理物件 HelloProxy 將代理真正的 HelloSpeaker 來執行 hello(),並在其前後加上日誌的動作,這使得我們的 HelloSpeaker 在撰寫時不必介入日誌動作,HelloSpeaker 可以專心於它的職責。

在 JDK 1.3 之後加入了可協助開發動態代理功能的 API 等相關類別,您不必為特定物件與方法撰寫特定的代理物件,使用動態代理,可以使得一個處理者 (Handler)服務於各個物件,首先,一個處理者的類別設計必須實作 java.lang.reflect.InvocationHandler 介面, 以實例來進行說明,例如設計一個 LogHandler 類別:
LogHandler.java
package cc.openhome;

import java.util.logging.*;
import java.lang.reflect.*;

public class LogHandler implements InvocationHandler {
    private Logger logger =
            Logger.getLogger(this.getClass().getName());

    private Object delegate;

    public Object bind(Object delegate) {
        this.delegate = delegate;
        return Proxy.newProxyInstance(
            delegate.getClass().getClassLoader(),
            delegate.getClass().getInterfaces(),
            this
        );
    }

    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable 
    {
        Object result = null;

        try {
            log("method starts..." + method);

            result = method.invoke(delegate, args);

            logger.log(Level.INFO, "method ends..." + method);
        } catch (Exception e){
            log(e.toString());
        }

        return result;
    }

    private void log(String message) {
        logger.log(Level.INFO, message);
    }
}


主要的概念是使用 Proxy.newProxyInstance() 靜態方法建立一個代理物件(底層會使用 Native 的方式生成代理物件的 Class 實例),建立代理物件時必須告知所要代理的介面,之後您可以操作所 建立的代理物件,在每次操作時會呼叫 InvocationHandlerinvoke() 方法,invoke() 方法會傳入被代理物件的方法名稱與執行 參數,實際上要執行的方法交由 method.invoke(),您在 method.invoke() 前後加上記錄動作,method.invoke() 傳 回的物件是實際方法執行過後的回傳結果。

接下來撰寫一個測試的程式,您要使用 LogHandlerbind() 方法來綁定被代理物件,如下所示:
ProxyDemo.java
package cc.openhome;

public class ProxyDemo {
    public static void main(String[] args) {
        LogHandler logHandler  = new LogHandler();

        IHello helloProxy =
            (IHello) logHandler.bind(new HelloSpeaker());

        helloProxy.hello("Justin");
    }
}

[轉載] Java 型態通配字元

轉載自:Java Essence: 我只收這種東西

如果你定義了以下的類別:
class Node<T> {
    T value;
    Node<T> next;

    Node(T value, Node<T> next) {
        this.value = value;
        this.next = next;
    }
}


如果在以下的例子中:
class Fruit {}
class Apple extends Fruit {
    @Override
    public String toString() {
        return "Apple";
    }
}

class Banana extends Fruit {
    @Override
    public String toString() {
        return "Banana";
    }
}


public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<Fruit> fruit = apple;  // 編譯錯誤,incompatible types
    }
}



在範例中,apple 的型態是 Node<Apple>,而 fruit 的型態為 Node<Fruit>,你將 apple 所參考的物件 給 fruit 參考,那麼 Node<Apple> 該是一種 Node<Fruit> 呢?在上例中編譯器給你的答案為「不是」!

如 果 B 是 A 的子型態,而 Node<B> 被視為一種 Node<A> 型態,則稱 Node 具有共變性(Covariance)或有彈性的(flexible)。如 果 Node<A> 被視為一種 Node<B> 型態,則稱 Node 具有逆變性(Contravariance)。如果不具共變性或逆變性,則 Node 是不可變 的(nonvariant)嚴謹的(rigid)

Java 的泛型不支援共變性,不過可以使用型態通配字元 ?extends 來宣告變數,使其達到類似共變性,例如:
public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<? extends Fruit> fruit = apple; // 類似共變性效果
    }
}


一個實際應用的例子是:
public class Main {
    public static void main(String[] args) {
        Node<Apple> apple1 = new Node<Apple>(new Apple(), null);
        Node<Apple> apple2 = new Node<Apple>(new Apple(), apple1);
        Node<Apple> apple3 = new Node<Apple>(new Apple(), apple2);

        Node<Banana> banana1 = new Node<Banana>(new Banana(), null);
        Node<Banana> banana2 = new Node<Banana>(new Banana(), banana1);

        show(apple3);
        show(banana2);
    }

    static void show(Node<? extends Fruit> n) {
        Node<? extends Fruit> node = n;
        do {
            System.out.println(node.value);
            node = node.next;
        } while(node != null);
    }
}



你的目的是可以顯示所有的水果節點,由於 show() 方法使用型態通配字元宣告參數,使得 n 具備類似共變性的效果,因此 show() 方法就可以顯示 Node<Apple> 也可以顯示 Node<Banana>

Java 的泛型亦不支援逆變性,不過可以使用型態通配字元 ?super 來宣告變數,使其達到類似逆變性,例如:
public class Main {
    public static void main(String[] args) {
        Node<Fruit> fruit = new Node<Fruit>(new Fruit(), null);
        Node<? super Apple> apple = fruit;
        Node<? super Banana> banana = fruit;
    }
}


一個實際應用的例子如下:
class Fruit {
    int price;
    int weight;
    Fruit(int price, int weight) {
        this.price = price;
        this.weight = weight;
    }
}

class Apple extends Fruit {
     Apple(int price, int weight) {
         super(price, weight);
     }
}

class Banana extends Fruit {
     Banana(int price, int weight) {
         super(price, weight);
     }
}

interface Comparator<T> {
    int compare(T t1, T t2);
}

class Basket<T> {
    private T[] things;
    Basket(T... things) {
        this.things = things;
    }
    void sort(Comparator<? super T> comparator) {
        // 作一些排序
    }
}


籃子(Basket)中可以置放各種物品,並可以傳入一個比較器(Comparator)進行排序。假設你分別在兩個籃子中放置了蘋果(Apple)與香蕉(Banana):
public class Main {
    public static void main(String[] args) {
        Comparator<Fruit> comparator = new Comparator<Fruit>() {
            public int compare(Fruit f1, Fruit f2) {
                return f1.price - f2.price;
            }
        };
        Basket<Apple> b1 = new Basket<Apple>(
            new Apple(20, 100), new Apple(25, 150)
        );
        Basket<Banana> b2 = new Basket<Banana>(
            new Banana(30, 200), new Banana(25, 250)
        );
        b1.sort(comparator);
        b2.sort(comparator);
    }
}


現在 b1 的型態為 Basket<Apple>,而 b2 的型態為 Basket<Banana>,你可以如上實作一個水果(Fruit)比較器,比較水果的價格進行排序,這可以同時適用於 Basket<Apple>Basket<Banana>

[PHP][Java][C#] 用 XSD 驗證 XML

menu_config.xml 要驗證的 XML
<?xml version="1.0" encoding="utf-8"?>
<menu_config>
    <menu title="文章管理" url="~/Article" target="" allow="">
        <submenu title="列表" url="~/Article/list" target="" allow=""/>
        <submenu/>
        <submenu title="新增" url="~/Article/add" target="" allow=""/>
    </menu>
    <menu/>
    <menu title="帳號管理" url="~/Admin"/>
</menu_config>


menu_config.xsd 結構定義
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

    <xs:element name="menu_config">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="menu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="menuType">
        <xs:sequence>
            <xs:element name="submenu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
        </xs:sequence>
        <xs:attribute name="title" type="xs:string"/>
        <xs:attribute name="url" type="xs:string"/>
        <xs:attribute name="target" type="xs:string"/>
        <xs:attribute name="allow" type="xs:string"/>
    </xs:complexType>

</xs:schema>
element minOccurs
最少的出現次數,不設置為至少出現 1 次,設置 0 為可有可無。
element maxOccurs
最大的出現次數,設置 unbounded 為無上限。
attribute type
型別必須為 QName,常用的有 xs:string, xs:date, xs:int, xs:integer, xs:decimal, xs:boolean, xs:double, xs:float。


PHP
$xmlFile = 'menu_config.xml';
$xsdFile = 'menu_config.xsd';

/* 啟用自行錯誤處裡 */
libxml_use_internal_errors(true);

$xml = new DOMDocument();
$xml->load($xmlFile);

if (!$xml->schemaValidate($xsdFile)) {
    /* 顯示錯誤訊息 */
    print_r(libxml_get_errors());
    libxml_clear_errors();
}


C#
var menuConfig = XDocument.Load("menu_config.xml");
schemas.Add("", XmlReader.Create("menu_config.xsd"));

menuConfig.Validate(schemas, (o, e) => {
    e.Message.Dump();
});


Java
import java.io.File;
import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;


public class TestXsd {

    public static void main(String[] args) {
        File xsd = new File("menu_config.xsd");
        File xml = new File("menu_config.xml");

        try {
            SchemaFactory factory = SchemaFactory.newInstance(
                XMLConstants.W3C_XML_SCHEMA_NS_URI
            );
            Schema schema = factory.newSchema(xsd);
            Validator validator = schema.newValidator();

            validator.validate(new StreamSource(xml));
            System.out.println("xml is valid");
        }
        catch (SAXException|IOException e) {
            System.out.println("Reason: " + e.getLocalizedMessage());
        }
    }
}



參考資料:
XML 結構描述項目 - MSDN
XML Schema Tutorial W3Schools

[Java] 列出 HttpServletRequest header

Enumeration<String> names = request.getHeaderNames();

for (String name : Collections.list(names)) {
    System.out.println(name + " : " + request.getHeader(name));
}

[Java] ipToLong 與 longToIp

public class IpToLong {

    public static long ipToLong(String ip) {
        long result = 0;
        String[] numbers = (ip + ".0.0.0").split("\\.");

        for (int i = 0; i < 4; i++) {
            result = result << 8;
            result |= Long.parseLong(numbers[i]) & 0xFF;
        }

        return result;
    }


    public static String longToIp(long ip) {
        return (ip >> 24 & 0xFF) + "." +
               (ip >> 16 & 0xFF) + "." +
               (ip >> 8 & 0xFF) + "." +
               (ip & 0xFF);
    }



    public static void main(String[] args) {

        String[] testList = {
            "0.0.0.0",
            "255.255.255.255",
            "192.168.1.2"
        };

        for (String ip : testList) {
            long ipNum = ipToLong(ip);
            System.out.printf(
                "%s | %s | %s\n", ip, longToIp(ipNum), ipNum
            );
        }
    }
}

Output:
0.0.0.0 | 0.0.0.0 | 0
255.255.255.255 | 255.255.255.255 | 4294967295
192.168.1.2 | 192.168.1.2 | 3232235778

[Java] 取得 Client IP

public String getClientIp(HttpServletRequest request){
    String ip = request.getHeader("X-FORWARDED-FOR");

    if (ip == null) {
        ip = request.getRemoteAddr();
    }

    return ip;
}

[Java] array, List, Set, Map 排序

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class TestSort {

    public static void main(String[] args) {

        /* sort array */
        String[] array1 = new String[] {
            "Pineapple", "Apple", "Orange", "Banana"
        };
        Arrays.sort(array1);

        for (String value : array1) {
            System.out.printf("%s, ", value);
        }
        System.out.println("\n");



        /* reverse sort array */
        String[] array2 = new String[] {
            "Pineapple", "Apple", "Orange", "Banana"
        };
        Arrays.sort(array2, Collections.reverseOrder());

        for (String value : array2) {
            System.out.printf("%s, ", value);
        }
        System.out.println("\n");



        /* sort list */
        List<String> list1 = Arrays.asList(
            "Pineapple","Apple", "Orange", "Banana"
        );
        Collections.sort(list1);

        for (String value : list1) {
            System.out.printf("%s, ", value);
        }
        System.out.println("\n");



        /* reverse sort list */
        List<String> list2 = Arrays.asList(
            "Pineapple","Apple", "Orange", "Banana"
        );
        Collections.sort(list2, Collections.reverseOrder());

        for (String value : list2) {
            System.out.printf("%s, ", value);
        }
        System.out.println("\n");



        /* sort set */
        Set<String> set = new HashSet<>();
        set.add("Pineapple");
        set.add("Apple");
        set.add("Orange");
        set.add("Banana");

        Set<String> treeSet1 = new TreeSet<>(set);

        for (String value : treeSet1) {
            System.out.printf("%s, ", value);
        }
        System.out.println("\n");



        /* reverse sort set */
        Set<String> treeSet2 = new TreeSet<>(Collections.reverseOrder());
        treeSet2.addAll(set);

        for (String value : list2) {
            System.out.printf("%s, ", value);
        }
        System.out.println("\n");



        /* sort map */
        Map<String, String> map = new HashMap<>();
        map.put("Pineapple","z");
        map.put("Apple", "g");
        map.put("Orange", "a");
        map.put("Banana", "f");

        Map<String, String> treeMap1 = new TreeMap<>(map);

        for (Map.Entry<String, String> entry : treeMap1.entrySet()) {
            System.out.printf(
                "Key : %s Value : %s \n",
                entry.getKey(), entry.getValue()
            );
        }
        System.out.println();



        /* reverse sort map */
        Map<String, String> treeMap2 = 
            new TreeMap<>(Collections.reverseOrder());
        treeMap2.putAll(map);

        for (Map.Entry<String, String> entry : treeMap2.entrySet()) {
            System.out.printf("Key : %s Value : %s \n",
                entry.getKey(), entry.getValue()
            );
        }
        System.out.println();

    }
}

[SVN] 取得最新的版號日期

命令列指令:
svn info

Path: .
URL: https://xxxxxxxxxxxxxxxxxxxx
Repository Root: https://xxxxxxxxxxxxxxxxxxxx
Repository UUID: 7be9c518-46a6-4cff-bfa7-8a37a304b21d
Revision: 78
Node Kind: directory
Schedule: normal
Last Changed Author: weskerjax
Last Changed Rev: 78
Last Changed Date: 2015-01-09 23:48:14 +0800 (週五, 09 一月 2015)


輸出 XML 格式:
svn info --xml

<?xml version="1.0"?>
<info>
  <entry kind="dir" path="." revision="78">
    <url>https://xxxxxxxxxxxxxxxxxxxxxx</url>
    <repository>
      <root>https://xxxxxxxxxxxxxxxxxxxxxx</root>
      <uuid>7be9c518-46a6-4cff-bfa7-8a37a304b21d</uuid>
    </repository>
    <wc-info>
      <schedule>normal</schedule>
      <depth>infinity</depth>
    </wc-info>
    <commit revision="78">
      <author>weskerjax</author>
      <date>2015-01-09T15:48:14.049424Z</date>
    </commit>
  </entry>
</info>

2015-02-24

[Git] 取得最新的版號日期

git log -1 --date=short --format='%cd %h'
# 2015-02-13 e396510

git log -1 --date=short --format=%cd
# 2015-02-13

git log -1 --format=%h
# e396510

-1
只顯示第一筆 (筆數)

--date=short
短的日期格式

--format=%cd
commit date

--format=%h
短版的 commit hash

參考資料:git-log(1) Manual Page

[Java] 取得檔案編碼格式

使用 Mozilla juniversalchardet 套件

import java.io.FileInputStream;
import org.mozilla.universalchardet.UniversalDetector;

public class ReadFileCharset {

    public static void main(String[] args) throws Exception {

        /* 讀取檔案 */
        FileInputStream fis = new FileInputStream("input.txt");

        /* 建立分析器 */
        UniversalDetector detector = new UniversalDetector(null);

        int nread;
        byte[] buf = new byte[4096];
        while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
            /* 分析資料 */
            detector.handleData(buf, 0, nread);
        }
        fis.close();

        detector.dataEnd();

        /* 取得編碼格式 */
        String encode = detector.getDetectedCharset();
        System.out.println(encode);
    }
}

[Java] InputStream to OutputStream

Stream Read / Write
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class StreamCopy1 {

    public static void main(String[] args) throws Exception {

        InputStream input = new FileInputStream("input.txt");
        OutputStream output = new FileOutputStream("output.txt");

        int byteValue;
        while ((byteValue = input.read()) != -1) {
            output.write(byteValue);
        }

        input.close();
        output.close();
    }
}


Apache IOUtils.copy
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;

public class StreamCopy2 {

    public static void main(String[] args) throws Exception {

        InputStream input = new FileInputStream("input.txt");
        OutputStream output = new FileOutputStream("output.txt");

        IOUtils.copy(input, output);

        input.close();
        output.close();
    }
}

2015-02-23

[轉載] Java 字符串格式化:String.format()方法的使用

轉載自:java字符串格式化:String.format()方法的使用 - kgd1120 - ITeye技术网站

常規類型的格式化


String 類的 format() 方法用於創建格式化的字符串以及連接多個字符串對像。熟悉 C 語言的讀者應該記得 C 語言的 sprintf() 方法,兩者有類似之處。format() 方法有兩種重載形式。

format(String format, Object... args)
該方法使用指定的字符串格式和參數生成格式化的新字符串。 新字符串始終使用本地語言環境。例如當前日期信息在中國語言環境中的表現形式為 “2007-10-27”,但是在其他國家有不同的表現形式。
  • format:字符串格式。
  • args...:字符串格式中由格式說明符引用的參數。如果還有格式說明符以外的參數,則忽略這些額外的參數。參數的數目是可變的,可以為 0。

format(Locale locale, String format, Object... args)
該方法使用指定的語言環境、字符串格式和參數生成一個格式化的新字符串。新字符串始終使用指定的語言環境。
  • locale:指定的語言環境。
  • format:字符串格式。
  • args...:字符串格式中由格式說明符引用的參數。如果還有格式說明符以外的參數,則忽略這些額外的參數。參數的數目是可變的,可以為 0。

format() 方法中的字符串格式參數有很多種轉換符選項,例如:日期、整數、浮點數等:
轉換符說明示例
%s字符串類型"mingrisoft"
%c字符類型'm'
%b布爾類型true
%d整數類型(十進制)99
%x整數類型(十六進制)FF
%o整數類型(八進制)77
%f浮點類型99.99
%a十六進制浮點類型FF.35AE
%e指數類型9.38e+5
%g通用浮點類型(f和e類型中較短的)
%h散列碼
%%百分比類型%
%n換行符
%tx日期與時間類型(x代表不同的日期與時間轉換符)


使用各種轉換符實現不同數據類型到字符串的轉換:
public static void main(String[] args) {

    String str=null;

    // 格式化字符串
    str=String.format("Hi,%s", "飛龍");

    // 輸出字符串變量str的內容
    System.out.println(str);

    System.out.printf("字母a的大寫是:%c %n", 'A');

    System.out.printf("3>7的結果是:%b %n", 3>7);

    System.out.printf("100的一半是:%d %n", 100/2);

    System.out.printf("100的16進制數是:%x %n", 100);

    System.out.printf("100的8進制數是:%o %n", 100);

    System.out.printf("50元的書打8.5折扣是:%f 元%n", 50*0.85);

    System.out.printf("上面價格的16進制數是:%a %n", 50*0.85);

    System.out.printf("上面價格的指數表示:%e %n", 50*0.85);

    System.out.printf(
        "上面價格的指數和浮點數結果的長度較短的是:%g %n", 50*0.85
    );

    System.out.printf("上面的折扣是%d%% %n", 85);

    System.out.printf("字母A的散列碼是:%h %n", 'A');
}

輸出以下結果:
Hi,飛龍

字母a的大寫是:A

3>7的結果是:false

100的一半是:50

100的16進制數是:64

100的8進制數是:144

50元的書打8.5折扣是:42.500000 元

上面價格的16進制數是:0x1.54p5

上面價格的指數表示:4.250000e+01

上面價格的指數和浮點數結果的長度較短的是:42.5000

上面的折扣是85%

字母A的散列碼是:41


這些字符串格式參數不但可以靈活將其他數據類型轉換成字符串,而且可以與各種標誌組合在一起,生成各種格式的字符串:
標誌說明示例結果
+為正數或者負數添加符號("%+d",15)+15
左對齊("%-5d",15)|15 |
0數字前面補0("%04d", 99)0099
空格在整數之前添加指定數量的空格("% 4d", 99)| 99|
,以“,”對數字分組("%,f", 9999.99)9,999.990000
(使用括號包含負數("%(f", -99.99)(99.990000)
#如果是浮點數則包含小數點,
如果是16進制或8進制則添加0x或0
("%#x", 99)
("%#o", 99)
0x63
0143
<格式化前一個轉換符所描述的參數("%f 和 %<3.2f", 99.45)99.450000 和 99.45
$被格式化的參數索引("%1$d,%2$s", 99,"abc")99,abc


使用幾種常用的轉換符組合標誌實現字符串的格式化:
public static void main(String[] args) {

    String str=null;

    // 格式化字符串
    str=String.format("格式參數$的使用:%1$d,%2$s", 99,"abc");

    // 輸出字符串變量
    System.out.println(str);

    System.out.printf("顯示正負數的符號:%+d與%d%n", 99,-99);

    System.out.printf("最牛的編號是:%03d%n", 7);

    System.out.printf("Tab鍵的效果是:% 8d%n", 7);

    System.out.printf("整數分組的效果是:%,d%n", 9989997);

    System.out.printf("一本書的價格是:%2.2f元%n", 49.8);
}

輸出以下結果:
格式參數$的使用:99,abc

顯示正負數的符號:+99與-99

最牛的編號是:007

Tab鍵的效果是:       7

整數分組的效果是:9,989,997

一本書的價格是:49.80元




日期和時間字符串格式化


在程序界面中經常需要顯示時間和日期,但是其顯示的 格式經常不盡人意,需要編寫大量的代碼經過各種算法才得到理想的日期與時間格式。字符串格式中還有%tx轉換符沒有詳細介紹,它是專門用來格式化日期和時 間的。%tx轉換符中的x代表另外的處理日期和時間格式的轉換符,它們的組合能夠將日期和時間格式化成多種格式。


常見日期時間格式化

格式化日期與時間的轉換符定義了各種格式化日期字符串的方式,其中最常用的日期和時間的組合格式如下:
轉換符說明示例
c包括全部日期和時間信息星期六 十月 27 14:21:20 CST 2007
F“年-月-日”格式2007-10-27
D“月/日/年”格式10/27/07
r“HH:MM:SS PM”格式(12時制)02:25:51 下午
T“HH:MM:SS”格式(24時制)14:28:16
R“HH:MM”格式(24時制)14:28


使用轉換符格式化當前日期和時間:
public static void main(String[] args) {

    // 創建日期對像
    Date date=new Date();

    // 格式化輸出日期或時間
    System.out.printf("全部日期和時間信息:%tc%n",date);

    System.out.printf("年-月-日格式:%tF%n",date);

    System.out.printf("月/日/年格式:%tD%n",date);

    System.out.printf("HH:MM:SS PM格式(12時制):%tr%n",date);

    System.out.printf("HH:MM:SS格式(24時制):%tT%n",date);

    System.out.printf("HH:MM格式(24時制):%tR",date);

    System.out.printf("年-月-日 HH:MM:SS格式:%1$tF %1$tT%n",date);
}

輸出以下結果:
全部日期和時間信息:星期日十月28 13:53:24 CST 2007

年-月-日格式:2007-10-28

月/日/年格式:10/28/07

HH:MM:SS PM格式(12時制):01:53:24 下午

HH:MM:SS格式(24時制):13:53:24

HH:MM格式(24時制):13:53

年-月-日 HH:MM:SS格式:2007-10-28 13:53:24


格式化日期字符串

定義日期格式的轉換符可以使日期通過指定的轉換符生成新字符串:
轉換符說明示例
bh月份簡稱中:十月
英:Oct
B月份全稱中:十月
英:October
a星期的簡稱中:星期六
英:Sat
A星期的全稱中:星期六
英:Saturday
C年的前兩位數字(前補0至2位)20
y年的後兩位數字(前補0至2位)07
Y4位數字的年份(前補0至4位)2007
j一年中的天數(即年的第幾天)300
m兩位數字的月份(前補0至2位)10
d兩位數字的日(前補0至2位)27
e月份的日5


使用各種轉換符格式化當前系統的日期:
public static void main(String[] args) {

    // 創建日期對像
    Date date=new Date();

    // 格式化日期字符串
    String str=String.format(Locale.US,"英文月份簡稱:%tb",date);

    // 輸出字符串內容
    System.out.println(str);

    System.out.printf("本地月份簡稱:%tb%n",date);

    str=String.format(Locale.US,"英文月份全稱:%tB",date);

    System.out.println(str);

    System.out.printf("本地月份全稱:%tB%n",date);

    str=String.format(Locale.US,"英文星期的簡稱:%ta",date);

    System.out.println(str);

    System.out.printf("本地星期的簡稱:%tA%n",date);

    System.out.printf("年的前兩位數字(前補0至2位):%tC%n",date);

    System.out.printf("年的後兩位數字(前補0至2位):%ty%n",date);

    System.out.printf("一年中的天數(即年的第幾天):%tj%n",date);

    System.out.printf("兩位數字的月份(前補0至2位):%tm%n",date);

    System.out.printf("兩位數字的日(前補0至2位):%td%n",date);

    System.out.printf("月份的日:%te",date);
}

輸出以下結果:
英文月份簡稱:Oct

本地月份簡稱:十月

英文月份全稱:October

本地月份全稱:十月

英文星期的簡稱:Sun

本地星期的簡稱:星期日

年的前兩位數字(前補0至2位):20

年的後兩位數字(前補0至2位):07

一年中的天數(即年的第幾天):301

兩位數字的月份(前補0至2位):10

兩位數字的日(前補0至2位):28

月份的日:28


格式化時間字符串

和日期格式轉換符相比,時間格式的轉換符要更多、更精確。它可以將時間格式化成時、分、秒甚至時毫秒等單位:
轉換符說明示例
H2位數字24時制的小時(前補0至2位)15
I2位數字12時制的小時(前補0至2位)03
k2位數字24時制的小時15
l2位數字12時制的小時3
M2位數字的分鐘(前補0至2位)03
S2位數字的秒(前補0至2位)09
L3位數字的毫秒(前補0至3位)015
N9位數字的毫秒數(前補0至9位)562000000
p小寫字母的上午或下午標記中:下午
英:pm
z相對於GMT的RFC822時區的偏移量+0800
Z時區縮寫字符串CST
s1970-1-1 00:00:00 到現在所經過的秒數1193468128
Q1970-1-1 00:00:00 到現在所經過的毫秒數1193468128984


使用各種轉換符格式化當前系統的時間:
public static void main(String[] args) {

    // 創建日期對像
    Date date=new Date();

    System.out.printf("2位數字24時制的小時(前補0至2位):%tH%n",date);

    System.out.printf("2位數字12時制的小時(前補0至2位):%tI%n",date);

    System.out.printf("2位數字24時制的小時:%tk%n",date);

    System.out.printf("2位數字12時制的小時:%tl%n",date);

    System.out.printf("2位數字的分鐘(前補0至2位):%tM%n",date);

    System.out.printf("2位數字的秒(前補0至2位):%tS%n",date);

    System.out.printf("3位數字的毫秒(前補0至3位):%tL%n",date);

    System.out.printf("9位數字的毫秒數(前補0至9位):%tN%n",date);

    String str=String.format(
        Locale.US,"小寫字母的上午或下午標記(英):%tp",date
    );

    // 輸出字符串變量str的內容
    System.out.println(str);

    System.out.printf ("小寫字母的上午或下午標記(中):%tp%n",date);

    System.out.printf("相對於GMT的RFC822時區的偏移量:%tz%n",date);

    System.out.printf("時區縮寫字符串:%tZ%n",date);

    System.out.printf("1970-1-1 00:00:00 到現在所經過的秒數:%ts%n",date);

    System.out.printf("1970-1-1 00:00:00 到現在所經過的毫秒數:%tQ%n",date);
}

輸出以下結果:
2位數字24時制的小時(前補0至2位):15

2位數字12時制的小時(前補0至2位):03

2位數字24時制的小時:15

2位數字12時制的小時:3

2位數字的分鐘(前補0至2位):24

2位數字的秒(前補0至2位):56

3位數字的毫秒(前補0至3位):828

9位數字的毫秒數(前補0至9位):828000000

小寫字母的上午或下午標記(英):pm

小寫字母的上午或下午標記(中):下午

相對於GMT的RFC822時區的偏移量:+0800

時區縮寫字符串:CST

1970-1-1 00:00:00到現在所經過的秒數:1193556296

1970-1-1 00:00:00到現在所經過的毫秒數:1193556296828

2015-02-22

[轉載] Java Gossip: wait()、notify()

轉載自:Java Gossip: wait()、notify()

wait()notify()notifyAll() 是由 Object 所提供的方法,您在定義自己的類別時會繼承下來(記得 Java 中所有的物件最頂層都繼承自 Object),wait()notify() notifyAll() 都被宣告為 "final",所以您無法重新定義它們,透過這三個方法您可以控制執行緒是否為 Runnable 狀態。

您必須在同步化的方法或區塊中呼叫 wait() 方法,當物件的 wait() 方法被調用,目前的執行緒會被放入物件的「等待集合」(Wait set)中, 執行緒會釋放物件的鎖定,其它的執行緒可以競爭鎖定,取得鎖定的執行緒可以執行同步化區塊;被放在等待集中的執行緒將不參與執行緒的排班,wait() 可 以指定等待的時間,如果指定時間的話,則時間到之後執行緒會再度加入排班,如果指定時間 0 或不指定,則執行緒會持續等待,直到有被中斷(interrupt)或是被告知(notify)可以參與排班。

當物件的 notify() 被調用,它會從物件的等待集中選出「一個」執行緒加入排班,被選出的執行緒是隨機的,被選出的執行緒會與其它正在執行的執行緒共 同競爭對物件的鎖定;如果您呼叫 notifyAll(),則「所有」在等待集中的執行緒都會被喚醒,這些執行緒會與其它正在執行的執行緒共同競爭對物件的 鎖定。

簡單的說,當執行緒呼叫到物件的 wait() 方法時,表示它要先讓出物件的被同步區使用權並等待通知,或是等待一段指定的時間,直到被通知或時間到時再從 等待點開始執行,這就好比您要叫某人作事,作到一半時某人叫您等候通知(或等候1分鐘之類的),當您被通知(或時間到時)某人會繼續為您服務。

說明 wait()notify()notifyAll() 的應用最常見的一個例子,就是生產者(Producer)消費者(Consumer)的 例子,如果生產者會將產品交給店員,而消費者從店員處取走產品,店員一次只能持有固定數量產品,如果生產者生產了過多的產品,店員叫生產者等一下 (wait),如果店中有空位放產品了再通知(notify)生產者繼續生產,如果店中沒有產品了,店員會告訴消費者等一下(wait),如果店中有產品 了再通知(notify)消費者來取走產品。


以下舉一個最簡單的:生產者每次生產一個 int 整數交給在店員上,而消費者從店員處取走整數,店員一次只能持有一個整數。


以程式實例來看,首先是生產者:
Producer.java
package onlyfun.caterpillar;

public class Producer implements Runnable {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println("生產者開始生產整數......");

        // 生產1到10的整數
        for(int product = 1; product <= 10; product++) {
            try {
                // 暫停隨機時間
                Thread.sleep((int) (Math.random() * 3000));
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }

            // 將產品交給店員
            clerk.setProduct(product);
        }
    }
}


再來是消費者:
Consumer.java
package onlyfun.caterpillar;

public class Consumer implements Runnable {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println("消費者開始消耗整數......");

        // 消耗10個整數
        for(int i = 1; i <= 10; i++) {
            try {
                // 等待隨機時間
                Thread.sleep((int) (Math.random() * 3000));
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }

            // 從店員處取走整數
            clerk.getProduct();
        }
    }
}


生產者將產品放至店員,而消費者從店員處取走產品,所以店員來決定誰必須等待並等候通知。
Clerk.java
package onlyfun.caterpillar;

public class Clerk {
    // -1 表示目前沒有產品
    private int product = -1;

    // 這個方法由生產者呼叫
    public synchronized void setProduct(int product) {
        while(this.product != -1) {
            try {
                // 目前店員沒有空間收產品,請稍候!
                wait();
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.product = product;
        System.out.printf("生產者設定 (%d)%n", this.product);

        // 通知等待區中的一個消費者可以繼續工作了
        notify();
    }

    // 這個方法由消費者呼叫
    public synchronized int getProduct() {
        while(this.product == -1) {
            try {
                // 缺貨了,請稍候!
                wait();
            }
            catch(InterruptedException e) {
                e.printStackTrace();
            }
        }

        int p = this.product;
        System.out.printf(
                  "消費者取走 (%d)%n", this.product);
        this.product = -1;

        // 通知等待區中的一個生產者可以繼續工作了
        notify();

        return p;
    }
}


根據 規格書中所說明 ,執行緒也有可能在未經 notify()interrupt() 或逾時的情況下自動甦醒(spurious wakeup),雖然這種情況實務上很少發生,但應用程式應考量這種情況,你必須持續檢測這種情況,因而 wait() 必須總是在迴圈中執行,例如:
synchronized (obj) {
    while (執行條件不成立時)
        obj.wait(timeout);
    ... // 執行一些動作進行判斷
}


使用這麼一個程式來測試:
WaitNotifyDemo.java
package onlyfun.caterpillar;

public class WaitNotifyDemo {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Thread producerThread = new Thread(
            new Producer(clerk)
        );
        Thread consumerThread = new Thread(
            new Consumer(clerk)
        );

        producerThread.start();
        consumerThread.start();
    }
}


執行結果:
生產者開始生產整數......
消費者開始消耗整數......
生產者設定 (1)
消費者取走 (1)
生產者設定 (2)
消費者取走 (2)
生產者設定 (3)
消費者取走 (3)
生產者設定 (4)
消費者取走 (4)
生產者設定 (5)
消費者取走 (5)
生產者設定 (6)
消費者取走 (6)
生產者設定 (7)
消費者取走 (7)
生產者設定 (8)
消費者取走 (8)
生產者設定 (9)
消費者取走 (9)
生產者設定 (10)
消費者取走 (10)


生產者會生產10個整數,而消費者會消耗10個整數,由於店員處只能放置一個整數,所以每生產一個就消耗一個,其結果如上所示是無誤的。

如果一個執行緒進入物件的等待集中,您可以中斷它的等待,這時將會發生InterruptedException例外物件,interrupt()方法可用來進行這項工作。

[轉載] Java 類靜態域,非靜態域,構造函數的初始化順序

轉載自:java类静态域、块,非静态域、块,构造函数的初始化顺序 - 艾妮 - ITeye技术网站

面試的時候,經常會遇到這樣的考題:給你兩個類的程式,它們之間是繼承的關系,每個類裡只有建構子方法和一些變數,建構子裡可能還有一段程式對變數值進行了某種運算,另外還有一些將變數值輸出到控制台的程式,然後讓我們判斷輸出的
結果。這實際上是在考查我們對於繼承情況下類的初始化順序的了解。

我們大家都知道,對於靜態變數、靜態初始化、變數、初始化、建構子,它們的初始化順序以此是:
(靜態變數、靜態初始化) > (變數、初始化) > 建構子

我們也可以通過下面的測試程式來驗證這一點:
public class InitialOrderTest {
    // 靜態變數
    public static String staticField = "靜態變數";

    // 變數
    public String field = "變數";

    // 靜態初始化
    static {
        System.out.println(staticField);
        System.out.println("靜態初始化");
    }

    // 初始化
    {
        System.out.println(field);
        System.out.println("初始化");
    }

    // 建構子
    public InitialOrderTest() {
        System.out.println("建構子");
    }

    public static void main(String[] args) {
        new InitialOrderTest();
    }
}

運行以上程式,我們會得到如下的輸出結果:
靜態變數
靜態初始化
變數
初始化
建構子

這與上文中說的完全符合。



那麼對於繼承情況下又會怎樣呢?我們仍然以一段測試程式來獲取最終結果:
class Parent {
    // 靜態變數
    public static String p_StaticField = "父--靜態變數";

    // 變數
    public String p_Field = "父--變數";

    // 靜態初始化
    static {
        System.out.println(p_StaticField);
        System.out.println("父--靜態初始化");
    }

    // 初始化
    {
        System.out.println(p_Field);
        System.out.println("父--初始化");
    }
    // 建構子
    public Parent() {
        System.out.println("父--建構子");
    }
}


public class SubClass extends Parent {

    // 靜態變數
    public static String s_StaticField = "子--靜態變數";

    // 變數
    public String s_Field = "子--變數";

    // 靜態初始化
    static {
        System.out.println(s_StaticField);
        System.out.println("子--靜態初始化");
    }

    // 初始化
    {
        System.out.println(s_Field);
        System.out.println("子--初始化");
    }

    // 建構子
    public SubClass() {
        System.out.println("子--建構子");
    }


    public static void main(String[] args) {
        new SubClass();
    }
}

運行一下上面的程式,結果馬上呈現在我們的眼前:
父--靜態變數
父--靜態初始化
子--靜態變數
子--靜態初始化
父--變數
父--初始化
父--建構子
子--變數
子--初始化
子--建構子

現在,結果已經不言自明了。大家可能會注意到一點,那就是,並不是父類完全初始化完畢後才進行子類的初始化,實際上子類的靜態變數和靜態初始化的初始化是在父類的變數、初始化和建構子初始化之前就完成了。那麼對於靜態變數和靜態初始化之間、變數和初始化之間的先後順序又是怎樣呢?是否靜態變數總是先於靜態初始化,變數總是先於初始化就被初始化了呢?實際上這取決於它們在類中出現的先後順序。



我們以靜態變數和靜態初始化為例來進行說明。 同樣,我們還是寫一個類來進行測試:
public class TestOrder {
    // 靜態變數
    public static TestA a = new TestA();

    // 靜態初始化
    static {
        System.out.println("靜態初始化");
    }

    // 靜態變數
    public static TestB b = new TestB();

    public static void main(String[] args) {
        new TestOrder();
    }
}

class TestA {
    public TestA() {
        System.out.println("TestA");
    }
}

class TestB {
    public TestB() {
        System.out.println("TestB");
    }
}

運行上面的程式,會得到如下的結果:
TestA
靜態初始化
TestB

大家可以隨意改變變數 a、變數 b 以及靜態初始化的前後位置,就會發現輸出結果隨著它們在類中出現的前後順序而改變,這就說明靜態變數和靜態初始化是依照他們在類中的定義順序進行初始化的。同樣,變數和初始化也遵循這個規律。了解了繼承情況下類的初始化順序之後,如何判斷最終輸出結果就迎刃而解了。



測試函數:
public class TestStaticCon {
    public static int a = 0;

    static {
        a = 10;
        System.out.println("靜態初始化 a = " + a);
    }

    {
        a = 8;
        System.out.println("初始化 a = " + a);
    }

    public TestStaticCon() {
        this(a);
        System.out.println("無參數建構子 a = " + a);
    }
    public TestStaticCon(int n) {
        System.out.println("參數建構子 n = " + n);
        System.out.println("參數建構子 a = " + a);
    }


    public static void main(String[] args) {
        new TestStaticCon();
    }
}

運行結果:
靜態初始化 a = 10
初始化 a = 8
參數建構子 n = 10
參數建構子 a = 8
無參數建構子 a = 8


結論:
靜態初始化是在類加載時自動執行的,非靜態初始化是在創建對像時自動執行的程式,不創建對像不執行該類的非靜態初始化。且執行順序為
靜態初始化 -> 非靜態初始化 -> 建構子


擴展( 靜態初始化 與 靜態方法 ):
一般情況下,如果有些程式必須在項目啟動的時候就執行的時候,需要使用靜態初始化,這種程式是主動執行的。
需要在項目啟動的時候就初始化,在不創建對像的情況下,其他程序來調用的時候,需要使用靜態方法,這種程式是被動執行的。
兩者的區別就是:靜態初始化是自動執行的。靜態方法是被調用的時候才執行的。


作用:
靜態初始化可用來初始化一些項目最常用的變數或類別靜態方法可用作不創建物件也可能需要執行的程式。



阿裡筆試題:
public class Test1 {

    public static int k = 0;
    public static Test1 t1 = new Test1("obj t1");
    public static Test1 t2 = new Test1("obj t2");
    public static int i = print("var I");
    public static int n = 99;
    public int j = print("var J");

    static{
        print("static");
    }

    {
        print("init");
    }

    public Test1(String str){
        print(str);
        ++i;
        ++n;
    }

    public static int print(String str){
        k++;
        System.out.printf("%-3s: %-8s i=%-4d n=%d", k, str, i, n);
        ++n;
        return ++i;
    }

    public static void main(String[] args) {
        new Test1("main");
    }
}

運行結果:
1  : var J    i=0    n=0
2  : init     i=1    n=1
3  : obj t1   i=2    n=2
4  : var J    i=4    n=4
5  : init     i=5    n=5
6  : obj t2   i=6    n=6
7  : var I    i=8    n=8
8  : static   i=9    n=99
9  : var J    i=10   n=100
10 : init     i=11   n=101
11 : main     i=12   n=102

[Java] URL 筆記

Request URL
http://user:passwd@30thh.loc:8480/test%3F+b;?p+1=c+d&p+2=e+f#ref

MethodResult
getAuthority()user:passwd@30thh.loc:8480
getDefaultPort()80
getFile()/test%3F+b;?p+1=c+d&p+2=e+f
getHost()30thh.loc
getPath()/test%3F+b;
getPort()8480
getProtocol()http
getQuery()p+1=c+d&p+2=e+f
getRef()ref
getUserInfo()user:passwd
toString()http://user:passwd@30thh.loc:8480/test%3F+b;?p+1=c+d&p+2=e+f#ref

URL base1 = new URL("http://localhost:8090/home/");

System.out.println(new URL(base1,"Service.json"));
// http://localhost:8090/home/Service.json

System.out.println(new URL(base1,"/Service.json"));
// http://localhost:8090/Service.json

System.out.println(new URL(base1,"./Service.json"));
// http://localhost:8090/home/Service.json


URL base2 = new URL("http://localhost:8090/home/index");

System.out.println(new URL(base2,"Service.json"));
// http://localhost:8090/home/Service.json

System.out.println(new URL(base2,"/Service.json"));
// http://localhost:8090/Service.json

System.out.println(new URL(base2,"./Service.json"));
// http://localhost:8090/home/Service.json

[Java] 讀取全部的檔案內容到字串

Scanner
import java.io.File;
import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) throws Throwable {
        Scanner sc = new Scanner(new File("input.txt"), "UTF8");
        String inputStr = sc.useDelimiter("\\Z").next();
        sc.close();

        System.out.println(inputStr);
    }
}
useDelimiter 是設置讀取的正規表示式分隔符,這裡可以用 "\\A" 或 "\\Z"
\A : 輸入的開始。
\Z : 輸入的結尾去掉結尾符號的部分。


Java 7 readAllBytes
import static java.nio.file.Files.readAllBytes;
import static java.nio.file.Paths.get;

public class TestReadAllBytes {

    public static void main(String[] args) throws Exception {
        String inputStr = new String(readAllBytes(get("input.txt")), "UTF8");
        System.out.println(inputStr);
    }
}


Apache Commons IO
import java.io.File;

import org.apache.commons.io.FileUtils;

public class TestReadFileToString {

    public static void main(String[] args) throws Throwable {
        File file = new File("input.txt");
        String inputStr = FileUtils.readFileToString(file, "UTF8");
        System.out.println(inputStr);
    }
}


DataInputStream available
import java.io.DataInputStream;
import java.io.FileInputStream;

public class TestDataInputStream {

    public static void main(String[] args) throws Exception {

        FileInputStream fileStream = new FileInputStream ("input.txt");
        DataInputStream dis = new DataInputStream (fileStream);

        byte[] datainBytes = new byte[dis.available()];
        dis.readFully(datainBytes);
        dis.close();

        String inputStr = new String(datainBytes, "UTF8");
        System.out.println(inputStr);
    }
}

[Java] 檔案讀寫

FileReader & FileWriter
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class FileReadWrite1 {

    public static void main(String[] args) throws Exception {

        FileReader fReader = new FileReader("input.txt");
        FileWriter fWriter = new FileWriter("output.txt");

        /* Buffered 提供單行讀取的功能 */
        BufferedReader bReader = new BufferedReader(fReader);
        BufferedWriter bWriter = new BufferedWriter(fWriter);

        String line;
        while ((line = bReader.readLine()) != null) {
            System.out.println(line);
            bWriter.write(line);
            bWriter.newLine();
        }

        bReader.close();
        bWriter.close();
    }
}


InputStreamReader & OutputStreamWriter
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class FileReadWrite2 {

    public static void main(String[] args) throws Exception {

        InputStreamReader fReader = new InputStreamReader(
            new FileInputStream("input.txt"), "UTF8"
        );
        OutputStreamWriter fWriter = new OutputStreamWriter(
            new FileOutputStream("output.txt"), "UTF8"
        );

        /* Buffered 提供單行讀取的功能 */
        BufferedReader bReader = new BufferedReader(fReader);
        BufferedWriter bWriter = new BufferedWriter(fWriter);

        String line;
        while ((line = bReader.readLine()) != null) {
            System.out.println(line);
            bWriter.write(line);
            bWriter.newLine();
        }

        bReader.close();
        bWriter.close();
    }
}

2015-02-21

[轉載] Java中常用的加密方法

轉載自:Java中常用的加密方法(JDK) - Java - language - ITeye论坛

加密,是以某種特殊的演算法改變原有的信息數據,使得未授權的用戶即使獲得了已加密的信息,但因不知解密的方法,仍然無法了解信息的內容。大體上分為雙向加密和單向加密,而雙向加密又分為對稱加密和非對稱加密(有些資料將加密直接分為對稱加密和非對稱加密)。

雙向加密大體意思就是明文加密後形成密文,可以通過演算法還原成明文。而單向加密只是對信息進行了摘要計算,不能通過演算法生成明文,單向加密從嚴格意思上說不能算是加密的一種,應該算是摘要演算法吧。具體區分可以參考:
(本人解釋不清呢 …… )
http://security.group.iteye.com/group/wiki/1710-one-way-encryption-algorithm


一、雙向加密


(一)、對稱加密


采用單鑰密碼系統的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。
需要對加密和解密使用相同密鑰的加密演算法。由於其速度,對稱性加密通常在消息發送方需要加密大量數據時使用。對稱性加密也稱為密鑰加密。
所謂對稱,就是采用這種加密方法的雙方使用方式用同樣的密鑰進行加密和解密。密鑰是控制加密及解密過程的指令。

演算法是一組規則,規定如何進行加密和解密。因此對稱式加密本身不是安全的。   
常用的對稱加密有:DES、IDEA、RC2、RC4、SKIPJACK、RC5、AES 演算法等

對稱加密一般 Java 類中中定義成員
// KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
private KeyGenerator keygen;
// SecretKey 負責保存對稱密鑰
private SecretKey deskey;
// Cipher負責完成加密或解密工作
private Cipher c;
// 該字節數組負責保存加密的結果
private byte[] cipherByte;


在構造函數中初始化
Security.addProvider(new com.sun.crypto.provider.SunJCE());
// 實例化支持 DES 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
keygen = KeyGenerator.getInstance("DES");//
// 生成密鑰
deskey = keygen.generateKey();
// 生成 Cipher 物件,指定其支持的 DES 演算法
c = Cipher.getInstance("DES");


1. DES
演算法為密碼體制中的對稱密碼體制,又被成為美國數據加密標准,是 1972 年美國 IBM 公司研制的對稱密碼體制加密演算法。 明文按 64 位進行分組, 密鑰長 64 位,密鑰事實上是 56 位參與 DES 運算(第 8、16、24、32、40、48、56、64 位是校驗位, 使得每個密鑰都有奇數個 1)分組後的明文組和 56 位的密鑰按位替代或交換的方法形成密文組的加密方法。

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

public class EncrypDES {

    // KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
    private KeyGenerator keygen;
    // SecretKey 負責保存對稱密鑰
    private SecretKey deskey;
    // Cipher 負責完成加密或解密工作
    private Cipher c;
    // 該字節數組負責保存加密的結果
    private byte[] cipherByte;

    public EncrypDES()
        throws NoSuchAlgorithmException, NoSuchPaddingException
    {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        // 實例化支持 DES 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
        keygen = KeyGenerator.getInstance("DES");
        // 生成密鑰
        deskey = keygen.generateKey();
        // 生成 Cipher 物件,指定其支持的 DES 演算法
        c = Cipher.getInstance("DES");
    }

    /**
     * 對字符串加密
     *
     * @param str
     * @return
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public byte[] Encrytor(String str)
        throws InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        // 根據密鑰,對 Cipher 物件進行初始化,ENCRYPT_MODE 表示加密模式
        c.init(Cipher.ENCRYPT_MODE, deskey);
        byte[] src = str.getBytes();
        // 加密,結果保存進 cipherByte
        cipherByte = c.doFinal(src);
        return cipherByte;
    }

    /**
     * 對字符串解密
     *
     * @param buff
     * @return
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public byte[] Decryptor(byte[] buff)
        throws InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        // 根據密鑰,對 Cipher 物件進行初始化,DECRYPT_MODE 表示加密模式
        c.init(Cipher.DECRYPT_MODE, deskey);
        cipherByte = c.doFinal(buff);
        return cipherByte;
    }

    /**
     * @param args
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeyException
     */
    public static void main(String[] args) throws Exception {
        EncrypDES de1 = new EncrypDES();
        String msg ="郭XX-搞笑相聲全集";
        byte[] encontent = de1.Encrytor(msg);
        byte[] decontent = de1.Decryptor(encontent);
        System.out.println("明文是:" + msg);
        System.out.println("加密後:" + new String(encontent));
        System.out.println("解密後:" + new String(decontent));
    }
}


2. 3DES
又稱 Triple DES,是 DES 加密演算法的一種模式,它使用 3 條 56 位的密鑰對 3DES
數據進行三次加密。數據加密標准(DES)是美國的一種由來已久的加密標准,它使用對稱密鑰加密法,並於 1981 年被 ANSI 組織規範為 ANSI X.3.92。DES 使用 56 位密鑰和密碼塊的方法,而在密碼塊的方法中,文本被分成 64 位大小的文本塊然後再進行加密。比起最初的 DES,3DES 更為安全。   
3DES(即Triple DES)是 DES 向 AES 過渡的加密演算法(1999年,NIST 將 3-DES 指定為過渡的加密標准),是 DES 的一個更安全的變形。它以 DES 為基本模塊,通過組合分組方法設計出分組加密演算法,其具體實現如下:
設 Ek() 和 Dk() 代表 DES 演算法的加密和解密過程,K 代表 DES 演算法使用的密鑰,P 代表明文,C 代表密文,

3DES 加密過程為:C=Ek3(Dk2(Ek1(P)))
3DES 解密過程為:P=Dk1((EK2(Dk3(C)))

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

public class EncrypDES3 {

    // KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
    private KeyGenerator keygen;
    // SecretKey 負責保存對稱密鑰
    private SecretKey deskey;
    // Cipher 負責完成加密或解密工作
    private Cipher c;
    // 該字節數組負責保存加密的結果
    private byte[] cipherByte;

    public EncrypDES3()
        throws NoSuchAlgorithmException, NoSuchPaddingException
    {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        // 實例化支持 DESede 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
        keygen = KeyGenerator.getInstance("DESede");
        // 生成密鑰
        deskey = keygen.generateKey();
        // 生成 Cipher 物件,指定其支持的 DESede 演算法
        c = Cipher.getInstance("DESede");
    }

    /**
     * 對字符串加密
     *
     * @param str
     * @return
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public byte[] Encrytor(String str)
        throws InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        // 根據密鑰,對 Cipher 物件進行初始化,ENCRYPT_MODE 表示加密模式
        c.init(Cipher.ENCRYPT_MODE, deskey);
        byte[] src = str.getBytes();
        // 加密,結果保存進 cipherByte
        cipherByte = c.doFinal(src);
        return cipherByte;
    }

    /**
     * 對字符串解密
     *
     * @param buff
     * @return
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public byte[] Decryptor(byte[] buff)
        throws InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        // 根據密鑰,對 Cipher 物件進行初始化,DECRYPT_MODE 表示加密模式
        c.init(Cipher.DECRYPT_MODE, deskey);
        cipherByte = c.doFinal(buff);
        return cipherByte;
    }

    /**
     * @param args
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeyException
     */
    public static void main(String[] args) throws Exception {
        EncrypDES3 des = new EncrypDES3();
        String msg ="郭XX-搞笑相聲全集";
        byte[] encontent = des.Encrytor(msg);
        byte[] decontent = des.Decryptor(encontent);
        System.out.println("明文是:" + msg);
        System.out.println("加密後:" + new String(encontent));
        System.out.println("解密後:" + new String(decontent));
    }
}


3. AES
密碼學中的高級加密標准(Advanced Encryption Standard,AES),又稱 高級加密標准
Rijndael 加密法,是美國聯邦政府采用的一種區塊加密標准。這個標准用來替代原先的 DES,已經被多方分析且廣為全世界所使用。經過五年的甄選流程,高級加密標准由美國國家標准與技術研究院(NIST)於 2001 年 11 月 26 日發布於 FIPS PUB 197,並在 2002 年 5 月 26 日成為有效的標准。2006 年高級加密標准已然成為對稱密鑰加密中最流行的演算法之一。
該演算法為比利時密碼學家 Joan Daemen 和 Vincent Rijmen 所設計,結合兩位作者的名字,以 Rijndael 之命名之,投稿高級加密標准的甄選流程。(Rijdael 的發音近於 "Rhinedoll")

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

public class EncrypAES {

    // KeyGenerator 提供對稱密鑰生成器的功能,支持各種演算法
    private KeyGenerator keygen;
    // SecretKey 負責保存對稱密鑰
    private SecretKey deskey;
    // Cipher 負責完成加密或解密工作
    private Cipher c;
    //該字節數組負責保存加密的結果
    private byte[] cipherByte;

    public EncrypAES()
        throws NoSuchAlgorithmException, NoSuchPaddingException
    {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        // 實例化支持 AES 演算法的密鑰生成器(演算法名稱命名需按規定,否則拋出異常)
        keygen = KeyGenerator.getInstance("AES");
        //生成密鑰
        deskey = keygen.generateKey();
        //生成 Cipher 物件,指定其支持的 AES 演算法
        c = Cipher.getInstance("AES");
    }

    /**
     * 對字符串加密
     *
     * @param str
     * @return
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public byte[] Encrytor(String str)
        throws InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        // 根據密鑰,對 Cipher 物件進行初始化,ENCRYPT_MODE 表示加密模式
        c.init(Cipher.ENCRYPT_MODE, deskey);
        byte[] src = str.getBytes();
        // 加密,結果保存進 cipherByte
        cipherByte = c.doFinal(src);
        return cipherByte;
    }

    /**
     * 對字符串解密
     *
     * @param buff
     * @return
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public byte[] Decryptor(byte[] buff)
        throws InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        // 根據密鑰,對 Cipher 物件進行初始化,DECRYPT_MODE 表示加密模式
        c.init(Cipher.DECRYPT_MODE, deskey);
        cipherByte = c.doFinal(buff);
        return cipherByte;
    }

    /**
     * @param args
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeyException
     */
    public static void main(String[] args) throws Exception {
        EncrypAES de1 = new EncrypAES();
        String msg ="郭XX-搞笑相聲全集";
        byte[] encontent = de1.Encrytor(msg);
        byte[] decontent = de1.Decryptor(encontent);
        System.out.println("明文是:" + msg);
        System.out.println("加密後:" + new String(encontent));
        System.out.println("解密後:" + new String(decontent));
    }
}


(二)、非對稱加密


1976 年,美國學者 Dime 和 Henman 為解決信息公開傳送和密鑰管理問題,提出一種新的密鑰交換協議,允許在不安全的媒體上的通訊雙方交換信息,安全地達成一致的密鑰,這就是“公開密鑰系統”。相對於“對稱加密演算法”這種方法也叫做“非對稱加密演算法”。 與對稱加密演算法不同,非對稱加密演算法需要兩個密鑰:公開密鑰(publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密;如果用私有密鑰對數據進行加密,那麼只有用對應的公開密鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種演算法叫作非對稱加密演算法。

1. RSA
公鑰加密演算法是 1977 年由 Ron Rivest、Adi Shamirh 和 LenAdleman 在(美國麻省理工學院)開發的。RSA 取名來自開發他們三者的名字。RSA 是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前為止已知的所有密碼攻擊,已被 ISO 推薦為公鑰數據加密標准。RSA 演算法基於一個十分簡單的數論事實:將兩個大素數相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。

import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

public class EncrypRSA {

    /**
     * 加密
     * @param publicKey
     * @param srcBytes
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    protected byte[] encrypt(RSAPublicKey publicKey,byte[] srcBytes)
        throws NoSuchAlgorithmException, NoSuchPaddingException,
               InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        if(publicKey == null){ return null; }

        // Cipher 負責完成加密或解密工作,基於 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 根據公鑰,對 Cipher 物件進行初始化
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] resultBytes = cipher.doFinal(srcBytes);
        return resultBytes;
    }

    /**
     * 解密
     * @param privateKey
     * @param srcBytes
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    protected byte[] decrypt(RSAPrivateKey privateKey,byte[] srcBytes)
        throws NoSuchAlgorithmException, NoSuchPaddingException,
               InvalidKeyException, IllegalBlockSizeException,
               BadPaddingException
    {
        if(privateKey == null){ return null; }

        // Cipher負責完成加密或解密工作,基於RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 根據公鑰,對 Cipher 物件進行初始化
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] resultBytes = cipher.doFinal(srcBytes);
        return resultBytes;
    }

    /**
     * @param args
     * @throws NoSuchAlgorithmException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     */
    public static void main(String[] args)
        throws NoSuchAlgorithmException, InvalidKeyException,
               NoSuchPaddingException, IllegalBlockSizeException,
               BadPaddingException
    {
        EncrypRSA rsa = new EncrypRSA();
        String msg = "郭XX-精品相聲";

        // KeyPairGenerator 類用於生成公鑰和私鑰對,基於 RSA 演算法生成物件
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        // 初始化密鑰對生成器,密鑰大小為 1024 位
        keyPairGen.initialize(1024);
        // 生成一個密鑰對,保存在 keyPair 中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私鑰
        RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
        // 得到公鑰
        RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();

        // 用公鑰加密
        byte[] srcBytes = msg.getBytes();
        byte[] resultBytes = rsa.encrypt(publicKey, srcBytes);

        // 用私鑰解密
        byte[] decBytes = rsa.decrypt(privateKey, resultBytes);

        System.out.println("明文是:" + msg);
        System.out.println("加密後是:" + new String(resultBytes));
        System.out.println("解密後是:" + new String(decBytes));
    }

}


2. DSA
Digital Signature Algorithm (DSA)是 Schnorr 和 ElGamal 簽名演算法的變種,被美國 NIST 作為 DSS(DigitalSignature Standard)。(感覺有點復雜,沒有附代碼)
詳見http://63938525.iteye.com/blog/1051565



(三)、題外話 MySQL加密解密函數


MySQL有兩個函數來支持這種類型的加密,分別叫做 ENCODE() 和 DECODE()。
下面是一個簡單的實例:
mysql> INSERT INTO users (username,password) VALUES ('joe',ENCODE('guessme','abr'));

Query OK, 1 row affected (0.14 sec)

其中,Joe 的密碼是 guessme,它通過密鑰 abracadabra 被加密。要注意的是,加密完的結果是一個二進制字符串,如下所示:

提示:雖然 ENCODE() 和DECODE() 這兩個函數能夠滿足大多數的要求,但是有的時候您希望使用強度更高的加密手段。在這種情況下,您可以使用 AES_ENCRYPT() 和 AES_DECRYPT() 函數,它們的工作方式是相同的,但是加密強度更高。

單向加密與雙向加密不同,一旦數據被加密就沒有辦法顛倒這一過程。因此密碼的驗證包括對用戶輸入內容的重新加密,並將它與保存的密文進行比對,看是否匹配。一種簡單的單向加密方式是MD5校驗碼。MySQL 的 MD5() 函數會為您的數據創建一個“指紋”並將它保存起來,供驗證測試使用。下面就是如何使用它的一個簡單例子:
mysql> INSERT INTO users (username,password) VALUES ('joe',MD5('guessme'));

Query OK, 1 row affected (0.00 sec)



或者,您考慮一下使用 ENCRYPT() 函數,它使用系統底層的 crypt() 系統調用來完成加密。這個函數有兩個參數:一個是要被加密的字符串,另一個是雙(或者多)字符的“salt”。它然後會用 salt 加密字符串;這個 salt 然後可以被用來再次加密用戶輸入的內容,並將它與先前加密的字符串進行比對。下面一個例子說明了如何使用它:
mysql> INSERT INTO users (username,password) VALUES('joe', ENCRYPT('guessme','ab'));

Query OK, 1 row affected (0.00 sec)

PS:ENCRYPT() 只能用在 UNIX、LINIX 系統上,因為它需要用到底層的 crypt() 庫。




二、單向加密(信息摘要)


Java 一般需要獲取物件 MessageDigest 來實現單項加密(信息摘要)。

1. MD5
即 Message-Digest Algorithm 5(信息-摘要演算法 5),用於確保信息傳輸完整一致。是計算機廣泛使用的雜湊演算法之一(又譯摘要演算法、哈希演算法),主流編程語言普遍已有MD5實現。將數據(如漢字)運算為另一固定長度值,是雜湊演算法的基礎原理,MD5 的前身有 MD2、MD3 和 MD4。MD5 的作用是讓大容量信息在用數字簽名軟件簽署私人密鑰前被"壓縮"成一種保密的格式(就是把一個任意長度的字節串變換成一定長的十六進制數字串)。
除了 MD5 以外,其中比較有名的還有 sha-1、RIPEMD 以及 Haval 等

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class EncrypMD5 {

    public byte[] eccrypt(String info) throws
        NoSuchAlgorithmException
    {
        // 根據 MD5 演算法生成 MessageDigest 物件
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] srcBytes = info.getBytes();
        // 使用 srcBytes 更新摘要
        md5.update(srcBytes);
        // 完成哈希計算,得到 result
        byte[] resultBytes = md5.digest();
        return resultBytes;
    }

    public static void main(String args[]) throws
        NoSuchAlgorithmException
    {
        String msg = "郭XX-精品相聲技術";
        EncrypMD5 md5 = new EncrypMD5();
        byte[] resultBytes = md5.eccrypt(msg);

        System.out.println("密文是:" + new String(resultBytes));
        System.out.println("明文是:" + msg);
    }
}


2. SHA
是一種數據加密演算法,該演算法經過加密專家多年來的發展和改進已日益完善,現在已成為公認的最安全的散列演算法之一,並被廣泛使用。該演算法的思想是接收一段明文,然後以一種不可逆的方式將它轉換成一段(通常更小)密文,也可以簡單的理解為取一串輸入碼(稱為預映射或信息),並把它們轉化為長度較短、位數固定的輸出序列即散列值(也稱為信息摘要或信息認證代碼)的過程。散列函數值可以說時對明文的一種“指紋”或是“摘要”所以對散列值的數字簽名就可以視為對此明文的數字簽名。

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class EncrypSHA {

    public byte[] eccrypt(String info) throws
        NoSuchAlgorithmException
    {
        MessageDigest sha = MessageDigest.getInstance("SHA");
        byte[] srcBytes = info.getBytes();
        // 使用 srcBytes 更新摘要
        sha.update(srcBytes);
        // 完成哈希計算,得到 result
        byte[] resultBytes = sha.digest();
        return resultBytes;
    }

    /**
     * @param args
     * @throws NoSuchAlgorithmException
     */
    public static void main(String[] args) throws
        NoSuchAlgorithmException
    {
        String msg = "郭XX-精品相聲技術";
        EncrypSHA sha = new EncrypSHA();
        byte[] resultBytes = sha.eccrypt(msg);
        System.out.println("明文是:" + msg);
        System.out.println("密文是:" + new String(resultBytes));

    }
}


附件中是以上幾種的源代碼,附帶額外的兩種使用方式。

增加一種關於文件的哈希演算法源代碼:

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;

public class FileHashUtil {

    public static final char[] hexChar = {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };
    public static final String[] hashTypes = new String[] {
        "MD2", "MD5", "SHA1", "SHA-256", "SHA-384", "SHA-512"
    };

    public void MD5File(String fileName) throws Exception{
        //String fileName = args[0];
        System.out.println("需要獲取 hash 的文件為: " + fileName);

        java.util.List<MessageDigest> mds = new java.util.ArrayList<>();
        for (String hashType : hashTypes) {
            MessageDigest md = MessageDigest.getInstance(hashType);
            mds.add(md);
        }

        InputStream fis = null;
        try {
            fis = new FileInputStream(fileName);
            byte[] buffer = new byte[1024];
            int numRead = 0;
            while ((numRead = fis.read(buffer)) > 0) {
                for (MessageDigest md : mds) {
                    md.update(buffer, 0, numRead);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (fis != null) { fis.close(); }
        }

        for (MessageDigest md : mds) {
            System.out.println(
                md.getAlgorithm() +
                " == " +
                toHexString(md.digest())
            );
        }
    }


    public static void main(String[] args) throws Exception {
        String[] fileName = new String[] {
            "D:/hapfish/ShellFolder.java",
            "D:/hapfish/ShellFolder - 副本.java",
            "E:/ShellFolder - 副本.java",
            "E:/ShellFolder.txt",
            "D:/hapfish/ShellFolder.jpg",
            "E:/ShellFolder增加字符.txt",
            "D:/hapfish/birosoft.jar"
        };

        FileHashUtil files  = new FileHashUtil();
        for(int i=0;i<fileName.length;i++){
            files.MD5File(fileName[i]);
        }
    }

    public static String toHexString(byte[] b) {
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (int i = 0; i < b.length; i++) {
            sb.append(hexChar[(b[i] & 0xf0) >>> 4]);
            sb.append(hexChar[b[i] & 0x0f]);
        }
        return sb.toString();
    }
}


運行說明
"D:/hapfish/ShellFolder.java",
"D:/hapfish/ShellFolder - 副本.java",
"E:/ShellFolder - 副本.java",
"E:/ShellFolder.txt",
"D:/hapfish/ShellFolder.jpg",
以上五個文件是同一文件經過復制、改擴展名的,最後計算哈希結果是一致的。

"E:/ShellFolder增加字符.txt" 增加了幾個字符串,就不一樣了

"D:/hapfish/birosoft.jar" 完全不相關的另外一個文件


運行結果:
需要獲取 hash 的文件為: D:/hapfish/ShellFolder.java
  MD2 == 3a755a99c5e407005cd45ebd856b4649
  MD5 == 5d08d440fa911d1e418c69a90b83cd86
  SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
  SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
  SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
  SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: D:/hapfish/ShellFolder - 副本.java
  MD2 == 3a755a99c5e407005cd45ebd856b4649
  MD5 == 5d08d440fa911d1e418c69a90b83cd86
  SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
  SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
  SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
  SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: E:/ShellFolder - 副本.java
  MD2 == 3a755a99c5e407005cd45ebd856b4649
  MD5 == 5d08d440fa911d1e418c69a90b83cd86
  SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
  SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
  SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
  SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: E:/ShellFolder.txt
  MD2 == 3a755a99c5e407005cd45ebd856b4649
  MD5 == 5d08d440fa911d1e418c69a90b83cd86
  SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
  SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
  SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
  SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: D:/hapfish/ShellFolder.jpg
  MD2 == 3a755a99c5e407005cd45ebd856b4649
  MD5 == 5d08d440fa911d1e418c69a90b83cd86
  SHA1 == 522c8c4f4ff1dd669e251c2ab854c3033a51ca63
  SHA-256 == d1feb0c73c10a759e88bd240cb9d56d0598b4ff83a0704c6679f7ba12f6c4d99
  SHA-384 == 8f8c9da4cd7241c58af3c52b49199033f2dcf3d67f421753999f87511618d9ea2d738e8c16b9b68a7572d06108ff10f6
  SHA-512 == 4711579daee3ddacbaea189310348956cb43bcaaf0099f3be047b06f16c1a20a6b71ee3a4ee018128d647e9f2ef0d644747672238e49a8da3d0cd26dfe597458
需要獲取 hash 的文件為: E:/ShellFolder增加字符.txt
  MD2 == f2717c24c6c0e110457bd17221c9ca6c
  MD5 == c49e353a7c4c26bd7ccb5e90917c230f
  SHA1 == 477c8a9e465bfaa4be42d35c032a17f7e6b42b97
  SHA-256 == 9fa18adaf242ebcdc6563922d84c2a163c82e1a24db2eb2b73978ed1f354a8a3
  SHA-384 == 4eee8f8e6d64d21c15dc01fa049f4d12a3b8e1d94d87763fe0bea75ab5ea8432fa8251289ece45ee39fe3d36b3c3020c
  SHA-512 == e852ec0ff77250be497389d2f5a1818c18bb66106b9905c4ee26fe0d256eb3b77e0ce9a28a84e4b67e4332ba37ec3aa7518148e3a682318c0fc34c391f45c201
需要獲取 hash 的文件為: D:/hapfish/birosoft.jar
  MD2 == 38c5e1404718916dec59c33cafc909b3
  MD5 == dc3e2cc4fb3949cf3660e0f5f8c3fba3
  SHA1 == cde3dc25498afc5a563af0bb0eb54dc45f71bb28
  SHA-256 == adf6a961c70c6ea677dff066fc5d896fb0beb4dd442ca0eb619ae1d1b04291e5
  SHA-384 == fe7c6b754893c53ebd82bb53703fb5cc32115c9a38f98072f73def90729b271ee3c5c78e258bd9ff5ee5476193c2178b
  SHA-512 == a15376f327256a6e049dfbdc5c2ad3a98bffccc6fa92ee01ff53db6b04471ca0f45ca28f76ff4a6911b57825afa046671299141f2499d71f1dac618c92385491


最後,把運行結果貼出來有點占空間,主要為了說明表述自己的猜想。一般來說同一哈希演算法對同一文件(鏡像、擴展名被修改)所產生的結果應該是一致的。

因此有個猜想,在 baidu 文庫、騰訊的群共享上傳時,先會判斷是否有相同文件,從某種可能上來說也采用了對文件的哈希演算法,畢竟從本地運算一個哈希演算法後獲得的數值要比把整個文件傳過去比較實惠得多。而且字符串的比較也是很方便的。

對於某一種哈希演算法,存在一種可能:就是兩個不同的文件,計算出來的哈希值可能是一樣的。當然為了保險,可以用兩種甚至更多的哈希演算法,只有在每種演算法獲得的哈希值都相同時,才能判斷是同一個文件。
如果我們也對用戶上傳的文件進行哈希計算的話,就可以節省資源,同樣的文件按理說可以減少上傳次數……

[轉載] 淺談 Java 中的 Set、List、Map 的區別

轉載自:浅谈Java中的Set、List、Map的区别 - 51CTO.COM

就學習經驗,淺談 Java 中的 Set,List,Map 的區別,對 Java 的集合的理解是想對於陣列:

陣列是大小固定的,並且同一個陣列只能存放類型一樣的數據(基本類型/引用類型),Java 集合可以存儲和操作數目不固定的一組數據。所有的 Java 集合都位於 java.util包中!Java 集合只能存放引用類型的的數據,不能存放基本數據類型。


Java 集合主要分為三種類型:
  • Set(集)
  • List(列表)
  • Map(映射)




Collection 介面 :


Collection 是最基本的集合介面,聲明了適用於 Java 集合(只包括 Set 和 List)的通用方法。Set 和 List 都繼承了 Conllection

Collection 介面的方法:
  • boolean add(Object o)向集合中加入一個物件的引用
  • void clear() 刪除集合中所有的物件,即不再持有這些物件的引用
  • boolean isEmpty() 判斷集合是否為空
  • boolean contains(Object o) 判斷集合中是否持有特定物件的引用
  • Iterartor iterator() 返回一個 Iterator 物件,可以用來遍歷集合中的元素
  • boolean remove(Object o) 從集合中刪除一個物件的引用
  • int size() 返回集合中元素的數目
  • Object[] toArray() 返回一個陣列,該陣列中包括集合中的所有元素

關於:Iterator() 和 toArray() 方法都用於集合的所有的元素,前者返回一個 Iterator 物件,後者返回一個包含集合中所有元素的陣列。

Iterator 介面聲明了如下方法:
  • hasNext() 判斷集合中元素是否遍歷完畢,如果沒有,就返回 true
  • next() 返回下一個元素
  • remove() 從集合中刪除上一個有 next() 方法返回的元素。



Set(集合):


Set 是最簡單的一種集合。集合中的物件不按特定的方式排序,並且沒有重復物件。

Set 介面主要實現了兩個實現類:
  • HashSet 按照哈希算法來存取集合中的物件,存取速度比較快。
  • TreeSet 實現了 SortedSet 介面,能夠對集合中的物件進行排序。

Set 的用法:存放的是物件的引用,沒有重復物件
Set set=new HashSet();  
 
String s1=new String("hello");  
String s2=s1;  
String s3=new String("world");  
 
set.add(s1);  
set.add(s2);  
set.add(s3);  

System.out.println(set.size()); //打印集合中对象的数目 为 2。  

Set 的 add()方法是如何判斷物件是否已經存放在集合中?
boolean isExists=false;  
Iterator iterator=set.iterator();  
 
while(it.hasNext()) {
    String oldStr=it.next();  
    if(newStr.equals(oldStr)){  
        isExists=true;  
    }  
}  



List(列表):


List的特征是其元素以線性方式存儲,集合中可以存放重復物件。

List介面主要實現類包括:
  • ArrayList 代表長度可以改變得陣列。可以對元素進行隨機的訪問,向 ArrayList() 中插入與刪除元素的速度慢。
  • LinkedList 在實現中采用鏈表數據結構。插入和刪除速度快,訪問速度慢。

對於 List 的隨機訪問來說,就是只隨機來檢索位於特定位置的元素。 List 的 get(int index) 方法放回集合中由參數 index 指定的索引位置的物件,下標從“0” 開始。最基本的兩種檢索集合中的所有物件的方法:

1: for 循環和 get()方法:
for(int i=0; i < list.size(); i++){  
    System.out.println(list.get(i));  
}    

2: 使用 迭代器(Iterator):
Iterator it = list.iterator();  
while(it.hashNext){  
    System.out.println(it.next);  
}  



Map(映射):


Map 是一種把鍵物件和值物件映射的集合,它的每一個元素都包含一對鍵物件和值物件。 Map 沒有繼承於 Collection 介面從 Map 集合中檢索元素時,只要給出鍵物件,就會返回對應的值物件。

Map 的常用方法:
  • Object put(Object key, Object value) 向集合中加入元素
  • Object remove(Object key) 刪除與KEY相關的元素
  • void putAll(Map t) 將來自特定映像的所有元素添加給該映像
  • void clear() 從映像中刪除所有映射
  • boolean containsKey(Object key) 判斷映像中是否存在關鍵字 key
  • boolean containsValue(Object value) 判斷映像中是否存在值 value
  • int size() 返回當前映像中映射的數量
  • boolean isEmpty() 判斷映像中是否有任何映射
  • Object get(Object key) 獲得與關鍵字 key 相關的值 。Map 集合中的鍵物件不允許重復,也就說任意兩個鍵物件通過 equals() 方法比較的結果都是 false,但是可以將任意多個鍵獨享映射到同一個值物件上。



Collections 集合實用類:

Collections 提供了供 Java 集合實用的靜態方法。



List 的功能方法


實際上有兩種 List:
一種是基本的 ArrayList,其優點在於隨機訪問元素,另一種是更強大的 LinkedList,它並不是為快速隨機訪問設計的,而是具有一套更通用的方法。
List
次序是 List 最重要的特點:它保證維護元素特定的順序。List 為 Collection 添加了許多方法,使得能夠向 List 中間插入與移除元素(這只推薦 LinkedList 使用)。一個 List 可以生成 ListIterator,使用它可以從兩個方向遍歷 List,也可以從 List 中間插入和移除元 素。
ArrayList
由陣列實現的 List。允許對元素進行快速隨機訪問,但是向 List 中間插入與移除元素的速度很慢。ListIterator 只應該用來由後向前遍歷 ArrayList,而不是用來插入和移除元素。因為那比 LinkedList 開銷要大很多。
LinkedList
對順序訪問進行了優化,向 List 中間插入與刪除的開銷並不大。隨機訪問則相對較慢(使用 ArrayList 代替)。還具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何介面或基類中定義過)使得 LinkedList 可以當作堆棧、隊列和雙向隊列使用。



Set的功能方法


Set 具有與 Collection 完全一樣的介面,因此沒有任何額外的功能,不像前面有兩個不同的 List。實際上 Set 就是 Collection,只是行為不同(這是繼承與多態思想的典型應用:表現不同的行為)。Set 不保存重復的元素(至於如何判斷元素相同則較為負責)

Set:存入 Set 的每個元素都必須是唯一的,因為 Set 不保存重復元素。加入 Set 的元素必須定義 equals() 方法以確保物件的唯一性。Set 與 Collection 有完全一樣的介面。Set 介面不保證維護元素的次序。
HashSet
為快速查找設計的 Set。存入 HashSet 的物件必須定義 hashCode()。
TreeSet
保存次序的 Set,底層為樹結構。使用它可以從 Set 中提取有序的序列。
LinkedHashSet
具有 HashSet 的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。於是在使用迭代器遍歷 Set 時,結果會按元素插入的次序顯示。



Map的功能方法


方法 put(Object key, Object value) 添加一個“值”(想要得東西)和與“值”相關聯的“鍵”(key)(使用它來查找)。方法 get(Object key) 返回與給定“鍵”相關聯的“值”。可以用 containsKey() 和 containsValue() 測試 Map 中是否包含某個“鍵”或“值”。 標准的 Java 類庫中包含了幾種不同的 Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它們都有同樣的基本介面Map,但是行為、效率、排序策略、保存物件的生命周期和判定“鍵”等價的策略等各不相同。

執行效率是 Map 的一個大問題。看看 get() 要做哪些事,就會明白為什麼在 ArrayList 中搜索“鍵”是相當慢的。而這正是 HashMap 提高速度的地方。HashMap 使用了特殊的值,稱為“散列碼”(hash code),來取代對鍵的緩慢搜索。“散列碼”是“相對唯一”用以代表物件的 int 值,它是通過將該物件的某些信息進行轉換而生成的。所有 Java 物件都 能產生散列碼,因為 hashCode() 是定義在基類 Object 中的方法。
HashMap
Map 基於散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量 capacity 和負載因子 load factor,以調整容器的性能。HashMap 就是使用物件的 hashCode() 進行快速查詢的。此方法能夠顯著提高性能。
LinkedHashMap
類似於 HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比 HashMap 慢一點。而在迭代訪問時發而更快,因為它使用鏈表維護內部次序。
TreeMap
基於紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由 Comparabel 或 Comparator 決定)。TreeMap 的特點在 於,你得到的結果是經過排序的。TreeMap 是唯一的帶有 subMap() 方法的 Map,它可以返回一個子樹。
WeakHashMao
弱鍵(weak key)Map,Map 中使用的物件也被允許釋放: 這是為解決特殊問題設計的。如果沒有 map 之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。
IdentifyHashMap
使用 == 代替 equals() 對“鍵”作比較的 hash map。專為解決特殊問題而設計。



總結:


Java 集合的基本用法,都歸納了,上面這些是平常最常用的 Java 集合,具體的其他的,還要參考JDK幫助文檔了,呵呵 關於 Map的應用,還有很多,具體就是這個,Collections 提供了很多 List / Map 實用的方法,對平常開發非常有用。

List 按物件進入的順序保存物件,不做排序或編輯操作。Set 對每個物件只接受一次,並使用自己內部的排序方法(通常,你只關心某個元素是否屬於 Set,而不關心它的順序,否則應該使用 List)。Map 同樣對每個元素保存一份,但這是基於"key"的,Map 也有內置的排序,因而不關心元素添加的順序。如果添加元素的順序對你很重要,應該使用 LinkedHashSet 或者 LinkedHashMap.

[轉載] Get Tables And Columns Details In Schema Using JDBC

轉載自:JavaRoots: Get Tables And Columns Details In Schema Using JDBC

This is a sample program written using java and JDBC API , to print all the tables in a database schema . It also prints columns available in the particular table.

package com.datamigration.main;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.datamigration.db.DataBase;

/**
 * @author Abhishek Somani
 */
public class PrintTable {

    public static String SCHEMA_NAME = "${YOUR_SCHEMA_NAME}";

    public static void main(String[] args) {

        // create and setup your database and get db connection
        DataBase db = new DataBase();
        db.init();

        try {
            Connection con = db.getConnection();
            DatabaseMetaData metaData = con.getMetaData();

            String tableType[] = { "TABLE" };

            StringBuilder builder = new StringBuilder();

            ResultSet result = metaData.getTables(
                null, SCHEMA_NAME, null, tableType
            );

            while (result.next()) {
                String tableName = result.getString(3);

                builder.append(tableName + "( ");
                ResultSet columns = metaData.getColumns(
                    null, null, tableName, null
                );

                while (columns.next()) {
                    String columnName = columns.getString(4);
                    builder.append(columnName);
                    builder.append(",");
                }
                builder.deleteCharAt(builder.lastIndexOf(","));
                builder.append(" )");
                builder.append("\n");
                builder.append("----------------");
                builder.append("\n");

            }

            System.out.println(builder.toString());

        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

This program will print tables along with colmuns like this :
TABLE1( ID,NAME,STATUS,CREATED_AT,UPDATED_AT)
-----------------------------------
TABLE2( ID,NAME,STATUS,CREATED_AT,UPDATED_AT)

[轉載] [JSP] Expression Language (EL)

轉載自:JSP 表达式语言 | w3cschool菜鸟教程

JSP 表達式語言(EL)使得訪問存儲在 JavaBean 中的數據變得非常簡單。JSP EL 既可以用來創建算術表達式也可以用來創建邏輯表達式。在 JSP EL 表達式內可以使用整型數,浮點數,字符串,常量 true、false,還有 null。


一個簡單的語法


典型的,當您需要在JSP標簽中指定一個屬性值時,只需要簡單地使用字符串即可:
<jsp:setProperty name="box" property="perimeter" value="100"/>

JSP EL 允許您指定一個表達式來表示屬性值。一個簡單的表達式語法如下:
${expr}

其中,expr 指的是表達式。在 JSP EL 中通用的操作符是"."和"[]"。這兩個操作符允許您通過內嵌的 JSP 物件訪問各種各樣的 JavaBean 屬性。

舉例來說,上面的 <jsp:setProperty> 標簽可以使用表達式語言改寫成如下形式:
<jsp:setProperty name="box" property="perimeter" 
                  value="${2*box.width+2*box.height}"/>

當 JSP 編譯器在屬性中見到"${}"格式後,它會產生代碼來計算這個表達式,並且產生一個替代品來代替表達式的值。

您也可以在標簽的模板文本中使用表達式語言。
比如 <jsp:text> 標簽簡單地將其主體中的文本插入到 JSP 輸出中:
<jsp:text>
  <h1>Hello JSP!</h1>
</jsp:text>

現在,在 <jsp:text> 標簽主體中使用表達式,就像這樣:
<jsp:text>
  Box Perimeter is: ${2*box.width + 2*box.height}
</jsp:text>

在 EL 表達式中可以使用圓括號來組織子表達式。
比如 ${(1 + 2) * 3} 等於9,但是 ${1 + (2 * 3)} 等於7。

想要停用對 EL 表達式的評估的話,需要使用 page 指令將 isELIgnored 屬性值設為 true:
<%@ page isELIgnored ="true|false" %>

這樣,EL 表達式就會被忽略。若設為 false,則容器將會計算 EL 表達式。



EL 中的基礎操作符


EL表達式支持大部分Java所提供的算術和邏輯操作符:
操作符描述
.訪問一個 Bean 屬性或者一個映射條目
[]訪問一個數組或者鏈表的元素
()組織一個子表達式以改變優先級
+
-減或負
*
/ 或 div
% 或 mod取模
== 或 eq測試是否相等
!= 或 ne測試是否不等
< 或 lt測試是否小於
> 或 gt測試是否大於
<= 或 le測試是否小於等於
>= 或 gt測試是否大於等於
&& 或 and測試邏輯與
|| 或 or測試邏輯或
! 或 not測試取反
empty測試是否空值



JSP EL 中的函數


JSP EL 允許您在表達式中使用函數。這些函數必須被定義在自定義標簽庫中。函數的使用語法如下:
${ns:func(param1, param2, ...)}

ns 指的是命名空間(namespace),func 指的是函數的名稱,param1 指的是第一個參數,param2 指的是第二個參數,以此類推。比如,有函數 fn:length,在 JSTL 庫中定義,可以像下面這樣來獲取一個字符串的長度:
${fn:length("Get my length")}

要使用任何標簽庫中的函數,您需要將這些庫安裝在服務器中,然後使用 <taglib> 標簽在JSP文件中包含這些庫。



JSP EL 隱含物件


JSP EL支持下表列出的隱含物件:
隱含物件描述
pageScopepage 作用域
requestScoperequest 作用域
sessionScopesession 作用域
applicationScopeapplication 作用域
paramRequest 物件的參數,字符串
paramValuesRequest 物件的參數,字符串集合
headerHTTP 信息頭,字符串
headerValuesHTTP 信息頭,字符串集合
initParam上下文初始化參數
cookieCookie 值
pageContext當前頁面的 pageContext

您可以在表達式中使用這些物件,就像使用變量一樣。接下來會給出幾個例子來更好的理解這個概念。



pageContext 物件


pageContext 物件是 JSP 中 pageContext 物件的引用。通過 pageContext 物件,您可以訪問 request 物件。比如,訪問 request 物件傳入的查詢字符串,就像這樣:
${pageContext.request.queryString}



Scope 物件


pageScope,requestScope,sessionScope,applicationScope 變量用來訪問存儲在各個作用域層次的變量。

舉例來說,如果您需要顯式訪問在 applicationScope 層的 box 變量,可以這樣來訪問:
${applicationScope.box}



param 和 paramValues 物件


param 和 paramValues 物件用來訪問參數值,通過使用 request.getParameter 方法和 request.getParameterValues 方法。

舉例來說,訪問一個名為 order 的參數,可以這樣使用表達式:${param.order} 或者 ${param["order"]}

接下來的例子表明了如何訪問request中的username參數:
<%@ page import="java.io.*,java.util.*" %>
<%
    String title = "Accessing Request Param";
%>
<html>
  <head>
    <title><% out.print(title); %></title>
  </head>
  <body>
    <center>
      <h1><% out.print(title); %></h1>
    </center>
    <div align="center">
      <p>${param["username"]}</p>
    </div>
  </body>
</html>

param 物件返回單一的字符串,而 paramValues 物件則返回一個字符串數組。



header 和 headerValues 物件


header 和 headerValues 物件用來訪問信息頭,通過使用 request.getHeader 方法和 request.getHeaders 方法。

舉例來說,要訪問一個名為 user-agent 的信息頭,可以這樣使用表達式:${header.user-agent} 或者 ${header["user-agent"]}

接下來的例子表明了如何訪問 user-agent 信息頭:
<%@ page import="java.io.*,java.util.*" %>
<%
    String title = "User Agent Example";
%>
<html>
  <head>
    <title><% out.print(title); %></title>
  </head>
  <body>
    <center>
      <h1><% out.print(title); %></h1>
    </center>
    <div align="center">
      <p>${header["user-agent"]}</p>
    </div>
  </body>
</html>

運行結果如下:
User Agent Example
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; HPNTDF; .NET4.0C; InfoPath.2)

header 物件返回單一值,而 headerValues 則返回一個字符串數組。