LinkedList源碼解析

LinkedList特點(diǎn)總結(jié)

  • LinkedList實(shí)現(xiàn)List接口,使用雙向鏈表實(shí)現(xiàn)陆盘,元素可以是null
  • 可以被當(dāng)作堆棧,隊(duì)列冒版,雙端隊(duì)列使用
  • 因其使用鏈表實(shí)現(xiàn),查詢需要遍歷O(n)時(shí)間復(fù)雜度逞姿,插入時(shí)不再需要復(fù)制移動(dòng)元素O(1)時(shí)間復(fù)雜度
  • 類中的iterator()方法和listIterator()方法返回的iterators迭代器是fail-fast的:當(dāng)某一個(gè)線程A通過iterator去遍歷某集合的過程中辞嗡,若該集合的內(nèi)容被其他線程所改變了;那么線程A訪問集合時(shí)滞造,就會(huì)拋出ConcurrentModificationException異常续室,產(chǎn)生fail-fast事件
  • 和ArrayList一樣,不是線程安全的谒养,可以使用Collections.synchronizedList()變成支持并發(fā)的容器

LinkedList是由一個(gè)雙向鏈表來維護(hù)的挺狰,對(duì)于增刪改查元素理解最清晰地理解就是畫一張圖

源碼分析

考慮到之前是直接拷貝jdk源碼在源碼上通過注釋的方式進(jìn)行解讀,這樣看起來會(huì)比較雜亂蝴光,所以這次采用分塊解讀的方式

  1. 節(jié)點(diǎn)類

因?yàn)椴捎面湵淼姆绞綄?shí)現(xiàn),所以需要先定義一個(gè)結(jié)點(diǎn)類达址,源碼中使用靜態(tài)內(nèi)部類的方式定義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;
        }
    }

可以看到定義的是一個(gè)雙向鏈表的Node類蔑祟,next指向其下一個(gè)Node,prev指向其上一個(gè)Node

  1. LinkedList的成員變量

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

size是當(dāng)前List中的元素個(gè)數(shù),first和last都是Node類型的變量沉唠,分別指向鏈表的第一個(gè)節(jié)點(diǎn)和最后一個(gè)節(jié)點(diǎn)疆虚,這三個(gè)變量都是transient,說明不希望進(jìn)行序列化

  1. 構(gòu)造方法
    public LinkedList() {
    }

    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

第一個(gè)構(gòu)造方法創(chuàng)建了一個(gè)空的LinkedList,第二個(gè)構(gòu)造方法是通過一個(gè)Collection接口的實(shí)現(xiàn)類對(duì)象進(jìn)行初始化径簿,首先調(diào)用第一個(gè)構(gòu)造方法創(chuàng)建一個(gè)空的LinkedList然后將參數(shù)指向的Collection中的元素添加到LinkedList中罢屈,元素的順序是Collection的iterator返回的順序

  1. 增加元素

LinkedList提供了頭插入addFirst(E e)、尾插入addLast(E e)篇亭、add(E e)缠捌、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)译蒂、add(int index, E element)這些添加元素的方法

源碼中提供了幾種增加元素的方法

  public boolean add(E e) {
        linkLast(e);
        return true;
    }

首先看add方法曼月,add方法中調(diào)用了linkLast方法,可以看出來是在list的末尾添上一個(gè)Node柔昼,這個(gè)方法與源碼中的addLast方法效果是相同的,只是add方法是有返回的哑芹,凡是返回比較雞肋

   public void addLast(E e) {
        linkLast(e);
    }

