一、集合概述
1.1集合是一个容器,是一个载体,可以一次容纳多个对象
1.2集合不能直接存储基本数据类型,另外集合也不能存储java对象,集合当中存储的都是java对象的内存地址(或者说集合中存储的是引用)
list.add(100);//自动装箱Integer
1.3在java中每个不同的集合,底层会对应不同的数据结构,往不同集合中存储元素,等于将数据放到了不同的数据结构中
什么是数据结构?数据存储的结构就是数据结构,不同的数据结构,数据存储方式不同
如:数组、二叉树、链表、哈希表。。。
1.4所有的类和集合接口都在java.util包下
1.5集合的继承结构图
1.6在java中集合分为两大类:
一类是单个方式存储元素:单个方式存储元素,这一类集合种超级父接口:java.util.Collection;
一类是以键值对儿的方式存储元素:以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;
迭代 == 遍历
背过!!!!
Collection结构图
Map结构图
总结(所有的实现类):
ArrayList:底层是数组
LinkedList:底层是双向链表
Vector:底层是数组,线程安全的,效率较低,使用较少
HashSet:底层时HashMap,方法HashSet集合中的元素等同于放到HashMap集合key部分
TreeSet:底层时TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分
HashMap:底层是哈希表
Hashtable:底层是哈希表,线程安全,效率较低,使用较少
Properties:是线程安全的,且key和value只能存储字符串String
TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序
List集合存储元素的特点:
有序可重复
有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
可重复:存进去1,可以再存储一个1
Set集合存储元素的特点:
无序不可重复
无序:存进去的顺序和取出的顺序不一定相同
不可重复:存进去1,不能再存储1
SortSet集合存储元素的特点:
首先是无序不可重复,但SortSet集合中的元素是可排序的
可排序:可以按照大小顺序排列
Map集合中的key,就是一个Set集合
在Set集合中存放数据,实际上放到了Map集合的key部分
二、Collection
1.Collection中能存放什么元素?
没有使用“泛型”之前,Collection中可以存储Object的所有子类型
使用了“泛型”之后,Collection中只能存储某个具体的类型
只要是Object的子类型,Collection中就可以存储
集合中不能直接存储基本数据类型,也不能存储java对象,只是存储java对象的内存地址
2.Collection中的常用方法
boolean add(Object e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean isEmpty() 判断该集合中元素个数是否为0
Object[ ] toArray() 调用该方法可以把集合转换成数组
例子见CollectionTest01
3.Collection集合的迭代(重点)
以下遍历方式/迭代方式,是所有Collection通用的方式
第一步:通过集合对象的迭代器对象Iterator
Iterator it = c.iterator();
第二步:通过以上获取的迭代器对象开始迭代/遍历集合
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext() 如果仍有元素可以迭代,则返回true
Object next() 返回迭代的下一个元素
例:
//创建集合对象
Collection c1 = new ArrayList();//ArrayList集合:有序可重复
//添加元素
c1.add(1);
c1.add(2);
c1.add(3);
c1.add(4);
//迭代集合
Iterator it = c1.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
4.Collection集合中的contains方法
boolean contains(Object o):判断集合中是否包含某个对象o,若包含,返回true,反之,返回false
注意:
测试contains方法:存放一个集合中的类型,一定要重写equals方法
//创建集合对象
Collection c = new ArrayList();
//创建用户对象
User u1 = new User(“jack”);
User u2 = new User(“jack”);
//加入集合
c.add(u1);
//判断集合中是否包含u2
//没有重写equals方法之前,结果为false
//重写equals方法之后,结果为true
System.out.println(c.contains(u2));
5.remove方法
remove方法会调用equals方法,s1和s2一样时,删除时一起删除
Collection c2 = new ArrayList();
String s1 = new String(“hello”);
c2.add(s1);
String s2 = new String(“hello”);
c2.remove(s2);
System.out.println(c2.size());//0
迭代:
//创建集合
Collection c = new ArrayList();
//添加元素
c.add(1);
c.add(2);
c.add(3);
//获取迭代器
Iterator it = c.iterator();
while(it.hasNext()){
//编写代码时,next()方法返回值类型必须是Object
//Integer i = it.next();
Object obj = it.next();
System.out.println(obj);
}
注意:集合结构只要发生改变,迭代器必须重新获取
在迭代集合元素过程中,不能调用集合对象的remove方法,删除元素
c.remove(o);迭代过程中不能这样
会出现:java.util.ConcurrentModficationException
//使用迭代器删除可以?
it2.remove();//删除的一定是迭代器指向的当前元素
迭代器删除时,会自动更新迭代器,且更新集合(删除集合中的元素)
6.List接口
集合存储元素特点:有序可重复
有序:List集合中的元素有下标,从0开始,以1递增
可重复:存储一个1,还可以在存储1
List接口的特有的常用的方法:
例子见ListTest01
//在列表指定位置插入指定元素(第一个参数就是下标)
void add(int index,E element)
//根据下标获取元素
Object get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
Object remove(int index)
//修改指定位置的元素
Object set(int index,Object element)
//通过下标遍历
//list集合特有的遍历方式
for (int i = 0;i < list.size();i++){
Object obj = list.get(i);
System.out.println(obj);
}
ArrayList:
①初始化容量为10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10)
②底层是Object类型的数组Object[]
③构造方法:
new ArrayList();
new ArrayList(20);
④ArrayList集合的扩容:
扩容到原容量的1.5倍
ArrayList集合底层是数组,怎么优化?
尽可能少扩容,因为数组扩容效率较低,建议在使用ArrayList集合时预估计元素个数,给定一个初始化容量
⑤数组的优点:检索效率较高(每个元素占用空间大小相同,内存地址连续,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高)
数组的缺点:随机增删元素的效率较低,数组无法存储大数据量
注意:向数组末尾添加元素时,效率较高
面试题:
这么多集合中,你用哪个集合最多?
答:ArrayList集合
因为向数组末尾添加元素,效率不受影响
另外,我们检索/查找某个元素的操作较多
List list1 = new ArrayList();
List list2 = new ArrayList(100);
Collection c = new HashSet();
c.add(100);
c.add(200);
c.add(900);
c.add(50);
//通过此构造方法可以将HashSet集合转换成List集合
List list3 = new ArrayList©;
for(int i = 0;i < list3.size();i++){
System.out.println(list3.get(i));
}
7.单向链表数据结构
对于链表数据结构,基本的单元节点Node
对于单向链表来说,任何一个节点Node中都有两个属性:
①存储的数据
②下一节点的内存地址
链表优点:
由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高,在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList
链表缺点:不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头结点开始遍历,直到找到为止,所以LinkedList集合检索/查找的效率较低
ArrayList:把检索发挥到极致
LinkedList:把随机增删发挥到极致
加元素都是往末尾添加,所以ArrayList用的比LinkedList多
LinkedList集合没有初始化容量,最初链表中没有任何元素,first和last引用都是null
LinkedList
①双向链表
②对于链表数据结构来说,随机增删效率较高,检索效率较低
③链表中的元素在空间存储上,内存地址不连续
Vector
①底层是数组
②初始化:10
③如何扩容?
扩容之后是元容量的2倍,10---->20----->40---->80
ArrayList集合扩容特点:
扩容是元容量的1.5倍
④Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率较低,使用较少
⑤怎么讲一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:
java.util.Collections;
java.util.Collection是集合接口
java.util.Collections;是集合工具类
三、泛型机制
1.泛型这种语法机制:只在程序编译阶段起作用,只是给编译器参考(运行阶段泛型没用)
2.使用泛型有什么好处?
①集合中存储的元素类型统一
②从集合中取出的元素类型泛型指定的类型,不需要进行大量的“向下转型”
3.泛型的缺点:
导致集合中存储的元素缺乏多样性
大多数业务中,集合中元素的类型还是统一的,所以这种泛型特性被大家所认可
见GenericTest01
4.自动类型推断机制(钻石表达式)
List myList = new ArrayList**<>****();**
自定义泛型
自定义泛型时,<>尖括号中的是一个标识符,随便写
java中常见的是:和
E:是Element单词首字母
T:是Type单词首字母
public class GenericTest03<标识符随便写> {
public void doSome(标识符随便写 o){
System.out.println(o);
}
public static void main(String[] args) {
**//new对象时指定了了泛型是:String类型**
**GenericTest03<String> gt = new GenericTest03<>();**
**//类型不匹配**
**//gt.doSome();**
**gt.doSome("abc");**
**GenericTest03<Integer> gt2 = new GenericTest03<>();**
**gt2.doSome(123);**
**//类型不匹配**
**//gt2.doSome("abc");**
}
}
例2:
class MyIterator{
public T get(){
return null;
}
}
foreach
缺点:没有下标
语法:
for(元素类型 变量名 :数组或集合){
System.out.println(变量名);
}
例:
//增强for循环(foreach)
for (int data : arr){
//data就是数组中的元素
System.out.println(data);
}
集合也可使用foreach
HashSet集合:无序不可重复
TreeSet集合:无序不可重复(取出来是按大小排序的,也称为可排序集合)
四、Map
1.Map和Collection没有继承关系
2.Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型
key和value都是存储对象的内存地址
key起到主导的地位,value是key的一个附属品
3.Map集合中的常用方法(记住)
V put(K key,V value):想Map集合中添加键值对
V get(Object key):通过key获取value
void clear():清空Map集合
boolean containsKey(Object key):判断Map中是否包含某个key
boolean containsValue(Object value):判断Map中是否包含某个value
boolean isEmpty():判断Map集合中元素个数是否为0
Set keySet():获取Map集合所有的key(所有的键是一个set集合)
V remove(Object key):通过key删除键值对
int size():获取Map集合中键值对的个数
Collection values():获取Map集合中所有的value,返回一个Collection
Set<Map.Entry<K,V>> entrySet():将Map集合转换成Set集合
详见MapTest01
4.Map集合的遍历【非常重要】
//第一种方式:获取所有的key,通过遍历key,来遍历value
Map<Integer,String> map = new HashMap<>();
map.put(1,“zhangsan”);
map.put(2,“lisi”);
map.put(3,“wangwu”);
map.put(4,“zhaoliu”);
①迭代器方式
//获取所有的key,所有的key是一个Set集合
Set keys = map.keySet();
//遍历key,通过key获取value
//迭代器可以
Iterator it = keys.iterator();
while(it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key + “=” + value);
}
②foreach方式
//foreach也可以
for (Integer key : keys){
System.out.println(key + “=” + map.get(key));
}
//第二种方式:Set<Map.Entryr<K,V>> entrySet()
//以上这个方法是吧Map集合直接全部转换成Set集合
//Set集合中元素的类型是Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历Set集合,每一次取出一个Node
//迭代器
Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + “=” + value);
}
//foreach
//这种方式效率较高,因为获取key和value都是直接从node对象中获取的属性值
for (Map.Entry<Integer,String> node : set){
System.out.println(node.getKey() + “—>” + node.getValue());
}
五、HashMap集合
1.HashMap集合底层是哈希表/散列表的数据结构
2.哈希表是一个怎样的数据结构?
哈希表是一个数组和单向链表的结合体
数组:在查询方面效率很高,随机增删方面效率很低
单向链表:在随机增删方面效率很高,在查询方面效率很低
哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点
3.HashMap集合底层的源代码
public class HashMap{
//HashMap底层实际上是一个数组(一维数组)
Node<K,V> table;
//静态的内部类HashMap.Node
static class Ndoe<K,V> implements Map.Entry<K,V>{
final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果,hash值通过哈希函数/算法,可以转换成数组的下标)
final K key;//存储到Map集合中的那个key
V value;//存储到Map集合中的那个value
Node<K,V> next;//下一个结点的内存地址
}
}
哈希表/散列表:是一个一维数组,这个数组中每个元素是一个单向链表(数组和链表的结合体)
4.最主要掌握的是
map.put(k,v)
v = map.get(k)
以上两个方法的实现原理,必须掌握
map.put(k,v)的实现原理:
①先将k,v封装到Node对象中
②底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将哈希值转换成数组下标,下标位置上如果没有任何元素,就把Node添加到这个位置;若下标对应位置上有链表,此时会用k和链表每一个结点中的k进行equals,若所有的equals方法返回false,那么这个新结点将被添加到链表的末尾,若其中有一个equals返回true,,那么这个节点的value就会被覆盖
v = map.get(k)的实现原理:
①先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,若这个位置上什么都没有,则返回null;若这个位置上有单向链表,那么会拿着参数k和单向链表上的每个结点中的k进行equals,若所有equals方法返回false,那么get方法返回null,只要其中有一个结点的k和参数k equals时,返回true,那么此时这个节点的value就是我们要找的value,get方法最终返回这个要找的value
为什么哈希表的堆积增删,以及查询效率都很高?
增删是在链表上完成的
查询也不需要全部扫描,只需要部分扫描
【重点】:通过讲解可以得出HashMap集合的key,辉县后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写
5.HashMap集合的key部分特点:
无序,不可重复
为什么无序?因为不一定挂到那个单向链表上
不可重复是怎么保证的?equals方法保证HashMap集合的key不可重复,若重复,value会覆盖
放在HashMap集合key部分的元素其实是放到HashSet集合中,所以HashSet集合中的元素也需要同时重写hashCode() + equals()方法
注意:同一个单向链表上所有节点的hash相同,因为他们的数组下标是一样的,但同一个链表上k和k的equals方法肯定返回的是false,都不相等
哈希表HashMap使用不当时无法发挥性能!结社将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成纯单向链表,这种情况称为:散列分布不均匀
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个结点,这是最好的,是萨列分布均匀;
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗?有什么问题?
不行,因为这样会导致底层哈希表就称为一维数组了,没有链表的概念了,也是散列分布不均匀
【重点】:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法
HashMap集合的默认初始化容量是16,默认加载因子是0.75,这个默认加载因子是当HashMap集合底层容量达到75%时,数组开始扩容
重点:记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率所必须的
向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后在调用equals方法,equals方法有可能调用,也有可能不调用
put(k,v)什么时候不需要调用equals方法?
数组下标位置上如果是null,equals不需要执行
get(k)什么时候不需要调用equals方法?
数组下标位置上如果是null,不需要调用equals方法
注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写,且equals方法返回如果是true,hashCode()方法的返回值必须一样
equals方法返回true,表示两个对象相同,在同一个单向链表上比较,那么对于同一个单向链表上的结点来说,他们的哈希值是相同的,所以hashCode()方法的返回值也应该相同(equals和hashCode可以用Idea生成,但必须同时生成)
终极结论:放在HashMap集合key部分的,以及方法HashSet集合中的元素,需要同时重写hashCode方法和equals方法
对于哈希数据结构来说,如果o1和o2的hash相同,一定是放到同一个单向链表上
如果o1和o2的hash值不同,但由于哈希算法执行结束后转换的数组下标可能相同,此时会发生“哈希碰撞”
6.HashMap和Hashtable的区别
①HashMap集合key部分允许null吗?允许,HashMap集合的key null值只能有一个
②Hashtable的key可以为null吗?
Hashtable的key和value都不能为null
HashMap集合的key和value都可以为null
③Hashtable方法都带有synchronize:线程安全的
线程安全有其他方案,这个Hashtable对线程的处理导致效率较低,使用较少
④Hashtable和hashMap一样,底层都是哈希表数据结构
HashTable的初始化容量是11,默认加载因子是:0.75f
Hashtable扩容后:原容量*2+1
六、Properties属性类
掌握Properties属性类对象的相关方法
**Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型,**Properties被称为属性类对象,是线程安全的
//创建一个Properties对象
Properties pro = new Properties();
//需要掌握两个方法,一个存,一个取
pro.setProperty("username","root");
pro.setProperty("password","123");
//通过key获取value
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(username);
System.out.println(password);
七、TreeSet
1.TreeSet集合底层实际上是一个TreeMap,
TreeMap集合底层是一个二叉树,
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了,
TreeSet集合中的元素:无序不可重复,但可以按照元素的大小顺序自动排序,称为可排序集合
例子见TreeSetTest
八、自平衡二叉树数据结构
自平衡二叉树,遵循左小右大原则存放
遍历二叉树时有三种方式:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
注意:前中后说的是“根”的位置
TreeSet集合/TreeMap集合采用的是:中序遍历方式
Iterator迭代器采用的是中序遍历方式,左根右
TreeSet集合中的元素可排序的第二种方式:使用比较器的方式
最终结论:
放大TreeSet或TreeMap集合key部分的元素要想做到排序,包括两种方式:
①放在集合中的元素实现java.lang.Comparable接口
②在构造TreeSet或TreeMap集合时给它穿一个比较器对象
Comparable和Comparator怎么选择?
当比较规则不发生改变时,或当比较规则只有1个时,建议使用Comparable接口
若比较规则有多个,且需要多个比较规则之间频繁切换,建议使用Comparator接口
Comparator接口的设计符合OCP原则
九、Collections工具类
Comparable接口和Comparator接口
十、集合中需要掌握的内容
1.每个集合对象的创建(new)
2.向集合中添加元素
3.从集合中取出某个元素
4.遍历集合
5.主要的集合类:
ArrayList
LinkedList
HashSet(HashMap的key,存储在HashMap集合key的元素需要同时重写hashCode+equals)
TreeSet
HashMap
Properties
TreeMap