Repository: wangguanquan/eec
Pull request #23 (2019-06-05 02:34:27)

Fix#21


Patch:
diff --git a/src/main/java/cn/ttzero/excel/entity/SharedStrings.java b/src/main/java/cn/ttzero/excel/entity/SharedStrings.java
index 1aee955a..51c2fa8c 100644
--- a/src/main/java/cn/ttzero/excel/entity/SharedStrings.java
+++ b/src/main/java/cn/ttzero/excel/entity/SharedStrings.java
@@ -18,7 +18,8 @@ package cn.ttzero.excel.entity;
 
 import cn.ttzero.excel.manager.Const;
 import cn.ttzero.excel.annotation.TopNS;
-import cn.ttzero.excel.reader.Hot;
+import cn.ttzero.excel.reader.Cache;
+import cn.ttzero.excel.reader.FixSizeLRUCache;
 import cn.ttzero.excel.util.ExtBufferedWriter;
 import cn.ttzero.excel.util.FileUtil;
 import cn.ttzero.excel.util.StringUtil;
@@ -86,7 +87,7 @@ public class SharedStrings implements Storageable, AutoCloseable {
      * Cache the string which read twice and above
      * Use LRU elimination algorithm
      */
-    private Hot<String, Integer> hot;
+    private Cache<String, Integer> hot;
 
     /**
      * Storage into temp file on disk
@@ -99,7 +100,7 @@ public class SharedStrings implements Storageable, AutoCloseable {
     private int expectedInsertions = 1 << 20;
 
     SharedStrings() {
-        hot = new Hot<>();
+        hot = FixSizeLRUCache.create();
         ascii = new int[1 << 7];
         // -1 means the keyword not exists
         Arrays.fill(ascii, -1);
@@ -177,7 +178,7 @@ public class SharedStrings implements Storageable, AutoCloseable {
             if (n < 0) {
                 n = add(key);
             }
-            hot.push(key, n);
+            hot.put(key, n);
         }
         return n;
     }
@@ -272,8 +273,8 @@ public class SharedStrings implements Storageable, AutoCloseable {
 //        for (String key : hot) {
 //            filter.put(key);
 //        }
-        for (Hot.E<String, Integer> e : hot) {
-            filter.put(e.getK());
+        for (Cache.Entry<String, Integer> e : hot) {
+            filter.put(e.getKey());
         }
     }
 
diff --git a/src/main/java/cn/ttzero/excel/reader/Cache.java b/src/main/java/cn/ttzero/excel/reader/Cache.java
new file mode 100644
index 00000000..826450cb
--- /dev/null
+++ b/src/main/java/cn/ttzero/excel/reader/Cache.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.ttzero.excel.reader;
+
+/**
+ * Create by guanquan.wang at 2019-06-04 16:07
+ */
+public interface Cache<K, V> extends Iterable<Cache.Entry<K, V>> {
+
+    /**
+     * Returns the value to which the specified key is mapped,
+     * or {@code null} if this cache contains no mapping for the key.
+     *
+     * @param k the key whose associated value is to be returned
+     * @return the value to which the specified key is mapped, or
+     *      {@code null} if this cache contains no mapping for the key
+     */
+    V get(K k);
+
+    /**
+     * Associates the specified value with the specified key in this cache.
+     * If the cache previously contained a mapping for
+     * the key, the old value is replaced by the specified value.
+     *
+     * @param k key with which the specified value is to be associated
+     *          the key must not be null
+     * @param v value to be associated with the specified key
+     */
+    void put(K k, V v);
+
+    /**
+     * Removes the mapping for a key from this cache if it is present
+     * (optional operation).   More formally, if this cache contains a mapping
+     * from key <tt>k</tt> to value <tt>v</tt>, that mapping
+     * is removed.
+     *
+     * @param k key whose mapping is to be removed from the cache
+     * @return the previous value associated with <tt>key</tt>, or
+     *      <tt>null</tt> if there was no mapping for <tt>key</tt>.
+     */
+    V remove(K k);
+
+    /**
+     * Removes all of the mappings from this cache (optional operation).
+     * The cache will be empty after this call returns.
+     */
+    void clear();
+
+    /**
+     * Returns the number of key-value mappings in this cache.
+     *
+     * @return the number of key-value mappings in this cache.
+     */
+    int size();
+
+    /**
+     * A map entry (key-value pair)
+     */
+    interface Entry<K, V> {
+        /**
+         * Returns the key corresponding to this entry.
+         *
+         * @return the key corresponding to this entry
+         */
+        K getKey();
+
+        /**
+         * Returns the value corresponding to this entry.  If the mapping
+         * has been removed from the backing cache (by the iterator's
+         * <tt>remove</tt> operation), the results of this call are undefined.
+         *
+         * @return the value corresponding to this entry
+         */
+        V getValue();
+    }
+}
diff --git a/src/main/java/cn/ttzero/excel/reader/FixSizeLRUCache.java b/src/main/java/cn/ttzero/excel/reader/FixSizeLRUCache.java
new file mode 100644
index 00000000..8a9f6fab
--- /dev/null
+++ b/src/main/java/cn/ttzero/excel/reader/FixSizeLRUCache.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.ttzero.excel.reader;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Implemented by the LRU page elimination algorithm.
+ * The time complexity of the push, get, and remove is O(1).
+ *
+ * Create by guanquan.wang at 2018-10-31 16:06
+ */
+public class FixSizeLRUCache<K, V> implements Cache<K, V> {
+    private static class E<K, V> implements Cache.Entry<K, V> {
+        private K k;
+        private V v;
+
+        private E(K k, V v) {
+            this.k = k;
+            this.v = v;
+        }
+
+        public K getKey() {
+            return k;
+        }
+
+        public V getValue() {
+            return v;
+        }
+    }
+
+    private static class Node<V> {
+        private V data;
+        private Node<V> prev, next;
+
+        private Node(V data, Node<V> prev, Node<V> next) {
+            this.data = data;
+            this.prev = prev;
+            this.next = next;
+        }
+    }
+
+    /**
+     * Double linked
+     */
+    private Node<E<K,V>> first, last;
+
+    /**
+     * The elements limit
+     */
+    private int limit;
+
+    /**
+     * Size of elements
+     */
+    private int size;
+
+    private Map<K, Node<E<K,V>>> table;
+
+    public static <K, V> FixSizeLRUCache<K, V> create() {
+        return new FixSizeLRUCache<>();
+    }
+
+    public static <K, V> FixSizeLRUCache<K, V> create(int size) {
+        return new FixSizeLRUCache<>(size);
+    }
+
+    /**
+     * Create a fix size cache witch size is {@code 1 << 9}
+     */
+    private FixSizeLRUCache() {
+        this(1 << 9);
+    }
+
+    private FixSizeLRUCache(int limit) {
+        this.limit = limit;
+        table = new HashMap<>(Math.round(limit * 1.25f));
+    }
+
+    /**
+     * Returns the value to which the specified key is mapped,
+     * or {@code null} if this cache contains no mapping for the key.
+     *
+     * @param k the key whose associated value is to be returned
+     * @return the value to which the specified key is mapped, or
+     *      {@code null} if this cache contains no mapping for the key
+     */
+    @Override
+    public V get(K k) {
+        final Node<E<K,V>> o;
+        // Not found
+        if (size == 0 || (o = table.get(k)) == null) return null;
+
+        // Move node to head
+        if (o != first) {
+            if (o.next != null) {
+                o.prev.next = o.next;
+                o.next.prev = o.prev;
+            } else {
+                o.prev.next = null;
+            }
+
+            first.prev = o;
+            o.next = first;
+            o.prev = null;
+            first = o;
+        }
+
+        return o.data.v;
+    }
+
+    /**
+     * Associates the specified value with the specified key in this cache.
+     * If the cache previously contained a mapping for
+     * the key, the old value is replaced by the specified value.
+     *
+     * @param k key with which the specified value is to be associated
+     *          the key must not be null
+     * @param v value to be associated with the specified key
+     */
+    @Override
+    public void put(K k, V v) {
+        final Node<E<K,V>> o;
+        // Insert at header if not found
+        if (size == 0 || (o = table.get(k)) == null) {
+            final Node<E<K, V>> f = first;
+            final Node<E<K, V>> newNode = new Node<>(new E<>(k, v), null, f);
+            first = newNode;
+            if (f == null) last = newNode;
+            else f.prev = newNode;
+
+            table.put(k, newNode);
+
+            if (size < limit) size++;
+            else remove();
+            // Replace the old value
+        } else {
+            o.data.v = v;
+        }
+    }
+
+    /**
+     * Removes the mapping for a key from this cache if it is present
+     * (optional operation).   More formally, if this cache contains a mapping
+     * from key <tt>k</tt> to value <tt>v</tt>, that mapping
+     * is removed.
+     *
+     * @param k key whose mapping is to be removed from the cache
+     * @return the previous value associated with <tt>key</tt>, or
+     *      <tt>null</tt> if there was no mapping for <tt>key</tt>.
+     */
+    @Override
+    public V remove(K k) {
+        final Node<E<K,V>> o;
+        // Not found
+        if (size == 0 || (o = table.get(k)) == null) return null;
+
+        final Node<E<K, V>> prev = o.prev, next = o.next;
+
+        if (prev == null) {
+            first = next;
+        } else {
+            prev.next = next;
+            o.prev = null;
+        }
+
+        if (next == null) {
+            last = prev;
+        } else {
+            next.prev = prev;
+            o.next = null;
+        }
+
+        if (--size == 0) {
+            first = null;
+        }
+
+        V v = o.data.v;
+        o.data = null;
+
+        return v;
+    }
+
+    /**
+     * Remove the last item
+     *
+     * @return the last item
+     */
+    protected E<K,V> remove() {
+        final Node<E<K,V>> l = last;
+        if (l == null) {
+            return null;
+        }
+        final E<K,V> data = l.data;
+
+        last = last.prev;
+        if (last != null) {
+            last.next = null;
+        }
+
+        table.remove(data.k);
+        if (--size == 0) {
+            first = null;
+        }
+        return data;
+    }
+
+    /**
+     * Removes all of the mappings from this cache (optional operation).
+     * The cache will be empty after this call returns.
+     */
+    @Override
+    public void clear() {
+        first = null;
+        last = null;
+        table.clear();
+        size = 0;
+    }
+
+    /**
+     * Returns the number of key-value mappings in this cache.
+     *
+     * @return the number of key-value mappings in this cache
+     */
+    @Override
+    public int size() {
+        return size;
+    }
+
+    /**
+     * An inner iterator
+     */
+    private class CacheIterator implements Iterator<Entry<K,V>> {
+        private Node<E<K,V>> first;
+        private CacheIterator(Node<E<K,V>> first) {
+            this.first = first;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return first != null;
+        }
+
+        @Override
+        public E<K,V> next() {
+            E<K,V> e = first.data;
+            first = first.next;
+            return e;
+        }
+    }
+
+    /**
+     * Returns an iterator over elements of type {@code Cache.Entry<K, V>}.
+     *
+     * @return an Iterator.
+     */
+    @Override
+    public Iterator<Entry<K, V>> iterator() {
+        return new CacheIterator(first);
+    }
+
+    /**
+     * Performs the given action for each element of the {@code Iterable}
+     * until all elements have been processed or the action throws an
+     * exception.  Unless otherwise specified by the implementing class,
+     * actions are performed in the order of iteration (if an iteration order
+     * is specified).  Exceptions thrown by the action are relayed to the
+     * caller.
+     *
+     * @implSpec
+     * <p>The default implementation behaves as if:
+     * <pre>{@code
+     *     for (T t : this)
+     *         action.accept(t);
+     * }</pre>
+     *
+     * @param action The action to be performed for each element
+     * @throws NullPointerException if the specified action is null
+     * @since 1.8
+     */
+    @Override
+    public void forEach(Consumer<? super Entry<K, V>> action) {
+        Node<E<K, V>> f = first;
+        for (; f != null; f = f.next) {
+            action.accept(f.data);
+        }
+    }
+
+}
diff --git a/src/main/java/cn/ttzero/excel/reader/Hot.java b/src/main/java/cn/ttzero/excel/reader/Hot.java
deleted file mode 100644
index 382b4db8..00000000
--- a/src/main/java/cn/ttzero/excel/reader/Hot.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cn.ttzero.excel.reader;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.function.Consumer;
-
-/**
- * 热词区
- * 由LRU页面淘汰算法实现push, get, remove方法时间复杂度均为O(1)
- * <p>
- * Create by guanquan.wang at 2018-10-31 16:06
- */
-public class Hot<K,V> implements Iterable<Hot.E<K,V>> {
-    public static class E<K,V> {
-        private K k;
-        private V v;
-
-        private E(K k, V v) {
-            this.k = k;
-            this.v = v;
-        }
-
-        public K getK() {
-            return k;
-        }
-
-        public V getV() {
-            return v;
-        }
-    }
-
-    private static class Node<V> {
-        private V data;
-        private Node<V> prev, next;
-
-        private Node(V data, Node<V> prev, Node<V> next) {
-            this.data = data;
-            this.prev = prev;
-            this.next = next;
-        }
-    }
-
-    /**
-     * double linked
-     */
-    private Node<E<K,V>> first, last;
-    /**
-     * elements limit
-     * default size 64
-     */
-    private int limit;
-    /**
-     * size of elements
-     */
-    private int size;
-
-    private Map<K, Node<E<K,V>>> table;
-
-    public Hot() {
-        this(1 << 9);
-    }
-
-    public Hot(int limit) {
-        this.limit = limit;
-        table = new HashMap<>(Math.round(limit * 1.25f));
-    }
-
-    /**
-     * get by key
-     *
-     * @param k int
-     * @return value
-     */
-    public V get(K k) {
-        final Node<E<K,V>> o;
-        // Not found
-        if (size == 0 || (o = table.get(k)) == null) return null;
-
-        // Move node to head
-        if (o != first) {
-            if (o.next != null) {
-                o.prev.next = o.next;
-                o.next.prev = o.prev;
-            } else {
-                o.prev.next = null;
-            }
-
-            first.prev = o;
-            o.next = first;
-            o.prev = null;
-            first = o;
-        }
-
-        return o.data.v;
-    }
-
-    /**
-     * insert first
-     *
-     * @param v element
-     */
-    public void push(K k, V v) {
-        final Node<E<K,V>> f = first;
-        final Node<E<K,V>> newNode = new Node<>(new E<>(k, v), null, f);
-        first = newNode;
-        if (f == null) last = newNode;
-        else f.prev = newNode;
-
-        table.put(k, newNode);
-
-        if (size < limit) size++;
-        else remove();
-    }
-
-    /**
-     * Remove the last value
-     *
-     * @return the last item
-     */
-    public E<K,V> remove() {
-        final Node<E<K,V>> l = last;
-        if (l == null) {
-            return null;
-        }
-        final E<K,V> data = l.data;
-
-        last = last.prev;
-        if (last != null) {
-            last.next = null;
-        }
-
-        table.remove(data.k);
-        if (--size == 0) {
-            first = null;
-        }
-        return data;
-    }
-
-    /**
-     * Clear all cache data
-     */
-    public void clear() {
-        first = null;
-        last = null;
-        table.clear();
-        size = 0;
-    }
-
-    /**
-     * Returns the cache word size
-     * @return size of cache
-     */
-    public int size() {
-        return size;
-    }
-
-    private class HotIterator implements Iterator<E<K,V>> {
-        private Node<E<K,V>> first;
-        private HotIterator(Node<E<K,V>> first) {
-            this.first = first;
-        }
-
-        @Override
-        public boolean hasNext() {
-            return first != null;
-        }
-
-        @Override
-        public E<K,V> next() {
-            E<K,V> e = first.data;
-            first = first.next;
-            return e;
-        }
-    }
-
-    @Override
-    public Iterator<E<K,V>> iterator() {
-        return new HotIterator(first);
-    }
-
-    @Override
-    public void forEach(Consumer<? super E<K,V>> action) {
-        Node<E<K,V>> f = first;
-        for (; f != null; f = f.next) {
-            action.accept(f.data);
-        }
-    }
-
-}
diff --git a/src/main/java/cn/ttzero/excel/reader/Row.java b/src/main/java/cn/ttzero/excel/reader/Row.java
index aca6103a..4086c0e1 100644
--- a/src/main/java/cn/ttzero/excel/reader/Row.java
+++ b/src/main/java/cn/ttzero/excel/reader/Row.java
@@ -62,6 +62,24 @@ public abstract class Row {
         return index;
     }
 
+    /**
+     * Returns the index of the first column (zero base)
+     *
+     * @return the first column index
+     */
+    public int getFirstColumnIndex() {
+        return fc;
+    }
+
+    /**
+     * Returns the index of the last column (zero base)
+     *
+     * @return the last column index
+     */
+    public int getLastColumnIndex() {
+        return lc;
+    }
+
     /**
      * Test unused row (not contains any filled or formatted or value)
      *
diff --git a/src/main/java/cn/ttzero/excel/reader/SharedStrings.java b/src/main/java/cn/ttzero/excel/reader/SharedStrings.java
index 6574a448..d26ec133 100644
--- a/src/main/java/cn/ttzero/excel/reader/SharedStrings.java
+++ b/src/main/java/cn/ttzero/excel/reader/SharedStrings.java
@@ -129,7 +129,7 @@ public class SharedStrings implements AutoCloseable {
     /**
      * High frequency word
      */
-    private Hot<Integer, String> hot;
+    private Cache<Integer, String> hot;
     /**
      * Size of hot
      */
@@ -184,8 +184,8 @@ public class SharedStrings implements AutoCloseable {
                     default_cap = max / page + 1;
                 count_area = new HashMap<>(default_cap);
 
-                if (hotSize > 0) hot = new Hot<>(hotSize);
-                else hot = new Hot<>();
+                if (hotSize > 0) hot = FixSizeLRUCache.create(hotSize);
+                else hot = FixSizeLRUCache.create();
             } else {
                 forward = new String[max];
             }
@@ -302,7 +302,7 @@ public class SharedStrings implements AutoCloseable {
             value = forward[index - offset_forward];
             if (test(index)) {
                 logger.debug("put hot {}", index);
-                hot.push(index, value);
+                hot.put(index, value);
             }
             total_forward++;
         } else {


Test patch:
diff --git a/src/test/java/cn/ttzero/excel/reader/CacheTest.java b/src/test/java/cn/ttzero/excel/reader/CacheTest.java
new file mode 100644
index 00000000..8cdb31a5
--- /dev/null
+++ b/src/test/java/cn/ttzero/excel/reader/CacheTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.ttzero.excel.reader;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static cn.ttzero.excel.Print.println;
+
+/**
+ * Create by guanquan.wang at 2019-05-07 15:17
+ */
+public class CacheTest {
+    @Test public void testPut1() {
+        Cache<Integer, String> hot = FixSizeLRUCache.create();
+        hot.put(1, "a");
+        hot.put(2, "b");
+        hot.put(3, "c");
+        hot.put(4, "d");
+
+        assert hot.size() == 4;
+        assert hot.get(2).equals("b");
+
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+
+        println(hot.get(3));
+    }
+
+    @Test public void testPut2() {
+        Cache<String, Integer> hot = FixSizeLRUCache.create();
+        hot.put("a", 1);
+        hot.put("b", 2);
+        hot.put("a", 8);
+        hot.put("c", 3);
+        hot.put("d", 4);
+        hot.put("e", 5);
+
+        assert hot.size() == 5;
+        assert hot.get("c") == 3;
+
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+    }
+
+    @Test public void testIterator() {
+        Cache<String, Integer> hot = FixSizeLRUCache.create();
+        hot.put("a", 1);
+        hot.put("b", 2);
+        hot.put("c", 3);
+        hot.put("d", 4);
+        hot.put("e", 5);
+
+        for (Iterator<Cache.Entry<String, Integer>> ite = hot.iterator(); ite.hasNext();) {
+            Cache.Entry<String, Integer> e = ite.next();
+            println(e.getKey() + ": " + e.getValue());
+        }
+    }
+
+    @Test public void testRemove() {
+        FixSizeLRUCache<Integer, String> hot = FixSizeLRUCache.create();
+        hot.put(1, "a");
+        hot.put(2, "b");
+        hot.put(3, "c");
+        hot.put(4, "d");
+
+        assert hot.size() == 4;
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+
+        hot.remove();
+
+        assert hot.size() == 3;
+
+        hot.remove();
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+        hot.remove();
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+        hot.remove();
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+        hot.remove();
+        hot.remove();
+        hot.remove();
+        hot.remove();
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+    }
+
+    @Test public void testClear() {
+        Cache<Integer, String> hot = FixSizeLRUCache.create();
+        hot.put(1, "a");
+        hot.put(2, "b");
+        hot.put(3, "c");
+        hot.put(4, "d");
+
+        assert hot.size() == 4;
+        hot.clear();
+
+        assert hot.size() == 0;
+        hot.forEach(e -> println(e.getKey() + ": " + e.getValue()));
+    }
+}
diff --git a/src/test/java/cn/ttzero/excel/reader/ExcelReaderTest.java b/src/test/java/cn/ttzero/excel/reader/ExcelReaderTest.java
index 3cd8cf47..0aa9cb53 100644
--- a/src/test/java/cn/ttzero/excel/reader/ExcelReaderTest.java
+++ b/src/test/java/cn/ttzero/excel/reader/ExcelReaderTest.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.net.URL;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Iterator;
 
 import static cn.ttzero.excel.Print.println;
 
@@ -56,4 +57,19 @@ public class ExcelReaderTest {
             e.printStackTrace();
         }
     }
+
+    @Test public void testColumnIndex() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("1.xlsx"))) {
+            Sheet sheet = reader.sheet(0);
+            for (Iterator<Row> it = sheet.iterator(); it.hasNext();) {
+                Row row = it.next();
+                println(row.getRowNumber()
+                    + " | " + row.getFirstColumnIndex()
+                    + " | " + row.getLastColumnIndex()
+                    + " => " + row);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
 }
diff --git a/src/test/java/cn/ttzero/excel/reader/HotTest.java b/src/test/java/cn/ttzero/excel/reader/HotTest.java
deleted file mode 100644
index d1782a08..00000000
--- a/src/test/java/cn/ttzero/excel/reader/HotTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cn.ttzero.excel.reader;
-
-import org.junit.Test;
-
-import java.util.Iterator;
-
-import static cn.ttzero.excel.Print.println;
-
-/**
- * Create by guanquan.wang at 2019-05-07 15:17
- */
-public class HotTest {
-    @Test public void testPut1() {
-        Hot<Integer, String> hot = new Hot<>();
-        hot.push(1, "a");
-        hot.push(2, "b");
-        hot.push(3, "c");
-        hot.push(4, "d");
-
-        assert hot.size() == 4;
-        assert hot.get(2).equals("b");
-
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-
-        println(hot.get(3));
-    }
-
-    @Test public void testPut2() {
-        Hot<String, Integer> hot = new Hot<>();
-        hot.push("a", 1);
-        hot.push("b", 2);
-        hot.push("c", 3);
-        hot.push("d", 4);
-        hot.push("e", 5);
-
-        assert hot.size() == 5;
-        assert hot.get("c") == 3;
-
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-    }
-
-    @Test public void testIterator() {
-        Hot<String, Integer> hot = new Hot<>();
-        hot.push("a", 1);
-        hot.push("b", 2);
-        hot.push("c", 3);
-        hot.push("d", 4);
-        hot.push("e", 5);
-
-        for (Iterator<Hot.E<String, Integer>> ite = hot.iterator(); ite.hasNext();) {
-            Hot.E<String, Integer> e = ite.next();
-            println(e.getK() + ": " + e.getV());
-        }
-    }
-
-    @Test public void testRemove() {
-        Hot<Integer, String> hot = new Hot<>();
-        hot.push(1, "a");
-        hot.push(2, "b");
-        hot.push(3, "c");
-        hot.push(4, "d");
-
-        assert hot.size() == 4;
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-
-        hot.remove();
-
-        assert hot.size() == 3;
-
-        hot.remove();
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-        hot.remove();
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-        hot.remove();
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-        hot.remove();
-        hot.remove();
-        hot.remove();
-        hot.remove();
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-    }
-
-    @Test public void testClear() {
-        Hot<Integer, String> hot = new Hot<>();
-        hot.push(1, "a");
-        hot.push(2, "b");
-        hot.push(3, "c");
-        hot.push(4, "d");
-
-        assert hot.size() == 4;
-        hot.clear();
-
-        assert hot.size() == 0;
-        hot.forEach(e -> println(e.getK() + ": " + e.getV()));
-    }
-}