順藤摸瓜,我們來看一個(gè)linkLast方法

   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;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
  • 首先通過last指針找到list的最后一個(gè)node捕透,然后創(chuàng)建一個(gè)新的node聪姿,這個(gè)newNode的next指向null,prev指向鏈表的最后一個(gè)節(jié)點(diǎn)
  • 將鏈表的last指向創(chuàng)建的newNode
  • 判斷之前找到的last node 如果該node是null說明原來list中沒有元素乙嘀,將創(chuàng)建的newNode賦值給first表示新node是最后一個(gè)元素也是第一個(gè)元素末购,如果l不是null,表示原來的list中有元素乒躺,此時(shí)將l的next指向newNode
  • 最后需要將size++ 表示list元素多了一個(gè)

除了在list的尾巴上添加元素招盲,jdk源碼中還提供了在鏈表頭上添加元素的方法

   public void addFirst(E e) {
       linkFirst(e);
   }
  
   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;
       else
           f.prev = newNode;
       size++;
       modCount++;
   }

addFirst會(huì)直接調(diào)用linkFirst完成插入操作,和上面linkLast方法其實(shí)是相仿的

  • 首先找到first指向的元素嘉冒,有可能是一個(gè)null
  • 創(chuàng)建一個(gè)新的node曹货,其prev指向null,因?yàn)閚ewNode是鏈表的第一個(gè)元素
  • first指向newNode
  • 如果f指向null讳推,表示鏈表原先是沒有元素的顶籽,那么newNode就是當(dāng)前唯一的元素,last也要指向newNode银觅,如果f不是null礼饱,表示原先鏈表中就有元素,需要將f指向的(此時(shí)是鏈表的第二個(gè)元素)的prev指向newNode

LinkedList同時(shí)也提供了指定index插入元素

    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

  • 首先進(jìn)行index的檢查究驴,保證index在[0,size]之間镊绪,如果是非法的index就拋出一個(gè)異常
  • 如果index==size 就是在鏈表的最后進(jìn)行添加
  • 否則調(diào)用linkBefore方法,完成元素添加
   Node<E> node(int index) {
       // assert isElementIndex(index); 

       if (index < (size >> 1)) {
           Node<E> x = 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;
       }
   }

node(int index)這個(gè)方法返回在鏈表index位置上的node,返回的node是一個(gè)不為null的node洒忧,// assert isElementIndex(index);被注釋掉是因?yàn)榍懊嬉呀?jīng)進(jìn)行index == size 判斷就不再這邊進(jìn)行判斷了蝴韭,此外需要注意的是這邊使用了一個(gè)小技巧

  • 如果index屬于鏈表的前面一半 index < (size >> 1) 就從first指向的元素進(jìn)行遍歷
  • 如果index屬于鏈表的后面一半元素 就從last指向的元素進(jìn)行遍歷
  • 返回node
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

linkBefore方法是最終完成插入操作的方法,同樣succ熙侍!=null也被注釋掉了榄鉴,因?yàn)樯厦鎛ode方法返回的是一個(gè)不為null的node履磨,linkBefore其實(shí)是一個(gè)雙向鏈表的插入操作

  • 首先找到succ這個(gè)node prev指針指向的node,即succ左手邊的一個(gè)node
  • 創(chuàng)建一個(gè)新的node庆尘,這個(gè)newNode prev指向pred剃诅,next指向succ
  • 因?yàn)閖dk中在add(index,e)中僅對(duì)index==size的情況做了特殊處理,并沒有對(duì)index==0的時(shí)候做出特殊處理驶忌,即在第一個(gè)位置進(jìn)行插入矛辕,此時(shí)pred會(huì)是null,當(dāng)pred == null時(shí)表示index == 0在第一個(gè)位置進(jìn)行插入了位岔,pred右手邊指針需要指向newNode如筛,其他情況下pred.next指向newNode即可
  1. 訪問元素

LinkedList提供了getFirst()、getLast()抒抬、contains(Object o)杨刨、get(int index)、indexOf(Object o)擦剑、lastIndexOf(Object o)這些查找元素的方法

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

