类集、Collection集合、Map


本文介绍Java的类集:Collection、Map、Iterator


一、类集设置的目的

普通对象数组的最大问题在于数组中的元素个数固定,不能动态扩充大小,所以早期通过链表来实现动态对象数组,这样非常复杂。Java为了方便用户操作各个数据结构,引入了类集的概念,可以把类集成为java对数据结构的实现。

类集中最大的几个操作接口:Collection、Map、Iterator
所有类集操作的接口或类都在java.util包中。

Java类集结构图:
java类集结构图

二、Collection接口

Collection 中的子接口为List、Set
List 和Set 公用的方法:

public boolean add(E e) : 把给定的对象添加到当前集合中 。
public void clear() :清空集合中所有的元素。
public boolean remove(E e) : 把给定的对象在当前集合中删除。
public boolean contains(E e) : 判断当前集合中是否包含给定的对象。
public boolean isEmpty() : 判断当前集合是否为空。
public int size() : 返回集合中元素的个数。
public Object[] toArray() : 把集合中的元素,存储到数组中。

1.List接口

常用的实现类有:ArrayList、Vector、LinkedList

特点:

  1. 存取有序,例如存元素的顺序是4,2,1,那么元素在集合中就是按照4,2,1的顺序存储的。
  2. 是一个带有索引的集合(与数组索引相同)。
  3. 可以有重复的元素,可以通过元素的equals 方法来比较是否为重复的元素。

常用方法

public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。
public E get(int index) :返回集合中指定位置的元素。
public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

1.1 ArrayList

ArrayList 的构造函数什么参数都不传递是,默认长度为0,当添加第一个元素进去后,长度变为10,已满还要添加,扩容为原来的1.5倍。
优点:查询遍历顺序快
缺点:增删慢

1.2 LinkedList

使用了大量首尾操作的方法,可以作为堆栈和队列的就够使用。

public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true。

代码如下(示例):

LinkedList<Integer> data = new LinkedList<>();
data.addFirst(123);
data.addFirst(234);
Integer i = data.removeFirst();
//压栈
data.push(100);
data.push(200);
//弹栈
Integer i = data.pop();

2.Iterator迭代器

Iterator主要用于迭代访问操作,可以遍历Collection集合中存入的元素
主要方法有:

public Iterator iterator() : 获取集合对应的迭代器,用来遍历集合中的元素的。
public E next() :返回迭代的下一个元素。
public boolean hasNext() :如果仍有元素可以迭代,则返回 true。

代码如下(示例):

ArrayList<Integer> data = new ArrayList<>();
data.add(1);
data.add(2);
data.add(3);
data.add(4);
for(int i=0;i<data.size();i++){
	System.out.println(data[i]);
}
// 取数据
Iterator<Integer> iterator = data.iterator();
while(iterator.hasNext()){  //判断
	//指针向后移动并取出数据
	Itegar i = iterator.next(); 
}
// 删除数据
iterator.next();
Iterator.remove();

3.for each(增强for循环)

它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。

代码如下(示例):

int[] arr = {6,5,4,2,1};
for(int data:arr){
	// 遍历arr数组,data代表遍历到的每一个元素
	System.out.println(data);
}

ArrayList<String> data = new ArrayList<>();
data.add("123")
data.add("234")
for(String s:data){
	// 遍历ArrayList,s代表集合中的每一个元素
	System.out.println(s);
}

4.Set接口

与List接口不同的是:Set 接口中元素无序(存取顺序不一致),并且都会以某种规则保证存入的元素不出现重复
常用的实现类有:HashSet、LinkedHashSet
Set取元素的方法可以用:迭代器、增强for

4.1 HashSet 集合

它所存储的元素是不可重复的,并且所有元素都是无序的(存取顺序不一致)。HashSet 底层的实现是HashMap支持的

HashSet 是根据对象的哈希值来确定元素在集合中的存取位置,因此具有良好的存取和查找性能
保证元素唯一性的方式依赖于:hashCode、equals方法。

代码如下(示例):

//创建 Set集合 
HashSet<String> set = new HashSet<String>(); 
//添加元素 
set.add(new String("123")); 
set.add("123"); 
set.add("123"); 
set.add("321"); 
//遍历 
for (String name : set) 
{ 
	System.out.println(name); 
}

输出结果

123
321

4.2 HashSet 存储数据的结构(哈希表)

