1. 程式人生 > >菜雞的Java筆記 第二十七 連結串列基本概念

菜雞的Java筆記 第二十七 連結串列基本概念

連結串列基本概念
        1.連結串列的基本形式
        2.單向連結串列的完整實現
        
    認識連結串列
        連結串列= 可變長的物件陣列,屬於動態物件陣列的範疇
        連結串列是一種最簡單的線性資料結構,之所以會存在有資料結構問題主要是解決亮點:儲存的資料不受限制,查詢速度快
        物件陣列有那些問題呢?
            物件陣列可以儲存一組物件方便開發
            物件陣列的長度固定,而且資料的刪除,修改,增加處理麻煩
        所有的開發之中都100%不可能避免掉物件陣列的使用
        正因為如此現在如果要想讓其可以編寫出便於維護的程式碼,那麼就需要實現一個動態物件陣列,那麼就可以使用連結串列完成
        但是現在如果要想實現動態的物件陣列,要考慮兩個問題:
            為了適應於所有的開發要求,此物件陣列要求可以儲存所有的資料型別,那麼一首選Object型別
            為了可以儲存多個數據,需要採用引用的方式來進行儲存,但是資料本身是不可能儲存順序的
                所以需要有一個可以負責儲存順序的類來包裝這個資料
        分析 結論:
            儲存資料為了方便使用 Object
            資料本身不包含有先後的邏輯關係,所以將資料封裝在一個 Node 類,負責關係的維護
        範例:定義出如下的一個類

    class Node{// b表示定義的節點
        private Object data;// 要儲存的資料
        private Node next; // 儲存下一個節點
        public Node(Object data){ // 有資料才可以儲存節點
            this.data = data;
        }
        public void setNext(Node next){// 設定節點
            this.next = next;
        }
        public Node getNext(){ //
取得節點 return this.next; } } public class linkedList{ public static void main(String args[]){ } }

           
        完成節點之後,下面就可以進行節點的基本使用了
        範例:採用迴圈的方式操作節點

    class Node{// b表示定義的節點
        private Object data;// 要儲存的資料
        private Node next; // 儲存下一個節點
        public Node(Object data){ // 有資料才可以儲存節點
            this.data = data;
        }
        public void setNext(Node next){// 設定節點
            this.next = next;
        }
        public Node getNext(){ // 取得節點
            return this.next;
        }
        public Object getData(){
            return this.data;
        }
    }
    public class linkedList{
        public static void main(String args[]){
            //1.定義各自獨立的操作節點
            Node root = new Node ("火車頭");
            Node n1 = new Node("車廂1");
            Node n1 = new Node("車廂2");
            //2.設定彼此間的關係
            root.setNext(n1);
            n1.setNext(n2);
            // 3.輸出
            Node currentNode = root;// 從根節點開始取出資料
            while(currentNode != null){
                System.out.println(currentNode.getData());// 取出資料
                currentNode = currentNode.getNext();//下一個節點
            }
        }
    }

           
        以上的操作如果使用迴圈並不方便。最好的做法是遞迴呼叫
        範例:利用遞迴的方式實現內容的取得

    class Node{// b表示定義的節點
        private Object data;// 要儲存的資料
        private Node next; // 儲存下一個節點
        public Node(Object data){ // 有資料才可以儲存節點
            this.data = data;
        }
        public void setNext(Node next){// 設定節點
            this.next = next;
        }
        public Node getNext(){ // 取得節點
            return this.next;
        }
        public Object getData(){
            return this.data;
        }
    }
    public class linkedList{
        public static void main(String args[]){
            //1.定義各自獨立的操作節點
            Node root = new Node ("火車頭");
            Node n1 = new Node("車廂1");
            Node n1 = new Node("車廂2");
            //2.設定彼此間的關係
            root.setNext(n1);
            n1.setNext(n2);
            // 3.輸出
            
            }
            public static void print(Node node){
                if(node == null){
                    return;// 結束方法呼叫
                }
                System.out.println(node.getData());
                print(node,getNext());
            }
        }
    }

           
        通過以上的結構講解,應該已經清楚了連結串列在整個實現的關鍵就是 Node 類,Node 類要儲存資料與下一個節點

    連結串列開發入門
        雖然以上的程式碼已經實現了鏈的形式,但是以上的程式碼之中存在有兩個問題:
            使用者需要自己手工定義Node 類,但是實際上Node 類對使用者沒用
            Node 類的先後關係如果交由使用者處理,那麼整個程式碼就亂了
        所以現在發現,需要有一個類,這個類可以負責所有的Node 的關係匹配,而使用者只需要通過這個類儲存資料或取得資料即可
        那麼就可以編寫一個Link類完成
        範例:基本結構

    class Node{// b表示定義的節點
        private Object data;// 要儲存的資料
        private Node next; // 儲存下一個節點
        public Node(Object data){ // 有資料才可以儲存節點
            this.data = data;
        }
        public void setNext(Node next){// 設定節點
            this.next = next;
        }
        public Node getNext(){ // 取得節點
            return this.naxt;
        }
        public Object getData(){
            return this.data;
        }
    }
    class link{ // 表示一個連結串列操作類,利用此類來隱藏Node的節點匹配
        public void add(Object obj){// 向連結串列裡面追加資料
            
        }
        public void print(){// 輸出連結串列中的全部資料
            
        }
    }
    public class linkedList{
        public static void main(String args[]){
            Link all = new Link();
            all.add("商品1");
            all.add("商品2");
            all.add("商品3");
            all.print();
        }
    }

           
            使用者不關心Node,使用者只關心通過Link操作完成後可以取得資料
        範例:完善程式

    class Node{// b表示定義的節點
        private Object data;// 要儲存的資料
        private Node next; // 儲存下一個節點
        public Node(Object data){ // 有資料才可以儲存節點
            this.data = data;
        }
        public void setNext(Node next){// 設定節點
            this.next = next;
        }
        public Node getNext(){ // 取得節點
            return this.next;
        }
        public Object getData(){
            return this.data;
        }
        // 第一次呼叫:Link.root
        // 第一次呼叫:Link.root.next
        // 第一次呼叫:Link.root.next.next
        public void addNode(Node newNode){
            if(this.next == null){ // 當前節點之後沒有節點
                this.next = newNode;
            }else{// 如果現在當前節點後有節點
                this.next.addNode(newNode);
            }
        }
        // 第一次呼叫:this = Link.root
        // 第一次呼叫:this = Link.root.next
        public void printNode(){
            System.out.println(this.data);// 當前節點資料
            if(this.next != null){ // 還有後續的節點
                this.next.printNode();
            }
        }
    }
    class Link{ // 表示一個連結串列操作類,利用此類來隱藏Node的節點匹配
        private Node root;// 需要有一根元素
        public void add(Object obj){// 向連結串列裡面追加資料
            // 將操作的資料包裝為Node類物件,這樣才可以進行先後關係的排列
            Node newNode = new Node(obj);
            //x現在沒有根節點
            if(this.root == null){// this出現在Link類,表示Link類的當前物件
                this.root = newNode;// 將第一個節點作為根節點
            }else{// 根節點存在了
                // this.root.setNext(newNode);
                this.root.addNode(newNode);  // 由根節點負責呼叫
            }//(Node 負責排序Link 負責根)
        }
        public void print(){// 輸出連結串列中的全部資料
            if(this.root != null){ // 現在有資料
                this.root.printNode(); // 輸出節點資料
            }
        }
    }
    public class linkedList{
        public static void main(String args[]){
            Link all = new Link();
            all.add("商品1");
            all.add("商品2");
            all.add("商品3");
            all.print();
        }
    }

           
            此時的程式碼就實現了連結串列的基本操作,整個過程之中,使用者不關心Node的處理,只關心資料的儲存和輸出
            
    
    開發可用連結串列
        以上的程式碼只能夠說是基本的連結串列結構形式,但是從另外一個方面,以上的程式碼給我們提供了連結串列的實現思路
        可是如何才能設計一個好的連結串列呢?
        連結串列的實現必須依靠於節點類Node類來實現,但是整個的過程之中一定要清楚,使用者不需要操作Node
        而且通過現在的程式碼可以發現Node類裡的操作有特定的需要
        但是這個時候Node類寫在了外面,那麼就表示使用者可以直接操作Node類物件
        所以程式現在的問題在於:如何可以讓Node類只為Link類服務,但是有不讓其他類所訪問
        那麼自然就要想到使用內部類完成,而且內部類的好處在於:可以與外部類直接進行私有屬性的訪問
        範例:合理的結構規劃

            class Link{ //外部的程式只關心此類
                private class Node{//使用私有內部類,防止外部使用此類
                    private Object data;
                    private Node next;
                    
                    public Node(Object data){
                        this.data = data;
                    }
                }
                //************************************
                private Node root; // 根元素
            }
            public class linkedList{
                public static void main(String args[]){
                    
                }
            }

           
            如果要開發程式,那麼一定要建立自己的操作標準,那麼一旦說到標準就應該想到使用介面來完成
            

    interface Link{
        
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
        }
        //************************************
        private Node root; // 根元素
    }
    public class linkedList{
        public static void main(String args[]){
            
        }
    }

           
            在隨後完善程式碼的過程之中,除了功能的實現之外,實際上也屬於介面功能的完善
            
        實現資料增加操作  public void add(Object data)
            1.需要在接口裡面定義好資料增加的操作方法

    interface Link{
        public void add(Object data);//資料增加
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
        }
        //************************************
        private Node root; // 根元素
    }
    public class linkedList{
        public static void main(String args[]){
            
        }
    }

               
            2.進行程式碼的實現,同樣實現的過程之中 LinkImpl 類只關心根節點,而具體的子節點的排序都交由 Node 類負責處理
                在Link類中實現add()方法:

    interface Link{
        public void add(Object data);//資料增加
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
        }
        //************************************
        private Node root; // 根元素
        public void add(Object data){
            if(data == null){//現在沒有要增加的資料
                return;//結束呼叫
            }
            Node newNode = new Node(data);//建立新的節點
            if(this.root == null){//保留有根節點
                this.root = root;
            }else{//應該交由Node類負責處理
                this.root.addNode(newNode);
            }
        }
    }
    public class linkedList{
        public static void main(String args[]){
            
        }
    }                

               
                在Node類中進行資料的追加操作:

    interface Link{
        public void add(Object data);//資料增加
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
            public void addNode(Node newNode){
                if(this.next == null){
                    this.next = newNode;
                }else{
                    this.next.addNode(newNode);
                }
            }
        }
        //************************************
        private Node root; // 根元素
        public void add(Object data){
            if(data == null){//現在沒有要增加的資料
                return;//結束呼叫
            }
            Node newNode = new Node(data);//建立新的節點
            if(this.root == null){//保留有根節點
                this.root = root;
            }else{//應該交由Node類負責處理
                this.root.addNode(newNode);
            }
        }
    }
    public class linkedList{
        public static void main(String args[]){
            
        }
    }

               
                此時的程式碼實現過程與基本的實現是完全一樣的
                
        取得儲存元素個數: public int size()
            每個Link介面的物件都要儲存各自的內容,所以為了方便控制儲存個數,可以增加一個Link類中的屬性,並且用此屬性在資料成功追加之後實現自增操作
            在Link類中定義一個 count 屬性,預設值為:0

