java集合
为什么集合只能存放引用数据类型?
java集合实际存放的是对象的引用,每个集合元素都是一个引用变量,实际内容都存放在堆内存或方法区中,但是基本数据类型是在栈内存上分配空间的,栈上的数据会随时被收回的
java中的常用的集合有几种?
(set,list,map)除了这几个常用的,还有一个queue(队列), 并且set,list,queue的同一个父类是collection,map和collectin是同一级,但是没有什么联系,是相互独立的。
Set集合:集合元素是不能重复的。元素是没有顺序的。所以它不能基于位置访问元素。TreeSet和HashSet是它的实现类。
List集合: 集合元素是可以重复的。元素是有顺序的。所以它可以基于位置访问元素。ArrayList和LinkedList是它的实现类。
Map:它包含键值对。Map的键是不能重复的。Map不能保证存储的顺序。HashMap和TreeMap是它的实现类。
Queue用于模拟队列这种数据结构。
集合的数据结构
(1)Set接口是collection的子接口:
HashSet 其底层是由哈希表(其实还是数组和单链表实现)实现,hashset的底层是基于hashMap来实现的。Hash可以存入null值,但是只能存储一个null。
HashSet遍历的三种方法:foreach遍历,使用数组遍历,使用迭代器
LinkedHashSet 底层实现:继承hashset类,基于LinkedHashMap来实现的,和hashSet的区别主要在于它是双向链表结构,有顺序。在插入元素的时候,也是通过元素的哈希值来进行来决定元素的存储位置。但是利用链表的来保证以插入的顺序来进行保存元素
TreeSet 底层实现是通过treeMap(二叉树)实现的,是一个有序集合类,存入Treeset集合的值,并不是按照插入顺序来排序。排序的实现有两种:
自然排序:实现comparable要使用要排序的元素的compareTo(object obj)方法进行比较元素之间的大小关系,然后将元素进行升序排列。
定制排序:如果要定制排序,就要实现comparator接口,实现int compare方法
(2)List接口是collection的子接口:
ArrayList 数据结构:底层实现是object数组(动态数组,可以扩容),它的随机访问速度极快,但是插入和删除的操作需要移动数组中的元素,比较麻烦。
注意:1)它在没有初始化长度的时候,默认长度是10
2)如果向ArrayLIst添加元素,超过了原来的容量,那么扩容方案:扩容到原数组的1.5倍,如果扩容完之后还是小于返回到mincapacity(最小容量)
3)ArrayList是线程不安全的,在多线程的情况下不要使用
如果非要使用list,就推荐使用vector,它基本上和ArrayList一样,区别在于vector中大部分方法都使用了同步关键字synchronized修饰,这样在多线程不会出现并发错误,扩容区别vector默认扩容就是原来的2倍。
ArrayList遍历的几种方式:1:使用foreach遍历list 2:将list转化为数组,在进行遍历使用迭代器遍历
LinkedList 数据结构:双向链表 随机访问速度慢,查找元素是从头开始一个一个查找,但是适合于频繁的插入和删除操作。线程不安全
LinkedList内部实现原理:LinkedList内部有一个类似于C语言的1结构体的Entry内部类,它包含了前一个元素的地址引用和后一个元素的地址引用,类似C语言的指针。进行操作元素。
(3)Map集合:
HashMap 基于哈希表的map接口实现,底层使用数组和单链表实现。使用了哈希算法,以便进行快速查询,线程不安全,存入的顺序和遍历的顺序可能不一致。
LinkedHashMap是map接口的哈希表和链表来实现的。
TreeMap 底层实现是红黑树,可以用于排序。排序分为两种:TreeMap提供了四个构造方法,实现了方法的重载。无参构造方法中比较器的值为null,采用的是自然排序的方法,如果指定了比较器则是定制排序。
集合是否线程安全的总结:
线程安全:Vector,HashTable(jdk1.0引入),
线程不安全:ArrayList,LinkedList,HashMap,TreeMap,TreeSet,HashSet
HashTable和hashMap之间的区别:
1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
2.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
3. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashSet和HashMap区别:
1、HashSet底层是采用HashMap实现的。HashSet 的实现比较简单,HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。
2、HashMap的key就是放进HashSet中对象,value是Object类型的。
3、当调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的那个对象,该行的value就是一个Object类型的常量
TreeSet和TreeMap区别:
1. 最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口
2.TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)
3.TreeSet中不能有重复对象,而TreeMap中可以重复
4.TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。因此它要求一定要有Key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。
线程安全问题
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
hashmap为什么线程不安全?
第一:如果多线程同时使用put方法添加元素,如果两个线程添加数据的位置是一个位置,那么最终第二个线程添加的数据会把第一个线程添加的数据覆盖
第二:扩容时引起死循环。当两个线程同时检测到元素个数超过阀值,此时两个线程都会调用resize()方法进行扩容,两个线程同时修改一个链表结构会产生一个循环链表。接下来,在想通过get()获取某一个元素,就会出现死循环。
使hashmap线程安全?
方法一:通过Collections.synchronizedMap()返回一个新的Map,这个新的map就是线程安全的,Collections.synchronizedMap()封装所有不安全的HashMap的方法,就连toString, hashCode都进行了封装. 封装的关键点有2处,1)使用了经典的synchronized来进行互斥, 2)使用了代理模式new了一个新的类,这个类同样实现了Map接口.
方法二:重新改写了HashMap,具体的可以查看java.util.concurrent.ConcurrentHashMap.
使用了新的锁机制(可以理解为乐观锁)
把HashMap进行了拆分,拆分成了多个独立的块,这样在高并发的情况下减少了锁冲突的可能
ConcurrentHashMap的锁分段技术
concurrenthashmap容器里有多把锁,每一把锁用于锁住容器中的一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
什么实用concurentHashMap技术?
concurrentHashmap是支持完整的并发操作和更新,当有大量的并发和更新时,我们可以使用
hashtable为什么是线程安全的?
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下,HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
hashset线程不安全?
Set的特点就是存放的数据不会重复。因为它的内部会首先读内部保存的数据,是否存在,如果存在就不存放进去,否则存放进去。也就是说数据的存入操作是分两步,首先读取,然后写入。假设不是线程安全,那很可能出现的一种情形就是当线程A判断该set对象没有某个元素,正准备将该元素插入之前,线程B也判断该对象不存在该元素,也准备插入,最后的结果导致了两个相同的元素被插入了。
ArrayList为什么线程不安全
1、出现数组越界问题
当列表大小为9,size=9,线程A开始进入add方法,此时获取size=9进行是否需要扩容判断,线程B此时也进入add方法,它获取到的size也是9,进行扩容判断。线程A判断之后,不需要扩容,开始设置值操作,elementdata[size++]=e,结果size=10。线程B判断之后,也不需要扩容,进行设置值操作elementdata[size++]=e,但是此时就会出现数组越界异常(数组默认0-9),elementdata[10]超出范围,所以线程不安全
2、多线程环境下执行时,可能就会发生一个线程的值覆盖另一个线程添加的值
列表大小为0,即size=0
线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
线程A开始将size的值增加为1
线程B开始将size的值增加为2
解决ArrayList线程不安全
一:使用synchronized关键字,这个大家应该都很熟悉了,不解释了;
二:使用Collections.synchronizedList();使用方法如下:
假如你创建的代码如下:List
那么为了解决这个线程安全问题你可以这么使用Collections.synchronizedList(),如:
List
LInkedList线程安全问题解决?
1、List
2、LinkedList换成ConcurrentLinkedQueue
hashmap是如何工作的?
HashMap是一个针对数据结构的键值,每个键都会有相应的值,关键是识别这样的值。
HashMap 基于 hashing原理,我们通过 put ()和 get ()方法储存和获取对象。当我们将键值对传递给 put ()方法时,它调用键对象的 hashCode()方法来计算 hashcode,然后找到bucket 位置来储存
值对象。当获取对象时,通过键对象的equals ()方法找到正确的键值对,然后返回值对象。HashMap 使用 LinkedList(链表) 来解决碰撞问题,当发生碰撞了,对象将会储存在 LinkedList (链表)的下一个节点中。 HashMap 在每个 LinkedList(链表) 节点中储存键值对对象。
判断hashset中是否是重复的对象?
先判断两个对象的hashcode是否相同(如果两个对象的hashcode相同,不一定是一个对象,如果两个对象的hashcode不同,一定不是一个对象)如果两个hashcode相同,在进行equals方法判断,如果equals相同则是同一个对象,如果equals判断不相同,那么就不是一个对象。
hashset存储原理?
1、将要传入的数据根据系统的hash算法得到一个hash值
2、根据hash值计算出数据在hash表中的位置
3、判断该位置是否有值,没有值就将数据插入进来,如果有值则再次判断传入的值与已经存储的值的equals结果是否相同,如果相同则不存,如果不相同则通过单链表的形式存储
Arraylist添加元素的原理?
1、调用add方法,在插入数据之前,先要判断数组是否能够再容下元素(调用方法参数是size+1,是当前数组长度+1也就是mincapacity最小容量),此时要判断是默认数组还是自定义数组。如果是默认数组先判断mincapacity和默认数组长度10谁大,返回谁。如果是自定义的数组,直接返回mincapacity。根据返回的值和数组的定义长度大小比较,如果返回的值大,则进行扩容。
2、扩容:根据传入的mincapacity和数组的容量长度对比,如果mincapacity大于数组长度,需要扩容。先根据原来的数组的长度,进行扩容到原来数组长度的1.5倍。如果扩容之后的容量小于mincapacity,则将mincapacity赋值给新数组的容量。如果扩容之后的容量大于数组定义最大容量,那么赋值给新数组Integer.MAX_VALUE-8
3、调用copyof方法,新建了一个数组,将原数组的对象复制到新的数组中,并且将现有的数组指向新的数组。
4、在调用element[size++]=e添加元素。