Mercurial > hg > Papers > 2017 > tatsuki-master
view jungle.tex @ 37:14267d61122d
commit
author | tatsuki |
---|---|
date | Sun, 19 Feb 2017 10:19:11 +0900 |
parents | 4365c210d1cb |
children |
line wrap: on
line source
\chapter{非破壊的木構造データベースの利点} 本章では、非破壊的木構造を選択した理由について述べる。 従来の破壊的木構造は親から子へのポインタと子供から親へのポインタを持つ二重リンク構造を持っていた。 これによりノードの挿入削除をO(1)で行うことができる。 しかし、変更前の木を保存することとは両立せず、変更中に前の版の木を使用することはできない。 そこで、子供から親へのポインタを無くし、木を共有しながら変更部分を付け加える非破壊的木構造を提案する。 これにより、木の変更はO(n)になるが、並列実行向けの複数の版が存在できるデータベースを実現することができる。 \section{破壊的木構造} 破壊的木構造は親から子までのポインタとともに、子から親へのポインタを持つ。 ノードの属性値を書き換える場合は、そのまま上書きすることが可能である。 ノードの追加を行うときには、行う位置の親と子供の両方のポインタを変更する。 この複数の変更を一つのトランザクションとして行う必要がある。 変更中は親と子をロックする必要がある。 平行度を上げるために、前の版の木にアクセスするためには複製を作る必要がある。 一つの方法は、常に二つの木を用意し交互に変更を行うものである。 変更自体はO(1)だが、平行実行下では複雑な工夫が必要になる。 \section{非破壊的木構造} データの編集を一度生成した木を上書きせず、ルートから編集を行う位置までノードをコピーする{図\ref{fig:nondestractive})。 この時に、子供からの親へのポインタは持たないとする。 木のルートをAtomicに置き換えることで、木のアップデートを行う。 変更前の木が残っているので、そのまま使用できる。 変更されないノードは変更前と変更後のルートから共有されることになる。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.7]{figures/non_destructive_tree.pdf} \caption{非破壊的木構造の編集} \label{fig:nondestractive} \end{center} \end{figure} 破壊的変更に比べて、ノードの作成量は増えるが複数の版が同時に存在できる(\ref{fig:nondestractive_merit})。 したがって、現在および過去の木を分散ノードまたはディスクに安全に格納することができる。 実際には、木自体ではなく木の変更Logを分散ノードまたはディスクに書き出すのが妥当である。 変更の計算量は最悪O(n)になるので、大量の挿入を行うときには工夫が必要になる。 木のルートの上に新しくノードを付け加える場合はO(1)となる。 この場合は木は追加順と逆順の構成を持つことになる。 木の末端に追加する場所を覚えておき、そこにAtomicにノードを追加するとO(1)で正順で追加することができる。 この場合は木が破壊的に変更されているように見えるが、前の版の末端部分を超えてアクセスすることがなければ複数の版を同時に使用することができる。 これをDifferential Treeと呼ぶ。 木構造自体をバランス木、例えば赤黒木とすることもできる。 これにより、O(log n)で木の変更を行うことができる。 しかし、この場合木構造自体を自由に構成することはできないが、赤黒木のKeyを用いて任意のノードにO(Log n)でアクセスすることができる。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.7]{figures/non_destructive_merit.pdf} \caption{非破壊的木構造による利点} \label{fig:nondestractive_merit} \end{center} \end{figure} また、過去の木を保持する場合、破壊的木構造は、木の複製後編集を行う必要がある。 一方、非破壊的木構造は過去の木を上書きしないため、過去の木のルートノードを保持するだけで良い。 \chapter{Jungleの構成要素} 本章では、Jungleの構成要素について説明する。 Jungleは名前を用いて木の生成と木の取得を行う。 木の中の特定のノードにアクセスするには、木の中のノードの位置を表すNodePathを用いる。 木の変更は非破壊的に行われ、木のルートを変更後の木に置き換えるAtomicOperationがトランザクションとなる。 木の変更はLogとして記録され、分散ノードあるいはディスクに格納される。 JungleのAPIは、Eitherを返すようになっており、Eitherの型をチェックすることにより成功と失敗がわかるようになっている。 \section{木の生成} Jungleにおける木の生成について述べる。 Jungleは複数の木構造を、名前を利用して作成・編集・削除を行い管理している。 Jungleクラスが提供している木の生成・管理を行うAPIを表\ref{jungleAPI}に記す。 \begin{table}[htb] \begin{center} \caption{Jungleに実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\small {\tt JungleTree createNewTree(String treeName)}} &{\small Jungleに新しく木を生成する。木の名前が重複した場合、生成に失敗しnullを返す。} \\ \hline {\small {\tt JungleTree getTreeByName(String treeName)}} &{\small JungleからtreeNameと名前が一致するtreeを取得する。名前が一致するTreeがない場合取得は失敗しnullを返す。} \\ \hline \end{tabular} \label{jungleAPI} \end{center} \end{table} \section{JungleTree} Jungleは複数の木の集合で出来ている。 Jungleの木は、自身の情報をTreeContext という木構造のデータを持つ(表\ref{TreeContext1})オブジェクトに保持している。 \newpage \begin{table}[htb] \begin{center} \caption{TreeContextが保持している値} \begin{tabular}{|l|} \hline {\tt } 自身のルートノード\\ \hline {\tt } 1つ前のバージョンのTreeContext\\ \hline {\tt } 編集のログ\\ \hline {\tt } 木のuuid \\ \hline {\tt } 木の名前 \\ \hline {\tt } 木のversion \\ \hline {\tt } 木の検索に使うTraverser \\ \hline \end{tabular} \label{TreeContext1} \end{center} \end{table} Jungle の木は、自身の木構造に編集を行う Editor や 検索に使用する Traverser を提供しており、ユーザーはそれを用いて木構造にアクセスする。 また、過去のバージョンの木に対するアクセスや、特定のノードのPathを検索する機能も持っている。 以下にJungleTreeクラスが提供しているAPI(表\ref{JungleTreeAPI})を記す \begin{table}[htb] \begin{center} \caption{JungleTreeに実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\small {\tt TreeNode getRootNode()}} &{\small 木のルートノードを取得する。} \\ \hline {\small {\tt long revision()}} & {\small 木のバーションを取得する。初めは0から始まり、木への変更がCommitされる度に1上昇する。} \\ \hline {\small {\tt JungleTreeEditor getJungleTreeEditor()}} &{\small 木へ変更を加えるEditorを取得する。}\\ \hline {\small {\tt Either<Error, JungleTree> getOldTree(long revision)}} & {\small 引数で指定したint revisionに等しいバージョンの木を取得する。} \\ \hline {\small {\tt InterfaceTraverser getTraverser()}} & {\small 木の検索を行うTraverserを取得する。} \\ \hline {\small {\tt Either<Error, TreeNode> getNodeOfPath(NodePath path)}} &{\small {\tt NodePathで指定した位置と値なるノードを取得する。}} \\ \hline {\small {\tt NodePath getNodePath(TreeNode node)}} & {\small 引数で渡したノードの位置を表す{\tt NodePath}を返す。}\\ \hline \end{tabular} \label{JungleTreeAPI} \end{center} \end{table} \section{Either} Jungleは、失敗する可能性のある関数では返り値を{ \tt Either<A、B>}に包んで返す。 AにはError、Bには処理に成功した際の返り値の型が入る。 Eitherは、AかBどちらかの値しか持たない。 以下にEitherクラスが提供しているAPI(表\ref{EitherAPI})を記す。 \begin{table}[htb] \begin{center} \caption{Eitherに実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\small{\tt boolean isA()}} & {\small EitherがAを持っているかどうかを調べる。持っている場合trueを返す。}\\ \hline {\small{\tt boolean isB()}} & {\small EitherがBを持っているかどうかを調べる。持っている場合trueを返す。}\\ \hline {\small{\tt A a()}} &{\small Aの値を取得する。}\\ \hline {\small{\tt B b()}} &{\small Bの値を取得する。}\\ \hline \end{tabular} \label{EitherAPI} \end{center} \end{table} {\tt Either<A、B>} の使い方は、{\tt isA()}を用いて関数が{\tt Error}を返していないかを調べる。 {\tt Error}でない場合は{\tt b()}で関数の返り値を取得する。 \section{TreeNode} Jungleの木構造は、複数のノードの集合で出来ている。 ノードは、自身の子のリストと属性名と属性値の組でデータを持つ。 ノードに対するアクセスは、表\ref{TreeNodeAPI}に記述されているAPIを用いて行われる。 \begin{table}[htb] \begin{center} \caption{TreeNodeに実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\small {\tt Children getChildren()}} &{\small ノードの子供を扱うChildrenオブジェクトを返す} \\ \hline {\small {\tt Attribute getAttribute()}} &{\small ノードが保持しているデータを扱うAttribteオブジェクトを返す。} \\ \hline \end{tabular} \label{TreeNodeAPI} \end{center} \end{table} Childrenクラスは表\ref{Children}に記述されたAPIを、Attributeクラスは表\ref{Attribute}に記述されたAPIを提供する。 これらを利用しノードが保持している値や、子供にアクセスする。 \begin{table}[htbH] \begin{center} \caption{Childrenに実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\small{\tt int size()}} & {\small 子供の数を返す。}\\ \hline {\small{\tt <Either Error,TreeNode> at(int num)}} &{\small ノードが持つ子供の中から、 変数{\tt num}で指定された位置にある子ノードを返す。存在しない位置を指定した場合、{\tt Error} を返す。} \\ \hline \end{tabular} \label{Children} \end{center} \end{table} \begin{table}[htbH] \begin{center} \caption{Attributeに実装されているAPI} \begin{tabular}{|p{15em}|p{24em}|} \hline {\small{\tt ByteBuffer get(String key)}} &{\small ノードが持つ値から、属性名 {\tt key}とペアの属性値を{\tt ByteBuffer}型で返す。ノードが保持していない{\tt key}を渡した場合エラーを返す。} \\ \hline {\small{\tt String getString(String key)}} &{\small ノードが持つ値から、属性名 {\tt key} とペアの属性値を{\tt String}型で返す。ノードが保持していない{\tt key}を渡した場合エラーを返す。} \\ \hline \end{tabular} \label{Attribute} \end{center} \end{table} \newpage 以下にルートノードの2番目の子供から、属性名 {\tt name}とペアになっている属性値を取得するサンプルコード\ref{getAttributeCode}を記述する。 \begin{lstlisting}[frame=lrbt,numbers=left,label=getAttributeCode] JungleTree tree = jungle.getTreeByName("TreeName"); TreeNode root = tree.getRootNode(); Children children = root.getChildren(); Either<Error,TreeNode> either = children.at(1); if (either.isA()) return either.a(); TreeNode child = either.b(); Attribute attribute = child.getAttribute(); String value = attribute.getString("name"); \end{lstlisting} ソースコード\ref{getAttributeCode}の説明を行う。 1行目で Jungle から木を取得し、 2行目で、取得した木のルートノードを取得している。 3 - 7行目でルートノードの1番目の子ノードを取得し、 8 - 9行目で、ルートの1番目の子ノードから、属性名 "name" とペアの属性値を取得している。 \section{NodePath} Jungleでは、木のノードの位置を{\tt NodePath}クラスを使って表す。 {\tt NodePath}クラスはルートノードからスタートし、対象のノードまでの経路を数字を用いて指し示す。また、ルートノードは例外として-1と表記される。 {\tt NodePath}クラスを用いて{\tt< -1,1,2,3>}を表している際の例を図\ref{NodePath}に記す。 \begin{figure}[htpb] \begin{center} \includegraphics[scale=0.7]{figures/nodePath.pdf} \caption{NodePath} \label{NodePath} \end{center} \end{figure} \newpage \section{木の編集API} Jungleの木の編集は{\tt Default Jungle Tree Editor}クラスを用いて行われる。 {\tt Default Jungle Tree Editor}クラスは、木に対する編集を行うAPIが定義されているJungle Tree Editorインターフェースを実装している。 Default Jungle Tree Editorは、Jungle Tree から、{\tt getTreeEditor()}を用いて取得する。 表\ref{editor}にJungle Tree Editorインターフェースに定義されているAPIを記述する。 また、表\ref{editor}に記述しているAPIは全て、{\tt Either<Error,JungleTreeEditor>} を返す。 \newpage \begin{table}[htb] \begin{center} \caption{Editorに実装されているAPI} \begin{tabular*}{\textwidth}{|p{15em}|p{22em}|} \hline {\small\tt addNewChildAt( NodePath path, int pos)} & {\small\tt path}で指定したノードの{\tt pos}番目の子の後にノードを追加する。\\ \hline {\small\tt deleteChildAt( NodePath path,int pos)} & {\small\tt path}で指定したノードの{\tt pos}番目の子ノードを削除する。 \\ \hline {\small\tt putAttribute( NodePath path,String key,ByteBuffer value)} & {\small\tt path}で指定したノードに属性名 {\tt key} 属性値 {\tt value} のペアで値を挿入する。 \\ \hline {\small\tt deleteAttribute( NodePath path,String key)}& {\small\tt path}で指定したノードが持つ、属性名 {\tt key}とペアで保存されている属性値を削除する。\\ \hline {\small\tt moveChild( NodePath path,int num,String move)} & {\small\tt path}で指定したノードの{\tt num}番目の子供を{\tt move}の方向に移動させる。 \\ \hline %あとで直す {\small\tt pushPop()} & ルートノードの上に新しいルートノードを追加する。線形の木を作る際に使用することで木の変更の手間をO(n)からO(1)にできる。\\ \hline {\small\tt success()} & 木へ行った変更をコミットする。自分が編集を行っていた間に、他のJungleTreeEditorクラスによって木が更新されていた場合、コミットは失敗する。 \\ \hline \end{tabular*} \label{editor} \end{center} \end{table} 編集後に返される{\tt Default Jungle Tree Editor}クラスは、編集後の木構造を保持しているため、編集前の木構造を保持している{\tt Default Jungle Tree Editor}クラスとは別のオブジェクトである。 編集を行った後は、関数{\tt editor.success()}で今までの編集をコミットすることができる。他の{\tt Default Jungle Tree Editor}クラスによって木が更新されていた場合はコミットは失敗し、{\tt success()}は{\tt Error}を返す。 その場合は、木の編集を最初からやり直す必要がある。 以下に{\tt JungleTreeEditor}クラスを用いて、木の編集を行うサンプルコードを記述する。 \begin{lstlisting}[frame=lrbt,numbers=left,label=editorCode] JungleTreeEditor editor = tree.getTreeEditor(); DefaultNodePath editNodePath = new DefaultNodePath(); Either<Error, JungleTreeEditor> either = editor.addNewChildAt(editNodePath, 0); if (either.isA()) return either.a(); editor = either.b(); editor.success(); \end{lstlisting} 1行目で、木から Editor を取得している。 2行目で、編集を行うノードの Path を作成している。Default Node Path は、生成時はルートノードを指しているため、今回はルートノードに対する変更であることがわかる。 3行目で、実際にルートノードに対して、子ノードの追加を行っている。 4行目以降で、編集が成功したかどうかを {\tt Either }を使って確かめ、成功していた場合7行目で変更を木に Commit している。 \begin{comment} \begin{enumerate} \item 関数{\tt tree.getEditor()}で編集を行う木から、{\tt JungleTreeEditor}クラスを取得する。 \item 次に変更するノードの場所を示す、{\tt NodePath}クラスを作成する。 \item 関数{\tt editor.addNewChildAt()}を用いて、変数{\tt path}で指定したノードの子供の0番目に子ノードを追加する。 \item 返り値の変数{\tt either}が{\tt Error}クラスを保持していないか(編集に失敗していないか)を確認する。 \item {\tt Error}クラスを保持していた場合{\tt Either.a()}でErrorを返す。 \item 編集に成功していた場合、編集後木を持った、{\tt JungleTreeEditor}クラスを取得する。 \item 取得した{\tt JungleTreeEditor}クラスを用いて木の変更をコミットする。 \end{enumerate} \end{comment} また、木に対して行われた変更は、Logとして書き出される。 \section{Commit} Jungle Tree Editor を用いて木に変更を加えた後は、Commit を行う必要がある。 Commit は、前節で記述した Jungle Tree Editor が持つ関数 {\tt success()} を使用することで行われる。 Commit を行うと、Jungle Tree Editor は、保持している編集後の木構造のデータを持つ TreeContext を作成する。 そして、編集前の木が持つ TreeContext と 新しく作った TreeContext を置き換えることで Commit は完了する。 TreeContext の置き換えは、編集を行っている他の Thread と競合した際に、木の整合性を保つために以下の手順で行われる。 \begin{enumerate} \item 新しく作った TreeContext が持つ、1つ前のバージョンの TreeContext と、編集前の木構造の TreeContext を比較する。 \item 一致しなかった場合、他のが Thread が Commit をすでに行っているため、失敗する(競合に負けた)。 \item 一致した場合、TreeContext を Atomic に入れ替える(競合に勝った)。 \end{enumerate} 競合に負けた場合は、新しい木に対してもう一度同じ変更を行う必要がある。 これらのAPIにより、Jungleは木構造を格納、編集する機能を持っている。 \section{Log} Jungle は、 Editor を用いて木に編集を加える際、使用した API に応じて対応する NodeOperation を作成する。 NodeOperation は NodePath とペアで扱わなければならず、このペアを TreeOperation という。 Jungle によるデータの編集は TreeOperation が複数集まった単位で commit されていく. この TreeOperation の集まりを TreeOperationLog という。 TreeOperationLogの仕様をソースコード\ref{src:treeoperationlog}に示す. \begin{lstlisting}[frame=lrbt,label=src:treeoperationlog,caption=TreeOperationLogの仕様,numbers=left] public interface TreeOperationLog extends Iterable<TreeOperation> { public TreeOperationLog add(NodePath _p,NodeOperation _op); public TreeOperationLog append(TreeOperationLog _log); public int length(); } \end{lstlisting} \verb|Iterable<TreeOperation>|を継承しているためIteratorによりTreeOperationを取り出せるようになっている。 addやappendメソッドを使ってTreeOperationを積み上げていくことができる。 積み上げたLogをディスクに書き出すことで、Jungleは永続性を持つ。 分散版Jungleでは、Logを他ノードに送ることで、データの分散を行う。 \section{検索API} これまでに紹介したAPIにより、Jungleは木構造を構築できるようになった。 しかし、木に問い合わせを行う検索APIが実装されていなかったため、木の走査を行う{\tt Interface Traverser}クラス内に、lambda式を用いて実装した。 以下に検索を行う関数{\tt find}の定義を記述する。 \begin{lstlisting}[frame=lrbt,label=query,numbers=left] public Iterator<TreeNode> find(Query query, String key, String searchValue); \end{lstlisting} 関数{\tt find}は、第一引数には、探索の条件を記述する関数{\tt boolean comdition(TreeNode)}を定義した{\tt Query}を、 第二、第三引数には、Indexを用いた絞込に使用する{\tt String key、String value}を取り、条件に一致したノードの{\tt Iterator}を返す。 {\tt 関数find}の使用例を以下に記す。 \begin{lstlisting}[frame=lrbt,label=find,numbers=left] InterfaceTraverser traverser = tree.getTraverser(true); Iterator<TreeNode> resultNodeIterator = traverser.find((TreeNode node) -> { String name = node.getAttributes().getString("name"); if (name == null) return false; return name.equals("kanagawa"); }, "belong", "ryukyu"); \end{lstlisting} 上記コードについて解説する。 1行目で検索対象の木から、検索に使用する {\tt Interface Traverser} を取得する。 2行目で、検索を行う関数 {\tt find()} を使用する。 今回は、Index を使って、属性名 {\tt "belong"} 属性値 {\tt "ryukyu"} を持つノード取得し、 3 - 5行目で定義されたクエリに渡す。 そして、クエリの中で属性名 {\tt "name"} 属性値 {\tt "kanagawa"} を持つノードかどうかを確かめる。 結果として、属性名 {\tt "belong"} 属性値{\tt "ryukyu"}と、属性名 {\tt "name"} 属性値{\tt kanagawa}の2つのデータを持つノードの {\tt Iterator} が取得できる。 検索の際に使用する Index の実装については次節に記述する。 \begin{comment} \begin{enumerate} \item 木の走査を行う{\tt Traverser}クラスを取得する。 \item Indexから{\tt find}の第2、第3引数である、属性名 {\tt belong}、属性値 {\tt ryukyu}の組のデータを持つノードを取得し、Queryに渡す(ノードの絞込を行う)。 \item 引数のノードから関数{\tt getAttributes().getString("name")}で属性名 {\tt name}とペアになっている属性値を取得する。 \item 属性値が{\tt null}だった場合、このノードには属性名が{\tt name}の組のデータは存在しないので、{\tt false}を返し次のノードの評価を行う。 \item 属性値が{\tt null}でなかった場合、{\tt kanagawa}と一致するかどうかを調べ結果を返す。 \end{enumerate} \end{comment} \section{Index} Jungleは、非破壊的木構造というデータ構造上、過去の版の木構造を全て保持している。 よって、全ての版に独立したIndexが必要となるため、前の版のIndexを破壊すること無く、Indexを更新する必要がある。 既存のTreeMapでは、一度Indexの複製を行ない、その後更新する必要があったため、Indexの更新オーダーがO(n)となっていた。 その問題を解決するため、Java 上で関数型プログラミングを行えるライブラリである、 Functional Java の TreeMapを使用し、それを用いてIndexの実装を行った。 このTreeMapは、Jungleと同じようにルートから変更を加えたノードまでの経路の複製を行い、データの更新を行った際、前の版と最大限データを共有した新しいTreeMapを作成する。 Jungleとの違いは、木の回転処理が入ることである。 これにより複数の版全てに対応したIndexをサポートすることが可能になった。 以下にJungleにおけるIndexの型を記述する \begin{lstlisting}[frame=lrbt,label=index,numbers=left] TreeMap<String key,TreeMap<String attribute,List<TreeNode> nodeList> index> indexMap \end{lstlisting} JungleのIndexは{\tt IndexMap}内に保持されている。 属性名で{\tt IndexMap}に{\tt get}を行うと、対応したIndexが取得できる。 取得したIndexに属性値で{\tt get}を行うと、ノードのリストが返ってくる。 以下にIndexから属性名 name 属性値 kanagawaのデータを持つ、ノードのIteratorを取得するサンプルコードを記述する。 \begin{lstlisting}[frame=lrbt,label=find,numbers=left] Optional<TreeMap<String, List<TreeNode>>> indexOp = indexMap.get("name"); if (!indexOp.isPresent()) return new NulIterator<TreeNode>(); TreeMap<String, List<TreeNode>> index = indexOp.get(); Optional<List<TreeNode>> nodeListOp = index.get("kanagawa"); if (!nodeListOp.isPresent()) return new NulIterator<TreeNode>(); return nodeListOp.get().iterator(); \end{lstlisting} 1 - 4行目で IndexMap から 属性名 "name" に対する値を持つ Index を取得している。 5 - 8行目で 取得した Index から、 属性名 "kanagawa" を持つノードの{ \tt Iterator}を取得している。 \begin{comment} \begin{enumerate} \item {\tt indexMap}に、今回検索で使用する属性名 {\tt name}を使用して{\tt get("name")}を行う。すると属性名 {\tt "name"}に対応したIndexが、{\tt Optional}クラスで包まれて返ってくる。 \item {\tt Optional}オブジェクトに対して、中身を保持しているか(属性名 {\tt name}に対応したIndexを木が持っているか)を調べる。 \item 持っていなかった場合、空の{\tt Iterator}オブジェクトを返す。 \item 持っていた場合、{\tt Optional}オブジェクトからIndexを取得する。 \item 取得したIndexに、検索で使用する属性値{\tt "kanagawa"}で{\tt get()}を行う。すると、属性名 {\tt "name"} 属性値{\tt "kanagawa"}の値を持つノードのリストが、{\tt Optional}クラスに包まれて返ってくる。 \item {\tt Optional}オブジェクトに対して中身を保持しているか(属性名 {\tt "name"} 属性値 {\tt "kanagawa"}を持つノードを木が持っているか)を調べる。 \item 持っていなかった場合、空の{\tt Iterator}オブジェクトを返す。 \item 持っていた場合{\tt Optional}オブジェクトからノードリストの{\tt Iterator}を返す。 \end{enumerate} \end{comment} JungleはこれらのAPIにより、木構造を格納、編集、検索する機能を持っている。 \newpage