概念:
集合类存放于 Java.util 包中,
主要有 3 种:set、list和 map
1. Collection 是集合 List、Set、Queue 的最基本的接口。
2. Iterator:迭代器,可以通过迭代器遍历集合中的数据
3. Map:是映射表的基础接口
整个集合框架关系图:
Iterable 接口
实现此允许对象成为for-Each循环的目标, 也就是增强for循环,这是Java中的一种语法糖
,数组同样也可以使用for-each循环遍历;
int[] num = {1,2,3,4};
for(int i :num){
System.out.println(i);
}
Collection父接口
Collection 是一个顶层接口,主要是定义集合的约定,
List
接口List接口对Collection进行了简单的扩充,它的具体实现类常用的有ArrayList
和LinkedList
。
Set
接口也是Collection的一种扩展,而与List不同的时,在Set中的对象元素不能重复,也就是说你不能把同样的东西两次放入同一个Set容器中。它的常用具体实现有HashSet
和TreeSet
类。
Map
是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。
这张图看起来就特别香,至于他们之间的区别,还是用表格比较清楚:
是否包含重复元素 | 是否继承至Collection | 元素是否有序 | |
---|---|---|---|
List | 可以重复 | 是 | 有序 |
Set | 不可重复 | 是 | hashSet元素可以无序,但是元素不可重复;TreeSet的元素可以实现有序 |
Map | key不可重复,value可以重复 | 否 | hashMap是无序的,LinkedHashMap和TreeMap又可以是有序的 |
实现差异
1. ArrayList()跟LinkedList()
ArrayList底层是基于数组实现的,所以数组在查询的时候很方便,但是添加或者删除元素很慢,另外,Vector也是跟ArrayList一样基于数组实现,但是Vector是线程安全的集合.ArrayList不是
LinkedList底层是基于双向链表来实现的,相对于在添加或者删除元素等操作来讲十分方便快捷,但是查询起来比较麻烦
2. HashSet()跟TreeSet()
HashSet 底层用到了hash算法,通过给内存地址取余进行编号的方式,方便元素的查找.但是它不能保证集合的迭代顺序,但是HashSet允许又null元素
TreeSet的元素可以实现有序,但是需要实例类(比如自定义的一个Student类)自己去实现compareable<T>
接口,并且重写compareTo()方法.
2. HashMap()跟TreeMap()
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步
HashMap 是无序的,即不会记录插入的顺序
HashMap 的 key 与 value 类型可以相同也可以不同,可以是字符串(String)类型的 key 和 value,
也可以是整型(Integer)的 key 和字符串(String)类型的, value也可以是整型(Integer)的 key 和字符串(String)类型的 value键无序,并且不能重复, 值可以重复
TreeMap底层是基于红黑树实现的map集合, 这个map根据key自然排序存储,或者通过comparator进行定制排序 ,非线程安全的
通用方法
List<Integer> list = new ArrayList<>();
// 添加元素 一个参数,就在集合的尾部添加元素
list.add(5);
list.add(6);
list.add(7);
// 两个参数 add(index, obj) 表示在指定位置,添加元素,该位置原来的元素依次向后挪动位置
list.add(1, 4);// 5 4 6 7
// 如果指定位置超出了原来集合的长度,就会报数组越界异常
//list.add(5, 4);// IndexOutOfBoundsException
// 获取集合长度
System.out.println(list.size());// 4
// 获取指定的元素的下标
System.out.println(list.indexOf(5)); //0
// 如果指定元素不存在,就会返回-1
System.out.println(list.indexOf(3)); // -1
// 获取指定位置的元素
System.out.println(list.get(3));
// 如果指定下标位置超出集合长度-1,就会报数组越界异常IndexOutOfBoundsException
System.out.println(list.get(3));
// 判断集合是否为空
System.out.println(list.isEmpty());//false
// 判断集合中是否包含某个元素 返回的是一个Boolean值 true/false
System.out.println(list.contains(10));
// 遍历list元素并打印输出
for (Integer integer : list) {
System.out.println(integer);
}
// 清除元素
list.clear();
System.out.println(list.isEmpty());//true
遍历集合的方法
for循环:适用于有下标的集合类型
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(6);
list.add(7);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
for-each: 适用于无下标的集合
Set<Integer> set = new HashSet();
set.add(3);
set.add(5);
set.add(4);
// 因为Set集合元素是无序的,没有.get(index)获取元素的方法,所以for-each比较适合
for (Integer i : set){
System.out.println(i);
}
iterator(): 集合通用,但是不推荐(写起来比较麻烦)
List<String> list1 = new ArrayList<String>();
list1.add("hello");
list1.add("world");
Iterator<String> it = list1.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
面试常见问题
1. Java 中Comparator 和Comparable 有什么不同?
Comparator 接口是用于定义对象的自然排序,是排序接口,而Comparable 通常是用于定义用户定制的顺序,是比较接口,
如果我们需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口)的时候,我们就可以建立一个该类的比较器来实现自定义排序,
Comparable总是只有一个,但是可以有多个comparator比较器来定义类的顺序
2. Java 集合框架的基础接口有哪些?
Collection 为集合层级的根接口,一个集合代表一组对象,
Set 是一个不能包含重复元素的无序集合,
List 是一个有序集合,可以包含重复元素,可以铜鼓它的索引来访问元素,List就像一个长度动态变换的数组
Map 是一个将key映射到value的对象,一个Map不能包含重复的key,每个key最多只能映射到一个value
3. Java 中Collection和Collections有什么区别?
Collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如List ,Set等;
Collections 是一个包装类,它包含了很多静态方法,不能被实例化,是一个工具类,比如提供的排序方法:Collections.sort(list);
4. List, Set, Map 是否继承自Collection接口?
List Set是继承自Collection接口, Map不是,
Map是键值对映射容器,跟List和Set有明显的区别,而Set存储的是零散的元素并且不能重复,List是线性结构的容器,适用于按数值索引访问元素的情形
5. List, Set, Map 之间的区别是什么?
List, Set, Map 的区别主要是体现在两个方面: 元素是否有序, 元素是否允许重复
具体区别如图所示:
6. HashMap和HashTable的区别是什么?
- hashMap允许key和value的值为null,但是hashTable不允许
- hashTable是线程同步的,hashMap不是,所以 hashmap适合单线程,hashtable适合多线程环境
7. 什么时候用HashMap,什么时候用HashTable?
对于在Map中插入,删除,定位一个元素的操作比较多的话, hashMap是最好的选择,因为hashmap的插入会更快,如果要对一个key集合进行有序遍历的操作的话,treeMap会更好;
8. 简单概述一下HashMap和HashSet的实现原理
HashMap
是基于Hash算法实现的, 我们通过 put(key,value)来存储数据,通过get(key)来获取数据,
当我们传入key的时候,hashMap会根据key.hashCode()方法计算出hash值, 根据hash值,将value保存到bucket里面, 当计算出的hash值相同时,我们称之为 hash冲突, 出现hash冲突的时候,HashMap的做法是用链表和红黑树存储相同hash值的value, 如果这个hash冲突的个数比较少的情况下,就使用链表存储value的值,否则使用红黑树来存储.
HashSet
是基于HashMap实现的, HashSet底层使用HashMap来保存所有元素,因此HashSet的实现比较简单,相关的hashSet操作,基本上都是直接调用hashMap的相关方法来完成的,HashSet不允许重复的值.
9. ArrayList和LinkedList的区别是什么?
- 数据结构:
ArrayList
底层是基于动态数组实现的,LinkedList
是基于双向链表实现的- 访问效率:
ArrayList
比LinkedList
在随机访问的时候效率要高,因为LinkedList
是线性的数据存储方式,所以需要移动指针从前往后依次查找,ArrayList
可以通过索引的方式直接访问元素- 增加和删改效率: 在执行增加元素和删改元素的操作时,
LinkedList
要比ArrayList
效率高,因为ArrayList
增删改操作要影响数组内其他数据的下标.- 总的来说,需要频繁读取集合中数据的时候,推荐使用
ArrayList
,在增删改操作比较多时候,推荐使用LinkedList
.
10. Array和LinkedList有什么区别?
Array
可以存储基本数据类型和引用数据类型(对象),ArrayList
只能存储引用数据类型(对象)*Array
是指定固定大小的,ArrayList
的长度是自动扩展的*Array
内置方法没有ArrayList
多,比如addAll()
,removeAll()
等等方法只有ArrayList
才有*
11. 如何编写一个自定义的泛型类(Stack为例)?
- 创建一个IStack
<E>
泛型接口, 规定这个接口中需要被实现的基本方法- 创建一个实现IStack
<E>
接口的实例类MyStack- 测试MyStack类
//test.java
public static void main(String[] args) {
// 创建一个容器对象
IStack<Integer> mysk = new MyStack<>();
//添加三个元素到容器中
mysk.push(5);
mysk.push(6);
mysk.push(7);
// 查看容器是否为空
System.out.println(mysk.isEmpty());// false
// 查看容器是否放满
System.out.println(mysk.isFull()); // false
// 获取容器长度
System.out.println(mysk.size()); // 2
// 取出最后放入的元素
System.out.println(mysk.pop()); // 6
// 取出栈顶元素
System.out.println(mysk.peek()); // 5
// 取出下标为0 的元素
System.out.println(mysk.get(0));//5
// 获取元素55在容器中的下标 不存在返回-1
System.out.println(mysk.getIndexOf(55)); // -1
// 打印容器中所有元素
mysk.display(); //
}
// IStack.java
public interface IStack<E>{
/**
压栈 将传入进来的数据,放到容器数组当中
*/
public void push(E e);
/**
出栈 取出最后放入的数据,容器中的元素-1
*/
public E pop();
/**
取出栈顶元素
*/
public E peek();
/**
获取容器长度
*/
public int size();
/**
判断容器中是否为空 如果为空,返回true
*/
public boolean isEmpty();
/**
判断容器是否已经装满了,如果满了返回true
*/
public boolean isFull();
/**
获取指定下标的元素
*/
public E get(int index);
/**
根据传入的元素,获取该元素在容器中的位置(下标) 如果不存在,返回-1
public int getIndexOf(E e);
/**
打印容器中所有元素
*/
public void display();
}
// MyStack.java
public class MyStack<E> implements IStack<E>{
private Object[] data = null; // 用于存放数据的数组
private int top = -1; // 用于记录栈顶的位置 默认-1就是栈顶
private int maxSize ; // 栈顶最大容量
private static final int DEFAULT_SIZE = 10; // 默认容器的初始大小为10
public MyStack(){
this(DEFAULT_SIZE);
}
public MyStack(int initialSize){
if(initialSize > 0){
this.maxSize = initialSize;
this.data = new Object[initialSize];
}
else{
this.maxSize = DEFAULT_SIZE;
this.data = new Object[DEFAULT_SIZE];
}
}
@Override
public void push(E e){
if(isFull()){
System.out.println("栈空间已满,无法放入");
return;
}
top++;
data[top] = e;
}
@override
public E pop(){
if(isEmpty()){
System.out.println("栈空间为空");
throw new IndexOutBoundsEXception("栈为空");
}
E e = (E)data[top];
top--;
return e;
}
@override
public E peek(){
if(isEmpty()){
System.out.println("栈空间为空");
return null;
}
E e = (E)data[top];
return e;
}
@override
public int size(){
return this.top+1;
}
@override
public boolean isEmpty(){
return this.top == -1;
}
@override
public boolean isFull(){
return this.top >= this.maxSize-1;
}
@override
public E get(int index){
if(index < 0 || index > top){
throw new ArrayIndexOutBoundsEXception();
}
E e = (E) data[index];
return e;
}
@override
public int getIndexOf(E e){
int temp = this.top;
while(temp != -1){
if(get(temp).equals(e)){
return temp;
}
temp--;
}
return -1;
}
@override
public void display(){
int temp = this.top;
while(temp != -1){
System.out.println(data[temp]);
temp--;
}
}
}
>好了就以华丽的分割线结束了,总结的比较浅略,等我理解更深的时候再来补充吧...