浅谈java集合

Collection接口

Collection是一个单列集合,用来存储一个一个的对象。
当我们调用实现类的contains(Object obj)方法判断当前集合中是否含有某一对象时,会调用对象所在类的equals()方法对值进行对比;而当调用remove(Object obj)方法删除一个对象元素时,也会先调用对象所在类的equals()方法先对比是否存在被删元素对象,所以使用Collection存储的对象需要重写equals()方法。

使用.toArray()方法使集合转换为数组,
使用Arrays.asList(数组对象)方法使数组转换为集合。

Iterator接口

Iterator接口主要用于遍历Collection集合中的元素。
使用Iterator接口的 hasNext() 方法判断是否集合中还有元素和 next() 方法获取集合中下一个元素来进行集合元素的遍历。
Iterator实现原理:当集合对象调用hasNext方法时,判断集合中是否还有下一个元素,有则返回true并执行next方法,next方法执行时指针后移,并将后移的位置上的元素返回,hasNext方法执行结束后,指针停留在集合最后一个元素位置);集合对象每次调用iterator()方法都会返回一个新的迭代器对象,默认下标都在集合的第一个元素之前。Iterator内部的remove()方法可以在遍历中删除一些元素,但是未调用next方法或者在调用next方法之后连续调用remove方法则会报异常,前者是因为未调用next方法,迭代器中指针未移动没有拿到元素,后者是因为该元素已被删除,无法重复删除。

示例代码:

Iterator iterator = list.iterator() ;
while(itertor.hasNext()){
    itertor.next()//指针下移并返回下移位置的元素;
}

List接口

存储有序的,可重复的数据,可理解为动态数组。主要有以下实现类:

  1. ArrayList:作为List接口的主要实现类,线程不安全,执行效率高,底层使用Object[] elementData存储数据。
    ArrayList源码分析
    1- 在jdk7中:ArrayList有一个默认创建长度为10的对象数组Object[] elementData的空参构造器,当我们调用ArraryList的add方法时,会先调用ensureCapacityInternal(size+1 )方法来判断列表长度是否足够,如果长度不足就会调用grow(int minCapacity)方法进行扩容,默认扩容长度为原来的1.5倍,如果扩容后的长度不够,就直接扩容参数长度,仍然不足就给他一个最大长度,再不足就报错,最后把原先的数据复制到扩容后的数组中,建议使用带参构造器进行创建,直接赋值好创建的数组长度,提升效率。
    2- 在jdk8中,使用空参构造器创建ArrayList对象时,不会直接创建有默认初始长度的数组,而是当第一次调用add方法时才创建一个长度为10的数组Object[] elementData,后续添加扩容操作则与jdk7相同。
    相比而言:7中的创建方式类似于单例的饿汉式,8中的创建方式类似于单例的懒汉式,而这样的好处就是延迟创建数组可节省内存空间。
    解决ArrayList集合线程安全问题的方法
  1. Vector vector = new Vector<>();
  2. Collections.synchronizedList(new ArrayList<>())
  3. List<String> list = new CopyOnWriteArrayList<>();
  1. LinkedList:对于频繁的插入与删除操作效率高于ArrayList,底层使用双向链表存储数据。
    LinkedList源码分析当我们创建LinkedList对象时,内部声明了Node类型的first属性和last属性,默认初始值为null,用于存放链表的头节点和尾节点,同时还有一个内部类Node,用于将添加的数据封装为一个双向链表形式的对象进行操作,当我们使用LinkedList进行数据操作时,就会根据数据的节点值地址快速进行操作

  2. Vector:作为List接口的古老实现类,线程安全,执行效率低,底层使用Object[] elementData存储数据。
    Vector源码分析:当我们创建Vector对象时,无参构造默认创建一个长度为10的对象数组,扩容时为原来的二倍,其add方法为线程安全的。

List接口的常用方法

void add(int index, Object ele);
在index位置上插入ele元素

boolean addAll(int index Collection eles);
在index位置开始将eles中的元素添加进来

Object get(int index);
获取index位置上的元素

int indexOf(Object obj);
返回obj在集合中首次出现的位置,不存在则返回-1

int lastIndexOf(Object obj);
返回obj在当前集合中最后一次出现的位置

Object remove(int index);
移除指定位置index上的元素,并将其返回

Object set(int index, Object ele);
将指定位置index上的元素设置为ele

List subList(int fromIndex, int toIndex);
返回从formIndex到toIndex位置的子集合(左闭右开)

Set接口

存储无序的,不可重复的数据。主要有以下实现类:

  1. HashSet:作为Set接口的主要实现类,线程不安全,可存储null值,底层为HashMap,存储方式有数组也有链表。
    HashSet的添加操作: 首先会根据元素所在类的hashCode()方法计算出其hashcode,再以散列哈希算法来计算其存储位置,判断该位置是否有元素,没有则添加成功,若该位置有元素或以链表形式已存在多个元素,则比较其hashcode,不同则添加成功,相同的话则根据新增元素所在类的equals()方法与该位置所有数据进行一一对比,进行对比之后若返回true则添加失败,否则就以链表形式进行存储。向set中添加的数据其所在类一定要重写hashCode()和equals()方法
  2. LinkedSet:作为HashSet的子类,在添加数据时,每个数据还维护了一堆节点数据用来指向前一个数据和后一个数据,所以遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,效率高于HashSet。
  3. TreeSet:可以按照添加的对象的指定属性进行排序底层使用红黑二叉树存储数据,而且向其中添加的对象只能是同类型对象,在TreeSet中,有两种排序方式,一个自然排序(需要实现Comparable接口)和定制排序(创建Comparator对象重写compare()方法),在比较两个对象是否相同,自然排序是根据compareTo()方法返回值是否为0,0则相同;定制排序是根据compare()方法返回值是否为0,0则相同,两者都不再使用equals()方法进行对比)

