程序猿:我想要世界和平,程序猿写的程序永无bug
小爪:大白天的不要做梦,换一个
程序猿:我想要自己技术一流,bug国所有女猿都为我倾倒
小爪:这个愿望还不如上一个呢
程序猿:我想要一种类型,可以存储各种数据,还不用担心容量,还提供丰富的操作方法
小爪:这个可以有,安排...
数组不足的地方:
数据单一
数组的容量不可以修改
数组的方法少
10.1 集合介绍
集合:Java提供用于存放数据(基本类型、引用类型)的,可以动态扩容的工具类。
1)主要类和接口的关系
集合工具类位于java.util包下
2)集合框架API介绍
Collection接口:定义了操作集合的常用方法。
两个常用的子接口:
List接口:存放有序且可重复的元素集合
Set接口: 存放无序且不重复的元素
说明:
何为重复:集合中是否有两个相同对象
何为有序:元素取出的顺序与存入的顺序是否相同,相同表示有序,否则表示无序
Map接口:用于存储 key-value映射关系的键值对 数据的接口。
3)Colletion接口种常用的方法
方法 | |
---|---|
int size() | 返回此collection集合中的元素个数 |
boolean isEmpty() | 判断此collection中是否包含元素 |
boolean contains(Object obj) | 判断此collection是否包含指定的元素 |
boolean containsAll(Collection c) | 判断此collection是否包含指定collection中的所有元素 |
boolean add(Object element) | 向此collection中添加元素 |
boolean addAll(Collection c) | 将指定collection中的所有元素添加到此collection中 |
boolean remove(Object element) | 从此collection中移除指定的元素(第一个匹配的) |
boolean removeAll(Collection c) | 移除此collection中包含在指定collection中的所有元素 |
void clear() | 移除collection中所有的元素 |
Iterator iterator() | 返回此collection元素的迭代器 |
Object[] toArray() | 把此collection转成数组 |
Collection c = new ArrayList();
//添加元素
c.add("guanxi");
c.add("baizhi");
c.add("ajiao");
c.add("ajiao");
Collection c1 = new ArrayList();
c1.add("tingfeng");
c1.add("Tom");
//将一个集合中的元素添加到另外一个集合里
c.addAll(c1);
System.out.println(c);
//删除
c.remove("ajiao");
//从一个集合中删除另外一个集合中元素
c.removeAll(c1);
System.out.println(c);
//获取集合的大小(元素的个数)
int size = c.size();
System.out.println(size);
//判断集合是否为空
System.out.println(c.isEmpty());
//判断是否包含某个元素
System.out.println(c.contains("guanxi"));
//判断一个集合中是否包含另一个集合中的元素
System.out.println(c.containsAll(c1));
//清空集合
c.clear();
System.out.println(c);
10.2 List接口
存放的数据有序,且允许重复
List集合中的元素都对应一个索引(从0开始),可以根据索引存取集合中的单个元素。
List接口常用实现类:ArrayList、LinkedList
10.2.1 List接口常用方法
除了拥有Collection接口的方法外,List额外扩充了一些常用方法:
方法 | |
---|---|
public Object get(int index) | 返回列表中指定位置的元素 |
public void add(int index, Object element) | 在列表的指定位置插入指定元素,其他元素依次后移 |
public Object set(int index, Object element) | 用指定元素替换列表中指定位置的元素 |
List subList(int fromIndex, int toIndex) | 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分列表 |
int indexOf(Object o) | 返回此列表中第一次出现指定元素的索引;如果此列表不包含该元素,则返回 -1。 |
10.2.2 ArrayList
ArrayList是List接口的实现类,底层使用数组结构实现
优点:读取,遍历元素效率较高(随机访问)使用索引快速定位对象
缺点:元素的删除、插入速度较慢。因为使用数组结构,当删除、插入新元素时,需要移动已有元素以调整索引顺序
List list = new ArrayList();
//添加元素
list.add("guanxi");
list.add(123);
System.out.println(list);
for(Object item : list) {
// 异常,123不能强转为字符串
System.out.println(((String)item).length());
}
List接口可以容纳Object类型的数据,方便存放各种类型的数据。但是,当我们放入一些很明确的数据类型,如String、Student、News时,会自动向上提升为Object类型,获取时需要强转(存在安全隐患)
为此,我可以可以使用泛型解决这个问题
List<String> list = new ArrayList();
list.add("world");
//在指定索引处插入元素
list.add(0, "hello");
List list1 = new ArrayList();
list1.add("Tom");
list1.add("Jerry");
list1.add("Jerry");
//在指定索引处插入集合
list.addAll(1, list1);
System.out.println(list);
//返回值表示删除的对象
Object obj = list.remove(1);
System.out.println(obj);
System.out.println(list);
//获取指定索引处元素,如果索引超出范围,运行异常
Object str = list.get(0);
System.out.println(str);
//获取指定元素的索引,如果查找的元素不存在,返回-1
int index = list.indexOf("Jerr");
System.out.println(index);
//获取元素在集合中最后出现的索引
int lastIndex = list.lastIndexOf("Jerry");
System.out.println(lastIndex);
//设置指定索引处的元素
list.set(2, "Dog");
System.out.println(list);
//获取某个集合的子集,不包括toIndex索引处的元素
List subList = list.subList(1, 3);
System.out.println(subList);
10.2.3 Iterator接口
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个Iterator接口类型的实例对象。
Iterator接口的操作原理:Iterator是专门的集合迭代输出接口。所谓的迭代输出就是依次判断集合中有没有下一个元素,有的话输出,直到集合的末尾
Iterator对象称作迭代器,实现对集合内元素的遍历操作。
Iterator接口中定义了如下方法:
方法 | |
---|---|
boolean hasNext() | 判断游标右边是否有元素 |
Object next() | 返回游标右边的元素并将游标移动到下一个位置 |
void remove() | 删除游标左边的集合中的元素,要在next()之后使用,删除后集合将受到影响 |
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("tom");
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
//list.remove(i);
}
//直接遍历集合中的元素,所以针对有序或者无序的集合都可以遍历
//在foreach中,不能删除集合中元素,运行会报错
for (String str : list) {
System.out.println(str);
//list.remove(str);
}
//迭代器
//Iterable接口,Collection接口是它的子接口
//Iterable接口中提供了iterator()方法,用于获取集合的比较器
//先得到集合的迭代器
Iterator<String> ite = list.iterator();
//判断下个元素是否存在
while(ite.hasNext()){
//获得下个元素
String str = ite.next();
System.out.println(str);
//通过迭代器,可以安全的删除元素
//使用remove前,必须先next
ite.remove();
}
System.out.println(list);
//遍历完了,再次next,运行报错
//ite.next();
10.2.4 LinkedList
LinkedList是List接口的另一个常用实现类,底层使用双向链表实现
优点:对元素频繁的删除、插入效率较高
LinkedList除了实现List接口外,还实现了Queue接口,并提供了对列表开头、结尾操作的方法,使得LinkedList具有栈、队列的双重特点
1)常用方法
方法 | |
---|---|
public void addFirst(Object e) | 相当于add(0, e) |
public void addLast(Object e) | 相当于add(e) |
public Object getFirst() | 相当于get(0) |
public Object getLast() | 相当于get(al.size()-1) |
public Object removeFirst() | 相当于remove(0) |
public Object removeLast() | 相当于remove(al.size()-1) |
LinkedList<String> ll = new LinkedList();
ll.add("Hello");
ll.add("wangcai");
ll.add("fugui");
//在首位置添加元素
ll.addFirst("World");
//在最后的位置添加元素
ll.addLast("xiaoqiang");
//删除首位置元素
ll.removeFirst();
//删除最后位置的元素
ll.removeLast();
//获取首元素
System.out.println(ll.getFirst());
//获取最后的元素
System.out.println(ll.getLast());
2)栈
后进先出结构,LastInFirstOut,LIFO。比如:弹夹
栈结构限制了插入和删除的位置,只能在一端进行
插入也叫进栈或压栈
删除也叫出栈或弹栈
方法:
push() 压栈
pop() 出栈
LinkedList stack = new LinkedList();
//入栈,实现效果上,是将元素插入集合的首位置
stack.push("aaa");
stack.push("bbb");
stack.push("ccc");
//出栈,实现效果上,删除集合的首位置的元素
System.out.println(stack.pop());
System.out.println(stack.pop());
3)队列
后进后出,LastInLastOut,LILO。比如: 客服电话排队等待、银行排号
限制了插入和删除的位置,只能在一端进行插入,在另一端进行删除
相关方法:
offer() 入队
poll() 出队
peek() 获取头元素,但是不删除
LinkedList queue = new LinkedList();
//入队
queue.offer("ddd");
queue.offer("eee");
queue.offer("fff");
//出队,实现效果上,删除首元素
System.out.println(queue.poll());
//获取首元素,但是不会从集合中删除元素
System.out.println(queue.peek());
10.3 Set接口
Set接口存放的数据是无序的(相对于添加顺序),不可以重复的。
Set接口常用实现类:
HashSet
LinkedHashSet
TreeSet
10.3.1 HashSet
HashSet采用哈希(散列)数据结构来存储对象
HashSet不记录添加数据的顺序,且加入的元素不能重复,否则覆盖。
1)基本用法
Set<String> set = new HashSet<String>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("ccc");
System.out.println(set);
2)如何判断Set集合中已经存在该对象
先计算对象的哈希值 (调用hashCode()),如果该哈希值不存在,直接加入Set集合;
如果已经存在,则要加入的新对象跟已经存在于列表中的所有对象依次比较(调用equals()),不相等则加入,否则舍弃。
注意:加入HashSet集合的自定义类型的对象通常需要重写hashCode()和equals()方法。
Person p1 = new Person("wangcai", 10);
Person p2 = new Person("fugui", 5);
Set<Person> set = new HashSet<Person>();
set.add(p1);
set.add(p2);
Person p3 = p2;
set.add(p3);
Person p4 = new Person("fugui", 5);
//先判断hashCode是否存在,
//如果存在,继续通过equals判断对象内容是否相同
set.add(p4);
System.out.println(set);
public class Person {
String name;
int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name + " " + this.age;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return this.name.hashCode() + this.age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
10.3.2 LinkedHashSet(了解)
LinedHashSet根据元素的哈希值进行存放,同时用链表记录元素的插入顺序。
LinkedHashSet<String> ll = new LinkedHashSet<>();
ll.add("aaa");
ll.add("bbb");
ll.add("ccc");
ll.add("ccc");
System.out.println(ll);
10.3.3 TreeSet (了解)
TreeSet同样不记录添加数据的顺序,但是可以对存入的元素进行排序(基于红黑树实现),输出也会按照排序后的顺序。
因此,存入TreeSet的元素,必须是“可排序的”
TreeSet<Integer> tree = new TreeSet<>();
tree.add(56);
tree.add(91);
tree.add(89);
tree.add(95);
tree.add(45);
tree.add(23);
System.out.println(tree);//[23, 45, 56, 89, 91, 95]
10.4 Map接口
Map接口用来存储“键-值”映射对。
JDK API中Map接口的一些实现类:HashMap、TreeMap、Hashtable
Map中存储的“键-值”映射对是通过键来唯一标识,Map的“键”底层采用Set来存放,因此不保证顺序
Map中允许使用null值和null键
因此,根据“键”取“值”的效率较高,也是Map接口使用频率较高的一个实现类。
常用String作为Map的"键"。
10.3.1 Map接口方法
方法 | |
---|---|
put(K key, V value) | 将指定的"键-值"对存入Map中 |
get(Object key) | 返回指定键所映射的值 |
remove(Object key) | 根据指定的键把"键-值"对从Map中移除 |
boolean containsKey(Object key) | 判断Map是否包含指定的键 |
boolean containsValue(Object value) | 判断此Map是否包含指定的值 |
boolean isEmpty() | 判断此Map中是否有元素 |
int size() | 获得此Map中"键-值"对的数量 |
void clear() | 清空Map中的所有"键-值"对 |
Set<K> keySet() | 返回此Map中包含的键的Set集合 |
Collection<V> values() | 返回Map中包含值的Collection集合 |
Map<String, Integer> map = new HashMap<>();
//向map集合中添加元素
//hashmap中,根据key算hash值
//如果key不存在,添加键值对,如果存在,会修改对应的value
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
map.put("four", 4);
map.put("four", 14);
//根据key删除元素
map.remove("one");
System.out.println(map.isEmpty());
System.out.println(map.containsKey("four"));
System.out.println(map.containsValue(20));
//根据key查找value,如果key不存在,返回null
Integer v = map.get("fourwer");
System.out.println(v);
//返回所有key的set集合
Set<String> keys = map.keySet();
System.out.println(keys);
//获取所有的value的collection集合
Collection<Integer> values = map.values();
System.out.println(values);
//Entry 描述一对儿数据(key和value)
Set<Entry<String, Integer>> entrys = map.entrySet();
System.out.println(entrys);
//清空
//map.clear();
System.out.println(map);
10.3.2 HashMap遍历
遍历key
map.keySet()
map.entrySet()
Map.Entry是Map内部定义的一个static接口,专门用来保存key-value对的内容。
可以通过entrySet()方法取到Entry对象,然后使用getKey(),getValue()获取键、值
一般情况下,map多用于存放、查找数据,遍历输出的情况较少。
HashMap<String, String> map = new HashMap<>();
map.put("LOL", "英雄联盟");
map.put("CF", "穿越火线");
map.put("Dota", "星际联盟");
map.put("LLK", "练练看");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key + ":" + map.get(key));
}
Iterator<String> iterator = keySet.iterator();
while(iterator.hasNext()){
String key = iterator.next();
System.out.println(key + " " + map.get(key));
}
Set<Entry<String, String>> entrySet = map.entrySet();
Iterator<Entry<String, String>> iterator2 = entrySet.iterator();
while(iterator2.hasNext()){
Entry<String, String> next = iterator2.next();
//Entry中通过getKey和getValue方法得到key和value
System.out.println(next.getKey() + " " + next.getValue());
}
//省 市的关系
HashMap<String, List<String>> provinces = new HashMap<>();
List<String> henanCity = new ArrayList<String>();
henanCity.add("郑州市");
henanCity.add("开封市");
henanCity.add("洛阳市");
provinces.put("河南省", henanCity);
List<String> shanxiCity = new ArrayList<>();
shanxiCity.add("太原市");
shanxiCity.add("大同市");
provinces.put("山西省", shanxiCity);
System.out.println(provinces);
Set<String> keySet = provinces.keySet();
for (String p : keySet) {
System.out.println(p);
List<String> clist = provinces.get(p);
for(String c : clist){
System.out.println(c);
}
}
HashMap可能的面试题:
HashMap的底层数据结构是什么?
HashMap如何扩容?
HashMap不是线程安全的,有哪些线程安全的map结构?
等等
10.3.3 TreeMap(了解)
TreeMap内部使用红黑树结构对“key”进行排序存放,所以放入TreeMap中的“key-value”对的“key”必须是“可排序”的。
//根据key值排序
TreeMap<Integer, String> tmap = new TreeMap<>();
tmap.put(2, "1003");
tmap.put(45, "1005");
tmap.put(23, "1023");
tmap.put(90, "1503");
System.out.println(tmap);
10.5 选择依据
10.5.1 存放要求
无序,无下标,不可重复,不能随机访问-Set
有序,有下标,可重复,可以随机访问-List
“key-value”对,较多存放,较少遍历-Map
10.5.2 读和改(插入删除)的效率
Hash*-两者都较高
ArrayList-读(指定下标随机访问)快,插入/删除元素慢
LinkedList-读(指定下标随机访问)慢,插入/删除元素快
10.6 Collections工具类
Java提供的操作集合的工具类,位于java.util包下,类中的方法均为static
10.6.1 常用方法
方法 | |
---|---|
void sort(List list) | 按照自然顺序进行排序。List列表中的所有元素都必须实现 Comparable 接口 |
void shuffle(List list) | 对List列表内的元素进行随机排列 |
void reverse(List list) | 对List列表内的元素进行反转 |
int binarySearch(List list, Object obj) | 在指定列表中查找指定对象,需要先对列表进行sort |
ArrayList<String> arr = new ArrayList<>();
arr.add("qreqwe");
arr.add("asdwew");
arr.add("erety");
//排序,升序
Collections.sort(arr);
System.out.println(arr);
//二分查找,前提:集合有序(升序)
int index = Collections.binarySearch(arr, "qreqwe");
System.out.println(index);
Collections.sort(arr, new StringComparator());
System.out.println(arr);
int index1 = Collections.binarySearch(arr, "erety");
System.out.println(index1);
//匿名内部类
Collections.sort(arr, new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
// TODO Auto-generated method stub
return o2.compareTo(o1);
}
});
System.out.println(arr);
//得到集合中的最大元素
String max = Collections.max(arr);
System.out.println(max);
//将list中的指定元素替换为新的元素
Collections.replaceAll(arr, "erety", "hello");
System.out.println(arr);
//反转集合中的元素
Collections.reverse(arr);
System.out.println(arr);
//混排,按照随机顺序存放元素
Collections.shuffle(arr);
System.out.println(arr);
10.6.2 Comparable接口
上述示例中,如果对List<Student>进行sort,会出现什么问题?
对list中的元素进行排序,这些元素必须是“可排序的”,即:实现Comparable接口
实现该接口,需要重写compareTo方法,即自定义一个比较规则:让list知道“12岁的叫aa的学生和10岁的叫bb的学生到底谁大?”
public class Person implements Comparable<Person> {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return this.age - o.getAge();
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}
Person p = new Person(12, "hah");
Person p1 = new Person(14, "h4h");
Person p2 = new Person(6, "ha4h");
Person p3 = new Person(8, "ha55h");
List<Person> list = new ArrayList<Person>();
list.add(p);
list.add(p1);
list.add(p2);
list.add(p3);
Collections.sort(list);
System.out.println(list);
10.6.3 Comparator接口
使用Comparable接口定义排序顺序有局限性:实现此接口的类只能按compareTo(T t)定义的这种方式排序。
如果一个类的对象要有多种排序方式,可以为该类定义不同的比较器(自定义不同的类,实现Comparator接口)。
public class PersonComparator implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
// TODO Auto-generated method stub
return o1.getAge() - o2.getAge();
}
}
//实现字符串降序排列的比较器
public class StringComparator implements Comparator<String>{
@Override
public int compare(String o1, String o2) {
// TODO Auto-generated method stub
return o2.compareTo(o1);
}
}
附录
1 泛型(了解)
List接口可以容纳Object类型的数据,方便存放各种类型的数据。但是,当我们放入一些很明确的数据类型,如String、Student、News时,会自动向上提升为Object类型,获取时需要强转(存在安全隐患)
如果没有类型限制,List接口中可以同时存放Student、Dog等不同类型,获取数据极为不便。
泛型的本质
参数化类型,即用附属参数的形式,达到修饰限定类型的目的。
如,JDK中已有的一些声明:
Collection<E>
List<E>
Set<E>
Map<K,V>
可以在接口、类、方法上声明泛型约束,用<>表示,泛型名称一般习惯用一个大写字母表示,如E、K、V。
泛型只是一种类型约束、限定,在声明时,这些大写字母没有实际意义,也不代表任何类型。在使用时,会根据创建传入的实际类型,而进行擦除替换。
泛型类语法
[访问修饰符] class 类名<类型形参> {
[修饰符] 返回类型 方法名(类型形参 类型形参实例);
}
比如我们创建通过x、y坐标表示的Point类
public class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
Point<Integer> p = new Point<Integer>();
p.setX(10);
Integer x = p.getX();
System.out.println(x);
泛型方法语法
[访问修饰符] <类型形参> 返回值 方法名(不指定|类型形参|正常参数, ……) {
}
比如我们打印不同引用类型的数组
public static <E> void printArray(E[] arr) {
for (E element : arr){
System.out.println(element);
}
}
Integer[] intArr = {1, 2, 3};
String[] strArr = {"Hello", "World"};
printArray(intArr);
printArray(strArr);
泛型接口语法
[访问修饰符] interface 接口名<类型形参> {
[修饰符] 返回类型 方法名(类型形参 类型形参实例);
}
public interface Operation<T> {
public void add(T t);
}
public class OperationImpl implements Operation<Student> {
@Override
public void add(Student t) {
// TODO Auto-generated method stub
System.out.println("add:" + t);
}
}
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
Operation<Student> ss = new OperationImpl();
Student s = new Student("zhangsan", 20);
ss.add(s);
2 Arrays工具类
常用方法
int[] arr = {2, 34, 12, 56, 1};
//排序,升序
Arrays.sort(arr);
//数组转换为string
System.out.println(Arrays.toString(arr));
int[] arr1 = {2, 34, 12, 56, 1};
//比较两个数组内容是否一样
System.out.println(Arrays.equals(arr, arr1));
//数组的复制
int[] arr2 = Arrays.copyOf(arr1, 10);
System.out.println(Arrays.toString(arr2));
String[] strs = {"hello", "world"};
//数组转换为list集合
List<String> l = Arrays.asList(strs);
System.out.println(l);
Object[] objs = l.toArray();
数组与集合的转换
数组—>集合:
Arrays类的方法:asList(),注意数组元素必须是引用数据类型
Integer[] arr = {34, 465, 78};
List list = Arrays.asList(arr);
集合—>数组:
List接口的方法:toArray()
Integer[] temp = (Integer[])list.toArray();
3 Properties
Properties 继承于 Hashtable。可以理解为一个属性集,属性列表以key-value的形式存在,key和value都是字符串。
常用方法:
方法 | |
---|---|
getProperty(String key) | 根据指定的key值查询对应的value值 |
setProperty(String key, String value) | 设置属性的key-value |
load(InputStream inStream) | 从输入字节流中读取属性文件(properties后缀的文件),并加载其中的数据 |
Properties properties = new Properties();
// 设置key-value属性
properties.setProperty("name","zhangsan");
properties.setProperty("password","123");
// 根据key值获取属性
String value = properties.getProperty("name");
System.out.println(value);