一、集合概述
1.集合分类
(1)Collection 接口下常用接口有:List 和 Set
|--List:有存储顺序 ,可以重复.
|--Set:没有存储顺序,不可重复.
List
|--ArrayList: 底层为数组结构,查找快,增删慢.
|--LinkedList:底层为链表结构,查找慢,增删快.功能上与ArrayList没有区别.
|--Vector:不常用,被ArrayList替代,它是线程安全的.而ArrayList是线程不安全的.
Set
|--HashSet:只去重复,没有顺序.
|--LinkedHashSet:是HashSet的子类,可以去重复,并且保留存储顺序.
|--TreeSet:去重复,并且可以按照某种顺序排序.
Map:键值对,一次存两个对象,键必须唯一,可以根据键查找值.
Map接下常用类有:HashMap 和 TreeMap
HashMap: 底层是哈希表数据结构,允许使用null键和null值,该集合不同步,替代Hashtable.
|--LinkedHashMap: 去重复的同时保留存储顺序.
TreeMap: 底层是二叉树数据结构,线程不同步,可以用于给map集合中的键进行排序.
Hashtable: 底层是哈希表数据结构,不允许使用null键和null值,该集合线程同步.
|--Properties: 键和值都是String,用来存储一些配置项.
实际上Set底层就是使用了Map集合.
(2)HashSet 是如何保证元素唯一性的?
它是通过元素的两个方法, hashCode 和 equals 来完成的.
HashSet的add方法会先调用hashCode方法,如果元素或对象返回的hashCode值相同,才会判断equals是否为true.
如果hashCode返回值不同,则不会调用equals方法.
(3)TreeSet 是如何排序的?
两种方式:自然顺序(Comparable),比较器(Comparator)
自然顺序:在要放入TreeSet集合的类上实现Comparable接口,并重写compareTo()方法,
这样对象存入TreeSet的时候就会按照compareTo()方法排序.
比较器:compareTo()方法只有一个,无法定义两种比较方式,这就需要比较器了.
在创建TreeSet对象的时候,构造函数可以接受一个Comparator类型的对象,我们可以自定义一个Comparator
类的子类,并重写compare()方法.
一旦传入了比较器,TreeSet将不再按照Comparable中的顺序排序,add()方法执行时会按照我们自定义的比较器进行排序.
(4)HashMap 是如何保证键唯一的?
存储时:
和HashSet类似,HashMap存储一个键值的时候,会先调用键(key)对象的hashCode()方法得到哈希值,
然后在集合中查找是否有哈希值相同的键对象.
如果没有哈希值相同的键对象,直接将键值对存入.
如果有哈希值相同的键对象,就逐个和这些键对象进行equals()方法比较,结果为false就将键值对存入,结果为true时,就用新的值(value)覆盖掉原有值.
获取值时:
当HashMap在根据指定的键对象获取值的时候,会先对键对象调用hashCode()方法,得到哈希值,然后在集合中查找哈希值相同的对象,逐个equals()方法比较,
找到equals()结果为true的键值对,将值返回.
(5)TreeMap如何进行排序的?
TreeMap在存储键值对的时候,会调用键对象的compareTo()方法和集合中其它的键对象进行比较,根据比较结果以二叉树形式存储,当然和TreeSet类似,如果在创建
TreeMap的时候在构造函数中传入了比较器,那么存储顺序会以比较器为准.
(6)泛型
JDK5之后支持带有泛型的类, 集合上如果加了泛型, 只能存储同一类型的数据, 获取数据时的类型也被指定了.
这样做避免了一些不安全因素, 而且省去了强转的麻烦
(7)可变参数
JDK5之后函数的参数可以定义为可变参数
定义方式:(类型...变量名)
可变参数可以接收指定类型的0到多个实参,也可以接收指定类型的一个数组.
这些实参都会传入到形参变量代表的数组中, 传了几个实参, 形参数组的长度就是几.
可变参数必须是参数列表的最后一个.
什么时候用:当我们定义一个函数时,如果需要接受的数据个数不确定时.可用可变参数,这样可以按照实际需求传入不同数量的实参.
2.List接口介绍
常用方法:
(1)boolean add(Object obj) 添加任意类型对象
(2)Object remove(int index) 删除指定索引上的元素,并且返回这个元素.
(3)Object set(int index, Object obj) 将集合中指定位置的元素替换为指定对象.
(4)Object get(int index) 从集合中获取指定索引上的对象.
各举一例,欲要了解更多可以查API文档.
遍历:
(1)普通for循环.
(2)增强for循环.
(3)迭代器.
遍历时删除元素:
(1)普通for循:由于删除元素后,后面的元素会向前移动,所以每次删除之后需要将循环遍历-1操作(这里需要特别注意).
(2)增强for循环:无法在循环过程中修改集合.
(3)迭代器:迭代器在使用的过程中不允许修改集合,如果要删除,必须使用迭代器自身的remove()方法.
//代码示例:
//以下代码也适用于LinkedList
//导包是必须的.
import java.util.ArrayList;
import java.util.Iterator;
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 普通for循环
for (int i = 0; i < list.size(); i++)
System.out.print(list.get(i));
System.out.println();
// 增强for循环
for (String s : list)
System.out.print(s);
System.out.println();
// 迭代器
Iterator<String> it = list.iterator();
while (it.hasNext())
System.out.print(it.next());
System.out.println();
/*
//为了遍历完就释放迭代器,可以改用for循环
for(Iterator<String> it = list.iterator();it.hasNext(); )
System.out.print(it.next());
*/
}
}
3.List练习
(1)约瑟夫环问题,即7个小孩围成一个圈,从第一个开始,每数3个出列一个,问最后剩下哪个?
(2)自定义ArrayList,模拟Java提供的ArrayList,数组实现的列表.
//问题一
import java.util.LinkedList;
public class Demo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
whileMethod(list);
// forMethod(list);//两种实现方法,其实都一样.
System.out.println(list);
}
public static void whileMethod(LinkedList<String> list) {
int index = 0;
int num = 1;
while (list.size() > 1) {
if (num % 3 == 0)
list.remove(index--);
index = index == list.size() - 1 ? 0 : index + 1;
num++;
}
}
public static void forMethod(LinkedList<String> list) {
for (int index = 0, i = 1; list.size() > 1; i++) {
if (i % 3 == 0)
list.remove(index--);
index = index == list.size() - 1 ? 0 : index + 1;
}
}
}
//问题二
public class MyArrayList<E> { // <E>为泛型
private Object[] arr = new Object[10]; // 用来装载数据的容器
private int size; // 集合的大小
// 添加一个对象到ArrayList中, 内部就是将对象添加到数组中
public boolean add(E e) {
if (size == arr.length) { // 如果数组装满了
Object[] newArr = new Object[arr.length * 3 / 2 + 1]; // 创建一个更大的新数组
System.arraycopy(arr, 0, newArr, 0, size); // 将原有数据拷贝到新数组中
arr = newArr; // 成员变量记住新数组
}
arr[size++] = e;
return true;
}
//删除指定索引上的元素
public E remove(int index) {
checkRange(index);
Object oldElement = arr[index]; // 记住要删除位置上的元素
int length = size - index - 1; // 要删除的元素后面有几个元素
if (length > 0) // 如果要删除的位置后面有元素
System.arraycopy(arr, index + 1, arr, index, length); //见 ①
arr[--size] = null; // 将最后一个元素删除
return (E) oldElement; // 返回被删除的元素(在数组中被覆盖了)
}
//① 将要删除位置后面的元素向前移动一位(覆盖了要删除的位置)
//将集合中指定位置上的元素替换为指定元素
public E set(int index, E e) {
checkRange(index);
Object oldElement = arr[index]; // 记住原对象
arr[index] = e; // 将原对象替换为新对象
return (E) oldElement; // 返回原对象
}
// 检查索引是否越界
private void checkRange(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
}
//获取集合中指定索引上的元素
public E get(int index) {
checkRange(index);
return (E) arr[index];
}
// 获取集合的大小
public int size() {
return size;
}
//重写Object类的toString, 返回集合中每个元素的toString
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++)
// 将集合中每个元素添加到StringBuilder中
sb.append(arr[i] + (i < size - 1 ? ", " : ""));
sb.append("]");
return sb.toString();
}
}
4.Set接口介绍
常用方法:
由于它和List都实现了Collection接口,所以Collection接口中的方法他们都能用,具体请参考API文档.
由于它没有索引,所以有关索引的方法它都不能用.
遍历:
由于没有索引所以只有两种方式来遍历
迭代器
高级for循环(实际也是用迭代器实现的,只要迭代器能用,它就能用)
代码不再演示.
5.Set练习
从键盘接收3个学生的考试成绩, 对其按照总分排序, 输出到屏幕.
考试成绩输入格式:
张三,80,85,80
李四,70,70,80
王五,90,90,90
屏幕输出格式:
王五,90,90,90,270
张三,80,85,80,245
李四,70,70,80,220
//代码实现
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) throws IOException {
System.out.println("请输入学生成绩:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
TreeSet<Student> ts = new TreeSet<Student>();
for (int i = 0; i < 3; i++) {
String line = br.readLine();
String[] arr = line.split(",");//注意这里逗号为中文逗号,填写时就用中文逗号.
Student s = new Student(arr[0], Integer.parseInt(arr[1]),
Integer.parseInt(arr[2]), Integer.parseInt(arr[3]));
ts.add(s);
}
System.out.println("考试成绩排序后的结果为:");
for (Student s : ts)
System.out.println(s);
}
}
class Student implements Comparable<Student> {
private String name;
private int chinese;
private int math;
private int english;
private int sum;
public Student(String name, int chinese, int math, int english) {
super();
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
this.sum = chinese + math + english;
}
public String toString() {
return name + "," + chinese + "," + math + "," + english + "," + sum;
}
@Override
public int compareTo(Student o) {
int i = o.sum - this.sum; // 重写compareTo方法,按总分排序.
return i != 0 ? i : 1;
}
}
6.Map介绍
Map集合常用的方法
(1)V put(K key, V value)
存储一个键值对,如果键在集合中存在,值将会覆盖原有值,并返回原有值,原有值不存在则返回null.
当然,如果支持nul键值对的HashMap来说,如果原有值存的就是null,则返回的也是null.
(2)V get(Object key)
根据键获取值,如果不存在,返回null.
(3)V remove(Object key)
根据键删除值,并将值返回,如果不存在,返回null.
(4)boolean containsKey(Object key)
判断指定的键对象是否存在.
Map集合的遍历
(1)keySet: Map集合的keySet()方法可以得到一个所有键对象组成的Set集合,遍历这个Set集合可以得到每一个键对象
再根据键对象即可获取值.
(2)entrySet: Map集合的entrySet()方法可以得到一个所有Entry对象(键值对)组成的Set集合,遍历这个Set集合
可以得到每一个Entry对象,再使用Entry的getKey()方法和getValue()方法获取键和值.
//代码示例
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
public class MapIterator {
public static void main(String[] args) {
HashMap<String, String> h = new HashMap<String, String>();
h.put("张三", "10");
h.put("李四", "20");
h.put("王五", "30");
h.put("赵六", "40");
//遍历方式一
Set<String> s = h.keySet();//返回键的Set视图
for (String key : s)
System.out.println(key + ": " + h.get(key));
//遍历方式二
Set<Entry<String, String>> set = h.entrySet();// 封装成Entry对象然后装入Set集合
for (Entry<String, String> e : set)
System.out.println(e.getKey() + ": " + e.getValue());
}
}
7.练习
定义一个函数,函数中接收一个String,统计每个字符出现的次数
(1)不指定顺序打印
(2)按字母顺序打印
(3) 按存储顺序打印
(4) 按出现次数打印
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public class HomeWork {
public static void main(String[] args) {
countTimes1("bbcddddaaa");//没有顺序: {d=4, b=2, c=1, a=3}
countTimes2("bbcddddaaa");//字母顺序: {a=3, b=2, c=1, d=4}
countTimes3("bbcddddaaa");//出现顺序: {b=2, c=1, d=4, a=3}
countTimes4("bbcddddaaa");// 按出现的次数排序 {c=1, b=2, a=3, d=4}
countTimes5("bbcddddaaa");// 按出现的次数排序 {c=1, b=2, a=3, d=4}
}
// (1)
public static void countTimes1(String s) {
HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
char[] arr = s.toCharArray();
for (Character c : arr)
if (hm.containsKey(c))
hm.put(c, hm.get(c) + 1);
else
hm.put(c, 1);
System.out.println(hm);
}
// (2)
public static void countTimes2(String s) {
TreeMap<Character, Integer> hm = new TreeMap<Character, Integer>();
char[] arr = s.toCharArray();
for (Character c : arr)
if (hm.containsKey(c))
hm.put(c, hm.get(c) + 1);
else
hm.put(c, 1);
System.out.println(hm);
}
// (3)
public static void countTimes3(String s) {
LinkedHashMap<Character, Integer> hm = new LinkedHashMap<Character, Integer>();
char[] arr = s.toCharArray();
for (Character c : arr)
if (hm.containsKey(c))
hm.put(c, hm.get(c) + 1);
else
hm.put(c, 1);
System.out.println(hm);
}
// (4)很烂的方法
public static void countTimes4(String s) {
HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
LinkedHashMap<Character, Integer> lhm = new LinkedHashMap<Character, Integer>();// 定义保留存储顺序的map
char[] arr = s.toCharArray(); // 将字符串转为字符数组
for (Character c : arr)
// 循环遍历每个字符
if (hm.containsKey(c)) // 判断Map中是否包含这个字符
hm.put(c, hm.get(c) + 1); // 如果包含, 将以前的次数获取出来, 加1, 再覆盖
else
hm.put(c, 1); // 如果不包含, 将字符存入, 次数存1
int[] a = new int[hm.size()]; // 定义一个数组用来存储values(次数)
int i = 0;
for (Entry<Character, Integer> entry : hm.entrySet()) { // 遍历hm
a[i++] = entry.getValue(); // 将values(次数)存入数组
}
Arrays.sort(a); // 对数组进行排序
for (int j = 0; j < a.length; j++)
// 遍历数组
for (Entry<Character, Integer> entry : hm.entrySet())
// 遍历hm
if (entry.getValue() == a[j]) // 查找hm中value(次数)和数组中相等的
lhm.put(entry.getKey(), a[j]); // 如果相等,则把对应的Key存入保留存储顺序的lhm中
System.out.println(lhm);
}
// (4)第二种方法,这种比较好一些
public static void countTimes5(String s) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
for (char c : s.toCharArray())
map.put(c, map.containsKey(c) ? map.get(c) + 1 : 1);
// 以上的操作统计出了每个字符出现的次数, 但是没有顺序, 只要将这些结果排序即可
Set<Entry<Character, Integer>> entrySet = map.entrySet(); // 获取出map集合中的所有Entry
TreeSet<Entry<Character, Integer>> treeSet = new TreeSet<Entry<Character, Integer>>(
new EntryComparator()); // 创建TreeSet用来排序
treeSet.addAll(entrySet); // 将entrySet中的键值对都装入treeSet, 排序
System.out.println(treeSet); // 打印结果
}
}
class EntryComparator implements Comparator<Entry<Character, Integer>> {
public int compare(Entry<Character, Integer> o1,
Entry<Character, Integer> o2) {
int gap = o1.getValue() - o2.getValue(); // 值的比较结果
return gap != 0 ? gap : o1.getKey() - o2.getKey(); // 如果值不一样, 按照值排序,值一样的话按照键排序
}
}