目录
List subList(int start,int end)获取指定范围内的子集
集合框架
Java集合框架(Java Collections Framework,JCF)是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
集合框架-Collection:
Collection
集合框架
* java.util.Collection接口,该接口是所有接口的顶级接口,规定了所有集合都应当 具备的功能方法
* Collection 下面又分为不同的集合类型,常见的有两个:
* java.util.Set;接口: 不可重复集,常用实现类java.util.HashSet
* java.util.List:可重复集,并且有序,常见 实现类:java.util.ArrayList和java.util.LinkedList
* 这里指的重复元素是否可以重复,二重复元素的判定标准是元素自身equals比较是否为true
List和Set
- 在实际开发中,需要将使用的对象存储于特定数据结构的容器中。JDK提供 了这样的容器----集合(Collection)。
- Collection是一个接口, 定义了集合相关的操作方法,其有两个子接口: List与Set
- List:可重复集,元素是否重复,取决于元素的equals( )比较的结果
- Set:不可重复集
集合持有对象的引用
集合中存储的都是引|用类型元素, 并且集合只保存每个元素对象的引用,而并非将元素对象本身存入集合。
public void testRef( {
Collection<Cell> cells = new ArrayList <Cell> 0;
cells.add(new Cell(1, 2));
Cellcell = new Cell(2, 3);
cells.add(cell);
System.out.println(cell);// (2,3)
System.out.printIn(cells);// [(1,2), (2,3)]
cell.drop();
System.out.printn(cell);// (3,3)
System.out.println(cells);// [(1,2), (3,3)]
}
集合中常见方法
方式 | 方法 | 说明 |
集合中的操作 | boolean add(E e) | 向当前集合中添加给定的元素,返回值为true时表示该元素成功添加到集合中 |
int size( ) | 返回当前集合的元素个数 | |
boolean isEmpty( ) | 判断当前集合是否为空集(集合不含有任何元素,size为0时) | |
void clear( ) | 清空集合 | |
boolean contains(E e) | 判断当前集合是否包含给定元素,判断依据是给定元素与集合现有元素是否存在. equals比较为true的情况,存在则认为包含。 |
|
boolean remove(E e) | 删除给定元素,删除的也是与给定元素equals比较为true的元素。对于List集合而言重复元素只会删除一次 | |
集合间操作 |
boolean addAll(Collection c) | 将给定集合中的所有元素添加到当前集合中。 |
boolean containsAll(Collection c) | 判断当前集合是否包含给定集合中的所有元素。 | |
removeAll(Collection c) | 删除交集,即:删除当前集合中与给定集合的共有元素。但是给定的集合元素不受影响。 |
add方法
- Collection定义了一个add方法用于向集合中添加新元素。
- boolean add(E e)
- 该方法会将给定的元素添加进集合,若添加成功则返回true,否则返回false
size、clear、 isEmpty
int size( )
该方法用于返回当前集合中的元素总数。
void clear( )
该方法用于清空当前集合。
boolean isEmpty( )
该方法用于判断当前集合中是否不包含任何元素
package collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public class CollectionDemo1 {
public static void main(String[] args) {
Collection c = new ArrayList();
// Collection c = new HashSet();
/*
* boolean add(E e)
* 向当前集合中添加给定的元素,返回值为true时表示该元素成功添加到集合中
*/
c.add("one");
c.add("two");
c.add("three");
c.add("four");
c.add("five");
// c.add("one");//List集合可以存放,Set集合就不行了。
System.out.println(c);
/*
* int size()
* 返回当前集合的元素个数
*/
int size = c.size();
System.out.println("size:"+size);
/*
* boolean isEmpty()
* 判断当前集合是否为空集(集合不含有任何元素,size为0时)
*/
boolean isEmpty = c.isEmpty();
System.out.println("是否为空集:"+isEmpty);
/*
* void clear()
* 清空集合
*/
c.clear();
System.out.println(c);
System.out.println("size:"+c.size());
System.out.println("是否为空集:"+c.isEmpty());
}
}
contains方法
- boolean contains(Object o)
- 该方法会用于判断给定的元素是否被包含在集合中。若包含则返回true,否则返回false。
- 这里需要注意的是,集合在判断元素是否被包含在集合中是根据每个元素的equals(方法进行比较后的结果。
- 通常有必要重写equals(保证contains(方法的合理结果
remove方法
- boolean remove(E e)
- 删除给定元素,删除的也是与给定元素equals比较为true的元素。对于List集合而言重复元素只会删除一次
package collection;
import java.util.ArrayList;
import java.util.Collection;
/**
* 集合的很多操作与元素的equals方法有关。
*
*/
public class CollectionDemo2 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(new Point(1,2));
c.add(new Point(3,4));
c.add(new Point(5,6));
c.add(new Point(7,8));
c.add(new Point(1,2));
System.out.println(c);
Point p = new Point(1,2);
/*
* boolean contains(E e)
* 判断当前集合是否包含给定元素,判断依据是给定元素与集合现有元素是否存在
* equals比较为true的情况,存在则认为包含。
*/
boolean contains = c.contains(p);
System.out.println("是否包含:"+contains);
/*
* boolean remove(E e)
* 删除给定元素,删除的也是与给定元素equals比较为true的元素。对于List
* 集合而言重复元素只会删除一次
*/
c.remove(p);
System.out.println(c);
}
}
package collection;
import java.util.ArrayList;
import java.util.Collection;
/**
* 集合只能存放引用类型元素,并且保存的也是元素的引用(地址)
*
*/
public class CollectionDemo3 {
public static void main(String[] args) {
Point p = new Point(1,2);
Collection c = new ArrayList();
c.add(p);
System.out.println("p:"+p);//(1,2)
System.out.println("c:"+c);//[(1,2)]
p.setX(2);
System.out.println("p:"+p);//(2,2)
System.out.println("c:"+c);//[(2,2)]
}
}
addAll、containsAll、removeAll
boolean addAll(Collection<? extends E> c)
该方法需要我们传入一个集合, 并将该集合中的所有元素添加到当前集合中。
如果此collection由于调用而发生更改,则返回true
boolean containsAll(Collection<?> c)
该方法用于判断当前集合是否包含给定集合中的所有元素,若包含则返回true。
removeAll(Collection c)
删除交集,即:删除当前集合中与给定集合的共有元素。但是给定的集合元素不受影响。
package collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
/**
* 集合间的操作
* @author 范传奇
*
*/
public class CollectionDemo4 {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("java");
c1.add("c++");
c1.add(".net");
System.out.println("c1:"+c1);
Collection c2 = new HashSet();
c2.add("android");
c2.add("ios");
c2.add("java");
System.out.println("c2:"+c2);
/*
* boolean addAll(Collection c)
* 将给定集合中的所有元素添加到当前集合中。
*/
// c2.addAll(c1);//重复元素不能放入Set集合两次
c1.addAll(c2);
System.out.println("c1:"+c1);
System.out.println("c2:"+c2);
Collection c3 = new ArrayList();
c3.add(".net");
c3.add("ios");
// c3.add("php");
System.out.println("c3:"+c3);
/*
* boolean containsAll(Collection c)
* 判断当前集合是否包含给定集合中的所有元素。
*
* 注意区别昨天学习的contains方法,如果是:
* c1.contains(c3)
* 则是判断当前c1集合中是否有一个元素是c3集合。此时是把c3当成一个元素看待
* 由于c1集合中没有一个元素是一个集合类型,因此返回为false.
*/
boolean contains = c1.containsAll(c3);
System.out.println("c1是否包含c3所有元素:"+contains);
/*
* removeAll(Collection c)
* 删除交集,即:删除当前集合中与给定集合的共有元素。但是给定的集合元素不受
* 影响。
*
*/
c1.removeAll(c3);//删除交集,删除c1当中与c3集合的共有元素
System.out.println("c1:"+c1);//c1元素减少了
System.out.println("c3:"+c3);//c3元素没有变化
}
}
Iterator
java.util.Iterator接口
该接口是迭代器接口,规定了迭代器遍历集合的相关操作,不同的集合实现类都提供了一个用于遍历自身元素的迭代器实现类。我们无需知道每种集合提供的迭代器实现类的名字,只需要当它是Iterator看待即可。迭代器遍历集合的步骤遵循:问,取,删
其中删除元素不是遍历过程中的必须操作。
hasNext、next方法
- 迭代器用于遍历集合元素。获取迭代器可以使用Collection定义的方法:Iterator iterator( )
- 迭代器Iterator是个接口,集合在重写Collection的iterator( )方法时利用内部类提供了迭代器的实现。
- Iterator提供了统一的遍历集合元素的方式,其提供了用于遍历集合的两个方法:
- boolean hasNext( ):判断集合是否还有元素可以遍历。
- E next( ):返回迭代的下一个元素
remove方法
- 在使用迭代器遍历集合时,不能通过集合的remove方法删除集合元素,否则会抛出并发更改异常。我们可以通过迭代器自身提供的remove( )方法来删除通过next(迭代出的元素。
- - - -void remove( )
- 迭代器的删除方法是在原集合中删除元素
- 这里需要注意的是,在调用remove方法前必须通过迭代器的next( )方法迭代过元素,那么删除的就是这个元素。并且不能再次调用remove方法,除非再次调用next( )后方可再次调用。
package collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 集合的遍历
* Collection提供了统一的遍历集合元素的操作:迭代器
*
* 方法:
* Iterator iterator()
* 该方法会获取一个用于遍历当前集合的迭代器
*
* java.util.Iterator接口
* 该接口是迭代器接口,规定了迭代器遍历集合的相关操作,不同的集合实现类都提供了
* 一个用于遍历自身元素的迭代器实现类。我们无需知道每种集合提供的迭代器实现类
* 的名字,只需要当它是Iterator看待即可。迭代器遍历集合的步骤遵循:问,取,删
* 其中删除元素不是遍历过程中的必须操作。
* @author 范传奇
*
*/
public class CollectionDemo5 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("one");
c.add("#");
c.add("two");
c.add("#");
c.add("three");
c.add("#");
c.add("four");
c.add("#");
c.add("five");
System.out.println(c);
//获取遍历该集合的迭代器
Iterator it = c.iterator();
/*
* boolean hasNext()
* 判断集合是否还有下一个元素可以遍历(第一次遍历时相当于询问有没有第一个元素)
*/
while(it.hasNext()) {
/*
* E next()
* 获取集合下一个元素,同样的,第一次调用时获取的是第一个元素
*/
String str = (String)it.next();
System.out.println(str);
//遍历的过程中删除所有#
if("#".equals(str)) {
/*
* 迭代器有一个要求,遍历的过程中不可以通过集合的方法增删元素
* 否则遍历过程中会抛出异常:ConcurrentModificationException
*/
// c.remove(str);
/*
* 迭代器提供了remove方法,删除的是通过next获取的元素
*/
it.remove();
}
}
System.out.println(c);
}
}
增强型for循环
- Java5.0之后推出了一个新的特性,增强for循环,也称为新循环。该循环不通用于传统循环的工作,其只用于遍历集合或数组。
- 语法:
- for( 元素类型 e : 集合或数组 ){
- 循环体
- }
- 新循环并非新的语法,而是在编译过程中,编译器会将新循环转换为迭代器模式。所以新循环本质上是迭代器。
泛型机制
泛型在集合中的应用
- 泛型是Java SE 5.0引入的特性,泛型的本质是参数化类型。在类、接口和方法的定义过程中,所操作的数据类型被传入的参数指定。
- Java泛型机制广泛的应用在集合框架中。所有的集合类型都带有泛型参数,这样在创建集合时可以指定放入集合中元素的类型。Java编译 器可以据此进行类型检查,这样可以减少代码在运行时出现错误的可能性。
- ArrayList类的定义中,<E>中的E为泛型参数,在创建对象时可以将类型作为参数传递,此时, 类定义所有的E将被替换成传入的参数;
package collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* JDK5推出时,推出了一个特性:增强型for循环。
* 该特性使得我们可以用相同的语法遍历数组和集合。
*
* 语法:
* for(接收元素的变量定义 : 集合或数组){
* 循环体
* }
*
* JDK5推出时还推出了一个特性:泛型
* 泛型又称为参数化类型,可以在使用一个类时指定该类中某个属性或者方法的参数,返回值
* 的类型。使得使用时更灵活。
* 泛型在集合中广泛应用,用于指定集合中的元素类型。支持泛型的类在使用时若不指定泛型
* 的实际类型时,默认为Object
*
*
*/
public class NewForDemo {
public static void main(String[] args) {
String[] array = {"one","two","three","four","five"};
for(int i=0;i<array.length;i++) {
String str = array[i];
System.out.println(str);
}
/*
* 新循环语法是编译器认可的而非虚拟机,我们使用新循环遍历数组时会被
* 编译器改为使用普通for循环遍历。
*/
for(String str : array) {
System.out.println(str);
}
/*
* Collection<E>,集合定义时有一个泛型类型E,那么使用时可以指定E表示的
* 实际类型是什么,下面的例子中实际类型指定的为Integer
*/
Collection<Integer> c = new ArrayList<Integer>();
c.add(1);//自动装箱
c.add(2);
c.add(3);
c.add(4);
c.add(5);
// c.add("one");//编译不通过,编译器会检查传入的参数是否为Integer
System.out.println(c);
//迭代器使用时同样要指定泛型,类型与遍历的集合指定的泛型一致即可。
Iterator<Integer> it = c.iterator();
while(it.hasNext()) {
Integer i = it.next();//获取元素时无需再造型。
System.out.println(i);
}
/*
* 新循环遍历集合会被编译器改为迭代器遍历,因此使用新循环遍历集合的过程
* 中不能通过集合的方法增删元素。
*/
for(Integer i : c) {//集合指定泛型后,这里可以直接用泛型指定类型接收
System.out.println(i);
}
/*
* 使用新循环(迭代器)遍历集合不是并发安全的,也不会和集合的增删元素做
* 互斥,因此在多线程使用下,要自行维护多个线程对该集合的操作,避免一个
* 线程遍历的过程中其他线程进行增删元素操作,否则遍历这里会抛出异常。
*/
}
}
新的方法:foreach
JDK8之后集合推出了一个新的方法:foreach,可以使用lambda表达式进行遍历。
package collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* JDK8之后集合推出了一个新的方法:foreach,可以使用lambda表达式进行遍历。
* 如果一个集合是并发安全的集合,使用foreach方法遍历是可以和增删元素互斥的。
* 注:
* 哪怕是一个并发安全的集合,迭代器方式遍历也不和增删元素互斥,只能自行使用同步块
* 维护互斥关系。
*
* 我们常用的集合实现类:ArrayList,LinkedList,HashSet都不是线程安全的。
* @author 范传奇
*
*/
public class ForeachDemo {
public static void main(String[] args) {
//JDK7之后,实例化集合时,泛型可以只写<>,而不用再写一遍实际类型。
Collection<String> c = new ArrayList<>();
c.add("one");
c.add("two");
c.add("three");
c.add("four");
c.add("five");
System.out.println(c);
for(String s : c) {
System.out.println(s);
}
c.forEach(
(e)->System.out.println(e)
);
/*
* 将现有的集合转换为并发安全的集合
*/
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
System.out.println(list);
//将给定的list集合转换为线程安全的list集合
list = Collections.synchronizedList(list);
System.out.println(list);
/*
* 同样的也有synchronizedSet方法,可以将一个set集合转换为线程安全的
*
*/
}
}
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 线程安全的集合foreach方法和增删元素是互斥的,但是新循环(迭代器)则不是。
*/
public class ForeachDemo2 {
public static void main(String[] args) {
//创建一个线程安全的集合
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
System.out.println(list);
Thread t1 = new Thread() {
public void run() {
//新循环遍历(迭代器)
// for (String s : list) {
// System.out.println(getName()+":"+s);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// }
// }
//使用forEach遍历
list.forEach(//forEach会和下面线程调用add方法互斥保证并发安全
(e)->{
System.out.println(getName()+":"+e);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
}
}
);
System.out.println(getName()+":遍历集合完毕!");
}
};
Thread t2 = new Thread() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(getName()+":开始添加新元素");
list.add("6");
System.out.println(getName()+":新元素添加完毕!");
System.out.println(list);
}
};
t1.start();
t2.start();
}
}
集合操作线性表:
List
ArrayList和LinkedList
- List接口是Collection的子接口,用于定义线性表数据结构。可以将List理解为存 放对象的数组,只不过其元素个数可以动态的增加或减少。
- List接口的两个常见实现类为ArrayList和LinkedList ,分别用动态数组和链表的方式实现了List接口。
- 可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有定的差别。 ArrayList更适合 于随机访问,而LinkedList更适合于插入和删除。在性能要求不是特别苛刻的情形下可以忽略这个差别。
ArrayList和LinkedList区别
- java.util.ArrayList:内部由数组实现,查询性能更好
- java.util.LinkedList:内部由链表实现,增删元素性能更好。
get和set
List除了继承Collection定义的方法外,还根据其线性表的数据结构定义了一系列方法,其中最常用的就是基于下标的get和set方法:
E get(int index)
获取集合中指定下标对应的元素,下标从0开始。
E set(int index, E elment)
将给定的元素存入给定位置,并将原位置的元素返回。
插入和删除
List根据下标的操作还支持插入与删除操作。
void add(int index,E element):
将给定的元素插入到指定位置,原位置及后续元素都顺序向后移动。
E remove(int index):
删除给定位置的元素,并将被删除的元素返回。
package collection;
import java.util.ArrayList;
import java.util.List;
/**
* java.util.List集合
* List集合可以保存重复元素,并且有序。提供了一套通过下标操作元素的方法。
*
* 常用实现类:
* java.util.ArrayList:内部由数组实现,查询性能更好
* java.util.LinkedList:内部由链表实现,增删元素性能更好。
* 如果不是对性能有特别苛刻要求下,一般都使用ArrayList
*
*/
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
list.add("four");
System.out.println(list);
/*
* E get(int index)
* 获取指定下标处对应的元素
*/
//获取List集合中第三个元素
String str = list.get(2);
System.out.println(str);
for(int i=0;i<list.size();i++) {
str = list.get(i);
System.out.println(str);
}
/*
* E set(int i,E e)
* 将给定元素设置到指定位置,返回值为原位置对应的元素(被替换的元素)。
*/
//[one,2,three,four]
String old = list.set(1,"2");//指定的下标如果超范围会抛异常
System.out.println(list);
System.out.println(old);
/*
* 重载的add方法:
* void add(int index,E e)
* 将给定元素插入到指定位置
*/
//[one,2,3,three,four]
list.add(2,"3");
System.out.println(list);
/*
* 重载的remove方法
* E remove(int index)
* 删除并返回指定位置上的元素
*/
//[one,2,3,four]
old = list.remove(3);
System.out.println(list);
System.out.println(old);//three
}
}
List subList(int start,int end)获取指定范围内的子集
package collection;
import java.util.ArrayList;
import java.util.List;
/**
* List subList(int start,int end)
* 获取指定范围内的子集
*
*/
public class ListDemo2 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++) {
list.add(i);
}
System.out.println(list);
List<Integer> subList = list.subList(3, 8);
System.out.println(subList);
//将subList中每个元素扩大10倍
for(int i=0;i<subList.size();i++) {
subList.set(i,subList.get(i) * 10);
}
//[30,40,50,60,70]
System.out.println(subList);
/*
* 对子集元素的操作就是对原集合对应的操作
*/
System.out.println(list);
/*
* 删除list集合中2-8的元素
*/
list.subList(2,9).clear();
System.out.println(list);
}
}
List转换为数组
List的toArray方法用于将集合转换为数组。但实际上该方法是在Collection中定义的,所以所有的集合都具备这个功能。
其有两个方法:
Object[ ] toArray( )
< T > T [ toArray(TO a)
其中第二个方法是比较常用的,我们可以传入一个指定类型的数组,该数组的元素类型应与集合的元素类型一致。返回值则是转换后的数组,该数组会保存集合中所有的元素。
package collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* 集合转换为数组
*
*/
public class CollectionToArrayDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("one");
c.add("two");
c.add("three");
c.add("four");
System.out.println(c);
String[] arr = c.toArray(new String[c.size()]);
System.out.println(arr.length);
System.out.println(Arrays.toString(arr));
}
}
数组转换为List
- Arrays类中提供了一个 静态房方法asList ,使用该方法我们可以将一个数组转换为对应的List集合 。
- 其方法定义为: static <T>List<T> asList<T.. a>
- 返回的List的集合元素类型由传入的数组的元素类型决定。
- 并且要注意的是,返回的集合我们不能对其增删元素否则会抛出异常。并且对集合的元索进行修改会影响数组对应的元素。
package collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 数组转换为集合
* 使用数组的工具类Arrays的asList方法,可以将一个数组转换为一个List集合。
* 不支持转换为Set集合,因为Set集合不能放重复元素。
*
*/
public class ArrayToListDemo {
public static void main(String[] args) {
String[] array = {"one","two","three","four"};
System.out.println("array:"+Arrays.toString(array));
List<String> list = Arrays.asList(array);
System.out.println("list:"+list);
/*
* 修改集合元素就是修改原数组对应的元素
*/
list.set(1,"2");
System.out.println("list:"+list);
System.out.println("array:"+Arrays.toString(array));
//由于数组是定长的,因此该集合不支持增删元素,会抛出异常的。
// list.add("five");//
//单独创建一个集合,将数组转换的集合元素导入就可以随意增删了
/*
* 所有的集合都支持一个参数为Collection的构造方法,作用是在创建当前集合的
* 同时包含给定集合的所有元素。
*/
//创建list2集合的同时包含list所有元素
List<String> list2 = new ArrayList<>(list);
System.out.println("list2:"+list2);
list2.add("five");
System.out.println("list2:"+list2);
}
}
Collections是集合的工具类
Collection 是顶级接口
List排序
Colletions. sort方法实现排序
Collections是集合的工具类,它提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法。
该方法定义为:
void sort(List<T> list)
该方法的作用是对给定的集合元素进行自然排序。
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* 集合的排序
* 集合的工具类java.util.Collections提供了一个静态方法sort,可以对List集合进行
* 自然排序,即:从小到大
*
*/
public class SortListDemo1 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Random random = new Random();
for(int i=0;i<10;i++) {
list.add(random.nextInt(100));
}
System.out.println(list);
Collections.sort(list);
System.out.println(list);
//反转集合,如果是排序后的集合,则变为从大到小
Collections.reverse(list);
System.out.println(list);
//打乱集合
Collections.shuffle(list);
System.out.println(list);
}
}
Comparable
Collections的sort方法是对集合元索进行自然排序,那么两个元素对象之间就一定要有大小之分。这个大小之分是如何界定的?实际上,在使用Collections的sort排序的集合元素都必须是Comparable接的实现类,该接口表示其子类是可比较的,因为实现该接口必须重写抽象方法:
- int compareTo(T t);
- 该方法用于使当前对象与给定对象进行比较。
- -若当前对象大于给定对象,那么返回值应为> 0的整数。
- -若小于给定对象,那么返回值应为< 0的整数。
- -若两个对象相等,则应返回0。
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* 排序自定义元素的集合
*
*/
public class SortListDemo2 {
public static void main(String[] args) {
List<Point> list = new ArrayList<>();
list.add(new Point(3,4));
list.add(new Point(8,9));
list.add(new Point(6,5));
list.add(new Point(7,2));
list.add(new Point(1,8));
System.out.println(list);
/*
* 该sort方法要求集合元素必须实现Comparable,否则编译不通过。
* 当我们调用某个API时,该API要求我们为其修改其他额外的代码时,这样的操作
* 称为侵入性。修改的代码越多,侵入性越强。侵入性不利于系统架构的健康,不利
* 与程序的维护,应当尽量避免。
*
*/
// Collections.sort(list);
// Collections.sort(list, new Comparator<Point>() {
// public int compare(Point o1, Point o2) {
// int len1 = o1.getX()*o1.getX()+o1.getY()*o1.getY();
// int len2 = o2.getX()*o2.getX()+o2.getY()*o2.getY();
// return len1-len2;
// }
// });
//lambda表达式创建比较器
Collections.sort(list, (o1,o2)->
o1.getX()*o1.getX()+o1.getY()*o1.getY()-
o2.getX()*o2.getX()-o2.getY()*o2.getY()
);
System.out.println(list);
}
}
Comparator
- 一旦Java类实现了Comparable接口, 其比较逻辑就已经确定;如果希望在排序的操作中临时指定比较规则,可以采用Comparator接口回调的方式。
- Comparator接要求实现类必须重写其定义的方法: int compare(T o1,T o2)
- 该方法的返回值要求:
- -若o1> o2则返回值应>0
- -若01 < o2则返回值应<0
- -若o1= =o2则返回值应为0
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 排序字符串
*
*/
public class SortListDemo3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("苍老师");
list.add("传奇");
list.add("小泽老师");
System.out.println(list);
/*
* String本身实现了Comparable接口,比较规则为按照字符的unicode编码的
* 大小比较,先看首字符,如果一样再看第二个字符。
* 对于中文而言,几乎没有规律可言。
*/
// Collections.sort(list);
/*
* 自定义一个比较器,比较规则为按照字符多少排序,字少的在前,字多的在后
*/
Collections.sort(list,(o1,o2)->o1.length()-o2.length());
System.out.println(list);
}
}
练习:生成10个随机数(100)以内,按照大小顺序排序.要求自定义有比较规则
package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
/**
* 生成10个随机数(100)以内,按照大小顺序排序.要求自定义有比较规则
*
*/
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Random random = new Random();
for(int i=0;i<10;i++){
list.add(random.nextInt(100));
}
System.out.println(list);
Collections.sort(list, new Comparator<Integer>(){
public int compare(Integer o1, Integer o2) {
return o2 -o1;
}
});
System.out.println(list);
Collections.sort(list, (o1,o2)->o2-o1);
System.out.println(list);
}
}
队列和栈
Queue
java.util.Queue;接口,队列接口.集成自己的Collection.
常用实现类:java.util.LinkedList;
- 队列( Queue )是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另一端取出(poll)元素。
- 队列遵循先进先出( FIFO First Input First Output )的原则。
- JDK中提供了Queue接口,同时使得LinkedList实现了该接口(选择LinkedList实现Queue的原因在于Queue经常要进行添加和删除的操作,而LinkedList在这方面效率较高)
package collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
/**
* 队列
* 队列是经典的数据结构,可以保存一组元素,但是保存元素必须遵循先进先出原则.
*
* java.util.Queue;接口,队列接口.集成自己的Collection.
* 常用实现类:java.util.LinkedList;
*
*/
public class QueueDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
//入队操作,将元素添加到队列末尾
queue.offer("one");
queue.offer("two");
queue.offer("threee");
queue.offer("four");
queue.offer("five");
queue.offer("six");
System.out.println(queue);
//poll 出队操作,获取队首元素同时,将其从队列删除
String str = queue.poll();
System.out.println(str);
System.out.println(queue);
//peek 引用队首元素,获取队首元素后该元素不会出队
str = queue.peek();
System.out.println(str);
System.out.println(queue);
System.out.println("size:"+queue.size());
//迭代器遍历队列后,,元素还在队列
for(String s : queue){
System.out.println(s);
}
System.out.println(queue);
/*
* 用 poll 方法遍历队列,这种操作是一次性的,遍历后队列中就没有元素了
*/
// while(queue.size()>0){
// String s = queue.poll();
// System.out.println(s);
// }
// System.out.println(queue);
while(!queue.isEmpty()){
String s = queue.poll();
System.out.println(s);
}
System.out.println(queue);
}
}
Deque
java.util.Deque 接口.双端队列.Deque接口继承自Qeque接口.双端队列的特点是
* 队列两端都可以进行出入队操作
* 实现类:java.util.LinkedList;
- Deque是Queue的子接口, 定义了所谓“双端队列”即从队列的两端分别可以入队( offer )和出队( poll),LinkedList实现了该接口。
- 如果将Deque限制为只能从端入队和出队,则可实现"栈”( Stack )的数据结构,对于栈而言, 入栈称之为push ,出栈称之为pop。
- 栈遵循先进后出( FILO First Input Last Output )的原则。
package collection;
import java.util.Deque;
import java.util.LinkedList;
/**
* java.util.Deque 接口.双端队列.Deque接口继承自Qeque接口.双端队列的特点是
* 队列两端都可以进行出入队操作
* 实现类:java.util.LinkedList;
*
*/
public class DequeDemo {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
deque.offer("one");
deque.offer("two");
deque.offer("three");
System.out.println(deque);
//队首入队
deque.offerFirst("four");
System.out.println(deque);
//队尾入队
deque.offerLast("five");
System.out.println(deque);
String str = deque.poll();
System.out.println(str);
System.out.println(deque);
//队尾出队
str = deque.pollLast();
System.out.println(str);
System.out.println(deque);
//队首出队
str = deque.pollFirst();
System.out.println(str);
System.out.println(deque);
//peekFirst 引用队首元素,获取队首元素后该元素不会出队。
str = deque.peekFirst();
System.out.println(str);
//peekLast 引用队尾元素,获取队首元素后该元素不会出队。
str = deque.peekLast();
System.out.println(str);
}
}
stack( 栈)
栈( stack )是限定仅在表尾进行插入和删除操作的线性表
进栈 push( ) |
入栈:在栈顶(数组的尾部)添加指定的元素,并返回新数组的长度。 |
出栈 pop( ) |
出栈:删除栈顶(数组的尾部)的一个元素,并返回删除的元素。 |
package collection;
import java.util.Deque;
import java.util.LinkedList;
/**
* 栈结构,栈是经典的数据结构之一,可以保存一组元素,但是存取元素必须遵循先进后出
* 原则。
* 使用栈一般是为了实现如:后退,前进这样的功能
*
*/
public class StackDemo {
public static void main(String[] args) {
Deque<String> stack = new LinkedList<>();
//push(入栈)
stack.push("one");
stack.push("two");
stack.push("three");
stack.push("four");
stack.push("five");
System.out.println(stack);
//pop (出栈)
String str = stack.pop();
System.out.println(str);
System.out.println(stack);
}
}
集合操作-Map
Map接口
- java.util.Map;查找表
- Map 体现的样子是一个多行两列的表格,其中左列为Key,右列为value
- Map总是根据Key获取对应的value ,因此我们可以将查询的条件作为Key,对应的值作为value保存.
- Map有一个要求,Key是不允许重复的.
- 常用实现类: HashTable、LinkedHashMap、HashMap、TreeMap
- java.util.HashMap:散列表,当今查询速度最快的数据结构
Map接口
- Map接口定义的集合又称查找表,用于存储所谓"Key-Value"映射对。Key可以看 成是Value的索引,作为Key的对象在集合中不可以重复。
- 根据内部数据结构的不同, Map接有多种实现类,其中常用的有内部为hash表实现的HashMap和内部为排序二叉树实现的TreeMap。
put( )方法
Map接口中定义了向Map中存放元素的put方法:
一 V put(K key, V value)
将Key-Value对存入Map ,如果在集合中已经包含该Key ,则操作将替换该Key所对应的Value,返回值为该Key原来所对应的Value (如果没有则返回null )。
package map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
/*
* V put(K k,V v);
* 将给定的key,value 保存到Map中
* 由于Map要求Key不允许重复,因此如果使用Map中已有key保存value,则是替换
* value 操作,此时put 方法返回为就是被替换的value,否则返回值为null.
*/
map.put("语文", 99);
map.put("数学", 98);
map.put("英语", 97);
map.put("物理", 96);
Integer num = map.put("化学", 99);
System.out.println(map);
System.out.println(num);
num = map.put("数学", 77);//数学作为key 已经存在,因此77会替换原来的98
System.out.println(map);
System.out.println(num);//98
}
}
get( )方法
Map接中定义了从Map中获取元素的get方法:
- V get(Object key)
返回参数key所对应的Value对象,如果不存在则返回null。
package map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
/*
* V get(Object key)
* 根据给定的key获取对应的value ,若是给定的key不存在,则返回值为null
*/
//获取语文的成绩?
num = map.get("语文");
System.out.println(num);//98
num = map.get("体育");
System.out.println(num);//null
}
}
containsKey ( )方法
Map接口中定义了判断某个key是否在Map中存在
-- boolean containsKey(Object key);
若Map中包含给定的key则返回true ,否则返回false。
package map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
boolean has = map.containsKey("数学");
System.out.println("是否有数学科目:"+ has); //true
}
}
size()
获取当前Map元素个数
//获取当前Map元素个数
int size = map.size();
System.out.println("size:"+size);
remove(Object key)
删除给定的key 所对应的这组键值对,返回值为该key对应的value.
num = map.remove("语文");
System.out.println(map);//{物理=96, 数学=77, 化学=99, 英语=97}
System.out.println(num);//99
HashMap
哈希表
那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组” .
HashMap的存储结构
- HashMap底层是以数组方式进行存储的。将key-value键值对作为数组的一个元素进行存储。
- Key-value都是Map.Entry中的属性。其中将key的值进行hash之后进行存储,即每一个key都是计算hash值,然后再存储。每一个hash值对应一个数组下标,数组下标是根据hash值和数组长度计算得来的。
- 由于不同的key值可能具有相同的hash值,即一个数组的某个位置出现两个相同的元素,对于这种情况,hashmap采用链表的形式进行存储.
Hash表原理
hashCode方法
- 从HashMap的原理中我们可以看到, key的hashCode()方法的返回值对HashMap存储元素时会起着很重要的作用。而hashCode()方法实际上是在Object中定义的。那么应当妥善重写该方法:
- 对于重写了equals方法的对象, -般要妥善的重写继承自Object类的hashCode方法( Object提供的hashCode方法将返回该对象所在内存地址的整数形式)。
- 重写hashCode方法是需注意两点:其一、与equals方法的一致性,即equals比较返回true的两个对象其hashCode方法返回值应该相同;其二、hashCode返回的数值应符合hash算法的要求,试想如果有很多对象的hashCode方法返回值都相同,则会大大降低hash表的效率,一般情况下可以使用IDE (如Eclipse )提供的工具自动生成hashCode方法。
重写hashCode方法
public int hashCode() {
finalint prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((gender == nul)? 0:
gender.hashCodeO);
result = prime * result + ((name== nul) ? 0 :
name.hashCode();
long temp;
Temp = Double.doubleToLongBits(salary);
result = prime * result +(int) (temp ^ (temp>>> 32));
return result;
}
装在因子及HashMap优化
- Capacity :容量,hash表里bucket(桶)的数量,也就是散列数组大小。
- Initial capacity :初始容量,创建hash表时,初始bucket的数量,默认构建容量是16.也可以使用特定容量。
- Size :大小,当前散列表中存储数据的数量。
- Load factor :加载因子,默认值0.75(就是75%),当向散列表增加数据时如果size/capacity的值大于Load factor则发生扩容并且重新散列(rehash)。
- 性能优化:加载因子较小时,散列查找性能会提高,同时也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量减少rehash提高性能。
Map的遍历
- Map提供了三种遍历方式:
- 遍历所有的Key
- 遍历所有的Key-Value对
- 遍历所有的Value(不常用)
建议:使用keyset及entryset来进行遍历能取得相同的结果,但两者的遍历速度是有差别的,keySet( )的速度比entrySet( )慢了很多,也就是keySet方式遍历Map的性能不如entrySet性能好,为了提高性能,以后多考虑用entrySet( )方式来进行遍历。
源码比对:
public V get(Object key) {|
if (key == nu11)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return nu11 = entry ? nu1l : entry. getValue();
}
final Entry<K, V> getEntrykobject key) {
if (size == 0) {
return nu11;
}
int hash = (key = nu11) ? θ : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table. 1ength)];
e != nu1l;
e = e.next) {http://blog. csdn. net/
object k;
if (e.hash == hash 8&&
((k = e.key) == key | (key != nu11 && key. equals()))
return e;
}
return nu1l ;
}
使用keySet( )方法
遍历所有Key的方法:
-Set<K> keySet ( )
该方法会将当前Map中所有的key存入-个Set集合后返回。
将Map中所有的key存放到Set集合中。应为Set集合有迭代器,可以通过迭代器循环key,在通过get()方法,得到每个key所对应的value;
/*
*Set keySet()
* 将当前Map中所有的key以一个Set集合形式返回,遍历该集合等于遍历所有key
*/
Set<String> keySet = map.keySet();
for(String key: keySet){
System.out.println("key:"+key);
}
使用entrySet( )方法
遍历所有的键值对的方法:
- Set <Entry <K,V>> entrySet( )
该方法会将当前Map中每一组key-value对封装为一个Entry对象并存入一个Set集合后返回。
java.util.Map.Entry
Entry的每一个实例表示Map的一组键值对,其中提供了两个常用方法:
getKey 和 getValue 分别用来获取 对应key 和value
Set<Map.Entry<K,V>> entrySet() //返回此映射中包含的映射关系的 Set 视图。 Map.Entry表示映射关系。entrySet():迭代后可以e.getKey(),e.getValue()取key和value。返回的是Entry接口 。
/*
* 遍历每一组键值对
* Set<Entry> entrySet();
*
* java.util.Map.Entry
* Entry的每一个实例表示Map的一组键值对,其中提供了两个常用方法:
* getKey 和 getValue 分别用来获取 对应key 和value
*/
Set<Entry<String,Integer>> entrySet = map.entrySet();
for(Entry<String,Integer> e : entrySet){
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key+":"+value);
}
使用Collection values();方法
遍历所有value,该方法会将当前Map中所有的value以一个集合形式返回
获取集合中的所有的value值,也就是获取集合中的所有的值----没有键,没有对应关系,
Collection<Integer> values = map.values();
for(Integer value: values){
System.out.println(value);
}
有序的Map
LinkedHashMap实现有序Map
- 使用Map接口的哈希表和链表实现,具有可预知的迭代顺序。此实现与HashMap的不同之处在于:
- LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序通常就是存放元素的顺序。
- 需要注意的是,如果在Map中重新存入已有的key,那么key的位置不会发生改变,只是将value值替换。
统计各点PM2.5最大值(要求顺序)
public void testLinkedHashMap(
String[ positions, int] datas) {
LinkedHashMap <String,Integer> map
= new LinkedHashMap < String,Integer> 0;
for(int i=0; i<positions.length; i++){
Integer data = map.get(positions[i]);
int max = data==null ? 0 : data;
max = max> datas[] ? max:datas[i];
map.put(positions[i], max);
}
System.out.println(map);
}