NodeCachingLinkedList.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.list;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.Collection;

  23. /**
  24.  * A {@code List} implementation that stores a cache of internal Node objects
  25.  * in an effort to reduce wasteful object creation.
  26.  * <p>
  27.  * A linked list creates one Node for each item of data added. This can result in
  28.  * a lot of object creation and garbage collection. This implementation seeks to
  29.  * avoid that by maintaining a store of cached nodes.
  30.  * </p>
  31.  * <p>
  32.  * This implementation is suitable for long-lived lists where both add and remove
  33.  * are used. Short-lived lists, or lists which only grow will have worse performance
  34.  * using this class.
  35.  * </p>
  36.  * <p>
  37.  * <strong>Note that this implementation is not synchronized.</strong>
  38.  * </p>
  39.  *
  40.  * @param <E> the type of the elements in the list.
  41.  * @since 3.0
  42.  * @deprecated parent {@link AbstractLinkedList} is source incompatible with List methods added in Java 21
  43.  */
  44. @Deprecated
  45. public class NodeCachingLinkedList<E> extends AbstractLinkedList<E> implements Serializable {

  46.     /** Serialization version */
  47.     private static final long serialVersionUID = 6897789178562232073L;

  48.     /**
  49.      * The default value for {@link #maximumCacheSize}.
  50.      */
  51.     private static final int DEFAULT_MAXIMUM_CACHE_SIZE = 20;

  52.     /**
  53.      * The first cached node, or {@code null} if no nodes are cached.
  54.      * Cached nodes are stored in a singly-linked list with
  55.      * {@code next} pointing to the next element.
  56.      */
  57.     private transient Node<E> firstCachedNode;

  58.     /**
  59.      * The size of the cache.
  60.      */
  61.     private transient int cacheSize;

  62.     /**
  63.      * The maximum size of the cache.
  64.      */
  65.     private int maximumCacheSize;

  66.     /**
  67.      * Constructor that creates.
  68.      */
  69.     public NodeCachingLinkedList() {
  70.         this(DEFAULT_MAXIMUM_CACHE_SIZE);
  71.     }

  72.     /**
  73.      * Constructor that copies the specified collection
  74.      *
  75.      * @param coll  the collection to copy
  76.      */
  77.     public NodeCachingLinkedList(final Collection<? extends E> coll) {
  78.         super(coll);
  79.         this.maximumCacheSize = DEFAULT_MAXIMUM_CACHE_SIZE;
  80.     }

  81.     /**
  82.      * Constructor that species the maximum cache size.
  83.      *
  84.      * @param maximumCacheSize  the maximum cache size
  85.      */
  86.     public NodeCachingLinkedList(final int maximumCacheSize) {
  87.         this.maximumCacheSize = maximumCacheSize;
  88.         init();  // must call init() as use super();
  89.     }

  90.     /**
  91.      * Adds a node to the cache, if the cache isn't full.
  92.      * The node's contents are cleared, so they can be garbage collected.
  93.      *
  94.      * @param node  the node to add to the cache
  95.      */
  96.     protected void addNodeToCache(final Node<E> node) {
  97.         if (isCacheFull()) {
  98.             // don't cache the node.
  99.             return;
  100.         }
  101.         // clear the node's contents and add it to the cache.
  102.         final Node<E> nextCachedNode = firstCachedNode;
  103.         node.previous = null;
  104.         node.next = nextCachedNode;
  105.         node.setValue(null);
  106.         firstCachedNode = node;
  107.         cacheSize++;
  108.     }

  109.     /**
  110.      * Creates a new node, either by reusing one from the cache or creating
  111.      * a new one.
  112.      *
  113.      * @param value  value of the new node
  114.      * @return the newly created node
  115.      */
  116.     @Override
  117.     protected Node<E> createNode(final E value) {
  118.         final Node<E> cachedNode = getNodeFromCache();
  119.         if (cachedNode == null) {
  120.             return super.createNode(value);
  121.         }
  122.         cachedNode.setValue(value);
  123.         return cachedNode;
  124.     }

  125.     /**
  126.      * Gets the maximum size of the cache.
  127.      *
  128.      * @return the maximum cache size
  129.      */
  130.     protected int getMaximumCacheSize() {
  131.         return maximumCacheSize;
  132.     }

  133.     /**
  134.      * Gets a node from the cache. If a node is returned, then the value of
  135.      * {@link #cacheSize} is decreased accordingly. The node that is returned
  136.      * will have {@code null} values for next, previous and element.
  137.      *
  138.      * @return a node, or {@code null} if there are no nodes in the cache.
  139.      */
  140.     protected Node<E> getNodeFromCache() {
  141.         if (cacheSize == 0) {
  142.             return null;
  143.         }
  144.         final Node<E> cachedNode = firstCachedNode;
  145.         firstCachedNode = cachedNode.next;
  146.         cachedNode.next = null; // This should be changed anyway, but defensively
  147.                                 // set it to null.
  148.         cacheSize--;
  149.         return cachedNode;
  150.     }

  151.     /**
  152.      * Checks whether the cache is full.
  153.      *
  154.      * @return true if the cache is full
  155.      */
  156.     protected boolean isCacheFull() {
  157.         return cacheSize >= maximumCacheSize;
  158.     }

  159.     /**
  160.      * Deserializes the data held in this object to the stream specified.
  161.      *
  162.      * @param in  the input stream
  163.      * @throws IOException if an error occurs while reading from the stream
  164.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  165.      */
  166.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  167.         in.defaultReadObject();
  168.         doReadObject(in);
  169.     }

  170.     /**
  171.      * Removes all the nodes from the list, storing as many as required in the
  172.      * cache for reuse.
  173.      */
  174.     @Override
  175.     protected void removeAllNodes() {
  176.         // Add the removed nodes to the cache, then remove the rest.
  177.         // We can add them to the cache before removing them, since
  178.         // {@link AbstractLinkedList.removeAllNodes()} removes the
  179.         // nodes by removing references directly from {@link #header}.
  180.         final int numberOfNodesToCache = Math.min(size, maximumCacheSize - cacheSize);
  181.         Node<E> node = header.next;
  182.         for (int currentIndex = 0; currentIndex < numberOfNodesToCache; currentIndex++) {
  183.             final Node<E> oldNode = node;
  184.             node = node.next;
  185.             addNodeToCache(oldNode);
  186.         }
  187.         super.removeAllNodes();
  188.     }

  189.     /**
  190.      * Removes the node from the list, storing it in the cache for reuse
  191.      * if the cache is not yet full.
  192.      *
  193.      * @param node  the node to remove
  194.      */
  195.     @Override
  196.     protected void removeNode(final Node<E> node) {
  197.         super.removeNode(node);
  198.         addNodeToCache(node);
  199.     }

  200.     /**
  201.      * Sets the maximum size of the cache.
  202.      *
  203.      * @param maximumCacheSize  the new maximum cache size
  204.      */
  205.     protected void setMaximumCacheSize(final int maximumCacheSize) {
  206.         this.maximumCacheSize = maximumCacheSize;
  207.         shrinkCacheToMaximumSize();
  208.     }

  209.     /**
  210.      * Reduce the size of the cache to the maximum, if necessary.
  211.      */
  212.     protected void shrinkCacheToMaximumSize() {
  213.         // Rich Dougherty: This could be more efficient.
  214.         while (cacheSize > maximumCacheSize) {
  215.             getNodeFromCache();
  216.         }
  217.     }

  218.     /**
  219.      * Serializes this object to an ObjectOutputStream.
  220.      *
  221.      * @param out the target ObjectOutputStream.
  222.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  223.      */
  224.     private void writeObject(final ObjectOutputStream out) throws IOException {
  225.         out.defaultWriteObject();
  226.         doWriteObject(out);
  227.     }

  228. }