在JDK1.8之前,哈希表底层采用数组+链表 的结构实现。使用链表来处理冲突,同一hash值的元素都存储在同一个链表里。但是当一个桶(链表)中的元素较多时,即hash值相等的元素较多时,通过key值一次查找的效率较低。
在JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度大于8时,链表换为红黑树存储。
HashSet存储原理图

JDK1.8引入红黑树大程度优化了HashMap的性能,保证HashSet集合元素的唯一,其实就是根据存入对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

4.3 TreeSet

数据翻入后有序排列,在存放自己写的类时要实现Comparable接口(见4.2和4.3节)
存放String 代码如下(示例):

Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\ 
all.add("D"); 
all.add("X");  
all.add("A");

结果:

[A, D, X]

使用TreeSet 集合添加几个Person 类:

import java.util.Objects;
// 定义Person 类
public class Person{
    private String name;
    private int age;
    public Person() { }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String toString() {
        return "姓名:" + this.name + ",年龄:" + this.age;
    }
}


import java.util.TreeSet;

public class TreeSetPersonDemo01 {
    public static void main(String[] args) {
        Set<Person> all = new TreeSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

执行如上代码,程序报错ClassCastException:Person类不能向Comparable接口转型
如果要使用TreeSet 存放Person 对象,必须在Person 类中实现Comparable 接口

public class Person implements Comparable<Person>{
	...
	@Override
    public int compareTo(Person per) {
        if(this.age > per.age){
            return 1;
        }
        else if(this.age < per.age){
            return -1;
        }
        else{
            return 0;
        }
    }
}

程序运行结果如下:

[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]

从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则 此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。

public class Person implements Comparable<Person>{
	...
	@Override
    public int compareTo(Person per) {
        if(this.age > per.age){
            return 1;
        }
        else if(this.age < per.age){
            return -1;
        }
        else{
            return this.name.compareTo(per.name);
        }
    }
}

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

4.4 使用HashSet 区分自定义类的重复元素

改用HashSet 存储Person 对象

public class HashSetPersonDemo01 {
    public static void main(String[] args) {
        Set<Person> all = new HashSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

结果为:

[姓名:王五,年龄:11, 姓名:李四,年龄:10, 姓名:张三,年龄:10, 姓名:孙七,年龄:13, 姓名:李四,年龄:10, 姓名:赵六,年龄:12]

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable 接口间接完成的。
如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。

从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成: ·

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码 ·
  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。
    所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。
public class Person{
	...
	@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return getAge() == person.getAge() &&
                getName().equals(person.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getName(), getAge());
    }
}

运行结果

[姓名:李四,年龄:10, 姓名:孙七,年龄:13, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:张三,年龄:10]

发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠hashCode()和 equals()

4.5 总结

关于 TreeSet 的排序实现:如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 也就是要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。

TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。

三、 集合工具类Collections

部分方法如下:

public static boolean addAll (Collection c, T… elements) :往集合中添加一些元素。
public static void shuffle (List<?> list) 打乱顺序 :打乱集合顺序。
public static void sort (List list) :将集合中元素按照默认规则排序。
public static void sort (List list,Comparator<? super T> ) :将集合中元素按照指定规则排序。

代码如下(示例):

	ArrayList<Integer> list = new ArrayList<>();
	// 原来写法
	// list.add(12);
	// list.add(44);
	// 采用工具类
	Collections.addAll(list,12,44,1,2);
	System.out.println(list);
	// 排序方法
	Collections.sort(list);
	System.out.println(list);

结果:

[12, 44, 1, 2]
[1, 2, 12, 44]

经过sort() 的排序采用默认的顺序,如果要指定顺序需要用到public static void sort (List list,Comparator<? super T> ) 中的Comparator

四、Comparator 比较器

存储字符串类型的比较(示例):

public class CollectionsDemo2 { 
	public static void main(String[] args) { 
		ArrayList<String> list = new ArrayList<String>(); 
		list.add("cba"); 
		list.add("aba"); 
		list.add("sba"); 
		list.add("nba"); 
		//排序方法 
		Collections.sort(list); 
		System.out.println(list); 
	} 
}

结果:

[aba, cba, nba, sba]

1. 默认排序规则的定义

在JAVA中提供了两种实现两个对象之间比较大小的方式,一种是比较死板的采用java.lang.Comparable接口去实现,一种是灵活的当我需要做排序的时候在去选择
java.util.Comparator 接口完成

我们采用的 public static void sort(List list) 这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能,在String类型上如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

}

String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用

public static void sort(List list,Comparator<? super T> )

方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于位于java.util包下,排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:

public int compare(String o1, String o2) :比较其两个参数的顺序。
两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序,
则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
如果要按照降序排序
则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

操作如下:

public class CollectionsDemo3 { 
	public static void main(String[] args) 
	{ 
		ArrayList<String> list = new ArrayList<String>(); 
		list.add("cba"); 
		list.add("aba"); 
		list.add("sba"); 
		list.add("nba"); 
		//排序方法 按照第一个单词的降序 
		Collections.sort(list, new Comparator<String>() { 
			@Override 
			public int compare(String o1, String o2) { 
				return o2.charAt(0) - o1.charAt(0); 
			} 
		}); 
		System.out.println(list); 
	} 
}

结果:

[sba, nba, cba, aba]

2. 简述Comparable和Comparator两个接口的区别

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。只能在类中实现 compareTo() 一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无序指定比较器。

Comparator:强行对某个对象进行整体排序。可以将Comparetor 传递给sort 方法(如Collections.sort 或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序set 或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。

创建一个学生类,存储到ArrayList集合中完成指定排序操作。实例如下:

// Student 初始类
public class Student{ 
	private String name; 
	private int age; public Student() { }
	public Student(String name, int age) { 
		this.name = name; 
		this.age = age; 
	}
	public String getName() { 
		return name; 
	}
	public void setName(String name) 
	{ 
		this.name = name; 
	}
	public int getAge() { 
		return age;
	}
	public void setAge(int age) { 
		this.age = age; 
	}
	@Override 
	public String toString() { 
		return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; 
	} 
}

// 测试类
public class Demo { 
	public static void main(String[] args) { 
		// 创建四个学生对象 存储到集合中 
		ArrayList<Student> list = new ArrayList<Student>(); 
		list.add(new Student("rose",18)); 
		list.add(new Student("jack",16)); 
		list.add(new Student("abc",16)); 
		list.add(new Student("ace",17)); 
		list.add(new Student("mark",16)); 
		/*让学生 按照年龄排序 升序 */ 
		// Collections.sort(list);
		//要求 该list中元素类型 必须实现比较器Comparable接口 
		for (Student student : list) { 
			System.out.println(student); 
		} 
	} 
}

发现,当我们调用Collections.sort()方法的时候 程序报错了。
原因:如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口。
于是我们就完成了Student类的一个实现,如下:

public class Student implements Comparable<Student>{ 
	.... 
	@Override 
	public int compareTo(Student o) { 
		return this.age-o.age;//升序 
	} 
}

再次测试,代码就OK 了效果如下:

Student{name=‘jack’, age=16}
Student{name=‘abc’, age=16}
Student{name=‘mark’, age=16}
Student{name=‘ace’, age=17}
Student{name=‘rose’, age=18}

3. 扩展

如果在使用的时候,想要独立的定义规则去使用可以采用 Collections.sort(List list,Comparator c) 方法,自己定义规则:

Collections.sort(list, new Comparator<Student>() { 
	@Override 
	public int compare(Student o1, Student o2) { 
		//以学生的年龄降序 
		return o2.getAge()-o1.getAge();
	} 
});

结果:

Student{name=‘rose’, age=18}
Student{name=‘ace’, age=17}
Student{name=‘jack’, age=16}
Student{name=‘abc’, age=16}
Student{name=‘mark’, age=16}

如果想要规则更多一些,参考下面代码:

Collections.sort(list, new Comparator<Student>() { 
	@Override 
	public int compare(Student o1, Student o2) { 
		// 年龄降序 
		int result = o2.getAge()-o1.getAge();
		//年龄降序 
		if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序 
			result = o1.getName().charAt(0)-o2.getName().charAt(0); 
		}
		return result; 
	} 
});

结果:

Student{name=‘rose’, age=18}
Student{name=‘ace’, age=17}
Student{name=‘abc’, age=16}
Student{name=‘jack’, age=16}
Student{name=‘mark’, age=16}

五、Map

Collection中,每次操作的都是一个对象,如果要操作一对对象,则需使用Map。Map接口中所有的内容都按照 key->value 的形式保存

自己定义的对象最好不要存到键的位置,如果存了,尽量不要改变,否则哈希码会乱

1. HashMap

无序排放
向集合中添加内容,范例如下:

HashMap<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"张三A");
map.put(1,"张三B");  //新内容替换旧内容
map.put(2,"李四");
map.put(3,"王五");
// 根据指定的key 找到value,如果没有找到,则返回 null,找到了返回具体的内容
String val= map.get(6);   
System.out.println(val);

得到全部的key 或 value,范例如下:

HashMap<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"张三A");
map.put(1,"张三B");  //新内容替换旧内容
map.put(2,"李四");
map.put(3,"王五");
Set<Integer> set = map.keySet();   //得到全部的key
Collection<String> value = map.values();  //得到全部的value
Iterator<Integer> iter1 = set.iterator();
Iterator<String> iter2 = value.iterator();
System.out.println("全部的key:");
while(iter1.hasNext()){
	System.out.print(iter1.next()+" ");
}
System.out.print("\n全部的Value:");
while(iter2.hasNext()){
	System.out.print(iter2.next()+" ");
}