首先進(jìn)行index檢查妖胀,然后通過node(index)獲取index下的元素,最后通過node.item獲取到值惠勒,需要注意index檢查的時(shí)候與add index檢查的方法存在一點(diǎn)點(diǎn)細(xì)微的區(qū)別:index >= 0 && index < size;這里index必須在[0,size-1]

jdk還提供了一些取特殊位置元素的方法

    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;
    }
  1. 刪除元素

LinkedList提供了頭刪除removeFirst()赚抡、尾刪除removeLast()、remove(int index)纠屋、remove(Object o)涂臣、clear()這些刪除元素的方法

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

首先index檢查與上面get中的方式相同,然后調(diào)用找到index指向的元素售担,調(diào)用unlink完成刪除

    E unlink(Node<E> x) {
        // assert x != null;
        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;
        }

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

        x.item = null;
        size--;
        modCount++;
        return element;
    }

因?yàn)閚ode(index)方法返回一個(gè)非null的node赁遗,所以這邊把 x != null注釋掉了,unlink是一個(gè)雙向鏈表刪除的操作

  • 首先獲取x的值族铆,值作為最后的返回
  • 分別獲取x next和prev指向的元素
  • 如果prev==null 表示要?jiǎng)h除的是第一個(gè)元素岩四,同樣,如果next == null表示要?jiǎng)h除的是最后一個(gè)元素
  • 如果prev==null 將first指向鏈表的第二個(gè)元素即next指向的元素哥攘,否則prev.next = next;x.prev = null;
  • 如果next==null 表示要?jiǎng)h除的是最后一個(gè)元素剖煌,last = prev;表示將x前面的一個(gè)元素作為鏈表的最后一個(gè)元素,否則 next.prev = prev;x.next = null;
  • 最后x.item = null size-- modCount++
    最好的方式還是畫一張圖幫助理解

同樣的逝淹,jdk也提供了一些在特殊位置刪除元素的方法

    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);
    }
        private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

不詳細(xì)展開了耕姊,需要注意的是比如刪除第一個(gè)節(jié)點(diǎn)的時(shí)候需要注意鏈表只有一個(gè)元素的情況

  1. 修改元素的值
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

需要注意最后返回原先的值

  1. 其他常見操作
    public int size() {
        return size;
    }

返回當(dāng)前鏈表中元素的個(gè)數(shù)

   public boolean contains(Object o) {
        return indexOf(o) != -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;
    }

    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;
    }

返回元素o在鏈表中的index,準(zhǔn)確說應(yīng)該是第一個(gè)o的index栅葡,與ArrayList中處理的方式是一樣的茉兰,遍歷的時(shí)候?qū)ull和non-null做了判斷

  1. Queue操作

Queue操作提供了peek()、element()妥畏、poll()邦邦、remove()、offer(E e)這些方法

    //獲取但不移除此隊(duì)列的頭醉蚁;如果此隊(duì)列為空燃辖,則返回 null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //獲取但不移除此隊(duì)列的頭;如果此隊(duì)列為空网棍,則拋出NoSuchElementException異常
    public E element() {
        return getFirst();
    }

    //獲取并移除此隊(duì)列的頭黔龟,如果此隊(duì)列為空,則返回 null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //獲取并移除此隊(duì)列的頭滥玷,如果此隊(duì)列為空氏身,則拋出NoSuchElementException異常
    public E remove() {
        return removeFirst();
    }

    //將指定的元素值(E e)插入此列表末尾
    public boolean offer(E e) {
        return add(e);
    }

