之间有什么区别 HashMap 和一个 Hashtable 在Java中?

对于非线程应用程序来说,哪个更有效?

有帮助吗?

解决方案

之间有几个区别 HashMapHashtable 在爪哇中:

  1. Hashtable同步的, , 然而 HashMap 不是。这使得 HashMap 对于非线程应用程序来说更好,因为非同步对象通常比同步对象执行得更好。

  2. Hashtable 不允许 null 键或值。 HashMap 允许一个 null 密钥和任意数量的 null 价值观。

  3. HashMap 的子类之一是 LinkedHashMap, ,因此如果您想要可预测的迭代顺序(默认情况下是插入顺序),您可以轻松地交换 HashMap 为一个 LinkedHashMap. 。如果您使用的是,这不会那么容易 Hashtable.

由于同步对您来说不是问题,因此我建议 HashMap. 。如果同步成为问题,您还可以查看 ConcurrentHashMap.

其他提示

请注意,很多答案都表明哈希表是同步的。 实际上,这给你带来的好处很少。 访问器/修改器方法上的同步将阻止两个线程同时从映射中添加或删除,但在现实世界中,您通常需要额外的同步。

一个非常常见的习惯用法是“检查然后放置”——即在中查找条目 Map, ,如果尚不存在则添加它。无论您使用什么,这都不是原子操作 Hashtable 或者 HashMap.

等效同步 HashMap 可以通过以下方式获得:

Collections.synchronizedMap(myMap);

但要正确实现这个逻辑,你需要 额外的同步 形式:

synchronized(myMap) {
    if (!myMap.containsKey("tomato"))
        myMap.put("tomato", "red");
}

甚至迭代一个 Hashtable的条目(或 HashMap 获得于 Collections.synchronizedMap) 不是线程安全的,除非你也保护 Map 防止通过额外的同步进行修改。

的实施 ConcurrentMap 接口(例如 ConcurrentHashMap)通过包括来解决其中的一些问题 线程安全的检查然后执行语义 例如:

ConcurrentMap.putIfAbsent(key, value);

Hashtable 被视为遗留代码。没有什么关于 Hashtable 不能使用 HashMap 或派生 HashMap, ,所以对于新代码,我认为没有任何理由返回 Hashtable.

在面试中经常会问这个问题,以检查候选人是否理解集合类的正确用法并了解可用的替代解决方案。

  1. HashMap 类大致相当于 Hashtable,只不过它是非同步的并且允许 null。(HashMap 允许空值作为键和值,而 Hashtable 不允许空值)。
  2. HashMap 不保证映射的顺序随着时间的推移保持不变。
  3. HashMap 是非同步的,而 Hashtable 是同步的。
  4. HashMap 中的迭代器是故障安全的,而 Hashtable 的枚举器则不是,如果任何其他线程通过添加或删除除 Iterator 自己的 remove() 方法之外的任何元素来结构性地修改映射,则会抛出 ConcurrentModificationException。但这不是一个有保证的行为,将由 JVM 尽最大努力完成。

一些重要术语的注意事项

  1. 同步意味着只有一个线程可以在某一时间点修改哈希表。基本上,这意味着任何线程在对哈希表执行更新之前都必须获取该对象的锁,而其他线程则等待锁被释放。
  2. 故障安全与迭代器的上下文相关。如果在集合对象上创建了迭代器,并且其他某个线程尝试“结构性”修改该集合对象,则会引发并发修改异常。其他线程可以调用“set”方法,因为它不会“结构性”修改集合。但是,如果在调用“set”之前,集合已在结构上进行了修改,则会抛出“IllegalArgumentException”。
  3. 结构修改是指删除或插入元素,可以有效改变地图的结构。

HashMap 可以通过以下方式同步

Map m = Collections.synchronizeMap(hashMap);

MAP提供收集视图,而不是通过枚举对象直接支持迭代的直接支持。正如本节稍后讨论的那样,收集视图大大提高了界面的表现力。Map 允许您迭代键、值或键值对;Hashtable 不提供第三个选项。MAP提供了一种安全的方法,可以在迭代中删除条目;哈希表没有。最后,Map 修复了 Hashtable 接口中的一个小缺陷。Hashtable具有一种称为包含的方法,如果标题包含给定值,该方法将返回true。鉴于其名称,如果凸面包含一个给定的键,您希望此方法返回True,因为键是标签的主要访问机制。MAP接口通过重命名该方法包含值来消除这种混淆来源。此外,这提高了接口的一致性 - 包含值的包含键。