interface Link{
    public void add(Object data);//資料增加
}
class LinkImpl implements Link{ //外部的程式只關心此類
    private class Node{//使用私有內部類,防止外部使用此類
        private Object data;
        private Node next;
        
        public Node(Object data){
            this.data = data;
        }
        public void addNode(Node newNode){
            if(this.next == null){
                this.next = newNode;
            }else{
                this.next.addNode(newNode);
            }
        }
    }
    //************************************
    private Node root; // 根元素
    private int count = 0;//    當資料已經成功新增完畢之後實現計數的統計
    public void add(Object data){
        if(data == null){//現在沒有要增加的資料
            return;//結束呼叫
        }
        Node newNode = new Node(data);//建立新的節點
        if(this.root == null){//保留有根節點
            this.root = root;
        }else{//應該交由Node類負責處理
            this.root.addNode(newNode);
        }
        this.count ++;  // 當節點儲存完畢之後就可以進行資料增加了
    }
}
public class linkedList{
    public static void main(String args[]){
        
    }
}

               
                而在Link接口裡面追加 size() 的方法,同時在LinkImpl子類裡面進行方法的覆寫
                

    interface Link{
        public void add(Object data);//資料增加
        public int size();// 取得儲存元素的個數
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
            public void addNode(Node newNode){
                if(this.next == null){
                    this.next = newNode;
                }else{
                    this.next.addNode(newNode);
                }
            }
        }
        //************************************
        private Node root; // 根元素
        private int count = 0;//    當資料已經成功新增完畢之後實現計數的統計
        public void add(Object data){
            if(data == null){//現在沒有要增加的資料
                return;//結束呼叫
            }
            Node newNode = new Node(data);//建立新的節點
            if(this.root == null){//保留有根節點
                this.root = root;
            }else{//應該交由Node類負責處理
                this.root.addNode(newNode);
            }
            this.count ++;// 當節點儲存完畢之後就可以進行資料增加了
        }
        public int size(){
            return this.count;
        }
    }
    
    public class linkedList{
        public static void main(String args[]){
            Link all = new LinkImpl();
            System.out.println(all.size());
            all.add("商品1");
            all.add("商品2");
            all.add("商品3");
            System.out.println(all.size());
        }
    }

               
                此操作直接與最後的輸出有關
        
        判斷是否為空集合: public boolean isEmpty()
            所謂的空連結串列指的是連結串列之中沒有任何的資料存在
            如果要想判斷集合是否為空,有兩種方式:長度為 0 ,另外一個就是判斷根元素是否為 null
            範例:在Link 介面中追加一個新的方法: isEmpty

                interface Link{
                    public void add(Object data);//資料增加
                    public int size();// 取得儲存元素的個數
                    public boolean isEmpty();//判斷是否為空集合
                }

           
            範例:在LinkImpl類中實現此方法

    interface Link{
        public void add(Object data);//資料增加
        public int size();// 取得儲存元素的個數
        public boolean isEmpty();//判斷是否為空集合
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
            public void addNode(Node newNode){
                if(this.next == null){
                    this.next = newNode;
                }else{
                    this.next.addNode(newNode);
                }
            }
        }
        //************************************
        private Node root; // 根元素
        private int count = 0;//    當資料已經成功新增完畢之後實現計數的統計
        public void add(Object data){
            if(data == null){//現在沒有要增加的資料
                return;//結束呼叫
            }
            Node newNode = new Node(data);//建立新的節點
            if(this.root == null){//保留有根節點
                this.root = root;
            }else{//應該交由Node類負責處理
                this.root.addNode(newNode);
            }
            this.count ++;
        }
        public int size(){
            return this.count;
        }
        public boolean isEmpty()(
            return this.count == 0;
            // 或者  return this.root == null;
        )
    }
    
    public class linkedList{
        public static void main(String args[]){
            Link all = new LinkImpl();
            System.out.println(all.isEmpty());
            all.add("商品1");
            all.add("商品2");
            all.add("商品3");
            System.out.println(all.isEmpty());
        }
    }

               
                實際上此操作與 size() 幾乎一脈相承
    
        資料查詢: public boolean contains(Object data)
            任何情況下 Link 類只負責與根元素操作有關的內容,而所有額其他元素的資料的變更,查詢,關係的匹配都應該交由 Node 類來負責處理
            1.在Link接口裡面建立一個新的方法

                interface Link{
                    public void add(Object data);//資料增加
                    public int size();// 取得儲存元素的個數
                    public boolean isEmpty();//判斷是否為空集合
                    public boolean contains(Object data);// 判斷是否有指定的元素
                }

               
            2.在LinkImpl 子類裡面要通過根元素開始呼叫查詢,所有的查詢交由 Node 類負責
                在LinkImpl 發出具體的查詢要求之前,必須要保證有集合資料

                    public boolean contains(Object data){
                        if(this.root == null){// 沒有集合資料
                            return false;
                        }
                        return this.root.containsNode(data);
              // 根元素交給 Node 類完成 }

               
                在Node 類中實現資料的查詢

                    // 第一次:this.LinkImpl.root
                    // 第二次:this.LinkImpl.root.next
                    public boolean currentNode(Object data){
                        if(this.data.equals(data)){ // 該節點資料符合於查詢資料
                            return true;
                        }else{// 繼續向下查詢
                            if(this.next != null){// 當前節點之後還有下一個節點
                                return this.next.containsNode(data);
                            }else{
                                return false;
                            }
                        }
                    }

                這樣查詢的模式實質上也屬於逐行的判斷掃描     
        
        根據索引取得資料: public Object get(int index)
            既然連結串列屬於動態的物件陣列,所以陣列本身一定會提供有根據索引取得資料的操作支援,那麼在連結串列中也可以定義與之類似的方法
            但是在進行資料儲存的時候並沒有設定索引,那麼現在有兩個方案:
                修改 Node 類的結構,為每一個節點自動匹配一個索引,資料的刪除不方便
                在操作索引時動態生存索引,適合集合的修改
            
            1.修改Lnik 類為其增加一個 foot 的屬性,之所以將foor屬性定義在 LinkImpl 類之中,主要目的是方便多個 Node 共同進行屬性的操作使用的,,同時內部類可以方便的訪問外部類中的私有成員
                private int foot = 0;//操作索引的腳標
            2.在Link 接口裡面首先定義出新的操作方法    
                public Object get(int index);//根據索引取得內容,索引從0開始
            3.在LinkImpl 類裡面定義功能實現:
                在Node 類中應該提供有一個 getNode() 的方法,那麼這個方法的功能是依靠判斷每一個索引值的操作形式

                public Object getNode(int index){// 傳遞索引的序號
                            if(LinkImpl.this.foot++ == index){ 
                  // 當前的索引為要查詢的索引 return this.data;//返回當前節點物件 }else{ return this.next.getNode(index); } }


                在Link 類中實現 get() 方法

                public Object get(int index){
                        if(index >= this.count){ // 索引不存在
                            return null;
                        }
                        this.foot = 0;// 查詢之前執行一次清零操作
                        return this.root.getNode(index);
                        
                    }

 

                這種查詢的模式與 contains() 最大的不同一個是數字索引,一個是內容
 
        修改資料: public void set(int index,Object obj)
            與get() 相比 set() 方法依然需要進行迴圈的判斷,只不過 get() 索引判斷成功之後會返回資料,而 set() 只需要用新的資料更新已有節點資料即可
            1.在Link接口裡面建立一個新的方法
                public void set(int index,Object obj)
            2.修改LnikImopl 子類,流程與 get() 差別不大:
                在Node 類裡面追加一個新的 setNode() 方法;

                    public void setNode(int index,Object obj){
                        if(LinkImpl.this.foot ++ == index){
                            this.obj = obj;// 重新儲存資料
                        }else{
                            this.next.setNode(index,obj);
                        }
                    }

                   
                在LinkImpl 子類裡面覆寫 set() 方法,在 set() 方法編寫的時候也需要針對於給定的索引進行驗證

                    public void set(int index,Object obj){
                        if(index >= this.count){ // 索引不存在
                            return null;
                        }
                        this.foot = 0;// 查詢之前執行一次清零操作
                        this.root.setNode(index,obj);
                        
                    }


            set() 與 get() 方法實際上在使用時都有一個固定的條件:集合中的儲存資料順序應該為新增順序
            
            
        資料刪除: public void remove(Object obj)
            如果要進行資料的刪除,那麼對於整個連結串列而言就是節點的刪除操作
            而節點的刪除操作過程之中需要考慮的問題是什麼?
                要刪除的是根節點還是子節點問題
            1.要刪除的是根節點:Link 類處理,因為根節點有關的所有節點都應該交由 Link 類管理
                Link.root = Link.root.next
            2.要刪除的是子節點:交由 Node 類負責處理
                刪除節點的上一個節點.next = 刪除節點.next
                
            1.在Link接口裡面建立一個新的方法
                public void remove(Object data);// 刪除資料
            2.修改LnikImopl 類的操作:
                在Node 類中增加一個 removeNode() 的方法

                //第一次:this.LinkImpl.root.next,previous = LinkImpl.root;
                // 第二次:this.LinkImpl.root.next.next,previous = LinkImpl.root.next
                public void remove(Node previous,Object data){
                    if(this.data.equals(data)){
                        previous.next = this.next;// 空出當前節點
                    }else{
                        this.next.removeNode(this,data);
                    }
                }

               
                在Link類中增加新的操作:

                public void remove(Object data){
                    if(this.contains(data)){ // 資料如果存在則刪除
                        if(this.root.equals(data)){// 根元素為要刪除的元素
                            this.root = this.root.next; // 第二個元素作為根元素
                        }else{ // 不是根元素,根元素一斤判斷完了
                            this.root.next.removeNode(this.root,data);
                        }
                        this.count --;
                    }
                }

               
                整個刪除操作很好的體現了 this 的特性
                contains() 和 remove() 方法必須有物件比較的支援,物件比較使用的就是 Object 類中的 equals() 方法
                
        清空連結串列: public void clear()
            當連結串列中的資料不需要在使用的時候,那麼可以進行清空,而清空最簡單的做法就是將 root設定為 null
            1.在Link接口裡面建立一個新的方法
                public void clear();//清空連結串列
            2.直接在 LinkImpl 類中修改清空操作
               

public void clear(){
                    this.foot = null;
                    this.count = 0;  // 元素的儲存個數清0
                    System.gc();//回收記憶體空間
                }

               
            實際上這種程式碼還欠缺一個很好的記憶體釋放問題
            
            
        返回資料: public Object[] toArray()
            恆定的概念:連結串列就是動態物件陣列,但是要想操作連結串列中的資料,那麼最好的做法是將其轉換為物件陣列返回
            所以這個時候就需要針對資料做遞迴處理
            1.在Link 接口裡面定義返回物件陣列的方法
                public Object[] toArray()
            2.修改LnikImopl 子類
                由於Node 類需要操作連結串列資料讀取,所以應該在LinkImpl 子類裡面應該提供有一個物件陣列的屬性
                public Object retData[] = null;
                在LinkImpl 子類裡面覆寫 toArray() 方法,並且要根據長度開闢陣列空間

                public Object[] toArray(){
                    if(this.root == null){
                        return null;
                    }
                    this.retData = new Object[this.count];
                    this.foot = 0;
                    this.root.toArrayNode();
                    return this.retData;
                }

 

                在Node類裡面實現資料的儲存操作

                public void toArrayNode(){
                    LinkImpl.this.retData[LinkImpl.this.foot ++] = this.data;
                    if(this.next != null){
                        this.next.toArrayNode();
                    }
                }


                不過以上的設計都沒有考慮過效能問題


                

    interface Link{
        public void add(Object data);//資料增加
        public int size();// 取得儲存元素的個數
        public boolean isEmpty();//判斷是否為空集合
        public boolean contains(Object data);// 判斷是否有指定的元素
        public Object get(int index);//根據索引取得內容,索引從0開始
        public void set(int index,Object obj);
        public void remove(Object data);// 刪除資料
        public void clear();//清空連結串列
        public Object[] toArray();
    }
    class LinkImpl implements Link{ //外部的程式只關心此類
        private class Node{//使用私有內部類,防止外部使用此類
            private Object data;
            private Node next;
            
            public Node(Object data){
                this.data = data;
            }
            public void addNode(Node newNode){
                if(this.next == null){
                    this.next = newNode;
                }else{
                    this.next.addNode(newNode);
                }
            }
            public Object getNode(int index){// 傳遞索引的序號
                if(LinkImpl.this.foot++ == index){ // 當前的索引為要查詢的索引
                    return this.data;//返回當前節點物件
                }else{
                    return this.next.getNode(index);
                }
            }
            public void setNode(int index,Object data){
                 if(LinkImpl.this.foot ++ == index){
                    this.data = data;// 重新儲存資料
                 }else{
                    this.next.setNode(index,data);
                 }
             }
            //第一次:this.LinkImpl.root.next,previous = LinkImpl.root;
            // 第二次:this.LinkImpl.root.next.next,previous = LinkImpl.root.next
            public void remove(Node previous,Object data){
                if(this.data.equals(data)){
                    previous.next = this.next;// 空出當前節點
                }else{
                    this.next.removeNode(this,data);
                }
             }
            // 第一次:this.LinkImpl.root
            // 第二次:this.LinkImpl.root.next
            public boolean currentNode(Object data){
                if(this.data.equals(data)){ // 該節點資料符合於查詢資料
                    return true;
                }else{// 繼續向下查詢
                    if(this.next != null){// 當前節點之後還有下一個節點
                        return this.next.containsNode(data);
                    }else{
                        return false;
                    }
                }
            }
            public void toArrayNode(){
                LinkImpl.this.retData[LinkImpl.this.foot ++] = this.data;
                if(this.next != null){
                    this.next.toArrayNode();
                }
            }
        }
        //************************************
        private Node root; // 根元素
        private int count = 0;//    當資料已經成功新增完畢之後實現計數的統計
        private int foot = 0;//操作索引的腳標
        public Object retData[] = null;
        public void add(Object data){
            if(data == null){//現在沒有要增加的資料
                return;//結束呼叫
            }
            Node newNode = new Node(data);//建立新的節點
            if(this.root == null){//保留有根節點
                this.root = root;
            }else{//應該交由Node類負責處理
                this.root.addNode(newNode);
            }
            this.count ++;
        }
        public void remove(Object data){
            if(this.contains(data)){ // 資料如果存在則刪除
                if(this.root.data.equals(data)){// 根元素為要刪除的元素
                    this.root = this.root.next; // 第二個元素作為根元素
                }else{ // 不是根元素,根元素一斤判斷完了
                    this.root.next.removeNode(this.root,data);
                }
                this.count --;
            }
        }
        public void clear(){
            this.foot = null;
            this.count = 0;
            System.gc();//回收記憶體空間
        }
        public int size(){
            return this.count;
        }
        public boolean isEmpty(){
            return this.count == 0;
            // 或者  return this.root == null;
        }
        public boolean contains(Object data){
            if(this.root == null){// 沒有集合資料
                return false;
            }
            return this.root.containsNode(data);// 根元素交給 Node 類完成
        }
        public Object[] toArray(){
            if(this.root == null){
                return null;
            }
            this.retData = new Object[this.count];
            this.foot = 0;
            this.root.toArrayNode();
            return this.retData;
        }
        public Object get(int index){
            if(index >= this.count){ // 索引不存在
                return null;
            }
            this.foot = 0;// 查詢之前執行一次清零操作
            return this.root.getNode(index);
            
        }
        public void set(int index,Object data){
            if(index >= this.count){ // 索引不存在
                return null;
            }
            this.foot = 0;// 查詢之前執行一次清零操作
            this.root.setNode(index,data);
            
        }
    }
    public class linkedList{
        public static void main(String args[]){
            Link all = new LinkImpl();
            System.out.println(all.isEmpty());
            all.add("A");
            all.add("B");
            all.add(<