结果:

全部的key:
1 2 3
全部的Value:张三B 李四 王五

循环输出Map中的全部内容

Map<String, String> map = new HashMap<String, String>(); 
map.put("ZS", "张三"); 
map.put("LS", "李四"); 
map.put("WW", "王五"); 
map.put("ZL", "赵六"); 
map.put("SQ", "孙七"); 
Set<String> set = map.keySet(); // 得到全部的key 
Iterator<String> iter = set.iterator(); 
while (iter.hasNext()) { 
	String i = iter.next(); // 得到key 
	System.out.println(i + " --:> " + map.get(i)); 
}

2.TreeMap (较少使用)

按照 key 进行排序,另外,可以中的内容可以为任一对象,但要求对象所在的类必须实现Comparable接口。

Map<String, String> map = new TreeMap<String, String>(); 
map.put("ZS", "张三"); 
map.put("LS", "李四"); 
map.put("WW", "王五"); 
map.put("ZL", "赵六"); 
map.put("SQ", "孙七"); 
Set<String> set = map.keySet(); // 得到全部的key 
Iterator<String> iter = set.iterator(); 
while (iter.hasNext()) {
	String i = iter.next(); // 得到key 
	System.out.println(i + " --:> " + map.get(i)); 
}

3.Map集合的输出