地图界面

HashMap: :的实施 Map 使用哈希码来索引数组的接口。Hashtable: :你好,1998 打来电话。他们希望恢复他们的 Collections API。

说真的,你最好远离 Hashtable 共。对于单线程应用程序,您不需要额外的同步开销。对于高度并发的应用程序,偏执的同步可能会导致饥饿、死锁或不必要的垃圾收集暂停。就像蒂姆·霍兰指出的那样,你可以使用 ConcurrentHashMap 反而。

请记住 HashTable 在引入 Java Collections Framework (JCF) 之前是遗留类,后来经过改造以实现 Map 界面。也是如此 VectorStack.

因此,在新代码中始终远离它们,因为 JCF 中总是有更好的替代方案 正如其他人指出的那样。

这里是 Java 集合备忘单 您会发现有用的。请注意,灰色块包含遗留类 HashTable、Vector 和 Stack。

enter image description here

除了izb所说的, HashMap 允许空值,而 Hashtable 才不是。

另请注意 Hashtable 延长了 Dictionary 类,作为 Java文档 状态,已过时并已被替换 Map 界面。

已经发布了很多好的答案。我添加了一些新观点并进行了总结。

HashMapHashtable 两者都用来存储 键和值形式的数据. 。两者都使用散列技术来存储唯一密钥。但 HashMap 和 Hashtable 类之间存在许多差异,如下所示。

哈希映射

  1. HashMap 是非同步的。它不是线程安全的,如果没有正确的同步代码,则无法在许多线程之间共享。
  2. HashMap 允许一个空键和多个空值。
  3. HashMap 是 JDK 1.2 中引入的新类。
  4. HashMap 速度很快。
  5. 我们可以使 HashMap 通过调用此代码进行同步
    Map m = Collections.synchronizedMap(HashMap);
  6. HashMap 由Iterator遍历。
  7. 迭代器在 HashMap 是快速失败的。
  8. HashMap 继承AbstractMap类。

哈希表

  1. Hashtable 已同步。它是线程安全的,可以与多个线程共享。
  2. Hashtable 不允许任何空键或值。
  3. Hashtable 是一个遗留类。
  4. Hashtable 是慢的。
  5. Hashtable 是内部同步的,不能取消同步。
  6. Hashtable 由Enumerator和Iterator遍历。
  7. 枚举器在 Hashtable 不是快速失败的。
  8. Hashtable 继承Dictionary类。

进一步阅读 Java中HashMap和Hashtable有什么区别?

enter image description here

看看这张图表。它与 HashMap 和 Hashtable 一起提供不同数据结构之间的比较。对比准确、清晰、易懂。

Java集合矩阵

Hashtable 类似于 HashMap 并且有类似的界面。建议您使用 HashMap, ,除非您需要支持遗留应用程序或者需要同步,因为 Hashtables 方法是同步的。所以在你的情况下,因为你不是多线程, HashMaps 是你最好的选择。

hashtable 和 hashmap 之间的另一个关键区别是,HashMap 中的 Iterator 是快速失败的,而 Hashtable 的枚举器不是,如果任何其他线程通过添加或删除除 Iterator 自己的 remove() 方法之外的任何元素来结构性地修改映射,则会抛出 ConcurrentModificationException。但这不是一个有保证的行为,将由 JVM 尽最大努力来完成。”

我的来源: http://javarevisited.blogspot.com/2010/10/difference- Between-hashmap-and.html

除了这里已经提到的所有其他重要方面之外,Collections API(例如Map 接口)一直在修改,以符合 Java 规范的“最新和最伟大”的补充。

例如,比较 Java 5 Map 迭代:

for (Elem elem : map.keys()) {
  elem.doSth();
}

与旧的哈希表方法相比:

for (Enumeration en = htable.keys(); en.hasMoreElements(); ) {
  Elem elem = (Elem) en.nextElement();
  elem.doSth();
}

