集合框架
集合概念:
用于存储多个对象的单一对象(容器),存储的数据叫元素。
元素必须是引用数据类型的数据,不能是基本数据类型,可以是包装类
用于模拟现实生活中的存储容器,因此集合类型,不单单是一种,有很多很多种类型,
设计成各种存储的数据结构,这些结构统称为集合框架。
jdk1.5新特性:
在1.5以前,容器可以存储Object的任何子类型,但是在对元素进行操作时
比如,调用元素的方法等,我们必须知道元素的类型,将父类型的数据转成我们想要使用的类型
因此,在编程中增加了大量代码来进行强制转换。增加了开发难度。
因此1.5开始,支持了一个新特性,叫泛型机制,用来规定容器中存储的元素类型
此机制可以在编译期间就进行判断元素的类型
集合中的类型都需要声明泛型,不然会出现警告:
泛型机制:
概念:
1、jdk1.5版本开始使用的新特征,本质是进行“参数化类型”,将元素类型当成是形参,
通过形参来更改元素的类型,在类,接口, 方法的定义上都可以使用,用来指定数据类型名的,
可以自己定义一种泛型
像这种情况,我们可以根据构造器初始化时,给元素类型进行赋值,初始化后,这个类就只能使用我们定义的类型,
这样放在集合中的就是同一类型的了。测试一下自定义的类型
还用另外一种,在初始化时也可以直接将类型传禁区,所使用的方法也都是相应类型,保证了元素类型的一致性
测试一下:
也可以是这种,
2、集合在定义时,可以用泛型机制来指定元素的类型,这样编译器在编译期间
就可进行检查元素类型是否匹配,避免了程序在运行时出现过多的错误
3、集合框架中的所有类型(接口,抽象类,实现类)都是用了泛型机制
4、泛型机制的参数只能传引用数据类型,基本数据类型使用包装类。
要存储元素的类型
Collection与Collections的区别
Collection:是集合的父接口,定义了集合框架中常用的抽像方法
Collections:合的工具类,定义了很多用于操作集合对象的工具/工厂方法
常用的方法:
boolean add(E e)
将对象e添加到集合中
集合中重写了toString方法,可以直接输出。输出结果如下:
boolean addAll(Collection c)
将里的元素地址添加到此集合里。
重新建立一个集合,将集合中元素添加到上面的集合c中
输出结果为:
void clear()
清空集合元素
int size()
返回集合元素的个数
输出结果为:
boolean contains(Object obj)
用于判断集合中是否存在与obj相同的元素
boolean containsAll(Collection c)
用于判断此集合中,是否包含集合c中所有元素
与上面的调用方法类似
boolean isEmpty()
用于判断集合元素是否为空。
boolean remove(Object o)
用于移除集合中的某一元素
boolean removeAll(Collection c)
用于移除此集合中与c*有元素
boolean retainAll(Collection c)
用于保留此集合中和c*有元素
toArray()
返回一个包含此集合中所有元素的数组。
想使用数组元素时,必须强制转换,相对来说麻烦
toArray(T[] a)
返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型
为了避免强制转换这一步操作调用重载方法,传一个元素类型的数组对象即可,数组第项的长度任意。
只是传一个数组对象
Collection子接口:List与Set,Queue
List为collection的子接口,有实现类3种,根据需求不同可以指向不同的实现类。
List指的是表类型,每个元素都有自己的索引,即下标。
常用方法:
void add(int index,E element)
将某一元素,插入到此集合的下标index处,添加顺序就是集合中的顺序
E get(int index)
返回指定下标上的元素
输结果为:
int indexOf(Object o)
返回指定元素第一次出现的位置,如果没有返回-1;
输结果为:
第一个:
第二个:
int lastIndexOf(Object o)
最后一次出现的下标,如果此列表不包含元素, 则返回-1
结果为:
E remove(int index)
移除索引元素,返回移除元素
结果:
E set(int index , Object obj)
将下标index的元素替换成obj,返回被替换元素
结果为:
sublist(fromindex, endIndex)
截取操作,截取的是父类的一部分,对截取的更改,父类也一起变化
首先新建集合存入1~10,在新建一个集合接收集合1截取的部分,分别输出,之后将截取的部分元素都*10,
再输出,会发现之前集合元素的数据也发生了改变
结果为
sublist()方法所截取的是集合一部分,不会产生另外一个对象,截取的集合使用的是原集合中元素
2、数组转集合
List Arrays.asList(数组参数);
注意:数组转成的集合,不能进行增删操作,
否则会出现运行时异常java.lang.UnsupportedOperationException
可以进行替换操作,但是会对数组变量有影响
如果想要成功进行增删操作,可以将元素存到新的集合中。
对集合元素进行增删,会出现非检查性异常
对集合元素进行替换,会影响到原数组
LinkedList:双链表结构,双链表的每一个元素都存有上一个元素和下一个元素的下标,这样在遍历或者查找元素时
效率没有Array List的高,在增删操作时,双链表断开链子将元素插入到相应的下标,再将链子连接上
而ArrayList插入元素时,相应下标后的元素都需要移动,效率要低
Vector:是一个比较古老的集合类型,线程安全,但是效率特别低,虽然安全,但是不建议使用
有时候我们想要查看数组的元素,一个一个元素的取出来查看太麻烦,所以也就有了迭代器的概念
数组遍历有三种方式:
1、经典for循环,也就是正常使用的for循环,循环输出数组中的每一个元素
2、增强for循环,底层使用迭代器原理,也叫for-each循环
格式为:
for(数组元素类型 变量:要遍历的集合或者数组){
输出操作
}
与经典for循环的区别:
1、增强for循环中,无法使用下标,
2、经典for循环可以使用下标,跟下标有关的逻辑
3、Iterator迭代器接口;
1、迭代器的作用是用来遍历集合元素。是一个接口,Collection
提供一个方法Iterator iterator()
2、Collection的实现类使用内部类定义了迭代器子类
3、迭代器提供了统一的方法,用于遍历集合子类
常用方法:
boolean hasNext():
判断集合中是否有下一个元素
E next()
取出集合中的下一个元素
java.util.NoSuchElementException
在使用迭代器时,一般都先询问有没有下一个元素,如果有再进行不取操作,
如果不询问直接取,可能会出现异常,找不到元素
remove(Object obj)
注意:在使用迭代器对集合进行遍历时,不能使用集合的方法移除集合的元素
必须使用迭代器自己提供的方法才行,
如果使用集合的remove方法,会出现非检查性异常:
接口Queue
接口Queue:因为经常有增删操作,所以实现类为LinkedList,效率高一些
Queue也是Collection的子接口,是队列的一种数据结构
队列:通常都是一端进(offer),另一端出(poll)
进出原则:FIFO 类似于排队先结束的先出去
因为队列要经常进行增删操作,所以使用LinkedList来实现了Queue接口
常用的方法:
boolean offer(E e)
元素从队尾进入队列
E poll()
从队首移除元素,返回被移除的元素
当队列没有元素时,返回null
E peek()
查看队首元素,不移除,队列中没有元素时,返回null
注意:为了避免移除队列的队首时出现null,我们最好先查看队首是不是null
接口Deque:是Queue的子接口,实现的是双端队列的数据结构
实现类也是LinkedList, 栈的数据结构:先进后出:FILO,类似于羽毛球桶,先放进去的最后取出来
双端队列:两端都可以进也都可以出
E offerFirst()
E offerLast()
E pollFirst()
E pollLast()
E peekFirst()
E peekLast()
我们可以将双端队列的一端进行进制操作,另一端进或出,即Stack,是双端队列的一种特殊形式
push(E e)
将元素e推进栈中
pop()
将栈中的最后进来的元素移出。
运行结果:
在集合中,有时会需要比较大小的操作,而集合中都是引用类型的,我们无法正常的比较大小,
所以设计了比较接口,比较方法有三种
一、Compareble接口:
定义类时实现Compareable接口,实现接口内的方法compareTo(E e),实现此接口的类型的对象之间可以进行比较
之后重写方法int compareTo(E e):
比较规则:
(1)this与e比较,this-e 升序
(2) e-this,降序排序
二、工具类Collections
提供了一个sort(Collection c)方法,
对集合里的元素进行排序
运行结果:
三、Comparator比较器接口
如果元素类型已经实现了Comparable接口,定义了默认的比较
之后,在想换其他的比较规则时,不能改源码,可以利用比较器
来重新定义比较规则
实现一个比较器对象,重写方法: int compare(E o1,E o2);,
因为比较器对象只需要使用一次,或者说只需要定义一次,所以一般都会使用匿名内部类来定义
比较规则:
升序:o1-o2
降序:o2-o1
Set接口:底层使用的是Map,只存储key部分,value不存
特点
1、无序,存储的元素与添加顺序无关
2、不可重复,一个对象不可以放两次,长的一样的不能放进来
使用元素的equals方法来判定是否重复
3、能存储null元素,只能存一次
Set集合添加或存储元素时,当集合中的元素过多时,就会进行多次的比较,效率变低
在设计元素时,提供hash算法,用于返回对象的一个int值。
在内存中开辟很多小的区间,用于存储一定范围返回值的对象当我们想添加元素或查看元素,
先计算此元素的返回值,然后去相应区域中查找遍历。如果在这个区域没有找到对象,说明集合中可以存
这个对象,如果有,然后查看两个对象的equals的返回值
如果为true,不能添加
如果为false,可以添加,可以添加,添加至对应的链表结构中(尽可能的避免发生)
几个例子:将set集合所占的空间比喻成一个广场,将去广场的人比喻成我们要存储的对象,
将jvm比喻成管理广场的人,当广场上来了一个人,告诉管理人员说我要去跳广场舞,管理人员就会告诉
你去广场的哪个区域,也就是对象通过jvm计算一个hash值,将对象分配了区域,
有个孩子过来说,我要跳皮筋,也会有一块区域去跳皮筋。这时,有个男人找来找这个孩子,
管理的人就会告诉他孩子在跳皮筋那块区域,也就是通过计算hash值,可以很快的找到对象存储的区域,
之后男的来到了这片区域,看看哪个是要找孩子,怎么看谁是?肯定是长得像的,用到了equals方法,
比较出来是不是要找的孩子,没有就是不在,有就找到了。还有一种情况,假设有一个一模一样的孩子来了说要跳皮筋
管理人一看,这不是刚才进去的么,就不会让他去了,也就是说,两个相同的对象不能存入set集合中。
重写HashCode方法:
重写规则:尽可能的让所有的成员变量都参与运算,
尽可能的避免值重复,自动生成的方法,其中使用了素数,是碰撞的可能性降到很低
注意:
重写的必要性:
1、如果重写了equals方法,有必要重写HashCode方法
2、如果equals方法返回true,HashCode的返回值有必要一样
3、如果equals方法返回false,HashCode的返回值,不一定相同
如果返回值不同,可以提高检索的效率
反过来说:
1、HashCode值相同的时候,equals方法可能不同
2、Hashcode值不同的时候,equals方法一定不同
Set接口派生出的子类
HashSet:通过实现hash算法的一种数据结构
无序,不重复
结果:元素随机存放,不一定与存入的顺序一致
LinkedHashSet:通过实现Hash算法的一种数据结构,
但是,通过链表来维持顺序,顺序与添加顺序一致
TreeSet:使用了二叉树的一种数据结构,顺序与添加顺序一致。
顺序与自然排序有关系,支持定制排序
结果为:输出的结果是排列好顺序的,底层二叉树就是一种排序结构,
Set集合的遍历:
因为Set集合是无续的,无下标可言,因此不能使用经典for循环
我们可以使用迭代器
1、调用集合的Iterator方法获取迭代器
2、使用foreach循环
Set集合的元素:
不能轻易修改参与hashCode运算的成员变量,否则容易引起内存溢出
原因:成员变量修改后,会出现新的hash值,但是存储位置
还是在原hash值的位置上,因此操作时,找不到具体的存储位置,
过多的这样的元素会占用内存,最后堆满内存,如果更改的想要再删除
需要将修改的改回原来的
先定义数组并存入元素
之后修改c2的值
进行删除操作:
结果为:并没有删除
之后将修改改回来,再做删除:
Map集合:集合框架中的另一个父接口
Map集合:用于存储,一一对应的元素数据,第一个对象可以作为索引
第二个对象作为值,我们称之为key-value,键值对。
存储数据的特点:
1、以key-value的形式进行存储
2、key与value都必须是引用类型
3、key可以为null
4、key与value是单向一对一映射,一个key对应一个value,一个value可以对应多个key
5、key不能重复
存储机制:
Map是基于数组和链表的数据结构,进行存储数据的
作为key的元素采用了hash算法计算在数组(即散列数组,散列桶)的位置上;
如果数组计算出来的位置,数组中此位置没有元素,就可以添加到散列桶内
如果有元素,key的equals方法返回值为false时,就会存储在散列桶元素对应的单项链表中
key的equals方法返回值为true时,就进行替换(覆盖)
PS:使用Map集合,作为key的数据类型应该重写equals和HashCode
常用方法:
value put(K key,V value)
用于存储一对key-value,返回被替换的value值
如果不是替换,就返回null。
如果再存一个数学和分数,
之前的数学分数就会被替换掉
V get(K k)
作用:通过key对象,获取相应的value对象,如果集合中没有此key
返回null。
map集合的遍历
Set<K> keySet()
用于获取map中所有的key对象,返回是一个set集合
Set<Entry<k,y> entrySet();
将key-value封装成内部类对象,返回对象的set集合
Collection<V> values();
将map集合中的所有value封装到Collection集合中
装载因子(扩容的时机)和HashMap的优化
装载因子:DEFAULT_LOAD_FACTOR = 0.75f
默认容量:DEFAULT_INITIAL_CAPACITY
16,就是数组的容量
元素个数:size
当我们创建一个HashMap对象时,底层数组的初始容量为16,当存
个数size/DEFAULT_INITIAL_CAPACITY等于DEFAULT_LOAD_FACTOR
数组开始扩容,此时最佳
如果小于0.75扩容,比较占内存
如果大于0.75扩容,操作的元素比较多
Map接口的子类:
HashMap与HashTable的区别:
1、hashTable是一个古老的类,不建议使用
2、HashTable是一个线程安全的类,HashMap线程不安全
3、HashTable的key不能是null,HashMap可以是null
LinkedHashMap:是HashMap子类,使用链表来维护key-value的顺序
在迭代时顺序与添加顺序一致。
TreeMap:
是SortedMap子接口的实现类,使用了二叉树的数据结构维护
填入集合的顺序,将比较器实现类传入TreeMap的构造器即可更改
1、自然排序:往TreeMap里添加的key对象,可以实现comparable接口
compareTo方法。
2、定制排序:作为key对象的数据类型可以不实现Comparable接口
需要创建一个比较器对象Comparator,实现compare方法
Properties:
是HashTable的子类型,用于封装属性文件的key-value信息
因为在文件里写的都是字符串,因此Properties的key与value
都是字符串类型
通过key-value来文件中的数据
结果