• Java 常用数据结构对象的实现原理 集合类 List Set Map 哪些线程安全 (美团面试题目)


    Java中的集合包括三大类,它们是Set、List和Map,
    它们都处于java.util包中,Set、List和Map都是接口,它们有各自的实现类。
    List、Set都继承自Collection接口,Collection (所有集合类的接口)
    • Set(集) 实现类主要有HashSet和TreeSet
    • List(列表) 实现类主要有ArrayList,LinkedList,Vector
    • Map(映射)实现类主要有HashMap和TreeMap,HashTable
    • 线程安全类

    • 在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的。在jdk1.2之后,就出现许许多多非线程安全的类。 下面是这些线程安全的同步的类:

      vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。

      statck:堆栈类,先进后出

      hashtable:就比hashmap多了个线程安全

      enumeration:枚举,相当于迭代器

    • StringBuffer也是线程安全的,(StringBuilder 不是线程安全的)

    • 除了这些之外,其他的都是非线程安全的类和接口。

      线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低。  

    • Collection 和 Map 的区别

      容器内每个为之所存储的元素个数不同。Collection类型者,每个位置只有一个元素。Map类型者,持有 key-value pair,像个小型数据库。

      List,Set都是继承自Collection接口,Map则不是

      (a) Collection
           --List:将以特定次序存储元素。所以取出来的顺序可能和放入顺序不同。
                 --ArrayList / LinkedList / Vector
           --Set : 不能含有重复的元素
                 --HashSet / TreeSet
       (b) Map
           --HashMap
           --HashTable
           --TreeMap 

      总结
    • List,Set,Map将持有对象一律视为Object型别。

      Collection、List、Set、Map都是接口,不能实例化。    继承自它们的 ArrayList, Vector, HashTable, HashMap是具象class,这些才可被实例化。
      vector容器确切知道它所持有的对象隶属什么型别。vector不进行边界检查。

      1. 如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
      2. 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
      3. 在除需要排序时使用TreeSet,TreeMap外,都应使用HashSet,HashMap,因为他们 的效率更高。
      4. 要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。 
      5. 容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至数列某位置。一旦将对象置入容器内,便损失了该对象的型别信息。
      6. 尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。 
    • 7. set中不允许有重复的元素存在
    • 8. map的 key 集合和 set集合都不允许有重复的元素存在,list可以有重复的元素存在 

      注意:
      1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。
      2、Set和Collection拥有一模一样的接口。
      3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)
      4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。
      5、Map用 put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。
            HashMap会利用对象的hashCode来快速找到key。
      6、Map中元素,可以将key序列、value序列单独抽取出来。
      使用keySet()抽取key序列,将map中的所有keys生成一个Set。
      使用values()抽取value序列,将map中的所有values生成一个Collection。
      为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。  

        (2)List中的对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,如通过list.get(i)方式来获得List集合中的元素。
        (3)Set中的对象不按特定方式排序,并且没有重复对象。但它的有些实现类能对集合中的对象按特定方式排序,例如TreeSet类,它可以按照默认排序,也可以通过实现             java.util.Comparator<Type>接口来自定义排序方式。  
        (4) Map中的每一个元素包含一个键对象和值对象,它们成对出现。键对象不能重复,值对象可以重复。

    关系如图:

    或者:

    Collections (操作集合的工具类)
    对于集合类的操作不得不提到工具类Collections,它提供了许多方便的方法,如求两个集合的差集、并集、拷贝、排序等等。
    由于大部分的集合接口实现类都是不同步的,可以使用Collections.synchronized*方法创建同步的集合类对象。
    如创建一个同步的List:
    List synList = Collections.synchronizedList(new ArrayList());
    其实现原理就是重新封装new出来的对象,操作对象时用关键字synchronized同步。看源码很容易理解。
    Collections部分源码: 

    static class SynchronizedCollection<e> implements Collection<e>, Serializable {
     
            final Collection<e> c;  // Backing Collection
            final Object mutex;     // Object on which to synchronize
     
            SynchronizedCollection(Collection<e> c) {
                if (c==null)
                    throw new NullPointerException();
                this.c = c;
                mutex = this;
            }
            //...
            public boolean add(E e) {
                //操作集合时简单调用原本的ArrayList对象,只是做了同步
                synchronized (mutex) {return c.add(e);}
            }
            //...
    
     
    
    

    List(列表)

    ArrayList、Vector是线性表,使用Object数组作为容器去存储数据的,添加了很多方法维护这个数组,使其容量可以动态增长,极大地提升了开发效率。它们明显的区别是ArrayList是非同步的,Vector是同步的。不用考虑多线程时应使用ArrayList来提升效率。

    LinkedList是链表,略懂数据结构就知道其实现原理了。链表随机位置插入、删除数据时比线性表快,遍历比线性表慢。 

    由此可根据实际情况来选择使用ArrayList(非同步、非频繁删除时选择)、Vector(需同步时选择)、LinkedList(频繁在任意位置插入、删除时选择)。

    Map(存储键值对,key唯一)

    HashMap结构的实现原理是将put进来的key-value封装成一个Entry对象存储到一个Entry数组中,位置(数组下标)由key的哈希值与数组长度计算而来。如果数组当前下标已有值,则将数组当前下标的值指向新添加的Entry对象。

    public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable
    {
        transient Entry<k,v>[] table;
        public V put(K key, V value) {
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key);
            int i = indexFor(hash, table.length);
            //遍历当前下标的Entry对象链,如果key已存在则替换
            for (Entry<k,v> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
           addEntry(hash, key, value, i);
            return null;
        }
    }
    static class Entry<k,v> implements Map.Entry<k,v> {
        final K key;
        V value;
        Entry<k,v> next;
        int hash;
    }

    TreeMap是由Entry对象为节点组成的一颗红黑树,put到TreeMap的数据默认按key的自然顺序排序,new TreeMap时传入Comparator自定义排序。红黑树网上很多资料,我讲不清,这里就不介绍了。

    Set(保证容器内元素唯一性)

    之所以先讲Map是因为Set结构其实就是维护一个Map来存储数据的,利用Map结构key值唯一性。 
    HashSet部分源码:

    public class HashSet<e>
    extends AbstractSet<e>
    implements Set<e>, Cloneable, java.io.Serializable
    {    
    
        //无意义对象来作为Map的value 
        private static final Object PRESENT = new Object();
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    }

    HashMap和HashTable的区别

    具体的区别请参考:HashMap、HashTable、ConcurrentHashMap、HashSet区别 线程安全类

      StringBuffer是线程安全,而StringBuilder是线程不安全的。对于安全与不安全没有深入的理解情况下,易造成这样的错觉,如果对于StringBuffer的操作均是线程安全的,然而,Java给你的保证的线程安全,是说它的方法是执行是排它的,而不是对这个对象本身的多次调用情况下,还是安全的。看看下边的例子,在StringBufferTest中有一个数据成员contents它是用来扩展的,它的每一次append是线程安全的,但众多次append的组合并不是线程安全的,这个输出结果不是太可控的,但如果对于log和getContest方法加关键字synchronized,那么结果就会变得非常条理,如果换成StringBuider甚至是append到一半,它也会让位于其它在此基础上操作的线程: 

    参考:Java基础之常用数据结构及原理分析

     

  • 相关阅读:
    Delphi与C++的语法区别(六点区别) good
    Delphi检测用户是否具有administrator权限(OpenThreadToken,OpenProcessToken,GetTokenInformation,AllocateAndInitializeSid和EqualSid)
    QSqlDatabase::addDatabase第一次运行的时候,生成SQLite文件的同时会产生一个默认连接
    所有CN_消息的说明
    hdu 1671 Phone List(字典树)
    所有CM_消息的说明
    感悟:新事物的生命力是惊人的,存在无限的机会
    Delphi访问活动目录(使用COM,活动目录Active Directory是用于Windows Server的目录服务)
    Delphi中获取某类的祖先类及其所在单元名称(使用GetTypeData(PClass.ClassInfo)函数,并且该类是从TPersistent类的派生类才可以这么使用)
    消息函数一般是私有的,因为不需要程序员显示的调用,但子类如果需要改写这个方法,则改成保护方法Protected
  • 原文地址:https://www.cnblogs.com/aspirant/p/6789485.html
Copyright © 2020-2023  润新知