为什么要使用集合
数组和集合都是Java中存储数据的容器
既然数组也可以存储数据,那为什么需要引入集合
来看一下数组的缺点
-
数组的大小一经确定不能改变,不适合动态存储
-
数组中提供的方法有限
而集合可以解决以上数组的缺点
Java内部给我们提供了集合类,能存储任意对象,长度是可以改变的,有众多操作数据的方法
还需要注意的是:
数组可以存储基本数据类型,也可以存储引用类型
集合只能存储引用类型(基本数据类型会自动装箱为引用类型)
集合的分类
(图源:菜鸟教程)
Java 集合框架主要包括两种类型的容器,一种是Collection,存储一个元素集合,另一种是Map
- Collection
- List
- ArrarList
- LinkedList
- Vector
- Set
- HashSet
- LinkedHashSet
- TreeSet
- HashSet
- List
- Map
- HashMap
- LinkedHashMap
- TreeMap
- ConcurrentHashMap
- Hashtable
- HashMap
Collection常用方法
public boolean add(E e) //把给定的对象添加到当前集合中 。
public void clear()//清空集合中所有的元素。
public boolean remove(E e)//把给定的对象在当前集合中删除。
public boolean contains(Object obj)//判断当前集合中是否包含给定的对象。
public boolean isEmpty()//判断当前集合是否为空。
public int size()//返回集合中元素的个数。
public Object[] toArray()//把集合中的元素,存储到数组中
复制代码
List集合
List集合存储的数据是有序,可重复的
ArrayList
- 优点: 底层数据结构是数组,查询快,增删慢。
- 缺点: 线程不安全,效率高
来看一下ArrayList的源码分析(如何动态扩容):
- ArrayList的属性:
可以看出,默认的容量定义DEFAULT_CAPACITY
为10
定义了两个Object[]
空数组EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,当Arraylist
为空时,将空数组赋值给ArrayList(两个数组不同的地方在于一个是默认返回的,一个是用户指定容量为0时返回)
elementData
负责保存添加到ArrayList的元素
size
表示ArrayList的实际大小
- ArrayList的构造方法:
有参的构造方法:初始化容量为0时,返回EMPTY_ELEMENTDATA
无参的构造方法:默认返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- Add方法:动态扩容就是在add中实现的
add方法的执行步骤:
检查是否需要扩容,将元素插入elementData,数组长度也随之增加
Vector
- 优点: 底层数据结构是数组,查询快,增删慢。
- 缺点: 线程安全,效率低
与ArrayList最大的区别就是Vector是同步的
当然如果ArrayList需要实现同步,可以使用Collections中的方法来实现
List list =Collcetions.synchronizedList(new ArrayList());
复制代码
LinkedList
- 优点: 底层数据结构是链表,查询慢,增删快。
- 缺点: 线程不安全,效率高
可以看到,LinkedList是通过双向链表来实现
Set集合
Set集合存储的数据是无序,不可重复的
无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数组的哈希值决定(元素虽然无放入顺序,但是元素在set中的位置是由该元素的HashCode决定的,其位置其实是固定的)
public void test1(){
Set set=new HashSet();
set.add(123);
set.add("AA");
set.add(new People("Jack",20));
Iterator iterator=set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
复制代码
不可重复性:保证添加的元素按照equals()判断时,不能返回true
元素被加到数组中
先根据哈希值找到存储位置
新元素加入数组时,找到新元素的哈希值,通过算法计算出这个哈希值存储再底层数组中的位置,将新元素的哈希值与之前元素的哈希值对比,
哈希值不同,则两元素不同,元素添加成功。哈希值相同,再调用equals()方法比较两元素,返回结果为true则两元素相同
set.add(123);
set.add(123);
复制代码
HashSet
- 线程不安全,效率高,允许存储null元素
HashSet底层:哈希表+红黑树
HashSet 保证元素唯一性是靠元素重写hashCode()和equals()方法来保证的,如果不重写则无法保证。
LinkedHashSet
LinkedHashSet:线程不安全,效率高.
底层数据结构是哈希表+链表
在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
TreeSet
可以按照添加元素的指定属性进行排序
底层数据结构是红黑树
向TreeSet中添加的数据,要求时相同类的对象
两种排序方式:自然排序(实现Comparable接口)
定制排序(Comparator)
遍历Collection
- 迭代器
Collection都继承了Iterator,所有Collection都可以使用iterator来遍历集合
iterator中的主要方法:
-
hasNext():
判断是否还有下一个元素 -
next():
指针下移,将下移以后集合位置的元素返回
Iterator iterator =coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
复制代码
- 增强型for循环
for(集合对象 : 集合)
复制代码
Map集合
Map中的存储的元素是两个对象,一个对象作为键,一个对象作为值。键不可以重复,但是值可以重复
Map中常见方法:
V put(K key, V value) //将key,value放入map中
remove() //删除指定key对象
clear() //清空集合对象
boolean isEmpty() //长度为0返回true否则false
boolean containsKey(Object key) //判断集合中是否包含指定的key
value get(key) //判断键是否存在
复制代码
遍历Map的方式:
1、 将map集合中所有的键取出存入set集合。
Set<K> keySet()返回所有的key对象的Set集合,再通过get方法获取键对应的值。
2、 values()
获取所有的值,但是这样不能获取到key对象
3、 Map.Entry对象
将map集合中的键值映射关系打包成Entry对象
Map.Entry对象通过Map.Entry 对象的getKey,getValue获取其键和值。
复制代码
方式一:
public void test1(){
Map<String,Integer> map=new HashMap<String, Integer>();
map.put("a",20);
map.put("b",19);
map.put("c",18);
//方式一:将map集合转换为set集合,
//keySet() 返回所有的key对象的Set集合
//使用set中的迭代器遍历key
//通过map中的get获取key的value
Set<String> maptoset = map.keySet();
Iterator iterator=maptoset.iterator();
while(iterator.hasNext()){
String key= (String) iterator.next();
Integer value=map.get(key);
System.out.println("key="+key+" " +"value="+value);
}
}
复制代码
方式二:
public void Test2(){
Map<String,Integer> map=new HashMap<String, Integer>();
map.put("a",20);
map.put("b",19);
map.put("c",18);
//方式二:通过values获取所有值
Collection<Integer> value = map.values();
Iterator<Integer> integer=value.iterator();
while(integer.hasNext()){
Integer v = integer.next();
System.out.println(v);
}
}
复制代码
方式三:
public void Test3(){
Map<String,Integer> map=new HashMap<String, Integer>();
map.put("a",20);
map.put("b",19);
map.put("c",18);
//方式三:entrySet
//Map.Entry包含了key和value对象
//需要返回的Map.Entry对象的Set集合
Set<Map.Entry<String, Integer>> es= map.entrySet();
Iterator<Map.Entry<String, Integer>> integer=es.iterator();
while(integer.hasNext()){
Map.Entry<String,Integer> me=integer.next();
String key=me.getKey();
Integer value=me.getValue();
System.out.println("key="+key+" value"+value);
}
}
复制代码
HashMap
:
底层是:数组+链表+红黑树
线程是不同步的,可以存入null键,null值
因为要保证键的唯一性,需要重写hashCode方法和equals方法
HashTable
:
线程是同步的,不可以存入null键,null值
TreeMap
:
底层是:红黑树
TreeMap可以对map集合中的键进行排序
但是需要使用Comparable或者Comparator 进行比较排序
方式一:元素自身具备比较性
需要让存储在键位置的对象实现Comparable接口,重写compareTo方法
方式二:容器具备比较性
定义一个类实现接口Comparator,重写compare方法
List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
Collections工具类
是操作Collection和Map的工具类
常用方法:
sort(Collection)//对集合进行排序
binarySearch(Collection,Object)//查找指定集合中的元素,返回所查找元素的索引
reverse()//反转集合中元素的顺序
swap(List list,int i,int j)//交换集合中指定元素索引的位置
复制代码
举例:链表反转
List coll =new ArrayList();
coll.add(12);
coll.add(10);
coll.add(6);
Collections.reverse(coll);
System.out.println(coll);
复制代码