Deque操作提供了offerFirst(E e)、offerLast(E e)惑畴、peekFirst()蛋欣、peekLast()、pollFirst()如贷、pollLast()陷虎、push(E e)、pop()杠袱、removeFirstOccurrence(Object o)尚猿、removeLastOccurrence(Object o)這些方法

 //獲取但不移除此隊(duì)列的頭;如果此隊(duì)列為空楣富,則返回 null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //獲取但不移除此隊(duì)列的頭凿掂;如果此隊(duì)列為空,則拋出NoSuchElementException異常
    public E element() {
        return getFirst();
    }

    //獲取并移除此隊(duì)列的頭纹蝴,如果此隊(duì)列為空庄萎,則返回 null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //獲取并移除此隊(duì)列的頭,如果此隊(duì)列為空骗灶,則拋出NoSuchElementException異常
    public E remove() {
        return removeFirst();
    }

    //將指定的元素值(E e)插入此列表末尾
    public boolean offer(E e) {
        return add(e);
    }

    // Deque operations

    //將指定的元素插入此雙端隊(duì)列的開頭
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

    //將指定的元素插入此雙端隊(duì)列的末尾
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

    //獲取惨恭,但不移除此雙端隊(duì)列的第一個(gè)元素;如果此雙端隊(duì)列為空耙旦,則返回 null
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

    //獲取脱羡,但不移除此雙端隊(duì)列的最后一個(gè)元素;如果此雙端隊(duì)列為空免都,則返回 null
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

    //獲取并移除此雙端隊(duì)列的第一個(gè)元素锉罐;如果此雙端隊(duì)列為空,則返回 null
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //獲取并移除此雙端隊(duì)列的最后一個(gè)元素绕娘;如果此雙端隊(duì)列為空脓规,則返回 null
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

    //將一個(gè)元素推入此雙端隊(duì)列所表示的堆棧(換句話說,此雙端隊(duì)列的頭部)
    public void push(E e) {
        addFirst(e);
    }

    //從此雙端隊(duì)列所表示的堆棧中彈出一個(gè)元素(換句話說险领,移除并返回此雙端隊(duì)列的頭部)
    public E pop() {
        return removeFirst();
    }

    //從此雙端隊(duì)列移除第一次出現(xiàn)的指定元素侨舆,如果列表中不包含次元素秒紧,則沒有任何改變
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

    //從此雙端隊(duì)列移除最后一次出現(xiàn)的指定元素,如果列表中不包含次元素,則沒有任何改變
    public boolean removeLastOccurrence(Object o) {
        //由于LinkedList中允許存放null挨下,因此下面通過兩種情況來分別處理
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) { //逆向向前
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
  1. 遍歷方式
  • 使用迭代器iterator遍歷
Iterator iter = list.iterator();
   while (iter.hasNext())
   {
       System.out.println(iter.next());
   }    
  • 使用listIterator遍歷
ListIterator<String> lIter = list.listIterator();
  //順向遍歷
  while(lIter.hasNext()){
      System.out.println(lIter.next());
  }
  //逆向遍歷
  while(lIter.hasPrevious()){
      System.out.println(lIter.previous());
  }
  • 使用foreach遍歷
 for(String str:list)
    {
        System.out.println(str);
    }    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熔恢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臭笆,更是在濱河造成了極大的恐慌叙淌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愁铺,死亡現(xiàn)場(chǎng)離奇詭異鹰霍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茵乱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門茂洒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓶竭,你說我怎么就攤上這事获黔。” “怎么了在验?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵玷氏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我腋舌,道長(zhǎng)盏触,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任块饺,我火速辦了婚禮赞辩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘授艰。我一直安慰自己辨嗽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布淮腾。 她就那樣靜靜地躺著糟需,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谷朝。 梳的紋絲不亂的頭發(fā)上洲押,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音圆凰,去河邊找鬼杈帐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挑童。 我是一名探鬼主播累铅,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼站叼!你這毒婦竟也來了争群?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤大年,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后玉雾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翔试,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年复旬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垦缅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驹碍,死狀恐怖壁涎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情志秃,我是刑警寧澤怔球,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站浮还,受9級(jí)特大地震影響竟坛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钧舌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一担汤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洼冻,春花似錦崭歧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屋彪,卻和暖如春播掷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撼班。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工歧匈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人砰嘁。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓件炉,卻偏偏與公主長(zhǎng)得像勘究,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斟冕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容