第三話 初探容器

xiasuhuei321發表於2017-12-13

寫在前面

想看的東西太多,時間總是太少~ 好了,先甩個鍋給時間不夠,然後開始Java重造,抱歉的是可能有些東西我不會講的太詳細。對了,最近看了同學iamxiarui的文章。

容器

在日常擼code的過程中,很多時候我們都需要在執行時去建立新的物件。在此之前,我們可能不知道所需物件的數量甚至連型別都不知道,所以我們需要一個能在執行時儲存物件引用的玩意。事實上陣列是儲存一組物件的最有效的方式,但是很多時候我們也不知道我們需要儲存的物件的數量是多少,所以陣列長度固定這一限制會讓我們在實際應用中受到非常多的限制。不過好在Java提供了容器來幫我們搞定這些問題。首先上一個經典的容器圖譜來理解一下~

經典.jpg

當然了關於陣列有大小限制這個問題,也是可以解決的。我們先申請一個固定長度的陣列,每當有元素向裡面新增的時候就判斷一下,當前陣列是否已經滿了,如果不滿則直接新增進去,如果滿了就按照** 一定策略 申請一個新大小的陣列,將原來陣列元素全部移至新陣列。當陣列空元素過多時,可以按照 一定策略 **縮小陣列長度,這個策略取決於你的需求和應用場景。

容器&泛型

通常我們在使用容器的時候,需要的是一個型別統一的容器,即如果我插入了一個貓元素,那麼我絕對不希望一隻蟑螂被插入進去。我需要的是一個“貓”的容器而不是其他的,在Java SE5之前容器就存在著能向“貓”的容器中插入“蟑螂”的問題。但是說實話,《Thinking in java》作者也說了,在泛型沒有出來之前,程式設計師會經常犯這種錯誤嗎?不見得,在這種場景下泛型只是將錯誤提前在編譯期就告知使用者。但是在沒有泛型之前,一個程式碼風格良好的寫法應該是:

List cat = new ArrayList();
複製程式碼

如此明顯的一個cat集合你會插入什麼其他奇奇怪怪的東西嗎?所以說泛型之於容器是型別安全,但是泛型出現更重要的一個目的是 ** 讓程式設計師編寫更加通用的程式碼 **。

上面的話可能說的有些繁瑣,請容我再整理一下:在很多實用容器的場景中,我們希望在未使用容器錢,容器容納型別不確定,但是在放入一個型別後,我們只能使用該型別,泛型非常適合應用在這個場景。使用泛型可以讓執行期的錯誤在編譯器就被阻止。下面來個簡單的例子來說明一下:

//<> 尖括號內的是型別引數
List<String> strList = new ArrayList<String>();
strList.add(1);//編譯錯誤

List anotherList = new ArrayList();
anotherList.add(1);
anotherList.add("cat");//未檢查警告,但是此操作不會報錯
複製程式碼

讓我們用更直觀的形式看一下這樣操作的的結果:

情況如何

很明顯使用泛型能夠有效的避免將錯誤型別物件放置到容器中,但是關於泛型的使用不僅於此,更多的討論放到文末。

迭代器

在《Thiniking in Java》中說到,任何容器類,都必須有某種方式可以插入元素並將它們再次取回(當然在某些書中你可能聽說過bag這種只放入的容器,但是現在無需糾結這些東西)。

        List<Integer> integerList = new ArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            integerList.add(i);
        }

        Iterator<Integer> i = integerList.iterator();
        while (i.hasNext()) {
            Integer j = i.next();
            System.out.println("i-->" + j);
        }
複製程式碼

以上是一個存放了0-9個值的集合,現在我們利用迭代器列印每個元素的值,先結果如下:

輸出結果

LinkedList

其實我還有很多沒提到的東西,但是這是初探容器啊~肯定還會有再探容器啊~

Java裡的容器我用的比較多的大概就是HashMap、ArrayList和LinkedList,其中ArrayList用的最多。其實ArrayList和LinkedList都可以被理解為“連結串列”,只不過LinkedList更偏向於我們所熟知的“指標連結串列”,而ArrayList可以將之理解為內部是陣列的連結串列。說實話,陣列自帶鏈子啊~