在 Java 1.8 中,我们还承诺能够像在古老的脚本语言中一样构造和访问 HashMap:

Map<String,Integer> map = { "orange" : 12, "apples" : 15 };
map["apples"];

更新: 不,他们不会登陆 1.8...:(

Project Coin 的收藏增强功能会出现在 JDK8 中吗?

  • 哈希表 是同步的,如果您在单个线程中使用它,您可以使用 哈希映射, ,这是一个非同步版本。不同步的对象通常性能更高一些。顺便说一句,如果多个线程同时访问一个 HashMap,并且至少有一个线程在结构上修改了该映射,则必须进行外部同步。您可以使用以下方法将未同步的映射包装在同步的映射中:

    Map m = Collections.synchronizedMap(new HashMap(...));
    
  • HashTable 只能包含非空对象作为键或值。HashMap 可以包含一个空键和一个空值。

  • Map 返回的迭代器是快速失败的,如果在创建迭代器后的任何时间对映射进行结构修改,除了通过迭代器自己的remove方法之外的任何方式,迭代器都会抛出 ConcurrentModificationException. 。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒任意、非确定性行为的风险。 然而 Hashtable 的键和元素方法返回的枚举不是快速失败的。

  • HashTable 和 HashMap 是以下成员 Java集合框架 (从Java 2平台v1.2开始,HashTable被改造以实现Map接口)。

  • HashTable 被认为是遗留代码,文档建议使用 并发哈希映射 如果需要线程安全的高并发实现,则可以代替 Hashtable。

  • HashMap 不保证返回元素的顺序。对于哈希表,我想它是相同的,但我不完全确定,我没有找到明确说明这一点的资源。

HashMapHashtable 也有显着的算法差异。之前没有人提到过这一点,所以这就是我提出它的原因。 HashMap 将构造一个具有 2 倍大小的幂的哈希表,动态地增加它,以便在任何桶中最多有大约八个元素(冲突),并且对于一般元素类型来说,可以很好地搅拌元素。但是,那 Hashtable 如果您知道自己在做什么,那么实现可以对散列进行更好、更精细的控制,即您可以使用例如来修复表大小。与您的值域大小最接近的质数,这将导致比 HashMap 更好的性能,即在某些情况下减少碰撞。

与这个问题中广泛讨论的明显差异不同,我将 Hashtable 视为“手动驱动”汽车,您可以更好地控制散列,而 HashMap 作为“自动驱动”对应项,通常性能良好。

Hashtable 是同步的,而 HashMap 不是。这使得 Hashtable 比 Hashmap 慢。

对于非线程应用程序,请使用 HashMap,因为它们在功能方面是相同的。

根据信息 这里, ,我建议使用 HashMap。我认为最大的优点是Java会阻止你在迭代它时修改它,除非你通过迭代器来修改它。

A Collection - 有时称为容器 - 只是将多个元素分组为一个单元的对象。 Collection用于存储、检索、操作和通信聚合数据。集合框架 是用于表示和操作集合的统一架构。

HashMap JDK1.2 和哈希表 JDK1.0, ,两者都用于表示一组对象,这些对象在 <Key, Value> 一对。每个 <Key, Value> 对称为 Entry 目的。条目的集合由以下对象引用 HashMapHashtable. 。集合中的键必须是唯一的或独特的。[因为它们用于检索特定键的映射值。集合中的值可以重复。]


« 超类、旧版和集合框架成员

Hashtable 是一个遗留类,引入于 JDK1.0, ,它是 Dictionary 类的子类。从 JDK1.2 Hashtable 被重新设计以实现 地图界面 成为集合框架的成员。HashMap从一开始就是Java集合框架的成员 JDK1.2. 。HashMap 是 AbstractMap 类的子类。

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable { ... }

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { ... }

« 初始容量和负载率

容量是哈希表中桶的数量,初始容量就是创建哈希表时的容量。请注意,哈希表是开放的:在“的情况下hashcollision”,单个桶存储多个条目,必须按顺序查找。负载因子是衡量哈希表在其容量自动增加之前允许达到的满度的指标。

HashMap 用默认的初始容量构造一个空的哈希表 (16) 和默认负载因子 (0.75)。其中 Hashtable 构造具有默认初始容量的空哈希表 (11) 和负载系数/填充比(0.75)。

Hash Map & Hashtable

« 哈希冲突情况下的结构修改

HashMap, Hashtable 为了防止哈希冲突,它们将映射条目存储在链接列表中。 从 Java8 开始 HashMap 如果哈希桶增长超过某个阈值,该桶将从 linked list of entries to a balanced tree. 。它将最坏情况下的性能从 O(n) 提高到 O(log n)。将列表转换为二叉树时,使用哈希码作为分支变量。如果同一个桶中有两个不同的哈希码,则一个被认为较大并位于树的右侧,另一个位于左侧。但是当两个哈希码相等时, HashMap 假设键是可比较的,并比较键以确定方向,以便可以维持某种顺序。制作钥匙是一个很好的做法 HashMap 可比. 。如果桶大小达到则添加条目 TREEIFY_THRESHOLD = 8 将条目链接列表转换为平衡树,删除小于的条目 TREEIFY_THRESHOLD 并且至多 UNTREEIFY_THRESHOLD = 6 将平衡树重新转换为条目链接列表。 Java 8 SRC, 堆栈帖子

« 集合视图迭代、快速失败和故障安全

    +--------------------+-----------+-------------+
    |                    | Iterator  | Enumeration |
    +--------------------+-----------+-------------+
    | Hashtable          | fail-fast |    safe     |
    +--------------------+-----------+-------------+
    | HashMap            | fail-fast | fail-fast   |
    +--------------------+-----------+-------------+
    | ConcurrentHashMap  |   safe    |   safe      |
    +--------------------+-----------+-------------+

Iterator 本质上是快速失败。也就是说,如果在迭代它自己的remove()方法以外的方法时修改集合,它会抛出ConcurrentModificationException。然而 Enumeration 本质上是自动防故障的。如果在迭代时修改集合,它不会引发任何异常。

根据 Java API 文档,迭代器始终优于枚举。

笔记: Enumeration 接口的功能由 Iterator 接口复制。此外,Iterator 添加了一个可选的删除操作,并且具有更短的方法名称。新的实现应该优先考虑使用迭代器而不是枚举。

Java 5引入了ConcurrentMap接口: ConcurrentHashMap - 高并发、高性能 ConcurrentMap 由哈希表支持的实现。此实现在执行检索时不会阻塞,并允许客户端选择更新的并发级别。它旨在作为以下产品的直接替代品: Hashtable: :除了实施 ConcurrentMap, ,它支持所有特有的“遗留”方法 Hashtable.

  • 每个 HashMapEntrys 值为 易挥发的 从而确保竞争修改和后续读取的细粒度一致性;每次读取都反映最近完成的更新

  • 迭代器和枚举是故障安全的——反映自迭代器/枚举创建以来某个时刻的状态;这允许同时读取和修改,但代价是降低了一致性。它们不会抛出 ConcurrentModificationException。然而,迭代器被设计为一次只能由一个线程使用。

  • 喜欢 Hashtable 但不像 HashMap, ,此类不允许使用 null 作为键或值。

public static void main(String[] args) {

    //HashMap<String, Integer> hash = new HashMap<String, Integer>();
    Hashtable<String, Integer> hash = new Hashtable<String, Integer>();
    //ConcurrentHashMap<String, Integer> hash = new ConcurrentHashMap<>();

    new Thread() {
        @Override public void run() {
            try {
                for (int i = 10; i < 20; i++) {
                    sleepThread(1);
                    System.out.println("T1 :- Key"+i);
                    hash.put("Key"+i, i);
                }
                System.out.println( System.identityHashCode( hash ) );
            } catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }.start();
    new Thread() {
        @Override public void run() {
            try {
                sleepThread(5);
                // ConcurrentHashMap  traverse using Iterator, Enumeration is Fail-Safe.

                // Hashtable traverse using Enumeration is Fail-Safe, Iterator is Fail-Fast.
                for (Enumeration<String> e = hash.keys(); e.hasMoreElements(); ) {
                    sleepThread(1);
                    System.out.println("T2 : "+ e.nextElement());
                }

                // HashMap traverse using Iterator, Enumeration is Fail-Fast.
                /*
                for (Iterator< Entry<String, Integer> > it = hash.entrySet().iterator(); it.hasNext(); ) {
                    sleepThread(1);
                    System.out.println("T2 : "+ it.next());
                    // ConcurrentModificationException at java.util.Hashtable$Enumerator.next
                }
                */

                /*
                Set< Entry<String, Integer> > entrySet = hash.entrySet();
                Iterator< Entry<String, Integer> > it = entrySet.iterator();
                Enumeration<Entry<String, Integer>> entryEnumeration = Collections.enumeration( entrySet );
                while( entryEnumeration.hasMoreElements() ) {
                    sleepThread(1);
                    Entry<String, Integer> nextElement = entryEnumeration.nextElement();
                    System.out.println("T2 : "+ nextElement.getKey() +" : "+ nextElement.getValue() );
                    //java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode
                    //                                          at java.util.HashMap$EntryIterator.next
                    //                                          at java.util.Collections$3.nextElement
                }
                */
            } catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }.start();

    Map<String, String> unmodifiableMap = Collections.unmodifiableMap( map );
    try {
        unmodifiableMap.put("key4", "unmodifiableMap");
    } catch (java.lang.UnsupportedOperationException e) {
        System.err.println("UnsupportedOperationException : "+ e.getMessage() );
    }
}
static void sleepThread( int sec ) {
    try {
        Thread.sleep( 1000 * sec );
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

« 空键和空值

HashMap 最多允许一个空键和任意数量的空值。然而 Hashtable 甚至不允许单个 null 键和 null 值,如果键或值 null 则抛出 NullPointerException。 例子

« 同步、线程安全

Hashtable 是内部同步的。因此,使用起来非常安全 Hashtable 在多线程应用程序中。然而 HashMap 不是内部同步的。因此使用起来并不安全 HashMap 在没有外部同步的多线程应用程序中。您可以外部同步 HashMap 使用 Collections.synchronizedMap() 方法。

« 表现

作为 Hashtable 是内部同步的,这使得 Hashtable 比 稍微慢一点 HashMap.


@看

对于线程应用程序,您通常可以使用 ConcurrentHashMap - 取决于您的性能要求。

1.HashmapHashTable 两者都存储键和值。

2.Hashmap 可以将一个密钥存储为 null. Hashtable 无法存储 null.

3.HashMap 不同步但是 Hashtable 已同步。

4.HashMap 可以与同步 Collection.SyncronizedMap(map)

Map hashmap = new HashMap();

Map map = Collections.SyncronizedMap(hashmap);

除了已经提到的差异之外,还应该注意的是,从 Java 8 开始, HashMap 动态地将每个桶中使用的Nodes(链表)替换为TreeNodes(红黑树),这样即使存在高哈希冲突,最坏的情况 当搜索时

O(log(n)) 为 HashMap VS O(n) 中 Hashtable.

*上述改进尚未应用于 Hashtable 然而,但只是为了 HashMap, LinkedHashMap, , 和 ConcurrentHashMap.

仅供参考,目前,

  • TREEIFY_THRESHOLD = 8 :如果一个桶包含超过8个节点,则链表将转换为平衡树。
  • UNTREEIFY_THRESHOLD = 6 :当存储桶变得太小时(由于删除或调整大小),树将转换回链表。

HashTable 和 HashMap 有 5 个基本区别。

  1. Maps 允许您迭代和检索键、值以及键值对,而 HashTable 不具备所有这些功能。
  2. Hashtable中有一个函数contains(),使用起来很混乱。因为contains的意思略有偏差。是包含键还是包含值?很难理解。同样,在 Maps 中我们有 ContainsKey() 和 ContainsValue() 函数,它们非常容易理解。
  3. 在 hashmap 中,您可以在迭代时安全地删除元素。因为这在哈希表中是不可能的。
  4. 哈希表默认是同步的,因此可以轻松地与多个线程一起使用。由于 HashMap 默认情况下不同步,因此只能用于单线程。但是您仍然可以使用Collections util 类的synchronizedMap(Map m) 函数将HashMap 转换为synchronized。
  5. HashTable 不允许空键或空值。HashMap 允许一个空键和多个空值。

我的小贡献:

  1. 第一个也是最显着的区别 HashtableHashMap 就是它, HashMap 不是线程安全的 Hashtable 是一个线程安全的集合。

  2. 之间的第二个重要区别 HashtableHashMap 是性能,因为 HashMap 不同步,它的性能优于 Hashtable.

  3. 第三个区别 HashtableHashMap 就是它 Hashtable 是过时的类,你应该使用 ConcurrentHashMap 代替 Hashtable 在爪哇。

哈希表 是jdk中的遗留类,不应再使用。将其用法替换为 并发哈希映射. 。如果不需要线程安全,可以使用 哈希映射 这不是 线程安全 但速度更快并且使用更少的内存。

1)Hashtable是同步的,而hashmap不是。2)另一个区别是HashMap中的迭代器是故障安全的,而Hashtable的枚举器则不是。如果您在迭代时更改地图,您就会知道。

3)HashMap允许其中存在空值,而Hashtable则不允许。

哈希表和哈希表

  • 关于 HashMap 和 HashTable 的一些要点。请阅读以下详细信息。

1)Hashtable和Hashmap实现Java.util.map接口2)Hashmap和Hashtable都是基于哈希的集合。并致力于散列。所以这些是HashMap和HashTable的相似之处。

  • HashMap 和 HashTable 有什么区别?

