集合
集合概念
对象的容器,实现了对对象常用的操作,类似数组功能。
集合和数组的区别:
- 数组长度固定,集合长度不固定
- 数组可以存储基本数据类型和引用类型,集合不能直接存储基本数据类型另外集合也不能直接存储java对象(换句话按说,集合只能存储引用类型)。
位置都在 java.util包里。
java中集合分为两大类
一类是单个方式存储元素,这一类集合中超级父接口:java.util.Collection
一类是以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map
Collection体系集合
Collection是可迭代的,怎么迭代呢?它继承了Iterable接口,通过调用 **iterator()**方法获得一个Iterator对象,这个Iterator也是个接口,它有诸如hasNext(),next(),remove()等方法可用于遍历集合。
-
ArrayList: 底层是数组。ArrayList是非线程安全的。
-
LinkedList: 底层是双向链表。
-
Vector: 底层也是数组。Vector是线程安全的,通过查看源码可知,Vector内部也是用数组实现的,并且它的方法都用synchronized修饰了。但是Vector效率较低,现在保证线程安全有别的方案,所有Vector慢慢被弃用了。
-
HashSet: 通过查看源码,实际上HashSet集合在new的时候,底层实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap中的key部分了。HashMap集合底层是哈希表数据结构。
-
TreeSet: 通过查看源码,实际上TreeSet集合在new的时候,底层实际上new了一个TreeMap集合。向TreeSet集合中存储元素,实际上是存储到TreeMap中的key部分了。TreeMap集合底层是二叉树数据结构。
Map体系集合
- HashMap: 底层是哈希表。非线程安全的。
- Hashtable: 底层也是哈希表。是线程安全的,但是效率低,使用较少。
- Properties:是线程安全的(因为继承了Hashtable),并且key和value只能存储字符串String
- TreeMap:底层是自平衡二叉树。TreeMap集合的key可以自动按照大小顺序排序。
List集合存储元素的特点:有序,可重复。
有序:存进去的顺序和取出的顺序相同,每个元素都有下标。(并不是值的大小有序)
可重复:可以存储多个相同的元素。
Set(Map)集合存储元素的特点:无序,不可重复。
无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标。
不可重复:不能存储2个及以上相同的元素。
SortedSet(SortedMap)是接口,这个集合存储元素特点:由于继承了set集合,所以它的特点也是无序不可重复。但是放在SortedSet集合中的元素可以按照值大小自动排序。我们称为可排序集合。放到该集合中的元素是自动按照大小顺序排序的。
永远要记住:Map集合的key就是一个Set集合。往Set集合放数据,实际上放到了Map集合的key部分。Set家族底层都是调用了Map家族
通过查看源码可知,set的add()方法内实际上是map.put()
注意,集合不止上面列出的那几种,这些只是常用的。它包括的东西很多很多,面试的时候只答这几个是不行的。
Collection接口
常用方法:
- boolean add(Object e): 向集合中添加元素。
//Collection c = new Collection(); 接口是抽象的,无法实例化。
//多态
Collection c = new ArrayList();
c.add(1200);//自动装箱,实际上是放进去了一个对象的内存地址Integer x = new Integer(1200)
c.add(true);//自动装箱
c.add(3.14);//自动装箱
c.add(new Student());//自动装箱
System.out.println("集合中元素个数:"+c.size);
c.clear();//清空集合
//对Collection集合进行遍历/迭代
Interator it = c.iterator();//1.获取集合对象的迭代器对象Iterator
while(it.hasNext()){//2.判断有没有
Object obj = it.next();//3.取对象。不管当初你存进去的是什么,取出来的都是Object
System.out.println(obj);
}
集合结构只要发生改变,迭代器必须重新获取。否则迭代器会出现异常:java.util.ConcurrentModificationException
例如:
我们不能在迭代过程中直接调用集合对象来改变集合,要想在迭代过程删除集合元素,只能用迭代器的remove()方法。
it2.remove();它会自动删除迭代器当前指向的元素。
- contains()方法:判断是否存在某个元素
答案是true。为什么? contains底层调了equals()方法,但equals比较的是内存地址啊,x和s1的内存地址不一样啊,为什么会相等?
这里的x是String类型,别忘了String类重写了equals方法,这里进行的是内容比较。所以才会返回true。
说了这么多,我们要说的是什么?放在集合中的元素,你要重写它的equals方法。
- remove()方法
说明remove()方法底层也调了equals()方法。String类又重写了equals方法。
List接口
-
List集合存储元素特点:有序,可重复。
有序:List集合中元素有下标,从0开始以1递增。
可重复:存个1还可以再存1.
因为List有下标,所以遍历的时候就不用获取迭代器了,直接通过下标遍历就可以。
-
List特有常用的方法:(E表示泛型)
void add(int index,E element):往指定下标加元素
E set(int index, E element):修改指定下标的元素
E get(int index):获取指定下标的元素
int indexOf(Object o):返回集合中第一次改元素的下标
E remove(int index):删除指定下标的元素
ArrayList类 (重要*)
-
特点:
- ArrayList集合底层时Object类型的数组Object[]
- ArrayList默认初始化容量是10
- 非线程安全的
- 数组的优点:检索效率高。
- 数组缺点:随机增删效率较低,但向末尾添加元素,效率还是很高的。不能存储大数据量,因为很难找到一块足够大的连续的空间。
-
ArrayList集合的扩容,当集合达到设置容量,它会自动扩容。变为原容量的1.5倍
- 因为ArrayList底层是数组,因为数组扩容效率较低,尽可能少扩容,建议使用Arraylist的时候预估元素个数给定初始化容量。这是ArrayList集合比较重要的优化策略。
-
这么多的集合中,你用哪个最多?ArrayList
- 因为大部分情况下我们做检索操作比较多,ArrayList检索效率比较高,并且往ArrayList末尾增删元素,效率不受影响。
-
ArrayList另一个构造方法:直接传一个集合对象
-
Collection c = new HashSet(); c.add(1); c.add(2); List lst = new ArrayList(c);//通过这个方法就可以把set集合转换为list集合
-
-
遍历ArrayList集合的三种方式:
LinkedList类
LinkedList底层是双向链表。尽管他是链表,因他继承了List,所以LinkedList也是有下标的。
注意:ArrayList之所以检索效率高,不单纯因为下标,而是底层数组发挥的作用。
LinkedList照样有下标,但是检索效率比较低,因为只能从头结点开始遍历。
- 单链表
- 双链表
Vector类
- 底层也是数组
- 初始化容量10
- 满了后扩容,怎么扩容的?扩容后是原容量的2倍
- Vector所有方法都是线程同步的,都带有Synchronized关键字,是线程安全的。
- 效率较低,不常用。
泛型
- 使用泛型的好处:
- 集合中存储的元素类型同一了
- 从集合取出的元素类型是泛型指定的类型,不需要进行大量的向下转型。
- 泛型的缺点:
- 导致集合中存储的元素缺乏多样性。
JDK1.8之后,支持钻石表达式,即右边new的时候不用重复指定泛型了
Map接口
-
Map和Collection没有继承关系
-
Map集合以key和value的方式存储数据:键值对
- key和value都是引用数据类型
- key和value都是存储对象的内存地址。
- key起主导地位,value是key的一个附属品
-
Map接口常用方法:
-
V put(K key,V value) 向Map集合中添加键值对
-
V get (Object key) 通过key获取value
-
void clear() 清空集合
-
boolean containsKey(Object key) 判断Map是否含有key
-
boolean containsValue(Object value) 判断Map是否包含某value
-
boolean isEmpty() 判断Map是否为空
-
Set keySet() 获取Map中所有的key(是一个set集合)
-
Collection values() 获取Map集合中所有的value(返回一个collection)
-
V remove(Object key) 通过key删除键值对
-
int size() 获取Map集合中所有键值对的个数
-
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成set集合
-
-
遍历Map集合
-
第一种方式:先拿到map集合的所有key,通过遍历key来遍历map
可以用迭代器或者增强for:
-
第二种方式: 把map集合直接全部转换成set集合
同样迭代器、foreach均可:
-
HashMap类
- HashMap底层数据结构是哈希表
- 哈希表是一个怎样的数据结构呢?
- 哈希表是一个数组和单向链表的结合体。
- 数组在查询方面效率很高,增删方面效率很低。
- 单向链表在增删方面效率很高,查询方面效率很低。
- 哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点。
扩容之后的容量是原容量的2倍。
- HashMap底层结构图:
注意:JDK8之后,如果哈希表单向链表中元素个数超过8个,单向链表会变成红黑树,当红黑树上节点数量小于6时,会重新把红黑树变成单向链表。这种方式也是为了提高检索效率。
Hashtable
- Hashtable和HashMap一样,底层数据结构都是Hash表
- Hashtable方法都带有synchronized:是线程安全的。
- Hashtable效率较低,使用较少
- Hashtable的初始化容量是11,默认加载因子0.75,扩容为原来的2倍 + 1
Properties
- Properties是一个Map集合,继承Hashtable,因此是线程安全的
- Properties的key和value都只能是String类型
- Properties被称为属性类对象。
TreeSet
-
TreeSet集合底层实际上是一个TreeMap
-
TreeMap集合底层数据结构是自平衡二叉树
-
放到TreeSet集合中的元素,等于放到TreeMap集合中key部分了
-
TreeSet集合中的元素:无序,不可重复,但是可以按照元素大小顺序自动排序。称为可排序集合.
-
对于自定义类型的元素(如自定义的类)来说,TreeSet可以排序吗?
- 无法排序!因为没有指定对象之间的比较规则,谁大谁小并未说明。
-
如何让TreeSet对自定义元素也能自动排序呢?有两种方法。
- ①你自定义的类就要实现Compareble接口,并重写compareTo方法来指明比较规则。equals方法就不用重写了。
-
calss Customer implements Comparable<Customer>{ int age; public Customer(int age){ this.age = age; } /* 需要在这个方法中编写比较规则,按照说明来比较 k.compareTo(t.key) 拿着k和集合中的每个key进行比较,返回值可能>0,<0,=0 比较规则最终还是由程序员指定的。 */ @Override public int comparTo(Customer c){//c1.comparTo(c2) return this.age - c.age; } public String toString(){ return "Customer:age="+age; } }
-
自平衡二叉树结构:
-
这样通过中序遍历自平衡二叉树,得到的结果就是一个自动按照大小有序的集合。
-
②第二种方式,使用比较器:
- 也可以不写比较器类,直接用匿名内部类
-
结论:让TreeSet对自定义类型元素也能自动排序有两种方式:
- 第一种,放到TreeSet中的元素实现java.lang.Comparable接口。
- 第二种,在构造TreeSet或者TreeMap集合的时候传一个比较器。(当然我们要手写一个比较器,比较器实现java.util.Comparator接口)
Collections (集合工具类)
集合工具类方便集合操作。
对List集合中元素排序,需要保证List集合中元素实现了:Comparable接口。
从sort方法我们可知sort()接受的参数是List,那Collections工具类对Set集合怎么排序呢?