Set的无序性和不可重复性

  1. 无序性不等于遍历时输出数据的随机性,以HashSet为例,jdk7中底层默认创建一个长度为16的对象数组,当执行add方法时,会根据添加数据的hashcode来决定其在数组中的存储位置,这样就体现出了其无序性。
  2. 不可重复性是指在添加元素时,按照equals()方法进行判断不能返回true,也就是相同的元素只能添加一个,hashcode是根据对象实体类的hashCode()方法计算的,如果对象有相同属性,则hashcode相同, 如果多个hashcode不同的元素但通过散列哈希算法计算出其储存位置相同,则以链表的形式存储,不同的是在jdk7中,新数据在数组中,旧数据以链表方式连接存储,而在jdk8中,旧数据在数组中新数据以链表方式储存。如果多个hashcode相同的元素其存储位置相同,则调用新加元素的equals()方法进行对比,返回true则两元素相同添加失败,返回false则也以链表形式存储数据,添加成功。

Map接口

双列集合,用来存储键值对类型的数据,
Map中的key是无序且不可重复的,使用map对应的set存储所有的key(HashMap对应HashSet),key所在的类需要重写equals()和hashCode()方法;
Map中的vaue是无序可重复的,使用Collection存储所有的value,value所在的类要重写equals()方法。
一个键值对构成一个Entry对象,Map中的Entry对象是无序不可重复的,使用Set存储所有的Entry。主要有以下实现类:

  1. HashMap:作为Map的主要实现类,线程不安全,效率高,能存储键值为null的数据。
    HashMap底层实现原理jdk7中,当我们调用 new HashMap() 实例化一个HashMap时,底层创建一个长度为16的一维数组Entry[] table,当我们调用put(k1, v1)方法添加数据时,首先调用k1所在类的hashCode()方法计算k1的哈希值,并且通过散列哈希算法计算出该哈希值对应在数组中的存储位置,如果位置数据为空,则添加成功,若不为空(添加成功的数据与原来的数据以链表形式存储),这意味着该位置存在一个或多个数据(以链表形式存在),则比较k1和已存在数据的哈希值,如果不相同,添加成功,若相同,则调用k1所在类的equals()方法判断两者是否相同,equals()返回true,使用v1替换原有数据,返回false,则添加成功。默认扩容为原来的2倍,并复制原有数据。
    jdk8中,当我们实例化一个HashMap时,底层不创建长度为16的数组,当首次调用put()方法时,底层才创建长度为16的数组,jdk8中的底层数组时Node[],而非Entry[],相比jdk7的底层数据结构,jdk8在数组+链表的基础上添加了红黑树,当数组的某一索引位置上的元素以链表形式存在的数据个数大于8并且当前数组长度大于64时,为了提升查询效率,此索引位置上的所有数据改为使用红黑树存储。(小于64时进行扩容)

  2. LinkedHashMap:在原有的HashMap基础上添加了头尾节点属性,用来指向前一个和后一个数据,保证在遍历map元素时,能够按照添加的顺序实现遍历。
    LinkedHashMap底层实现原理:继承了父类HashMap,同时其内部类Entry继承了Node,并且有了新的节点属性用来存储前后元素的索引位置实现按顺序遍历元素。

  3. TreeMap:保证按照添加的键值对根据键来进行排序,进行排序遍历,底层使用红黑二叉树,键必须是同一个类创建的对象

  4. HashTable:作为古老的实现类,线程安全,效率低,不能存储键值为null的数据

  5. Properties:键值对都是String型,主要应用于配置文件。

Map接口的常用方法

Object put(Object key,Object value);
将指定的键值对添加/修改到当前map对象中

void putAll(Map map);
将参数map中的所有键值对添加到当前map中

Object remove(Object key);
移除指定的键值对,并返回value

void clear();
清空当前map中所有的数据
Object get(Object key);
获取指定key对应的value

boolean containsKey(Object key);
当前map中是否包含指定的key

boolean containsValue(Object value);
当前map中是否包含指定的value

int size();
返回map中键值对个数

boolean isEmpty();
判断当前map是否为空

boolean equals(Object obj);
判断当前map是否与参数对象obj相等
Set KeySet();
返回所有key构成的set集合
Collection values();
返回所有value构成的collection集合
Set entrySet();
返回所有key-value对构成的Set集合
entrySet可以调用对应的getkey与getValue方法获取对应的键值

HashMap与HashSet的线程安全问题

HashSet中的add()方法并未使用synchronized方法同步实现线程安全,所以也是线程不安全
解决方法Set<String> hashSet = new CopyOnWriteArraySet<>();

HashMap中的put()方法并未使用synchronized方法同步实现线程安全,所以也是线程不安全
解决方法Map<String,String> map = new ConcurrentHashMap<>();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值