1)第一个区别是 HashMap 不是线程安全的,而 HashTable 是线程安全的
2)HashMap 的性能更好,因为它不是线程安全的。而哈希表的性能并没有因为它是线程安全的而更好。所以多个线程不能同时访问Hashtable。

哈希映射:它是 java.util 包中可用的类,用于以键和值格式存储元素。

哈希表:它是一个在集合框架内被识别的遗留类。

Hashtable:

哈希表 是一种保留键值对值的数据结构。它不允许键和值都为 null。您将得到一个 NullPointerException 如果添加空值。它是同步的。所以它伴随着它的成本。只有一个线程可以访问 哈希表 在特定时间。

例子 :

import java.util.Map;
import java.util.Hashtable;

public class TestClass {

    public static void main(String args[ ]) {
    Map<Integer,String> states= new Hashtable<Integer,String>();
    states.put(1, "INDIA");
    states.put(2, "USA");

    states.put(3, null);    //will throw NullPointerEcxeption at runtime

    System.out.println(states.get(1));
    System.out.println(states.get(2));
//  System.out.println(states.get(3));

    }
}

哈希映射:

哈希映射 就好像 哈希表 但它也接受键值对。它允许键和值都为 null。其性能优于 HashTable, , 因为它是 unsynchronized.

例子:

