Collection集合、Collections工具类
集合框架概述
1.集合、数组都是对多个数据进行存储操作的结构,简称java容器
此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg数据库中)
2.数组在存储多个数据方面
(1)特点:一旦初始化后,长度确定;元素数据类型确定,只能操作指定类型的数据;如:String[ ]、Object[ ] arr2
(2)缺点:
a.一旦初始化,长度不可修改;
b.数组提供的方法有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高;
c.获取数组中实际元素个数的需求,数组没有现成的属性和方法;
d.数组有序、可重复,对于无序、不可重复需求,不能满足
java集合分为Collection和Map两种体系
Collection接口:单列数据,定义存取一个一个对象的方法集合
1. List接口:有序,可重复(动态数组)
|--------------------ArrayList:作为List的主要实现类,线程不安全,效率高;底层使用Object[ ]存储、
|--------------------LinkedList:对于频繁的插入、删除操作,其比ArrayList高;底层使用“双向链表”存储
|--------------------Vector:作为List接口的古老实现类,线程安全,效率低;底层使用Object[ ]存储
2.Set接口:无序,不可重复(数学中的“集合”),操作过滤问题,即相同数据过滤掉
|--------------------HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值
|-------------------------------LinkedHashSet:在HashSet基础上添加了Link,是HashSet的子类,使其在遍历内部数据时,可以按照添加的顺序遍历(不同于有序)
|--------------------TreeSet:可以按照添加对象的指定属性进行排序
Collection接口
Map:双列数据,保存具有映射关系”key-value对“的集合(数学中函数y=f(x))
|------HashMap:Map的主要实现类,线程不安全,效率高;可以存储null(jdk7之前,底层数组+链表;jdk8,数组+链表+红黑树)
|--------------------LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有HashMap的底层结构基础上,添加了一堆指针,指向前一个和后一个元素,对于频繁的遍历操作,执行效率高于HashMap
|------TreeMap:按照添加的key-value对进行排序,实现排序遍历。其中key的排序为自然排序或定制排序;底层使用的红黑树
|------Hashtable:古老的实现类,线程安全,效率低;不能存储null
|--------------------Properties:常用来处理配置文件,其中key和value都是String
Map接口
Collection接口中声明方法的测试
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
Collection c1 = new ArrayList();
c1.add(new Person("xiaohong",12));
c1.add(new Person("xiaohong",12));
//false,未重写equals方法时:即Object的equals方法相当于==,基本数据类型,比较值;引用数据,比较地址
// System.out.println(c1.contains(new Person("xiaohong", 12)));
System.out.println(c1.contains(new Person("xiaohong", 12)));//true,重写equals方法时,即比较值
Collection c2 = Arrays.asList(12);
System.out.println(c1.containsAll(c2));
下列方法操作的对象:
Collection c1 = new ArrayList();
c1.add(12);//表示数字
c1.add("12");//表示字符串
c1.add(new Person("xiaohong",12));
c1.add(new Person("xiaohong",12));
Collection c2 = Arrays.asList(12,13);
(1)retainAll(Colection coll):交集
System.out.println(c1.retainAll(c2));//true,两个集合的交集
System.out.println(c1);//[12] 交集后输出c1
(2)removeAll(Collection coll):差集
System.out.println(c1.removeAll(c2));//true,移除c1中存在的c2,即差集
System.out.println(c1);//[12, com.vv.string.Person@0, com.vv.string.Person@0]
(3)hashCode():哈希值
//返回当前对象的哈希值
System.out.println(c1.hashCode());
(4)toArray():集合--》数组
//1.集合---->数组
Object[] objects = c1.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.print(objects[i] + "\t");
}
System.out.println();
//2.数组----->集合
List<Object> objects1 = Arrays.asList(objects);
System.out.println(objects1);//[12, 12, com.vv.string.Person@0, false]
//a.基本数据类型数组--->集合 将数组整体存入
List<int[]> ints = Arrays.asList(new int[]{123, 145});
System.out.println(ints);//[[I@5d6f64b1] int型的一维数组
System.out.println(ints.size());//1
//b.包装类的数组--->集合
List<Integer> integers = Arrays.asList(new Integer[]{12, 45});
System.out.println(integers);//[12, 45]
System.out.println(integers.size());//2
(5)Iterator(迭代器)
1.主要用于遍历Collection集合中的元素;
2.Collection接口继承了java.lang.Iterable接口,该接口有一个iterator方法,因此所有实现了Collection接口的集合都有一个iterator方法,用来返回一个实现了iterator接口的对象
3.Iterator仅用于遍历集合,其本身并不提供承装对象的能力;但若需要创建Iterator对象,则必须有一个被迭代的集合
4.每次调用iterator方法都会得到一个全新的迭代器对象
迭代器执行原理
两种错误的迭代方式:
// 错误方式一:结果跳着输出且NoSuchElementException
Iterator iterator = c1.iterator();
while(iterator.next() != null){
System.out.println(iterator.next());//
}
//每次c1.iterator().hasNext()都会创建新的迭代器对象
//错误方式二:一直输出第一个位置的值
while (c1.iterator().hasNext()){
System.out.println(c1.iterator().next());
}
(6)remove(Object obj)
Iterator iterator = c1.iterator();
while (iterator.hasNext()){
if("13".equals(iterator.next())){//????数字如何和迭代器元素比较
iterator.remove();
}
// System.out.print(iterator.next()+"\t");//出错,NoSuchElementException,原因是迭代器中元素改变了
}
Iterator iterator1 = c1.iterator();
while (iterator1.hasNext()){
System.out.print(iterator1.next()+"\t");//12 Person{name='xiaohong', age=14} false
}
length、length()、size()区别
length——数组的属性;
length()——String的方法;
size()——集合的方法;
JDK5.0:ForEach遍历集合和数组
//for(集合元素类型 局部变量:集合对象)
for(Object obj:coll)
Iterator iterator1 = c1.iterator();
for(Object c : c1){//内部仍然调用了迭代器
System.out.print(iterator1.next()+"\t");
}
例题
int[] arr = new int[]{112,3,4};
//方式一赋值
for (int i = 0; i < arr.length; i++) {
arr[i] = 4;//对本身的数组元素进行修改
}
//方式二赋值
// for(int a : arr){
// a = 4;//取出来,重新对s赋值。在常量池中
// }
for(int a : arr){
System.out.print(a + "\t");//方式一:4 4 4 方式二:112 3 4
}
List接口
ArrayList、LinkedList、Vector三者异同
ArrayList
jdk7.0中的ArrayList对象的创建类似于单例的饿汉式,底层创建了长度为10的Object[ ];而jdk8.0中的ArrayList对象的创建类似于单例的懒汉式,底层的Object[ ]初始化为{ },延迟了数组创建,节省内存
扩容2倍
LinkedList
内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。其中Node用于保存数据,其prev和next两个变量分别记录前一个和后一个元素
扩容1.5倍
List接口中常用方法
List中:remove(int index)
ArrayList list = new ArrayList();
list.add(12);
list.add("xiaohong");
list.add(new Person("hh",10));
list.add(false);
System.out.println(list.remove(2));//Person{name='hh', age=10}
区别Colection中:remove(Object o)
c1.add("20");//表示字符串
c1.add(new Person("xiaohong",12));
System.out.println(c1.remove("20"));//true
System.out.println(c1);//[Person{name='xiaohong', age=12}]
subList(begin end)类似于String中的subString(begin end)都是左开右闭
List中主要方法
增/插add删remove改set查get长度size()遍历(1)Iterator(2)forEach(3)普通循环
例题:remove考察
@Test
public void test1(){
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
void updateList(List list){
// list.remove(2);//[1 ,2] 删除索引位置的值
list.remove(new Integer(2));//[1,3] 删除该对象
}
Set接口中的方法
Set接口中没有额外定义的新方法,使用的都是Collection中声明过的方法(无序即代表没有索引)
List接口中有额外定义的新方法,因为List中有索引(有顺序即代表有索引)
HashSet论证Set的无序性,并不是代表随机性
HashSet存储数据时,底层数组并非按照数组索引的顺序添加,而是根据数组的哈希值确定
不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个
@Test
public void test(){
Set set = new HashSet();
set.add(12);
set.add("hh");
set.add(new Person("zhangsan",10));
set.add(new Person("zhangsan",10));
System.out.println(set);
}
添加元素:以HashSet(其底层是数组+链表)为例:
向HashSet中添加元素a,先调用hashCode,计算其哈希值,然后哈希值接着通过某种计算方法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:若没有,则a添加成功;若有元素b,首先比较a,b的哈希值,若哈希值不相同,则a添加成功;若相同,进而需要调用元素a所在类的equals方法,equals返回true,a添加失败。返回false,a添加成功
对于a添加时其位置有元素而言,a和已经存在于指定索引位置上的数据之间以链表方式存储(jdk7,a放到数组中,指向原来的元素;jdk8,原来的元素在数组中,指向元素a)
HashSet底层结构
Set set = new HashSet();
set.add(12);
set.add("hh");
set.add(new Person("zhangsan",10));
set.add(new Person("zhangsan",10));
// //当未重写equals和hasCode时
// System.out.println(set);//[hh, Person{name='zhangsan', age=10}, 12, Person{name='zhangsan', age=10}]
// //当只重写equals时
// System.out.println(set);//[hh, Person{name='zhangsan', age=10}, 12, Person{name='zhangsan', age=10}]
//当只重写hasCode时
System.out.println(set);//[hh, Person{name='zhangsan', age=10}, 12, Person{name='zhangsan', age=10}]
// //当重写equals和hasCode时
// System.out.println(set);//[hh, Person{name='zhangsan', age=10}, 12]
向Set中添加的数据,其所在的类必须进行hashCode和equals重写,重写的hashCode和equals尽可能保持一致性,相等的对象必须具有相等的散列码(hashCode)
LinkedHashSet底层结构LinkedHashSet
作为HashSet的子类,在添加数据时,每个数组还维护了两个引用,记录此数据的前后数据,其对于“频繁的遍历操作”,LinkedHashSet频率高于HashSet
LinkedHashSet
TreeSet
1.向TreeSet添加数据,必须是相同的对象;
2.排序方式自然排序、定制排序
自然排序:
两对象进行比较时,为CompareTo方法,不再是equals方法
Set set = new TreeSet();
/*
TreeSet中存放不同对象出错java.lang.Integer cannot be cast to java.lang.String
set.add(12);
set.add("hh");
set.add(new Person("xiao",15));
System.out.println(set);
*/
/*
//1.基本数据类型存储,默认从小到大排序
set.add(12);
set.add(32);
set.add(2);
System.out.println(set);//[2, 12, 32]
*/
//2.对象存储,
/*
未对对象实现Comparable,重写CompareTo方法,即并未告知TreeSet以对象的哪种属性进行排序
com.vv.string.Person cannot be cast to java.lang.Comparable
*/
set.add(new Person("hong",15));
set.add(new Person("wang",5));
set.add(new Person("zhang",35));
set.add(new Person("zhang",25));
/*在Person中实现Comparable接口时,对属性进行按照指定大小进行排序*/
// //当仅对name属性进行比较时
// System.out.println(set);//[Person{name='hong', age=15}, Person{name='wang', age=5}, Person{name='zhang', age=35}]
//当对name和age属性进行比较时
System.out.println(set);//[Person{name='hong', age=15}, Person{name='wang', age=5}, Person{name='zhang', age=25}, Person{name='zhang', age=35}]
定制排序:Comparator
Set set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Person && o2 instanceof Person){
return -Integer.compare(((Person) o1).age,((Person) o2).age);
//[Person{name='zhang', age=35}, Person{name='zhang', age=25}, Person{name='hong', age=15}, Person{name='wang', age=5}]
}else{
throw new RuntimeException("对象类型不一致");
}
}
});
例题:
1.属性排序
//生日排序
@Test
public void test(){
Employee e1 = new Employee("xiaohong",12,new MyDate(1926,2,6));
Employee e2 = new Employee("zhangsan",82,new MyDate(1823,3,7));
Employee e3 = new Employee("lisi",62,new MyDate(1926,5,6));
Employee e4 = new Employee("zhaoliu",12,new MyDate(1753,2,9));
Employee e5 = new Employee("dawei",18,new MyDate(1926,5,3));
//使用Comparator對生日進行排序
TreeSet set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Employee && o2 instanceof Employee) {
Employee e1 = (Employee) o1;
Employee e2 = (Employee) o2;
MyDate m1 = e1.getBirthday();
MyDate m2 = e2.getBirthday();
/*//方式一:直接在该方法中比较
int year = m1.getYear() - m2.getYear();
//比较年
if (year != 0) {
return Integer.compare(m1.getYear(), m2.getYear());
}
int month = m1.getMonth() - m2.getMonth();
//比较月
if (month != 0) {
return Integer.compare(m1.getMonth(), m2.getMonth());
}
//比较日
return Integer.compare(m1.getDay(), m2.getDay());*/
//方式二:在MyDate中实现Comparable接口
return m1.compareTo(m2);
}
throw new RuntimeException("對象不一致");
}
});
// TreeSet set = new TreeSet();
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
// Iterator iterator = set.iterator();
for(Object o : set ){
System.out.println(o);
}
}
//name排序的同时,按照生日排序
//在Person类中比较
@Override
public int compareTo(Object o) {
if(o instanceof Employee){
Employee e = (Employee) o;
int i =this.name.compareTo(e.getName());
if(i == 0){
return this.getBirthday().compareTo(e.getBirthday());
}else{
return i;
}
}
throw new RuntimeException("对象类型不一致");
}
2.在List内去除重复元素
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Integer(2));
list.add(new Integer(2));
list.add(new Integer(1));
List list1 = updateSet(list);
for(Object o : list1){
System.out.print(o + "\t");
}
// for (int i = 0; i < list.size(); i++) {
// System.out.println(list.get(i));
// }
}
public static List updateSet(List list){
HashSet set = new HashSet();
//若list中添加的是自定义对象,则需要进行hashCode重写;Integer的equals方法已经重写了
set.addAll(list);
return new ArrayList(set);
}
3.set***********太坑了
HashSet set = new HashSet();
Person p1 = new Person("AA",11);
Person p2 = new Person("BB",22);
set.add(p1);
set.add(p2);
p1.setName("CC");
set.remove(p1);
// 首先算出此时name为CC和age为11的哈希值,再与name为AA和age为11的哈希值比较,结果不等;
// 因此,此时删除的位置其实为空,故存储结果为[Person{name='BB', age=22}, Person{name='CC', age=11}],
System.out.println(set);
set.add(new Person("CC",11));
//name为CC和age为11的位置存储添加的数据,其中结果的值,一个为AA所在位置的值替换,另一个为新的位置
// [Person{name='BB', age=22}, Person{name='CC', age=11}, Person{name='CC', age=11}]
System.out.println(set);
//虽然AA的位置仍在,但通过equals比较时发现,两者对象不一样,因此使用链表重新指向
//[Person{name='BB', age=22}, Person{name='CC', age=11}, Person{name='CC', age=11}, Person{name='AA', age=11}]
set.add(new Person("AA",11));
System.out.println(set);
Map接口
Map中的key:无序、不可重复,使用Set存储所有的key--》key所在类要重写equals和hashCode方法(例如:containsKey需要重写equals和hashCode)
Map中的value:无序、可重复,使用Collection存储所有的value--》value所在类要重写equals方法(例如:containsValue需要重写equals方法)
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序、不可重复,使用Set存储所有的entry
jdk7的HashMap底层原理
jdk8的HashMap底层原理
map元试图操作方法:
Map map = new HashMap();
map.put(1,2);
map.put(null,3);
map.put("AA",9);
//遍历key
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();
//遍历value
Collection values = map.values();
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
//遍历key-value
//方式一:
Set entrySet = map.entrySet();
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()){
Object obj = iterator2.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"......"+entry.getValue());
}
//方式二:
Set set1 = map.keySet();
Iterator iterator3 = set1.iterator();
while (iterator3.hasNext()){
Object obj = iterator3.next();
Object value = map.get(obj);
System.out.println(obj+"......"+value);
}
TreeMap的key必须是同一个类创建的对象,因此要按照key排序:自然排序或者定制排序
Map map = new TreeMap();
map.put(new User("zhangsan",16),"shanxi");
map.put(new User("lisan",16),"xian");
map.put(new User("lisan",15),"dali");
Set set = map.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"..........."+entry.getValue());
}
Collections是一个操作Collection(Set、List)和Map等集合的工具类
注意:copy方法
List list = new ArrayList();
list.add(12);
list.add(34);
list.add(34);
list.add(85);
// //错误写法一:Source does not fit in dest,看源码后:list.size() > list1.size()
// List list1 = new ArrayList();
// Collections.copy(list1,list);
//错误写法二:list1.size()为0,size()表示add后的数组长度
// List list1 = new ArrayList(list.size());//表示的是数组的长度
// Collections.copy(list1,list);
//eg:
List l = new ArrayList(9);//创建长度为9的数组
System.out.println(l.size());//0
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);
System.out.println(dest);//[12, 34, 34, 85]
Collections中的synchronizedXxx()方法,可使指定集合包装成线程同步的集合,从而解决多线程并发问题