为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用数组存储对象方面具有一些弊端,而Java集合就像一种容器,可以动态地把多个对象的引用放入容器中。
数组在存储内存方面的特点:
数组初始化以后,长度就确定了数组声明的类型,就决定了进行元素初始化时的类型数组在存储数据方面的弊端:
数组出刷以后,长度就不可变了,不便于添加数组中提供 属性和方法少,不便于进行添加、删除、插入等操作,且效率不高数组存储的数据是有序的、可以重复的。存储的数据特点单一。java集合类可以用于存储数据不等的多个对象,还可以保存具有映射关系的关联数组。
Collection即单列集合。统一定义了一套单列集合的接口。
boolean add(E e);将元素e添加到集合中(E类型为泛型)
int size();int size();获取集合中的元素个数
boolean addAll(Collection<? extends E> c);boolean addAll(Collection coll);添加集合coll中的元素到调用此方法的集合中
boolean isEmpty();boolean isEmpty();判断集合是否为空
void clear();void clear();清空集合
boolean contains(Object o);boolean contains(Object o);判断当前集合中是否包含o
boolean containsAll(Collection<?> c);boolean containsAll(Collection<?> c); 判断集合c中的所有元素是否都在调用此方法的集合中
boolean remove(Object o);boolean remove(Object o);移除集合中的元素o
boolean removeAll(Collection<?> c);boolean removeAll(Collection<?> c); 差集:从当前集合中移除集合c中的所有元素
boolean retainAll(Collection<?> c);boolean retainAll(Collection<?> c);交集:查询此集合中coll中也包含的元素
Object[] toArray();Object[] toArray();将此集合转换为Object类型的数组
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露改对象的内部细节。迭代器模式,就是为容器而生。
常用的遍历集合方法
public static void main(String[] args) { Collection coll = new ArrayList(); coll.add(321); coll.add(12); coll.add("java"); coll.add("abc"); coll.add(new Date()); coll.add(new Person("张三",21)); Iterator iterator = coll.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } }运行结果
迭代器Iterator执行原理JDK7 版本下: 当直接使用空参构造器创建对象
List list = new ArrayList();底层创建了长度是10的 Object[] 数组 elementData。
扩容问题 当添加数据导致底层数组的容量不足时,则要对底层数组进行扩容。 默认情况下扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中。如果扩容为1.5倍之后容量仍然不足,将使用数据长度为新的容量长度。
JDK8 版本下: 使用空参构造器直接创建对象,并不会立即创建长度为10的数组。size为0。 而是在调用add方法添加数据时开始创建数组。 DEFAULT_CAPACITY 为 10。 底层扩容机制
内部声明了Node类型的 first 和 last 属性,默认值为null
list.add(123);将123封装到Node中,创建了Node对象。
Vector的源码:在jdk7和jdk8中,通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
由于List是有序的集合,于是在List接口中提供了不少的基于角标的方法。
void add(int index, E element); boolean addAll(int index, Collection<? extends E> c); E get(int index); int indexOf(Object o); int lastIndexOf(Object o); E remove(int index); E set(int index, E element); List<E> subList(int fromIndex, int toIndex);set中没有定义新的方法,使用的都是Collection中的方法。
无序性不代表随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数组的哈希值决定的不可重复性:保证添加的元素按照equals()判断时,不能返回true,即:相同的元素只能添加一个添加元素的过程:以HashSet为例:
外面向HashSet中添加元素a,首先用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值。
如果hash值不同,则元素a添加成功。(情况2)
如果hash值相同,进而需要调用元素a所在类的equals()方法,equals返回true,元素a添加失败,返回false,则元素a添加成功。(情况3)
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
不同JDK版本中链表存储数据的方式: jdk 7 :元素a放在数组中,指向原来的元素
jdk 8:原来的元素在数组中,指向元素a
HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个类。
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet具有以下特点:
不能保证元素的排列顺序HashSet不是线程安全的集合元素可以是nullHashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals和hashCode方法,以实现对象相等规则。
LinkedHashSet 作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals();
定制排序中,比较两个对象是否相同的标准为:compare()还会0,不再是equals()
Map接口用于存储双列数据,以键值对(key-value)的形式存储。
HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key和value LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的频繁的遍历操作,此类执行效率高于HashMap。 TreeMap:保证按照添加的key-value进行排序,实现排序遍历。(按照key排序),底层使用红黑树。Hashtable:作为古老的实现类:线程安全,效率低 Properties:常用来处理配置文件。key和value都是String类型将指定key-value添加(修改)到当前Map对象中
public V put(K key, V value)将集合m中的所有key-value添加到当前Map对象中
public void putAll(Map<? extends K, ? extends V> m)将指定key的key-value从当前Map对象中删除
public V remove(Object key)清空当前Map对象
public void clear()获取指定key对应的value
public V get(Object key)查询是否包含指定key
public boolean containsKey(Object key)查询是否包含指定value
public boolean containsValue(Object value)查询当前Map对象中key-value的个数
public int size()判断当前Map对象是否为空
public boolean isEmpty()将当前Map对象中的所有key存放到Set集合中返回
public Set<K> keySet()将当前Map对象中所有value存放到Collection 集合中返回
public Collection<V> values()将当前Map对象中的所有key-value存放到Set集合中返回
public Set<Map.Entry<K,V>> entrySet()在JDK 8中
使用空参构造器创建对象。
HashMap<Object, Object> map = new HashMap<>();底层并没有立即创建数组。loadFactor为加载因子,用于底层数组扩容,DEFAULT_LOAD_FACTOR为默认加载因子,值为0.75。 添加键值对操作。 哈希值计算 添加键值对的实现 可以看到,在底层中,键值对是存放在Node对象里,底层里Node数组实现。
map.put(key1,value1);添加键值对。
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Node数组中的位置。如果此位置上数据为空,此时的key1 - value1添加成功。 — 情况1
如果此位置上的数据不为空,(意味着此位置上存放在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不同,此时key1 - value1 添加成功。 — 情况2
如果key1的哈希值和已经存在的某一个数据(key2 - value2)的哈希值相同,继续比较:调用key1所在类的equals方法,比较方式:
如果equals()返回false:此时key1 - value1添加成功 — 情况3
如果equals()返回true:使用value1替换value2.
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空时)扩容,默认的扩容方式:扩容为原来的2倍,并将原有的数据复制过来。
在JDK 7 中底层结构使用数组+链表,当出现哈希冲突问题时,相同哈希值的数据以链表的形式存储,并且使用的是头插的方式,新添加的数据指向数组中已存储的数据。在JDK 8中底层结构使用数组+链表+红黑树,当出现哈希冲突问题时,一开始使用链表形式存储,但是与JDK7不同的是,使用的是尾插的方式,新添加的数据存储到链表的尾部,原本的数据仍然存储在数组中。当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时索引位置上的所有数据改为使用红黑树存储。LinkedHashMap 是HashMap的子类。继承了HashMap,在HashMap的基础上添加了两个指针,保证在遍历map元素时,可以按照添加的顺序实现遍历。便于频繁的遍历操作。
底层中创建Node对象,但创建的是一个Entry对象 此Entry继承了Node,在Node基础上添加了两个指针。
Collections是操作Collection、Map的工具类
Collections 工具类相关API
使用自然排序,对list集合进行排序
public static <T extends Comparable<? super T>> void sort(List<T> list)使用定制排序,对list集合进行排序
public static <T> void sort(List<T> list, Comparator<? super T> c)将list集合中的元素反转
public static void reverse(List<?> list)将list集合中的元素随机排序
public static void shuffle(List<?> list)将list集合中角标为i和角标为j的元素对调
public static void swap(List<?> list, int i, int j)根据自然排序返回给定集合中最大的元素
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)根据定制排序返回给定集合中最大的元素
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)根据自然排序返回给定集合中最小的元素
public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)根据定制排序返回给定集合中最小的元素
public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)将集合src中的元素复制到dest集合中
public static <T> void copy(List<? super T> dest, List<? extends T> src)使用 newVal 替换 lsit集合中的所有 oldVal
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)返回对象o在指定集合中出现的次数
public static int frequency(Collection<?> c, Object o)