好了看上面的標題也該知道,我是不打算在這對其他的容器做更多的介紹的,大標題叫初探容器,說明我還會有再探容器的,那時再做更詳細的介紹。最近看到一個有趣的東西,你輸入一個"(1+(2-1))"這種格式的算是,可以用棧算出來,於是乎自己用jdk自帶的LinkedList封裝了一個stack,把那玩意做了出來,但是感覺不爽,因為連結串列用的是現成的,簡單的封裝也沒什麼難度。我不管我就要從Node開始搞一個Java的“指標連結串列”。

package thinking.Generator.other;

import java.util.NoSuchElementException;

/**
 * Created by luo-pc on 2016/9/30.
 * desc:一個雙向連結串列
 */
public class LinkList<T> {

    /**
     * 連結串列的大小
     */
    int size = 0;

    /**
     * 表頭
     */
    Node<T> first;

    /**
     * 表尾
     */
    Node<T> last;

    /**
     * 構造一個空的連結串列
     */
    public LinkList() {
    }

    public boolean add(T t) {
        insertToLast(t);
        return true;
    }

    /**
     * 在表尾插入一個元素
     *
     * @param t 元素值
     */
    private void insertToLast(T t) {
        //表尾元素
        Node<T> n = last;
        //新建一個指向表尾的元素
        Node<T> newNode = new Node<T>(n, t, null);
        //讓表尾指向新元素
        last = newNode;
        //如果連結串列為空
        if (n == null)
            first = newNode;
        else
            n.next = newNode;
        size++;

    }

    /**
     * 獲取表尾元素
     *
     * @return 返回表尾元素的值
     */
    public T getLast() {
        Node<T> n = last;
        if (n == null)
            throw new NoSuchElementException();
        return n.item;
    }

    /**
     * 根據索引獲取對應元素的值
     *
     * @param index 索引
     * @return T 值
     */
    public T get(int index) {
        return node(index).item;
    }

    /**
     * 根據索引查詢元素
     *
     * @param index 索引
     */
    Node<T> node(int index) {
        if (index < (size >> 1)) {
            Node<T> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<T> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.pre;
            return x;
        }
    }


    public T remove(int index) {
        checkNodeIndex(index);
        return unlink(node(index));
    }

    private void checkNodeIndex(int index) {
        if (!isNodeIndex(index))
            throw new IndexOutOfBoundsException("Index:" + index + "Size:" + size);
    }

    T unlink(Node<T> x) {
        T value = x.item;
        Node<T> next = x.next;
        Node<T> prev = x.pre;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.pre = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.pre = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        return value;
    }

    /**
     * 檢查該索引是否是該連結串列內的元素
     *
     * @param index 索引
     * @return 是或者不是
     */
    public boolean isNodeIndex(int index) {
        return index >= 0 && index < size;
    }

    /**
     * 遍歷並列印連結串列
     */
    public void travelList() {
        Node travelNode = first;
        if (travelNode == null)
            return;
        while (travelNode.next != null) {
            System.out.println(travelNode.item);
            travelNode = travelNode.next;
        }
    }

    public int size(){
        return this.size;
    }

    //--------------------------------Node-------------------------------//

    /**
     * 連結串列單個元素的型別
     *
     * @param <T>
     */
    private static class Node<T> {
        T item;
        Node<T> next;
        Node<T> pre;

        Node(Node<T> prev, T element, Node<T> next) {
            this.item = element;
            this.next = next;
            this.pre = prev;
        }
    }

    public static void main(String[] args) {
        LinkList<String> strList = new LinkList<>();
        strList.add("str1");
        strList.add("str2");
        strList.add("str3");
        strList.add("str4");

        strList.travelList();

    }
}

複製程式碼

我這實現了一個雙向連結串列,操作起來方便一點,實現的時候參考了一下Java的LinkedList。底下的main()函式僅僅作為測試之用。接下來簡單的封裝一下,將LinkedList封裝為一個棧:

