JAVA中的集合
JAVA中的集合大致分为两大类
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:Collection类
一类是以键值对的方式存储元素,这一类集合中超级父接口是Map类
集合的继承结构图如下
Map的继承结构图如下
线程安全:某个函数、库函数在多线程环境下被调用时,能够正确处理多个线程之间的共享变量,使程序正确执行。
Colletcion接口中的方法
1.boolean add(Object e)
添加元素,实际上是放了一个对象的内存地址
2.int size()
获取集合中元素的个数
3.void clear()
清空集合
4.boolean contains(Object o)
是否包含o,比较的是内容,是通过String方法的equals方法比较的
5.boolean remove(Object o )
删除集合中的某个元素(通过equals方法来比较)
6.boolean isEmpty()
判断集合是否为空
7.Object [] toArray()
将集合转换为一个数组
Collection工具类方法
1.让list变成线程安全
Collections.synchronizedList(list)
2.排序
Collection.sort(list)
若为自定义类需要实现Comparable接口或者创建list的时候构造参数传入一个Comparator对象
Collection.sort()只能排list,若要排set则需要将set转为list
这里整理了Array、List、Set的互转方法
Array转List
String[] s = new String[]{"A", "B", "C", "D","E"}; List<String> list = Arrays.asList(s);
注意,这里list存的是s中 元素的引用,若改变s,则list中的元素会发生改变
List转Array
String[] dest = list.toArray(new String[0]);//new String[0]是指定返回数组的类型 System.out.println("dest: " + Arrays.toString(dest));
这里dest里引用的并不是list里引用的String对象,换句话说,改变list中的元素并不会影响到dest
List转Set
Set<String> set = new HashSet<>(list); System.out.println("set: " + set);
直接用构造函数转换,简单粗暴
Set转List
List<String> list_1 = new ArrayList<>(set); System.out.println("list_1: " + list_1);
直接用构造函数转换,简单粗暴
迭代集合
第一步调用集合的Iterator方法,获取一个迭代器
Iterator it = 集合.iterator();
第二步通过以上获取的迭代器对象开始迭代/遍历集合
注意:迭代器最初并没有指向第一个元素
迭代器中的方法:
boolean hasNext();判断迭代器的下一位是否有元素
Object next(); 让迭代器前进一位,并且将指向的元素返回
if(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
注意:
集合结构发生改变,迭代器必须重新获取
若使用集合对象的remove方法,需重新获取迭代器;若调用迭代器的remove方法,则不需要,因为迭代器会自动更新
如果没有重新获取,则会报:java.util.ConcurrentModificationException
在迭代集合元素过程中,不能调用集合对象remove方法,删除元素,只能使用迭代器的remove方法。
原因:迭代器在获取的时候会对集合中的元素进行快照,当集合对象调用remove方法删除集合里的元素时,快照并没有进行任何改变,这时候,迭代器在进行迭代时候就会报异常,而使用迭代器删除元素,则会将快照中的元素和集合中的元素都删除
List接口中的方法
1.void add(Object o)
向列表末尾添加元素
2.void add(int index , Object o)
在列表指定位置添加元素
3.Object get(int index)
取出列表中索引处的元素
4.int indexOf(Object o)
返回列表中某个元素第一次出现的索引
5.int lastIndexOf(Object o)
返回列表中某个元素最后一次出现的索引
6.Object remove(int index)
删除指定下标的元素
7.Object set(int index, Object element)
修改某个索引的元素
ArrayList类
底层使用了Object类型的数组
优点
1、根据下标遍历元素效率较高。
2、根据下标访问元素效率较高。
3、在数组的基础上封装了对元素操作的方法。
4、可以自动扩容。
缺点
1、插入和删除的效率比较低。
2、根据内容查找元素的效率较低。
扩容规则:每次扩容现有容量的50%。
源码分析
ArrayList构造函数如下:
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
可以用一个集合来初始化列表
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
可以看到,构造函数new了一个Object数组,作为数据的载体。
初始化容量为10
若调用ArrayList无参的构造函数,首先会创建一个空的Object数组,然后,当第一个元素加进来的时候,会将数组的容量扩展到10。
每次扩容量后的容量为当前容量的1.5倍
ArrayList扩容是通过grow方法,如下所示
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//位运算,左移,相当于除以二。计算机中的左移右移运算速率优于除法和乘法,故而采用位
//运算
int newCapacity = oldCapacity + (oldCapacity >> 1);
//这个minCapatity初始为10,所以当数组加入第一个元素时,数组的容量会直接扩充至十,而
//不是数组当前容量的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE为int的最大值-8,
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
为什么MAX_ARRAY_SIZE为int的最大值-8呢?
数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。
这里再看一下hugeCapacity的源码
private static int hugeCapacity(int minCapacity) {
// 小于零,说明minCapacity超出了int的范围,发生了溢出
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
为什么数组长度不能比int范围大呢?
如果数组长度过大,可能出现的两种错误
OutOfMemoryError: Java heap space 堆区内存不足(这个可以通过设置JVM参数 -Xmx 来指定)。
OutOfMemoryError: Requested array size exceeds VM limit 超过了JVM虚拟机的最大限制,我的window64就是 Integer.MAX_VALUE-1 .
ArrayList是非线程安全的
vector是线程安全的,但是效率较低,不推荐使用。
Collection提供了synachronizedlist方法来将ArrayList转换为线程安全的。
Collections.synchronizedList(list)可以让ArrayList转换为线程安全的
LinkedList类
底层使用双向链表实现,有下标
链表的优点
由于链表上的元素在空间上不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
链表的缺点
每次查找都是从头节点开始遍历,直到找到为止,所以LinkList集合检索/查找的效率较低
源码分析
属性
transient Node<E> first; //指向了第一个节点
transient Node<E> last; //指向了最后一个节点
构造函数
添加元素
//e就是添加到节点中的元素
void linkLast(E e) {
//创建一个节点引用指向最后一个节点
final Node<E> l = last;
//创建一个新的节点,它的前驱节点为当前链表中的最后一个节点,后继节点为null,并把e添加到节点中
final Node<E> newNode = new Node<>(l, e, null);
//链表中最后一个变为newNode,所以要将last指向newNode
last = newNode;
//如果这个链表中没有元素,那么刚开始的时候last为null,而l又等于last,所以l也为null
if (l == null)
//链表中没有节点,现在加进来一个节点,所以需要让first指向newNode
first = newNode;
else
//让链表中的最后一个节点的后继引用指向新添加的节点
l.next = newNode;
size++;
modCount++;
}
未完待续。。。。
Foreach遍历元素
foreach是for的升级版,可以更简便的遍历元素
语法:
for(类型 data: 数组或集合){
System.out.println(data);
}
但是有个缺点是:没有下标,在需要使用下标的循环中,不能使用foreach
HashSet类
无序不可重复(存储时顺序喝取出的顺序不同)
不可重复
源代码:
//构造函数
public HashSet() {
map = new HashMap<>();
}
//添加一个元素
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
可以发现放到HashSet集合中的元素实际上是放到HashMap集合中的key部分了
应用举例:
public class test{
public static void main(String[] args) {
HashSet<String> s = new HashSet<>();
s.add("1");
s.add("2");
s.add("3");
s.add("3");
s.add("3");
s.add("4");
s.add("45");
for(String i:s){
System.out.println(i);
}
}
}
输出结果
TreeSet类
无序不可重复,但是存储的元素可以自动的按照大小顺序排序
public class test{
public static void main(String[] args) {
TreeSet<String> s = new TreeSet<>();
s.add("1");
s.add("2");
s.add("3");
s.add("3");
s.add("3");
for(String i:s){
System.out.println(i);
}
}
}
输出结果
TreeSet类
- TreeSet底层是一个TreeMap
- TreeMap底层是一个二叉树
- 放到TreeSet里的元素,等同于放到TreeMap部分
- TreeSet里的元素:无序不可重复,但是可以按照元素的大小进行自动排序
自定义类的排序
自定义类如果直接放入TreeSet类,会报java.lang.ClassCastException异常,因为TreeSet会将元素按照比较 规则进行排序,
但是自定义的类没有制定这种规则,因此不能直接放入集合。
这里有两种方法可以实现自定义类放入TreeSet类排序
1。自定义类实现java.lang.Comparable接口,并实现CompareTo方法
public class test{
public static void main(String[] args) {
Set<User> s = new TreeSet<User>();
User u1 = new User(1);
User u2 = new User(2);
User u3 = new User(3);
User u4 = new User(4);
User u5 = new User(5);
User u6 = new User(6);
s.add(u1);
s.add(u2);
s.add(u3);
s.add(u4);
s.add(u5);
s.add(u6);
Iterator<User> it = s.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
}
class User implements Comparable<User>{
private int age;
public User(int age){
this.age = age;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
'}';
}
@Override
public int compareTo(User o) {
return age - o.age;
}
}
2.创建TreeSet时候参数传一个Comparator接口的实现类对象
public class test{
public static void main(String[] args) {
Set<User> s = new TreeSet<User>(new UserComparable());
User u1 = new User(1);
User u2 = new User(2);
User u3 = new User(3);
User u4 = new User(4);
User u5 = new User(5);
User u6 = new User(6);
s.add(u1);
s.add(u2);
s.add(u3);
s.add(u4);
s.add(u5);
s.add(u6);
Iterator<User> it = s.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
}
class User{
public int age;
public User(int age){
this.age = age;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
'}';
}
}
class UserComparable implements Comparator<User>{
@Override
public int compare(User o1, User o2) {
return o1.age-o2.age;
}
}
这里可以使用匿名内部类,使程序更简洁
Set<User> s = new TreeSet<User>(new Comparator<User>(){
@Override
public int compare(User o1, User o2) {
return o1.age - o2.age;
}
});