集合框架Collection框架
Collection接口
|-----List接口(有序可重复)
|------ArrayList类(底层采用变长数组实现,查找速度快,不安全,效率高)
|------Vector类(底层采用变长数组实现,安全,效率低)
|------LinkedList类(底层采用双向链表实现,插入删除速度快)
|-----Set接口(无序不可重复,有序是指存储顺序和插入的顺序一样)
|----HashSet类(无序不可重复,用hash表实现,底层是HashMap,屏蔽了value的Map)
|------LinkedHashSet类(有序不可重复,底层维护了hash表和双向链表)
|----SortedSet接口(数据排序的,不可重复)
|------TreeSet类(对插入的数据排序,不可重复,底层采用二叉排序树实现,底层就是一个TreeMap)
Map接口(无序,key不可重复,value可重复)
|-----HashMap类(用hash表实现,无序,线程不安全,效率高,key值允许为null)
|-----HashTable类(用hash表实现,无序,线程安全,效率低,key值不允许为null)
|-----SortedMap接口(排好序的Map)
|------TreeMap类(排好序的,对插入的key值进行排序,底层采用排序二叉树实现)
Collection接口定义的方法
Collection接口是所有集合的顶级父接口。Collection接口中定义的方法是每个集合都具备的!
1. boolean add(Object o):向集合中添加新元素,返回值:true添加元素成功。
2. boolean remove(Object o);将给定元素从集合中删除,返回值:true删除成功
3. int size():返回当前集合的元素总数
4. boolean isEmpty();判断集合中是否含有元素。
5. boolean contains(Object obj);判断集合是否包含给定的元素
6. void clear();清空集合;
7. boolean addAll(Collection c);将给定集合中的所有元素添加到当前集合
8. boolean removeAll(Collection c);删除当前集合中与给定集合相同的元素
9. Iterator iterator();返回当前集合的迭代器
10. Object[] toArray();用于将集合转换为数组 T[ ] toArray( T[ ] a)
11. Arrays.asList()将数组转成list类型
List集合
List集合:有序且可重复集
两个常用的实现类:ArrayList和LinkedList
用法完全一样,只是因为实现方式不同,各有千秋
ArrayList使用数组实现,所以更适合读取存储的数据
LinkedList使用链表实现,所以更适合插入和删除元素。
List接口中定义的独有方法
(1)Object get(int index):获取给定索引出的元素
(2)Object set(int index,Object obj);将给定的元素存入集合指定位置
(3)add(int index ,Object obj);向集合指定位置插入元素
(4)remove(int index);删除指定位置的元素
(5)int indexOf(Object obj);查询指定元素在集合中的位置
在集合中查询给定元素第一次出现的位置。
这里也是使用给定元素与集合元素进行equals的比较方式
(6)int lastIndexOf(Object obj);
在集合中查询给定元素最后一次出现的位置。
(7)List<E> subList(int from,int to)
获取当前集合的部分内容。取子集
凡是表示范围的都是包含开始,不包含结束。
4、案例
(1)List list = new ArrayList();
ArrayList内部使用对象数组形式实现。
在创建ArrayList对象时,ArrayList会初始化一个数组,
当要存放的元素数量大于数组时,ArrayList会自动扩容数组长度。
list.add("一");
list.add("二");
System.out.println(list);
ArrayList重写了Object的toString方法
返回的字符串格式为: [元素1.toString(),元素2.toString(),....]
会顺序调用集合中每个元素的toString方法,并拼接在一起。
list.clear();
System.out.println(list==null);//false
System.out.println(list.isEmpty());//true
注意:判断null和isEmpty的区别
null指的是集合对象是否存在。
isEmpty()指的是集合对象是存在的,只不过没有元素。
(2)List list=new ArrayList();
Point p=new Point(1,2);
list.add(p);
list.add(p);
list.add(new Point(3,4));
Point point=new Point(1,2);
System.out.println(list.contains(point));
list.remove(point);
System.out.println(list);
注意:元素的equals方法对集合的很多操作都有影响!
判断集合是否包含给定的元素时,集合会将这个元素与集合中的元素分别进
行equals比较,若有返回值为true的,则认为集合包含给定的元素。
remove方法是将给定的元素与集合中每个元素进行equals比较,删除第一
个比较结果为true的元素。
(3) List list1 = new ArrayList();
List list2 = new ArrayList();
List list3 = new ArrayList();
list1.add("一");
list1.add("二");
list1.add("三");
System.out.println(list1);
list2.add("四");
list2.add("五");
list3.add("一");
list3.add("二");
list1.addAll(list2);
System.out.println(list1);
list1.removeAll(list3);
System.out.println(list1);
list1.retainAll(list2);
取交集:只保留list1中和list2中相同的元素
System.out.println(list1);
以上方法比较元素相同都是使用equals方法比较的。
(4) List list = new ArrayList();
list.add("one");
list.add("two");
list.add("three");
for(int i = 0;i<list.size();i++){
String str = (String)list.get(i);
System.out.println(str);
}
set方法用于替换集合中指定位置上的元素,set方法的返回值为被替换的元素
set方法指定的索引位置不能大于数组的元素数量,否则会出现下标越界异常
Object old = list.set(2, "二");
System.out.println(list);
System.out.println("被替换的元素:"+old);
(5)String[] array = (String[])list.toArray(new String[0]);
将集合转换为数组
注意:要确保集合中存放的元素类型是一致的!
且要转换的目标数组类型要与元素类型一致。
toArray()方法用于将集合转换为数组
toArray方法是Collection定义的方法。所有集合都具备
想转换什么类型的数组,toArray方法参数就传什么类型数组的实例
我们给定的数组实例不需要给长度,因为不会使用,
toArray方法只是借鉴了我们传入参数数组的类型。
(6) List<Integer> list=new ArrayList<Integer>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list);
List<Integer> subList=list.subList(0,4);
System.out.println(subList);
修改子集会影响原集合中的元素
for(int i=0;i<subList.size();i++){
subList.set(i, subList.get(i)*10);
}
System.out.println(subList);
System.out.println(list);
5、迭代器Iterator接口
Collection中提供了一个方法
Iterator iterator():该方法用于返回当前集合的迭代器
迭代器是用于遍历集合的。
由于每种集合的内部实现不同,迭代器遍历集合元素的方式也不尽相同,所以我们不需
要记住每一种迭代器的名字。我们就统一把他们看成迭代器去使用就可以了。
Iterator接口
定义了三个方法
boolean hashNext()::询问迭代器迭代的集合是否还有元素。
Object next()::next方法获取集合中下一个元素与get方法一样,
存放的时候以Object存的,取的时候也是以Object返回的,所以要造型。
void remove():删除刚刚迭代出来的元素。
使用迭代器的流程
问拿删。可以不删除元素。但一定要注意,问一次拿一次!
List迭代
List list = new ArrayList();
list.add("one");
list.add("#");
list.add("two");
list.add("#");
list.add("three");
list.add("#");
java.util.Iterator:迭代器是专门为while循环设计的
Iterator it = list.iterator();
while(it.hasNext()){
String element = (String)it.next();
if("#".equals(element)){
//将上面通过next方法获取的元素从集合中删除
it.remove();
迭代器在迭代过程中,不能通过使用集合定义的删除方法去删除集合元
素,一定要使用迭代器的删除方法,否则迭代过程中会产生异常!
//list.remove(element);
}
System.out.println(element);
}
如下迭代:对于链表性能低下(每次找元素都是从第一个开始往后找)
没有迭代器性能好!迭代器是集合的最优遍历算法
for(int i=0;i<eggs.size();i++){
System.out.println(eggs.get(i)) ;
}
6、泛型
java1.5之后支持的一个新特性
可以在我们使用某个类的时候,动态的给该类的属性或方法的参数,返回值指定类型。
public class ArrayList<E>{
public boolean add(E e){...}
public E get(int index){...}
}
List<String> list = new ArrayList<String>();
list.add("123");
String s =list.get(0);
当支持泛型的类我们在使用的时候不指定泛型类型时,那么泛型类型默认就是Object
集合对泛型的支持
集合中的泛型指定的是存放的元素是什么类型的。
List<String> list = new ArrayList<String>();
list.add("123");
list.add("456");
list.add("789");
//list.add(123);//参数类型不匹配!
for(int i =0;i<list.size();i++){
get方法获取元素时直接是泛型指定的类型,无需再进行造型了
String element = list.get(i);
System.out.println(element);
}
迭代器也支持泛型
但要注意!迭代器指定的泛型类型一定要和遍历的集合的泛型类型一致!
Iterator<String> it = list.iterator();
while(it.hasNext()){
使用迭代器获取元素时也不再需要造型
String element = it.next();
System.out.println(element);
}
自定义泛型
泛型的语法
在定义类的时候,在类名之后用<>定义泛型
泛型命名可以是字母与数字的组合,数字不能是第一个字符
若指定多个泛型,中间用","分开
class Point<X,Y>{
private X x;
private Y y;
public Point(X x, Y y) {
this.x = x;
this.y = y;
}
public X getX() {
return x;
}
public void setX(X x) {
this.x = x;
}
public Y getY() {
return y;
}
public void setY(Y y) {
this.y = y;
}
泛型是一个动态的过程,是用于告知jvm运行时该类的属性类型。
所以,不指定泛型时,默认就是Object
public static void dosome(Point p){
p.setX("12");
p.setY("12.2");
System.out.println(p);
}
}
public static void main(String[] args) {
Point<Integer, Double> point1=
new Point<Integer, Double>(1,1.2);
int x=point1.getX();
double y=point1.getY();
Point.dosome(point1);
}
JAVA泛型的? extends和? super的比较
(1)? extends叫做向上造型.
ArrayList<? extends BaseCls> list1 = new ArrayList<BaseCls>();
意味着这个list1里面放的都是BaseClse的子类,保证你可以通过list.get(index)
得到的类都是BaseCls或者BaseCls的子类.
BaseCls cls = list1.get(0);//合法的
list1.add(new BaseCls());或者list1.add(new CldCls());都是不合法的.
这里面是不能通过add函数放东西进去的,那这样有什么用呢.
一般来讲,定义成? extends BaseCls的参数通常只能用来从里面取数据.
如下downbound方法可以接受? extends BaseCls类型的list
public void downbound(List<? extends BaseCls> list) {
for(BaseCls cls:list){
cls.func();
}
}
ArrayList<BaseCls> list1 = new ArrayList<BaseCls>();
ArrayList<CldCls> list2 = new ArrayList<CldCls>();
downbound(list1);和downbound(list2);都是合法的。
(2)? super BaseCls叫做向下造型.
ArrayList<? super BaseCls> list2 = new ArrayList<BaseCls>();
意味着这个list2里面只能放BaseClse或者它的子类.
list2.add(new BaseCls());或者list2.add(new CldCls());都是合法的.
list2.get(index)返回的是Object类型,因为不知道是那个具体类.
限制了放进去的对象的类型
public void upperbound(List<? super BaseCls> list) {
list.add(new BaseCls()
list.add(new CldCls());
}
7、增强for循环
java1.5后的有一个新特性
增强for循环 又叫新循环
新循环的作用是遍历集合和数组的。不能用新循环代替传统循环。
for(TYPE ele : array){
....
}
TYPE:集合或数组的元素类型
ele:元素的引用变量
array:要遍历的集合或数组的实例
新循环的循环次数有遍历的集合或数组的长度决定。
每次循环时,会将集合或数组中的元素依次赋值给ele,然后进入循环体。
使用新循环注意两点
(1)新循环是在编译时动态将新循环转化为迭代器方式遍历
(2)因为新循环使用迭代器方式遍历,所以在遍历集合时,不能通过集合删除元素。
java1.5后出现的特性有:泛型 增强for循环 包装类的自动拆装箱
8、队列Queue接口(extends Collection)
队列是一种非常常用的数据结构,存取数据本着先进先出原则。
java中提供了Queue接口来描述队列。
常用的实现类为LinkedList。
boolean offer(E e):向队列末尾追加元素,追加成功返回true
E poll():从队首获取元素。注意:获取后,队首元素就从队列中删除了。
如果队列为空时,返回null。
E peek():获取队首元素,但该元素不会从队列中删除。
9、栈结构Deque接口
Deque接口
栈也可以保存一组数据,在存取方式上也有要求,本着先进后出原则。
LinkedList也是其一个实现类
push(E e):向栈顶压入新元素
E pop():返回栈顶元素,并从栈中删除。
当栈中没有元素时,调用该方法会引发异常
E peek():获取栈顶元素,但不删除
遍历栈结构:
(1)Iterator<String> it=deque.iterator();
while(it.hasNext()){
String str=it.next();
System.out.println(str);
}
(2)while(deque.peek() != null){
String element = deque.pop();
System.out.println(element);
}
10、Collection与Collections的区别?
Collection 抽象的集合概念,实现它的有List和Set。
Collections 集合静态工具类, 包含集合的工具方法,如
sort、reverse、shuffle、binarySearch等。
11、Comparable接口和Comparator接口
集合的排序
若要进行排序,就要确定元素的大小。对象与对象间的大小关系如何确定?
(1)java提供了一个接口Comparable
Comparable接口定义了一个抽象方法compareTo()
compareTo():用于定义对象间的比较规则。
当我们定义的一个类若实现了该接口,那么就说明这个类的实例是可比较的。
String类实现了Complarable接口,String对象时可以比较的。、
定义Point类可以比较
public class Point implements Comparable<Point>{
private int x;
private int y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
返回的int值不关心具体的值是多少,只关心取值范围
当返回值大于0 表示当前对象比参数对象大(当前对象往右移)
当返回值小于0 表示当前对象比参数对象小(当前对象往左移)
当返回值等于0 表示当前对象和参数对象相等
public int compareTo(Point o) {
int r = this.x * this.x + this.y * this.y;
int r1 = o.x * o.x + o.y * o.y;
return r-r1;//升序
}
}
public static void main(String[] args) {
List<Point> list=new ArrayList<Point>();
list.add(new Point(3,2));
list.add(new Point(5,2));
list.add(new Point(1,6));
注意:使用Collections的sort()方法,集合里的元素必须是可比较的
Collections.sort(list);
System.out.println(list);
}
(2)当类中的比较规则不能满足我们对排序的要求时,可以使用Collections的重载sort
方法。给定一个比较规则,按照我们的规则比较后进行自然排序。
Collections.sort(Collection c,Comparator cc)
Comparator接口:用于定义一种比较规则。
通常Comparator不需要定义额外的子类实现,都是使用匿名类的方式创建实例。
List<String> list = new ArrayList<String>();
list.add("mary");
list.add("Killer");
list.add("able");
字符串实现了Comparable接口,所以字符串本身是具备可比较的
Collections.sort(list);
System.out.println(list);
需求:按照字符串的长短来排序
字符串的比较规则不能满足这个排序需求时,
我们可以额外的定义比较规则来满足该排序的需求
Comparator<String> comparator =
new Comparator<String>(){
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};
Collections.sort(list, comparator);
System.out.println(list);
12、Set集合
Set集有别于List集最大的特点的是不存放相同的元素。Set集合不能通过索引的形式获取元素。
常见的实现类
HashSet:使用散列算法实现的Set集合
TreeSet:使用二叉树算法实现的Set集合(底层就是TreeMap,key是存放的数据,value为null。底层都是调用TreeMap的方法来实现的)
遍历Set集合的元素只有一种方式,迭代器。Set集合不支持索引,也不具备List集合的get方法。
(1)遍历Set集合中的元素
Set<String> set = new HashSet<String>();
无序是指元素存放的顺序与取出来的顺序不一致
但是在元素不修改的前提下,如论以什么顺序存放在Set集合中的顺序都是一定的。
set.add("two");
set.add("three");
set.add("one");
//迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
增强for循环同样可以遍历Set集合, 对于编译器而言,增强for循环在编译后会转换为iterator,所以可以用增强循环遍历
for(String str : set){
System.out.println(str);
}
(2)随机生成20个不重复的数字:
Set<Integer> set = new HashSet<Integer>();
Random r = new Random();
while(size<20){
set.add(r.nextInt(100));
}
hashcode方法与HashSet之间的关系
HashSet在存放某个元素时,会先获取该元素的hashcode值,然后进行一系列的运算,之后确定将元素存放在什么位置。这里通过的算法确定位置,算法就是“散列算法”。
可以看出,一个元素要想存入HashSet需要依赖hashcode()方法的返回值。
hashcode()方法是Object定义的方法,所有类都具有该方法。我们应该妥善的重写hashcode()方法。
javaAPI中对重写该方法有说明:
若我们重写了equals()方法,就应该重写hashcode()方法。
hashcode()方法应与equals()方法返回一致。
即:当两个对象equals()方法返回true时,hashcode的返回值应该相同。
在对象没有修改的前提下,多次调用hashcode方法返回的数字不应该发生改变。
当两个对象的equals方法返回false时,hashcode值不是必须不同的。
根据HashSet实现原理可以看出,若不同对象的hashcode值都相同,那么使用HashSet的效率会大大的降低。
13、Map接口
(1)Map是一种以键值(key-value)对的形式存放数据的结构。
Map的存储要求是,key不能重复。
散列表中元素的顺序是散列数组中元素的顺序,与散列算法的计算结果有关,与添加顺序无关称为“无序集合”。无序!=随机
(2)最常用实现类
HashMap:以散列算法实现的Map
TreeMap:以二叉树算法实现的Map
TreeMap是添加元素时会按照某种排序规则来寻找存放的位置,
如何排序:先看有没有传入比较器TreeMap tm = new TreeMap(new MyComparator());
有的话按照给的比较器来排序,没有的话根据TreeMap中的key自身的比较规则来排序(Comparable),这时key值如果是不可比较的,则报错。
(3)HashSet实际上使用HashMap实现的。
当我们将一个元素add到HashSet中时,HashSet将这个元素作为key存入了HashMap,value值设置为null。因为HashMap对key的要求是不重复且存放顺序与获取顺序不同,正好满足Set集合的特征。
HashMap要求Key对象在hashCode值不发生改变的情况下,只能保存一次。
(4)HashMap的性能
HsahMap散列表算法,面向查找优化的算法,查找性能优异(无论多少数据,查找次数少于3次)
Capacity 散列表容量:HashMap的散列数组的大小(能存储key-value数量的最大值)。
Initial capacity 初始容量:创建HashMap实例时,默认创建的散列数组的大小。默认为16。可以改变。但一般不会这样做。
Size 大小: HashMap中存储的数据总数。存储(key-value)的数量。
散列桶:散列值相同的元素集合(散列桶长度越小,性能越好)
size/容量<=加载因子(75%)
load factor 加载因子:加载因子的值默认为0.75。这是一个比值,size/capacity。当size与capacity的比值大于0.75时,hashmap会对散列数组进行扩容,并重新分配内部元素位置(重新散列rehash)。rehash是需要消耗一定的性能的。应减少rehash的次数来提高性能。
性能优化:
影响性能:散列表容量和加载因子
加载因子较小时候散列查找性能会提高,同时也浪费了散列桶空间容量。
0.75是性能和空间相对平衡结果。在创建散列表时候指定合理容量,减少rehash提高性能。
(5)Map的存取元素的方法
V put(K k,V v):将key-value对存入Map。若Key值已经在Map中存在的话,那么就将Value值替换。返回值则是被替换的Value,若该Key不存在,则将Value存入,返回值为null。
V get(Object k):根据给定的key值获取对应的Value。若当前给定的key在Map中不存在,则返回null。
boolean containsKey(Object key):查看当前Map中是否包含给定的key,包含返回true。
boolean containsValue(Object value):查看当前Map中是否包含给定的Value,包含返回true。
(6)遍历Map
遍历Map有三种方式:
a、遍历Map中所有的key
public Set keySet():调用keySet()方法会返回一个Set集合的实例,其中保存
的元素为Map中的所有key。
b、遍历Map中所有的键值对(Entry)
public Set entrySet():调用entrySet()方法会返回一个Set集合的实例,其中保存的元素为Map中的每一组键值对,每个键值对用一个Entry实例保存。
c、遍历Map中所有的value(不常用)
案例:
(1)统计str字符串中每组数字出现的次数
思路:
先将字符串按照","拆分,将每组数字作为key,将出现次数作为value存入map。这样每当统计一组数字时,我们只需要看这组数字作为key是否在map中存在,不存在则是第一次统计。若存在,则将出现次数累加即可。
String str = "123,456,778,908,123,454,678,234,908,123";
String[] array = str.split(",");
Map<String,Integer> map = new HashMap<String,Integer>();
for(String sub : array){
if(map.containsKey(sub)){
map.put(sub, map.get(sub) + 1);
}else{
map.put(sub, 1);
}
}
System.out.println(map);
(2)遍历Map中的所有Key
Map<String, Integer> map=new HashMap<String, Integer>();
map.put("A", 1);
map.put("C", 3);
map.put("B", 2);
Set<String> set=map.keySet();
for(String key:set){
Integer value=map.get(key);
System.out.println(key+","+value);
}
(3)遍历Map的键值对
Map中有一个内部类Entry,其每一个实例描述一组键值对。当我们调用put方法存放数据时,Map会创建一个Entry实例,并将key,value存入该对象后保存到Map中。所以,散列数组中,每一项的LinkedList中保存的都是Entry。
Map<String, Integer> map=new HashMap<String, Integer>();
map.put("A", 1);
map.put("C", 3);
map.put("B", 2);
Set<Entry<String, Integer>> set=map.entrySet();
for(Entry<String, Integer> entry:set){
String key=entry.getKey();
Integer value=entry.getValue();
System.out.println(key+":"+value);
}
(4)遍历Map中的所有Value
Map<String, Integer> map=new HashMap<String, Integer>();
map.put("A", 1);
map.put("C", 3);
map.put("B", 2);
为什么返回Collection而不返回Set?
因为Set集合不能存放重复元素,而Map中value是可以重复的,若返回为Set集合,可能会丢失信息。
Collection<Integer> set=map.values();
for(Integer integer:set){
System.out.println(integer);
}