Skip to content

程序猿:我想要世界和平,程序猿写的程序永无bug

小爪:大白天的不要做梦,换一个

程序猿:我想要自己技术一流,bug国所有女猿都为我倾倒

小爪:这个愿望还不如上一个呢

程序猿:我想要一种类型,可以存储各种数据,还不用担心容量,还提供丰富的操作方法

小爪:这个可以有,安排...

数组不足的地方:

数据单一

数组的容量不可以修改

数组的方法少

10.1 集合介绍

集合:Java提供用于存放数据(基本类型、引用类型)的,可以动态扩容的工具类。

1)主要类和接口的关系

集合工具类位于java.util包下

img

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转成数组
java
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

img

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接口的实现类,底层使用数组结构实现

优点:读取,遍历元素效率较高(随机访问)使用索引快速定位对象

缺点:元素的删除、插入速度较慢。因为使用数组结构,当删除、插入新元素时,需要移动已有元素以调整索引顺序

img

java
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类型,获取时需要强转(存在安全隐患)

为此,我可以可以使用泛型解决这个问题

java
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()之后使用,删除后集合将受到影响

img

java
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接口的另一个常用实现类,底层使用双向链表实现

优点:对元素频繁的删除、插入效率较高

img

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)
java
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() 出栈

java
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() 获取头元素,但是不删除

java
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不记录添加数据的顺序,且加入的元素不能重复,否则覆盖。

img

1)基本用法

java
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()方法。

java
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根据元素的哈希值进行存放,同时用链表记录元素的插入顺序。

java
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的元素,必须是“可排序的”

java
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集合
java
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多用于存放、查找数据,遍历输出的情况较少。

java
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());
}
java
//省 市的关系
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结构?

等等

img

10.3.3 TreeMap(了解)

TreeMap内部使用红黑树结构对“key”进行排序存放,所以放入TreeMap中的“key-value”对的“key”必须是“可排序”的。

java
//根据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
java
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的学生到底谁大?”

java
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接口)。

java
public class PersonComparator implements Comparator<Person>{

	@Override
	public int compare(Person o1, Person o2) {
		// TODO Auto-generated method stub
		return o1.getAge() - o2.getAge();
	}
	
}
java
//实现字符串降序排列的比较器
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。

泛型只是一种类型约束、限定,在声明时,这些大写字母没有实际意义,也不代表任何类型。在使用时,会根据创建传入的实际类型,而进行擦除替换。

泛型类语法

java
[访问修饰符] class 类名<类型形参> {
    [修饰符] 返回类型 方法名(类型形参 类型形参实例);
}

比如我们创建通过x、y坐标表示的Point类

java
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);

泛型方法语法

java
[访问修饰符] <类型形参> 返回值 方法名(不指定|类型形参|正常参数, ……) {
}

比如我们打印不同引用类型的数组

java
 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);

泛型接口语法

java
[访问修饰符] interface 接口名<类型形参> {
    [修饰符] 返回类型 方法名(类型形参 类型形参实例);
}
java
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工具类

常用方法

java
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后缀的文件),并加载其中的数据
java
Properties properties = new Properties();
// 设置key-value属性
properties.setProperty("name","zhangsan");
properties.setProperty("password","123");
// 根据key值获取属性
String value = properties.getProperty("name");
System.out.println(value);