昨天的Java 實現單例模式中,我們的雙重檢驗鎖機制因為指令重排序問題而引入了volatile關鍵字,不少朋友問我,到底為啥要加volatile這個關鍵字呀,而它,到底又有什麼神奇的作用呢?
對volatile這個關鍵字,在昨天的講解中我們簡單說了一下:被volatile修飾的共享變量,都會具有下面兩個屬性:
共享變量:如果一個變量在多個線程的工作內存中都存在副本,那麼這個變量就是這幾個線程的共享變量。
可見性:一個線程對共享變量值的修改,能夠及時地被其它線程看到。
對於重排序,不熟悉的建議直接Google 一下,這裡也就不多提了。只需要記住,在多線程中操作一個共享變量的時候,一定要記住加上volatile 修飾即可。
由於時間關係,我們還是得先進入今天的正題,對於volatile 關鍵字,在要求並發編程能力的面試中還是很容易考察到的,後面我也會簡單給大家講解。
輸入一個單鍊錶的頭結點,從尾到頭打印出每個結點的值。
我們的鍊錶有很多,單鍊錶,雙向鍊錶,環鍊錶等。這裡是最普通的單鍊錶模式,我們一般會在數據存儲區域存放數據,然後有一個指針指向下一個結點。雖然Java 中沒有指針這個概念,但Java 的引用恰如其分的填補了這個問題。
看到這道題,我們往往會很快反應到每個結點都有next 屬性,所以要從頭到尾輸出很簡單。於是我們自然而然就會想到先用一個while 循環取出所有的結點存放到數組中,然後再通過逆序遍歷這個數組,即可實現逆序打印單鍊錶的結點值。
我們假定結點的數據為int 型的。實現代碼如下:
public class Test05 { public static class Node { int data; Node next; } public static void printLinkReverse(Node head) { ArrayList<Node> nodes = new ArrayList<>(); while (head != null) { nodes.add(head); head = head.next; } for (int i = nodes.size() - 1; i >= 0; i--) { System.out.print(nodes.get(i).data + " "); } } public static void main(String[] args) { Node head = new Node(); head.data = 1; head.next = new Node(); head.next.data = 2; head.next.next = new Node(); head.next.next.data = 3; head.next.next.next = new Node(); head.next.next.next.data = 4; head.next.next.next.next = new Node(); head.next.next.next.next.data = 5; printLinkReverse(head); }}這樣的方式確實能實現逆序打印鍊錶的數據,但明顯用了整整兩次循環,時間複雜度為O(n²)。等等!逆序輸出?似乎有這樣一個數據結構可以完美解決這個問題,這個數據結構就是棧。
棧是一種「後進先出」的數據結構,用棧的原理更好能達到我們的要求,於是實現代碼如下:
public class Test05 { public static class Node { int data; Node next; } public static void printLinkReverse(Node head) { Stack<Node> stack = new Stack<>(); while (head != null) { stack.push(head); head = head.next; } while (!stack.isEmpty()) { System.out.print(stack.pop().data + " "); } } public static void main(String[] args) { Node head = new Node(); head.data = 1; head.next = new Node(); head.next.data = 2; head.next.next = new Node(); head.next.next.data = 3; head.next.next.next = new Node(); head.next.next.next.data = 4; head.next.next.next.next = new Node(); head.next.next.next.next.data = 5; printLinkReverse(head); }}既然可以用棧來實現,我們也極容易想到遞歸也能解決這個問題,因為遞歸本質上也就是一個棧結構。要實現逆序輸出鍊錶,我們每訪問一個結點的時候,我們先遞歸輸出它後面的結點,再輸出該結點本身,這樣鍊錶的輸出結果自然也是反過來了。
代碼如下:
public class Test05 { public static class Node { int data; Node next; } public static void printLinkReverse(Node head) { if (head != null) { printLinkReverse(head.next); System.out.print(head.data+" "); } } public static void main(String[] args) { Node head = new Node(); head.data = 1; head.next = new Node(); head.next.data = 2; head.next.next = new Node(); head.next.next.data = 3; head.next.next.next = new Node(); head.next.next.next.data = 4; head.next.next.next.next = new Node(); head.next.next.next.next.data = 5; printLinkReverse(head); }}雖然遞歸代碼看起來確實很整潔,但有個問題:當鍊錶非常長的時候,一定會導致函數調用的層級很深,從而有可能導致函數調用棧溢出。所以顯示用棧基於循環實現的代碼,健壯性還是要好一些的。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。