package thinking.Generator.other;

import java.util.NoSuchElementException;

/**
 * Created by luo-pc on 2016/10/1.
 * desc:
 */
@SuppressWarnings("unused")
public class MyStack<T> {
    private LinkList<T> stack;

    public MyStack() {
        stack = new LinkList<T>();
    }

    /**
     * 彈出棧頂元素並返回
     */
    public T pop() {
        if (stack.size() > 0) {
            return stack.remove(stack.size() - 1);
        } else {
            throw new NoSuchElementException();
        }
    }

    /**
     * 入棧操作
     *
     * @param t 入棧的值
     * @return 操作結果
     */
    public boolean push(T t) {
        return stack.add(t);
    }

    /**
     * 返回棧頂元素的值但是不彈出他
     */
    public T peek() {
        return stack.getLast();
    }

    public boolean isEmpty() {
        return stack.size == 0;
    }

    public int size() {
        return stack.size();
    }
}

複製程式碼

好了前期工作準備好了,那我們來看一下該怎麼算。一般的算術表示式我們可以通過二叉樹的遍歷來將其改造成中綴表示式從而用棧來算出其結果,但是上面的算式不用那麼麻煩,因為有左右括號,按照以下規則預算就成了:

  • 將運算元壓入運算元棧
  • 將運算子壓入運算子棧
  • 忽略左括號
  • 在遇到右括號時,彈出一個運算子,彈出所需數量的運算元,並將運算子和運算元的運算結果壓入運算元棧

好了演算法也有了,搞起來!

    public static void main(String[] args) {
        /**
         * 關於表示式(由括號、運算子和運算元組成)處理的規則:
         * 1.將運算元壓入棧
         * 2.將運算子壓入運算子棧
         * 3.忽略左括號
         * 4.在遇到右括號時彈出一個運算子,彈出所需數量的運算元,並將
         * 運算子和運算元的運算結果壓入運算元的棧
         */
        //運算元棧
        MyStack<Double> integerStack = new MyStack<>();
        //運算子棧
        MyStack<Character> opStack = new MyStack<>();

        String integers = "0123456789";
        String a = "(1+((2+3)*(4*5)))";
        char[] b = new char[a.length()];
        for (int i = 0; i < b.length; i++) {
            b[i] = a.charAt(i);
        }
        for (char i : b) {
            //1.將運算元壓入棧
            if (contain(integers, i)) {
                integerStack.push(Double.parseDouble("" + i));
            } else if (i == '+' || i == '-' || i == '*' || i == '/') {//2.將操作符壓入棧
                opStack.push(i);
            } else if (i == '(') {//3.忽略左括號
            } else if (i == ')') {//4.運算
                double opt1 = integerStack.pop();
                double opt2 = integerStack.pop();
                double opt3 = 0.0;
                char operator = opStack.pop();
                if (operator == '+') {
                    opt3 = opt1 + opt2;
                    System.out.println(opt1 + "+" + opt2 + "=" + opt3);
                }
                if (operator == '-') {
                    opt3 = opt1 - opt2;
                    System.out.println(opt1 + "-" + opt2 + "=" + opt3);
                }
                if (operator == '*') {
                    opt3 = opt1 * opt2;
                    System.out.println(opt1 + "*" + opt2 + "=" + opt3);

                }
                if (operator == '/') {
                    opt3 = opt1 / opt2;
                    System.out.println(opt1 + "/" + opt2 + "=" + opt3);
                }
                integerStack.push(opt3);
            }
        }
        System.out.println("the result is:" + integerStack.peek());
    }

    public static boolean contain(String s, char a) {
        char[] str = new char[s.length()];
        for (int i = 0; i < s.length(); i++) {
            str[i] = s.charAt(i);
        }
        for (char i : str) {
            if (a == i)
                return true;
        }
        return false;
    }

複製程式碼

看下結果~

看下結果

結果是不是很有趣~好了這次的複習就先到這,等到下次再探容器的時候再好好的瞭解一下容器。

相關文章