import java.util.HashMap;
import java.util.Map;

public class TestClass {

    public static void main(String args[ ]) {
    Map<Integer,String> states = new HashMap<Integer,String>();
    states.put(1, "INDIA");
    states.put(2, "USA");

    states.put(3, null);    // Okay
    states.put(null,"UK");

    System.out.println(states.get(1));
    System.out.println(states.get(2));
    System.out.println(states.get(3));

    }
}

HashMaps 为您提供同步的自由,并且调试更加容易

HashMap 被模拟,因此可用于 GWT client code 然而 Hashtable 不是。

同步或线程安全 :

Hash Map 不是同步的,因此它不是线程安全的,并且如果没有适当的同步块,它不能在多个线程之间共享,而 Hashtable 是同步的,因此它是线程安全的。

空键和空值 :

HashMap 允许一个空键和任意数量的空值。Hashtable 不允许空键或空值。

迭代值:

HashMap 中的迭代器是一个快速失败迭代器,而 Hashtable 的枚举器不是,如果任何其他线程通过添加或删除除 Iterator 自己的 remove() 方法之外的任何元素来结构性地修改映射,则会抛出 ConcurrentModificationException。

超类和遗产 :

HashMap 是 AbstractMap 类的子类,而 Hashtable 是 Dictionary 类的子类。

表现 :

由于 HashMap 不同步,因此与 Hashtable 相比速度更快。

参考 http://modernpathshala.com/Article/1020/difference- Between-hashmap-and-hashtable-in-java 与Java集合相关的示例和面试问题和测验

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top