黄连木浸种水的温度多少度为宜?

小说:黄连木浸种水的温度多少度为宜?作者:海宗更新时间:2019-05-23字数:78930

上一节,我们学习了ArrayList 类,本节我们来学习一下LinkedList,LinkedList相对ArrayList而言其使用频率并不是很高,因为其访问元素的性能相对于ArrayList而言比较慢,至于原因我们下面讲开始讲解,本节重点是了解其内部的结构,会简单实现一个简单的LinkedList 即可。

一、LinkedList的简单使用

任何代码在深入分析前,首先需要会使用,因此我们先看下基本的使用列子:

package study.collection;

import java.util.LinkedList;


public class TestLinkedList {
    
    public static void main(String[] args)
    {
        // 测试LinkedList的API
        testLinkedListAPIs() ;

        // 将LinkedList当作 LIFO(后进先出)的堆栈
        useLinkedListAsLIFO();

        // 将LinkedList当作 FIFO(先进先出)的队列
        useLinkedListAsFIFO();
    }
    
    /*
     * 测试LinkedList中部分API
     */
    private static void testLinkedListAPIs() {
        String val = null;
        //LinkedList llist;
        //llist.offer("10");
        // 新建一个LinkedList
        LinkedList llist = new LinkedList();
        //---- 添加操作 ----
        // 依次添加1,2,3
        llist.add("1");
        llist.add("2");
        llist.add("3");

        // 将“4”添加到第一个位置
        llist.add(1, "4");
        

        System.out.println("
Test "addFirst(), removeFirst(), getFirst()"");
        // (01) 将“10”添加到第一个位置。  失败的话,抛出异常!
        llist.addFirst("10");
        System.out.println("llist:"+llist);
        // (02) 将第一个元素删除。        失败的话,抛出异常!
        System.out.println("llist.removeFirst():"+llist.removeFirst());
        System.out.println("llist:"+llist);
        // (03) 获取第一个元素。          失败的话,抛出异常!
        System.out.println("llist.getFirst():"+llist.getFirst());


        System.out.println("
Test "offerFirst(), pollFirst(), peekFirst()"");
        // (01) 将“10”添加到第一个位置。  返回true。
        llist.offerFirst("10");
        System.out.println("llist:"+llist);
        // (02) 将第一个元素删除。        失败的话,返回null。
        System.out.println("llist.pollFirst():"+llist.pollFirst());
        System.out.println("llist:"+llist);
        // (03) 获取第一个元素。          失败的话,返回null。
        System.out.println("llist.peekFirst():"+llist.peekFirst());
    

        System.out.println("
Test "addLast(), removeLast(), getLast()"");
        // (01) 将“20”添加到最后一个位置。  失败的话,抛出异常!
        llist.addLast("20");
        System.out.println("llist:"+llist);
        // (02) 将最后一个元素删除。        失败的话,抛出异常!
        System.out.println("llist.removeLast():"+llist.removeLast());
        System.out.println("llist:"+llist);
        // (03) 获取最后一个元素。          失败的话,抛出异常!
        System.out.println("llist.getLast():"+llist.getLast());


        System.out.println("
Test "offerLast(), pollLast(), peekLast()"");
        // (01) 将“20”添加到第一个位置。  返回true。
        llist.offerLast("20");
        System.out.println("llist:"+llist);
        // (02) 将第一个元素删除。        失败的话,返回null。
        System.out.println("llist.pollLast():"+llist.pollLast());
        System.out.println("llist:"+llist);
        // (03) 获取第一个元素。          失败的话,返回null。
        System.out.println("llist.peekLast():"+llist.peekLast());

         

        // 将第3个元素设置300。不建议在LinkedList中使用此操作,因为效率低!
        llist.set(2, "300");
        // 获取第3个元素。不建议在LinkedList中使用此操作,因为效率低!
        System.out.println("
get(3):"+llist.get(2));


        // ---- toArray(T[] a) ----
        // 将LinkedList转行为数组
        String[] arr = (String[])llist.toArray(new String[0]);
        for (String str:arr) 
            System.out.println("str:"+str);

        // 输出大小
        System.out.println("size:"+llist.size());
        // 清空LinkedList
        llist.clear();
        // 判断LinkedList是否为空
        System.out.println("isEmpty():"+llist.isEmpty()+"
");

    }

    /**
     * 将LinkedList当作 LIFO(后进先出)的堆栈
     */
    private static void useLinkedListAsLIFO() {
        System.out.println("
useLinkedListAsLIFO");
        // 新建一个LinkedList
        LinkedList stack = new LinkedList();

        // 将1,2,3,4添加到堆栈中
        stack.push("1");
        stack.push("2");
        stack.push("3");
        stack.push("4");
        // 打印“栈”
        System.out.println("stack:"+stack);

        // 删除“栈顶元素”
        System.out.println("stack.pop():"+stack.pop());
        
        // 取出“栈顶元素”
        System.out.println("stack.peek():"+stack.peek());

        // 打印“栈”
        System.out.println("stack:"+stack);
    }

    /**
     * 将LinkedList当作 FIFO(先进先出)的队列
     */
    private static void useLinkedListAsFIFO() {
        System.out.println("
useLinkedListAsFIFO");
        // 新建一个LinkedList
        LinkedList queue = new LinkedList();

        // 将10,20,30,40添加到队列。每次都是插入到末尾
        queue.add("10");
        queue.add("20");
        queue.add("30");
        queue.add("40");
        // 打印“队列”
        System.out.println("queue:"+queue);

        // 删除(队列的第一个元素)
        System.out.println("queue.remove():"+queue.remove());
    
        // 读取(队列的第一个元素)
        System.out.println("queue.element():"+queue.element());

        // 打印“队列”
        System.out.println("queue:"+queue);
    }
}

 从上面的代码可以看出,LinkedList的功能非常多,既可以用于存放元素的集合功能,还具备了堆栈的功能,还拥有队列的功能。这均源于其不仅仅实现了List接口还是实现了Queue接口。

但在介绍LinkedList接口前,从名字可以看出LinkedList 即底层采用的是链表的结构,那什么是链表,需要提前有一个认识:顾名思义,链表就和链子一样,每一环都要连接着后边的一环和前边的一环,这样,当我们需要找这根链子的某一环的时候,只要我们能找到链子的任意一环,都可以找到我们需要的那一环。我们看一个图,就能很好的理解了。

在LinkedList中,我们把链子的“环”叫做“节点”,每个节点都是同样的结构。节点与节点之间相连,构成了我们LinkedList的基本数据结构,也是LinkedList的核心。链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表,下面简单就这四种链表进行图解说明。

 1.1.单向链表
          单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。

 

1. 2.单向循环链表

  单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点,形成一个“环”。

 1. 3.双向链表

  从名字就可以看出,双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

1. 4.双向循环链表
          双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。

更形象的解释下就是:双向循环链表就像一群小孩手牵手围成一个圈,第一个小孩的右手拉着第二个小孩的左手,第二个小孩的左手拉着第一个小孩的右手。。。最后一个小孩的右手拉着第一个小孩的左手。【接下来我们进入源代码的分析,说明linkedlist 我们采用的JDK1.8进行分析,只所以不用1.6是因为,在1.6  1.7 的时候进行过变动将1.6中的环形结构优化为了直线型了链表结构,然后回到了1.8又有部分的变动,因为从上面可以看出链表的由4种不向ArrayList单一演进,所以这里直接选择用1.8来分析】

 二、LinkedList的概述

LinkedList是List和Deque接口的双向链表的实现。实现了所有可选列表操作,并允许包括null值。

LinkedList既然是通过双向链表去实现的,那么它可以被当作堆栈、队列或双端队列进行操作。并且其顺序访问非常高效,而随机访问效率比较低。

注意,此实现不是同步的。 如果多个线程同时访问一个LinkedList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。这通常是通过同步那些用来封装列表的 对象来实现的。但如果没有这样的对象存在,则该列表需要运用{@link Collections#synchronizedList Collections.synchronizedList}来进行“包装”,该方法最好是在创建列表对象时完成,为了避免对列表进行突发的非同步操作。

List list = Collections.synchronizedList(new LinkedList(...));

类中的iterator()方法和listIterator()方法返回的iterators迭代器是fail-fast的:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

1.Node节点

private static class Node<E> {
        E item;    // 当前节点所包含的值
        Node<E> next;   //下一个节点
        Node<E> prev;    //上一个节点

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

2.LinkedList类结构

//通过LinkedList实现的接口可知,其支持队列操作,双向列表操作,能被克隆,支持序列化
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // LinkedList的大小(指其所含的元素个数)
    transient int size = 0;

    /**
     * 指向第一个节点
     * 不变的: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * 指向最后一个节点
     * 不变的: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    ......
}

 

LinkedList包含了三个重要的对象first、last 和 size。

(1) first 是双向链表的表头,它是双向链表节点所对应的类Node的实例

(2) last 是双向链表的最后一个元素,它是双向链表节点所对应的类Node的实例

(3) size 是双向链表中节点的个数。

3.构造函数

LinkedList提供了两种种方式的构造器,构造一个空列表、以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的。

//构建一个空列表
    public LinkedList() {
    }

    /**
     * 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的
     * @param  c 包含用于去构造LinkedList的元素的collection
     * @throws NullPointerException 如果指定的collection为空
     */
    //构建一个包含指定集合c的列表
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

三、LinkedList 方法功能源码分析

/**
 * LinkedList底层使用双向链表,实现了List和deque。实现所有的可选List操作,并可以只有所有元素(包括空值)
 * 其大小理论上仅受内存大小的限制
 *
 * 所有的操作都可以作为一个双联列表来执行(及对双向链表操作)。
 * 把对链表的操作封装起来,并对外提供看起来是对普通列表操作的方法。
 * 遍历从起点、终点、或指定位置开始
 * 内部方法,注释会描述为节点的操作(如删除第一个节点),公开的方法会描述为元素的操作(如删除第一个元素)
 *
 * LinkedList不是线程安全的,如果在多线程中使用(修改),需要在外部作同步处理。
 * 
 * 需要弄清元素(节点)的索引和位置的区别,不然有几个地方不好理解,具体在碰到的地方会解释。
 * 
 * 迭代器可以快速报错
 */
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    //容量
    transient int size = 0;
    //首节点
    transient Node<E> first;
    //尾节点
    transient Node<E> last;
    //默认构造函数
    public LinkedList() {
    }
    //通过一个集合初始化LinkedList,元素顺序有这个集合的迭代器返回顺序决定
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    //使用对应参数作为第一个节点,内部使用
    private void linkFirst(E e) {
        final Node<E> f = first;//得到首节点
        final Node<E> newNode = new Node<>(null, e, f);//创建一个节点
        first = newNode;        //设置首节点
        if (f == null)
            last = newNode;     //如果之前首节点为空(size==0),那么尾节点就是首节点
        else
            f.prev = newNode;   //如果之前首节点不为空,之前的首节点的前一个节点为当前首节点
        size++;                 //长度+1
        modCount++;             //修改次数+1
    }
    //使用对应参数作为尾节点
    void linkLast(E e) {
        final Node<E> l = last; //得到尾节点
        final Node<E> newNode = new Node<>(l, e, null);//使用参数创建一个节点
        last = newNode;         //设置尾节点
        if (l == null)
            first = newNode;    //如果之前尾节点为空(size==0),首节点即尾节点
        else
            l.next = newNode;   //如果之前尾节点不为空,之前的尾节点的后一个就是当前的尾节点
        size++;
        modCount++;
    }
    //在指定节点前插入节点,节点succ不能为空
    void linkBefore(E e, Node<E> succ) {
        final Node<E> pred = succ.prev;//获取前一个节点
        final Node<E> newNode = new Node<>(pred, e, succ);//使用参数创建新的节点,向前指向前一个节点,向后指向当前节点
        succ.prev = newNode;//当前节点指向新的节点
        if (pred == null)
            first = newNode;//如果前一个节点为null,新的节点就是首节点
        else
            pred.next = newNode;//如果存在前节点,那么前节点的向后指向新节点
        size++;
        modCount++;
    }
    //删除首节点并返回删除前首节点的值,内部使用
    private E unlinkFirst(Node<E> f) {
        final E element = f.item;//获取首节点的值
        final Node<E> next = f.next;//得到下一个节点
        f.item = null;
        f.next = null;      //便于垃圾回收期清理
        first = next;       //首节点的下一个节点成为新的首节点
        if (next == null)
            last = null;    //如果不存在下一个节点,则首尾都为null(空表)
        else
            next.prev = null;//如果存在下一个节点,那它向前指向null
        size--;
        modCount++;
        return element;
    }
    //删除尾节点并返回删除前尾节点的值,内部使用
    private E unlinkLast(Node<E> l) {
        final E element = l.item;//获取值
        final Node<E> prev = l.prev;//获取尾节点前一个节点
        l.item = null;
        l.prev = null;      //便于垃圾回收期清理
        last = prev;        //前一个节点成为新的尾节点
        if (prev == null)
            first = null;   //如果前一个节点不存在,则首尾都为null(空表)
        else
            prev.next = null;//如果前一个节点存在,先后指向null
        size--;
        modCount++;
        return element;
    }
    //删除指定节点并返回被删除的元素值
    E unlink(Node<E> x) {
        //获取当前值和前后节点
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        if (prev == null) {
            first = next;   //如果前一个节点为空(如当前节点为首节点),后一个节点成为新的首节点
        } else {
            prev.next = next;//如果前一个节点不为空,那么他先后指向当前的下一个节点
            x.prev = null;  //方便gc回收
        }
        if (next == null) {
            last = prev;    //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
        } else {
            next.prev = prev;//如果后一个节点不为空,后一个节点向前指向当前的前一个节点
            x.next = null;  //方便gc回收
        }
        x.item = null;      //方便gc回收
        size--;
        modCount++;
        return element;
    }
    //获取第一个元素
    public E getFirst() {
        final Node<E> f = first;//得到首节点
        if (f == null)          //如果为空,抛出异常
            throw new NoSuchElementException();
        return f.item;
    }
    //获取最后一个元素
    public E getLast() {
        final Node<E> l = last;//得到尾节点
        if (l == null)          //如果为空,抛出异常
            throw new NoSuchElementException();
        return l.item;
    }
    //删除第一个元素并返回删除的元素
    public E removeFirst() {
        final Node<E> f = first;//得到第一个节点
        if (f == null)          //如果为空,抛出异常
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    //删除最后一个元素并返回删除的值
    public E removeLast() {
        final Node<E> l = last;//得到最后一个节点
        if (l == null)          //如果为空,抛出异常
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    //添加元素作为第一个元素
    public void addFirst(E e) {
        linkFirst(e);
    }
    //店家元素作为最后一个元素
    public void addLast(E e) {
        linkLast(e);
    }
    //检查是否包含某个元素,返回bool
    public boolean contains(Object o) {
        return indexOf(o) != -1;//返回指定元素的索引位置,不存在就返回-1,然后比较返回bool值
    }
    //返回列表长度
    public int size() {
        return size;
    }
    //添加一个元素,默认添加到末尾作为最后一个元素
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    //删除指定元素,默认从first节点开始,删除第一次出现的那个元素
    public boolean remove(Object o) {
        //会根据是否为null分开处理。若值不是null,会用到对象的equals()方法
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    //添加指定集合的元素到列表,默认从最后开始添加
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);//size表示最后一个位置,可以理解为元素的位置分别为1~size
    }
    //从指定位置(而不是下标!下标即索引从0开始,位置可以看做从1开始,其实也是0)后面添加指定集合的元素到列表中,只要有至少一次添加就会返回true
    //index换成position应该会更好理解,所以也就是从索引为index(position)的元素的前面索引为index-1的后面添加!
    //当然位置可以为0啊,为0的时候就是从位置0(虽然它不存在)后面开始添加嘛,所以理所当前就是添加到第一个位置(位置1的前面)的前面了啊!
    //比如列表:0 1 2 3,如果此处index=4(实际索引为3),就是在元素3后面添加;如果index=3(实际索引为2),就在元素2后面添加。
    //原谅我的表达水平,我已经尽力解释了...
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);  //检查索引是否正确(0<=index<=size)
        Object[] a = c.toArray();   //得到元素数组
        int numNew = a.length;      //得到元素个数
        if (numNew == 0)            //若没有元素要添加,直接返回false
            return false;
        Node<E> pred, succ;
        if (index == size) {    //如果是在末尾开始添加,当前节点后一个节点初始化为null,前一个节点为尾节点
            succ = null;        //这里可以看做node(index),不过index=size了(index最大只能是size-1),所以这里的succ只能=null,也方便后面判断
            pred = last;        //这里看做noede(index-1),当然实现是不能这么写的,看做这样只是为了好理解,所以就是在node(index-1的后面开始添加元素)
        } else {                //如果不是从末尾开始添加,当前位置的节点为指定位置的节点,前一个节点为要添加的节点的前一个节点
            succ = node(index); //添加好元素后(整个新加的)的后一个节点
            pred = succ.prev;   //这里依然是node(index-1)
        }
        //遍历数组并添加到列表中
        for (Object o : a) {
            @SuppressWarnings("unchecked")
            E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);//创建一个节点,向前指向上面得到的前节点
            if (pred == null)
                first = newNode;    //若果前节点为null,则新加的节点为首节点
            else
                pred.next = newNode;//如果存在前节点,前节点会向后指向新加的节点
            pred = newNode;         //新加的节点成为前一个节点
        }
        if (succ == null) {
            //pred.next = null  //加上这句也可以更好的理解
            last = pred;        //如果是从最后开始添加的,则最后添加的节点成为尾节点
        } else {
            pred.next = succ;   //如果不是从最后开始添加的,则最后添加的节点向后指向之前得到的后续第一个节点
            succ.prev = pred;   //当前,后续的第一个节点也应改为向前指向最后一个添加的节点
        }
        size += numNew;
        modCount++;
        return true;
    }
    //清空表
    public void clear() {
        //方便gc回收垃圾
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }
    //获取指定索引的节点的值
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    //修改指定索引的值并返回之前的值
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
    //指定位置后面(即索引为这个值的元素的前面)添加元素
    public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size)
            linkLast(element);  //如果指定位置为最后,则添加到链表最后
        else                    //如果指定位置不是最后,则添加到指定位置前
            linkBefore(element, node(index));
    }
    //删除指定位置的元素,
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    //检查索引是否超出范围,因为元素索引是0~size-1的,所以index必须满足0<=index<size
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
    //检查位置是否超出范围,index必须在index~size之间(含),如果超出,返回false
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
    //异常详情
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }
    //检查元素索引是否超出范围,若已超出,就抛出异常
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //检查位置是否超出范围,若已超出,就抛出异常
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //获取指定位置的节点
    Node<E> node(int index) {
        //如果位置索引小于列表长度的一半(或一半减一),从前面开始遍历;否则,从后面开始遍历
        if (index < (size >> 1)) {
            Node<E> x = first;//index==0时不会循环,直接返回first
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    //获取指定元素从first开始的索引位置,不存在就返回-1
    //不能按条件双向找了,所以通常根据索引获得元素的速度比通过元素获得索引的速度快
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }
    //获取指定元素从first开始最后出现的索引,不存在就返回-1
    //但实际查找是从last开始的
    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }
    //提供普通队列和双向队列的功能,当然,也可以实现栈,FIFO,FILO
    //出队(从前端),获得第一个元素,不存在会返回null,不会删除元素(节点)
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    //出队(从前端),不删除元素,若为null会抛出异常而不是返回null
    public E element() {
        return getFirst();
    }
    //出队(从前端),如果不存在会返回null,存在的话会返回值并移除这个元素(节点)
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    //出队(从前端),如果不存在会抛出异常而不是返回null,存在的话会返回值并移除这个元素(节点)
    public E remove() {
        return removeFirst();
    }
    //入队(从后端),始终返回true
    public 5公分到16公分榉树,海量现货 山东红叶小檗种苗行情高吗? 冬天移栽木香苗成活率怎么样? 烟花波浪是爬藤月季吗? 木瓜树哪里有卖? 为什么美国凌霄不开花呢? 紫藤花在北方能开放吗? 北京可以栽植八宝景天吗? 广东的红花草都在哪里买的? 2017年2月17日最新垂柳报价

91863 14600 43241 66948 54615 70061 86700 85966 79145 75303 25811 11151 31671 95808 17314 17270 82637 23798 41245 44482 59113 50006 93722

我要说两句: (0人参与)

发布