在 Collection 接口中,可以使用 iterator() 方法为Iterator 接口实例化,并进行输出操作,但是在 Map 接口中并没有此方法的定义,不能直接用 Iterator 进行输出。
Map接口中存放的每一个内容都是一对值,而使用 Iterator 接口输出时,每次取出的都是一个完整的对象。如果此时非要用 Iterator 进行输出,则可按照如下步骤进行:

  1. 使用Map 接口中的entrySet() 方法将Map 接口的内容变为Set 集合
  2. 可以使用Set 接口中定义的 iterator() 方法为Iterator 接口进行实例化
  3. 之后使用Iterator 接口进行迭代输出,每一次迭代都可以取得一个Map.Entry 的实例
  4. 通过Map.Entry 进行key 和 value的分离

Map.Entry 接口中最常用的方法:

K getKey() 得到key
V getValue() 得到value

HashMap<String,String> map = new HashMap<>();
map.put("ZS","张三");
map.put("LS","李四");
map.put("WW","王五");
map.put("ZL","赵六");
Set<Map.Entry<String,String>> set = map.entrySet();  //变为Set 实例
Iterator<Map.Entry<String,String>> iter = set.iterator();
while(iter.hasNext()){
	Map.Entry<String,String> me = iter.next();
	System.out.println(me.getKey()+"->"+me.getValue());
}

以上的代码一定要记住,Map 集合中每一个元素都是 Map.Entry 的实例,只有通过 Map.Entry 才能进行 key 和 value 的分离操作。
除了以上的做法之外,在 JDK 1.5 之后也可以使用 foreach 完成同样的输出,只是这样的操作基本上不使用。

HashMap<String,String> map = new HashMap<>();
map.put("ZS","张三");
map.put("LS","李四");
map.put("WW","王五");
map.put("ZL","赵六");
for (Map.Entry<String, String> me : map.entrySet()) { 
	System.out.println(me.getKey() + " --> " + me.getValue()); 
}

4.HashMap、Hashtable、ConcurrentHashMap区别:

HashMap线程不安全
HashTable 线程安全,但是效率低,线程排队在整个集合外
ConcurrentHashMap 采用分段锁机制,保证线程安全,效率比较高。 操作不同下标的数据可以同时访问修改,但是操作相同的下标要排队

HashMap 不保证有序,因为是根据哈系码存入的
TreeMap 在存入的后进行排序
LinkedHashMap 既存在双向链表中又存在HashMap中,可以保证存储顺序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值