简述
∽ 集合类是用来存放某类对象的。集合类有一个共同特点,就是它们只容纳对象(实际上是对象名,即指向地址的指针)。这一点和数组不同,数组可以容纳对象和简单数据。如果在集合类中既想使用简单数据类型,又想利用集合类的灵活性,就可以把简单数据类型数据变成该数据类型类的对象,然后放入集合中处理,但这样执行效率会降低。
∽ 集合类容纳的对象都是Object类的实例,一旦把一个对象置入集合类中,它的类信息将丢失,也就是说,集合类中容纳的都是指向Object类对象的指针。这样的设计是为了使集合类具有通用性,因为Object类是所有类的祖先,所以可以在这些集合中存放任何类而不受限制。当然这也带来了不便,这令使用集合成员之前必须对它重新造型。
∽ 集合类是Java数据结构的实现。在编写程序时,经常需要和各种数据打交道,为了处理这些数据而选用数据结构对于程序的运行效率是非常重要的。
--------百度百科
背景
∽ 在没有集合类之前,实际上在Java语言里已经有一种方法可以存储对象,那就是数组。数组不仅可以存放基本数据类型也可以容纳属于同一种类型的对象。数组的操作是高效率的,但也有缺点。比如数组的长度是不可以变的,数组只能存放同一种类型的对象(或者说对象的引用)。
∽ 另外,在程序设计过程中,程序员肯定会经常构建一些特殊的数据结构以正确的描述或者表达现实情况。比如描述火车进站出站,他们会用到“栈”这个数据结构,常用的数据结构还有:队列、链接表、树和散列表等等。这些数据结构几乎在每一段程序设计过程中都会使用到,但是如果每次编程都要重新构建这些数据结构显然违背了软件组件化的思想。因此Java的设计者考虑把这些通用的数据结构做成API供程序员调用。
∽ 基于以上几点必须解决的问题。Java提供了对象的数种保存方式,除了内置的数组以外,其余的称为集合类。为了使程序方便地存储和操纵数目不固定的一组数据,JDK中提供了Java集合类,所有Java集合类都位于Java.util包中,与Java数组不同,Java集合不能存放基本数据类型数据,而只能存放对象的引用。集合就是为了替代数组。
--------百度百科
分类
Collection接口:单列数据,定义了存取一组对象的方法的集合
∽ List:元素有序、可重复的集合
∽ Set:元素无序、不可重复的集合
Map接口:双列数据,保存具有映射关系“key-value对”的集合
Collection
继承树
Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
java中不提供该接口的直接实现,而提供他的子接口,由子接口去实现。
常用方法:
1、添加
∽ add(Object obj)
∽ addAll(Collection coll)
2、获取有效元素的个数
∽ int size()
3、清空集合
∽ void clear()
4、是否是空集合
∽ boolean isEmpty()
5、是否包含某个元素
∽ boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
∽ boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
∽ boolean remove(Object obj) :通过元素的equals方法判断是否是
要删除的那个元素。只会删除找到的第一个元素
∽ boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
∽ boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
∽ boolean equals(Object obj)
9、转成对象数组
∽ Object[] toArray()
10、获取集合对象的哈希值
∽ hashCode()
11、遍历
∽ iterator():返回迭代器对象,用于集合遍历
public class CollectionTest {
/**集合框架
* |----Collection接口:单列集合,用来存储一个一个的对象
* |----List接口:存储有序的、可重复的数据。 -->“动态”数组
* |----ArrayList、LinkedList、Vector
*
* |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
* |----HashSet、LinkedHashSet、TreeSet
*
* |----Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y = f(x)-->一对一
* |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
*
*
*/
public static void main(String[] args) {
Collection coll =new ArrayList(); //左边声名,右边实现
//add方法(object e);
coll.add("A");
coll.add(123);//自动装箱
coll.add(new Date());
//size();长度
System.out.println(coll.size());//3
//addAll(Collection coll1);将集合coll1中的元素添加到coll中,集合的相互添加
Collection coll1 =new ArrayList();
coll1.add(456);
coll1.add("C");
coll.addAll(coll1);
System.out.println(coll.size());//5
System.out.println(coll);
//clear():清空集合元素
// coll.clear();
//isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());
//contains(Object obj)判断当前集合中是否包含obj
//底层调用的时equals方法,自定义的类需要重写equals方法
System.out.println(coll.contains(coll));//false
//containsAll(Collection coll)//用来判断coll中的元素是否都存在
System.out.println(coll.containsAll(coll));//true
System.out.println(coll.contains(456));//true
//底层源码
// public int indexOf(Object o) {
// if (o == null) {
// for (int i = 0; i < size; i++)
// if (elementData[i]==null)
// return i;
// } else {
// for (int i = 0; i < size; i++)
// if (o.equals(elementData[i]))
// return i;
// }
// return -1;
// }
//remove(Object obj),返回Boolean
System.out.println(coll.remove(123));//true
System.out.println(coll.remove(1234));//false
//removeAll(Collection coll)//移除coll中所有元素,也就是差集
coll.addAll(coll1);
System.out.println(coll);
coll.removeAll(coll1);
System.out.println(coll);
//retainAll(Collection coll) 交集
coll.removeAll(coll1);
//equals (Object obj) 顺序也应该相同 ,因为是ArrayList
Collection coll2 =new ArrayList();
coll2.addAll(coll);
System.out.println(coll2.equals(coll));//true
//hashCode();
System.out.println(coll.hashCode());
//集合转换为数组 toArray();
Object [] arr =coll.toArray();
for (Object object : arr) {
System.out.print(object+" ");
//数组转为集合 asList();调用Arrays类的静态方法asList()
String [] s = {"a","b","c"};
List<String> list =Arrays.asList(s);
System.out.println(list);
List arr1 = Arrays.asList(new int[] { 123, 456 });
System.out.println(arr1.size());// 1
List arr4 = Arrays.asList( 123, 456 );
System.out.println(arr4.size());// 2
List arr2 = Arrays.asList(new Integer[] { 123, 456 });
System.out.println(arr2.size());// 2
List arr3 = Arrays.asList(new int [] {123,456});//将整体结构当成一个元素
System.out.println(arr3);//[[I@5c647e05]
}
}
}
Iterator(迭代器)
遍历结合
迭代器只是一种方式,或设计模式,不属于集合
/**Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所
* 有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
* Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
* 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
*每一次遍历都需要创建一个新的迭代器。
* 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
* hasNext():判断是否还有下一个元素
*while(iterator.hasNext()){
* //next():①指针下移 ②将下移以后集合位置上的元素返回
*System.out.println(iterator.next());
*}
* Iterator iter = coll.iterator();//回到起点
* while(iter.hasNext()){
* Object obj = iter.next();
* if(obj.equals("Tom")){
* iter.remove();
* }
* }
* 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,
* 再调用remove都会报IllegalStateException。
*/
public class IteratorTest {
public static void main(String[] args) {
Collection coll =new ArrayList();
coll.add("刘");//字符串
coll.add(123);//int
coll.add(new String("name"));//对象
coll.add(true);//Boolean
Iterator iterator = coll.iterator();//建立迭代器
//遍历方式一,for循环
// for(int i = 0;i < coll.size();i++){
// System.out.println(iterator.next());
// }
System.out.println();
//方式二,直接返回元素,此方法不能多与内容数目。
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
//System.out.println(iterator.next());// NoSuchElementException
//方式三
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//删除操作
Iterator iterator= coll.iterator();//回到起点
while(iterator.hasNext()){
Object obj = iterator.next();
if(obj.equals("刘")){
iterator.remove();
}
}
//删除后遍历
Iterator iter= coll.iterator();//回到起点
while (iter.hasNext()){
System.out.println(iter.next());
}
}
}
增强for循环
遍历集合,数组
/**
* 遍历集合的底层调用Iterator完成操作。
*
*/
public class ForTest {
public static void main(String[] args) {
Collection coll=new ArrayList();
coll.add("刘");//字符串
coll.add(123);//int
coll.add(new String("name"));//对象
coll.add(true);//Boolean
Iterator iterator = coll.iterator();//建立迭代器
for (Object obj: coll) { //coll指代Collection对象,每次循环将值给obj,然后输出
System.out.println(obj);
}
//不能用来遍历其中部分类型
// for (String str:coll) {
// System.out.println(str);
// }
System.out.println();
int [] arr={1,2,9,8,7};
for (int t:arr) {
System.out.print(t+",");
}
System.out.println();
for(int t1:arr){//这里是赋值给t1,t1改值与arr以及t无关
t1=5;
System.out.print(t1+",");
}
}
}
List(元素有序、可重复的集合)
ArryList
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* List是一个接口,其实现类有ArrayList、LinkedList、Vector
* List接口可以看作是动态数组,存储有序的、可重复的数据
*ArrayList、LinkedList、Vector三者的异同
* 同: 三个类实现了List接口,都是存储有序的可重复的数据
* //列 ArrayList,效率高,线程不安全,底层主要使用Object[] EMPTY_ELEMENTDATA = {}存储,没有角标的说法,
* 找的时候一个一个去找
* //链表 LinkedList,底层使用双向链表,频繁插入和删除,比ArrayList效率高
* //向量 Vector,效率低,线程安全,底层主要使用Object[] EMPTY_ELEMENTDATA = {}存储
* jdk 7情况下
* * ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
* * list.add(123);//elementData[0] = new Integer(123);
* * ...
* * list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
* * 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
* *
* * 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
* *
* jdk 8中ArrayList的变化:
* * ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
* *
* * list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
* * ...
* * 后续的添加和扩容操作与jdk 7 无异。
* * 2.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
* * 的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
*
*
*
*源码分析1.7
* public ArrayList(int initialCapacity) {
* super();
* if (initialCapacity < 0)
* throw new IllegalArgumentException("Illegal Capacity: "+
* initialCapacity);
* this.elementData = new Object[initialCapacity];
* }
* public ArrayList() {
* this(10);
* }
* initialCapacity为10,然后将当前数组初始化为一个长度为10的Object类型数组
* 添加操作
*public boolean add(E e) {
* ensureCapacityInternal(size + 1); // Increments modCount!!
* elementData[size++] = e;
* return true;
* }
*private void ensureCapacityInternal(int minCapacity) {
* modCount++;
* // overflow-conscious code
* if (minCapacity - elementData.length > 0)如果是负数就不会扩容,
* grow(minCapacity);
* }
* 扩容
* private void grow(int minCapacity) {
* // overflow-conscious code
* int oldCapacity = elementData.length;
* int newCapacity = oldCapacity + (oldCapacity >> 1);向右移一位,默认扩容为原来的1.5倍
* if (newCapacity - minCapacity < 0)
* newCapacity = minCapacity;
* if (newCapacity - MAX_ARRAY_SIZE > 0)
* newCapacity = hugeCapacity(minCapacity);
* // minCapacity is usually close to size, so this is a win:
* elementData = Arrays.copyOf(elementData, newCapacity);elementData复制到新的(长度为1.5倍的)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);
* }
* }
*
*源码分析1.8
* public ArrayList() {
* this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
* }
*
* private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
* 开始的容量为空,造对象的时候,底层还没进行数组的创建,节省内存,add的时候创建数组。
* transient Object[] elementData;
* 添加操作
* public boolean add(E e) { 确定容量够不够
* ensureCapacityInternal(size + 1); // Increments modCount!!
* elementData[size++] = e;
* return true;
* }
* private void ensureCapacityInternal(int minCapacity) {
* ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
* }
* 那个值大返回那个
* private static int calculateCapacity(Object[] elementData, int minCapacity) {
* if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
* return Math.max(DEFAULT_CAPACITY, minCapacity);
* }
* return minCapacity;
* private void ensureExplicitCapacity(int minCapacity) {
* modCount++;
*
* // overflow-conscious code
* if (minCapacity - elementData.length > 0) elementData.length开始为0
* grow(minCapacity);
* }
* 扩容
* private void grow(int minCapacity) {
* // overflow-conscious code
* int oldCapacity = elementData.length;
* int newCapacity = oldCapacity + (oldCapacity >> 1);
* if (newCapacity - minCapacity < 0)
* newCapacity = minCapacity;
* if (newCapacity - MAX_ARRAY_SIZE > 0)
* newCapacity = hugeCapacity(minCapacity);
* // minCapacity is usually close to size, so this is a win:
* elementData = Arrays.copyOf(elementData, newCapacity);
* }
* @author lenovo
*/
public class ArrayListTest {
public static void main(String[] args) {
// 除了Collection接口中的方法 list常用方法
// void add(int index, Object ele):在index位置插入ele元素
// boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
// Object get(int index):获取指定index位置的元素
// int indexOf(Object obj):返回obj在集合中首次出现的位置
// int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
// Object remove(int index):移除指定index位置的元素,并返回此元素 ,与Collection是重载关系
// Object set(int index, Object ele):设置指定index位置的元素为ele
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
//
// 总结:常用方法
// 增:add(Object obj)
// 删:remove(int index) / remove(Object obj)
// 改:set(int index, Object ele)
// 查:get(int index)
// 插:add(int index, Object ele)
// 长度:size()
// 遍历:① Iterator迭代器方式
// ② 增强for循环
// ③ 普通的循环
ArrayList list=new ArrayList();
//字符串
list.add("刘");
//int
list.add(123);
//对象
list.add(new String("name"));
//Boolean
list.add(true);
System.out.println(list);
//void add(int index, Object ele):在index位置插入ele元素
list.add(1,456);
//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list1 = Arrays.asList(1, 2, 3);//使用Arrays.asList创建的列表是一个固定大小的列表
list.addAll(list1);
//list.add(list1);
System.out.println(list1);//6
System.out.println(list.size());//8
//Object get(int index):获取指定index位置的元素
list.get(1);
//int indexOf(Object obj):返回obj在集合中首次出现的位置,没有为-1
list.indexOf("刘");
System.out.println(list.indexOf("刘"));
//int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置,没有为-1
list.lastIndexOf("789");
System.out.println(list.lastIndexOf("789"));
//Object remove(int index):移除指定index位置的元素,并返回此元素
Object obj = list.remove(0);
System.out.println(obj);//"刘"
System.out.println(list);
// Object set(int index, Object ele):设置指定index位置的元素为ele
list.set(0,"王");
System.out.println(list);
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
List subList = list.subList(2, 4);
System.out.println(subList);
System.out.println(list);
}
}
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);//
}
private void updateList(List list) {
// list.remove(2); 删除下标二的元素
list.remove(new Integer(2)); 删除对象2
}
LinkedList
/**源码分析,1.7和1.8区别不大
* 链式存储,
* 没有数组的概念
*
* transient Node<E> first; 总链条的第一个
* Pointer to first node.
* * Invariant: (first == null && last == null) ||
* * (first.prev == null && first.item != null)
* 指向第一个节点的指针。不变式:
* (first == null && last == null) || (first.prev == null && first.item != null)
* transient Node<E> last; 总链条的最后一个
* Pointer to last node.
* * Invariant: (first == null && last == null) ||
* * (last.next == null && last.item != null)
* 指向最后一个节点的指针。不变式:
* (first == null && last == null) || (last.next == null && last.item != null)
* Node 体现了LinkedList的双向链表的说法
* private static class Node<E> { 一个内部类
* E item; 添加的数据 ,指向element
* Node<E> next; 链表的下一个
* Node<E> prev; 链表的上一个
*
* Node(Node<E> prev, E element, Node<E> next) {
* this.item = element;
* this.next = next;
* this.prev = prev;
* }
* }
*添加
* public boolean add(E e) {
* linkLast(e);
* return true;
* }
*
* void linkLast(E e) {
* final Node<E> l = last; 后一个
* final Node<E> newNode = new Node<>(l, e, null); 前一个 ,中间一个,下一个,此处的l指的是上一个的最后一个
* 例如456789 e为7 l值得就是678的6 值得是456的6,将头尾指向同一个元素
* last = newNode;
* if (l == null) 前面为空 就相当于只有一个元素 第一个是他最后一个也是他
* first = newNode;
* else
* l.next = newNode;
* size++; 加了一个元素 长度加1
* modCount++; (protected transient int modCount = 0)
* }
*
* LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
* * list.add(123);//将123封装到Node中,创建了Node对象
*
*
*
*
* void addFirst(Object obj)
* void addLast(Object obj)
* Object getFirst()
* Object getLast()
* Object removeFirst()
* Object removeLast()
* @author lenovo
*/
public class LinkedListTest {
public static void main(String[] args) {
LinkedList link=new LinkedList();
//字符串
link.add("刘");
//int
link.add(123);
//对象
link.add(new String("name"));
//Boolean
link.add(true);
System.out.println(link);
link.addFirst("rr");
System.out.println(link);
link.addLast("tt");
System.out.println(link);
System.out.println(link.getFirst());
System.out.println(link);
}
}
set(元素无序、不可重复的集合)
HashSet和LinkedHashSet
对象比较:
两个对象,如果equals相等,这两个对象一定相等,两个的hashcode()相等
如果两个对象的hashcode相等,不能说明对象一定相等,因此存在了哈希碰撞。
如果两个对象hashcode不相等,两个就不可能相等
LinkedHashSet在存储时存在一个前后链表来记录他的先后顺序
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
* * |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
* * |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
* * 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
* * |----TreeSet:可以按照添加对象的指定属性,进行排序。
* *
* *
* * 1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
* *
* * 2. 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
* * 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
* *
* 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
*一、Set:存储无序的、不可重复的数据
* 以HashSet为例说明:
* 1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
*
* 2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
*
* 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
* 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
* 数组此位置上是否已经有元素:
* 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
* 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
* 如果hash值不相同,则元素a添加成功。--->情况2
* 如果hash值相同,进而需要调用元素a所在类的equals()方法:
* equals()返回true,元素a添加失败
* equals()返回false,则元素a添加成功。--->情况2
*
* 对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
* jdk 7 :元素a放到数组中,指向原来的元素。
* jdk 8 :原来的元素在数组中,指向元素a
* 总结:七上八下
*
* HashSet底层:数组+链表的结构。
*HashSet底层就是用HashMap
*
*
*
*
*
*
*
* @author lenovo
*/
public class HashSetTest {
public static void main(String[] args) {
Set set = new HashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
Set set1 = new LinkedHashSet();
set1.add(456);
set1.add(123);
set1.add(123);
set1.add("AA");
set1.add("CC");
set1.add(129);
Iterator iterator1 = set.iterator();
while(iterator1.hasNext()){
System.out.println(iterator1.next());
}
}
}
遍历出来结果不同
TreeSet
import java.util.TreeSet;
/**
*
* 1.向TreeSet中添加的数据,要求是相同类的对象。
* 2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
*
* 需要重写
* 3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
* 4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
* 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable
* 接口。
* 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过
* compareTo(Object obj) 方法的返回值来比较大小。
* @author lenovo
*/
public class TreeSetTest {
public static void main(String[] args) {
TreeSet set = new TreeSet();
//向TreeSet中添加的数据,要求是相同类的对象
set.add(123);
set.add(456);
set.add("AA");
}
}
Map
HashSet,底层其实质就是HashMap,而HashMap底层本质是数据结构——hash table
Map的实现类的结构:
Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序底层使用红黑树
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
HashMap
import java.util.HashMap;
/**HashMap1.7
*空参构造器
* public HashMap() {
* this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);16 加载因子0.75f
* }
* public HashMap(int initialCapacity, float loadFactor) {
* if (initialCapacity < 0)
* throw new IllegalArgumentException("Illegal initial capacity: " +
* initialCapacity);
* if (initialCapacity > MAXIMUM_CAPACITY)
* initialCapacity = MAXIMUM_CAPACITY;
* if (loadFactor <= 0 || Float.isNaN(loadFactor))
* throw new IllegalArgumentException("Illegal load factor: " +
* loadFactor);
*
* // Find a power of 2 >= initialCapacity
* int capacity = 1;
* while (capacity < initialCapacity) 底层创建长度为16
* capacity <<= 1; 一定是2的多少次方
*
* this.loadFactor = loadFactor;
* 临界值 threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); 16*0.75=12 和最大值+1 取最小
* table = new Entry[capacity]; 给到entry类型的table
* useAltHashing = sun.misc.VM.isBooted() &&
* (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
* init();
* }
*添加put
* public V put(K key, V value) {
* if (key == null) 判断是否为空
* return putForNullKey(value);
* int hash = hash(key); 计算hash值
* int i = indexFor(hash, table.length); 得到数组中的存放位置
* for (Entry<K,V> e = table[i]; e != null; e = e.next) { 判断该位置是否有元素,如果有进行该哈希值上元素的判断,之后进行下一步。
* Object k;
* if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
* V oldValue = e.value; 新的替换原有的
* e.value = value;
* e.recordAccess(this);
* return oldValue;
* }
* }
*
* modCount++;
* addEntry(hash, key, value, i); 没有元素直接加进去
* return null;
* }
* 添加
* void addEntry(int hash, K key, V value, int bucketIndex) {
* if ((size >= threshold) && (null != table[bucketIndex])) { 长度大于12并且存放位置为不空
* resize(2 * table.length); 2倍扩容
* hash = (null != key) ? hash(key) : 0; 在新的位置重新进行hash进行运算
* bucketIndex = indexFor(hash, table.length);
* }
*
* createEntry(hash, key, value, bucketIndex);
* }
* 元素放入
* void createEntry(int hash, K key, V value, int bucketIndex) {
* Entry<K,V> e = table[bucketIndex]; 先去取出原有位置上的元素
* table[bucketIndex] = new Entry<>(hash, key, value, e);
* size++;
* }
* Entry(int h, K k, V v, Entry<K,V> n) { 此处相当于重新在原有位置上重新建立一个链表把新元素给到第一个,旧元素给到第二个。
* 新元素就在最初始的链表上
* value = v;
* next = n;
* key = k;
* hash = h;
* }
*计算hash值(了解)
* final int hash(Object k) {
* int h = 0;
* if (useAltHashing) {
* if (k instanceof String) {
* return sun.misc.Hashing.stringHash32((String) k);
* }
* h = hashSeed;
* }
*
* h ^= k.hashCode();
*
* // This function ensures that hashCodes that differ only by
* // constant multiples at each bit position have a bounded
* // number of collisions (approximately 8 at default load factor).
* h ^= (h >>> 20) ^ (h >>> 12);
* return h ^ (h >>> 7) ^ (h >>> 4);
* }
*存放位置
* static int indexFor(int h, int length) {
* return h & (length-1);
* }
*
*
*HashMap1.8
*构造器
* public HashMap() {
* this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
* }
*添加
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
*
* final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
* Node<K,V>[] tab; Node<K,V> p; int n, i;
* if ((tab = table) == null || (n = tab.length) == 0) 是否为首次添加
* n = (tab = resize()).length; 扩容以及创建数组
* if ((p = tab[i = (n - 1) & hash]) == null) 判断当前存key value的位置是否为空
* tab[i] = newNode(hash, key, value, null);
* else {
* Node<K,V> e; K k;
* if (p.hash == hash &&
* ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
* else if (p instanceof TreeNode)
* e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
* else {
* for (int binCount = 0; ; ++binCount) {
* if ((e = p.next) == null) {
* p.next = newNode(hash, key, value, null); 新的元素作为原有元素的下一个
* if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 长度为8变为红黑树
* treeifyBin(tab, hash);
* break;
* }
* if (e.hash == hash &&
* ((k = e.key) == key || (key != null && key.equals(k))))
* break;
* p = e; 回到for循环继续比较
* }
* }
* if (e != null) { // existing mapping for key 替换
* V oldValue = e.value;
* if (!onlyIfAbsent || oldValue == null)
* e.value = value;
* afterNodeAccess(e);
* return oldValue;
* }
* }
* ++modCount;
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict);
* return null;
* }
*扩容
* final Node<K,V>[] resize() { 扩容的时候创建数组
* Node<K,V>[] oldTab = table;
* int oldCap = (oldTab == null) ? 0 : oldTab.length;
* int oldThr = threshold;
* int newCap, newThr = 0;
* if (oldCap > 0) {
* if (oldCap >= MAXIMUM_CAPACITY) {
* threshold = Integer.MAX_VALUE;
* return oldTab;
* }
* else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
* oldCap >= DEFAULT_INITIAL_CAPACITY)
* newThr = oldThr << 1; // double threshold
* }
* else if (oldThr > 0) // initial capacity was placed in threshold
* newCap = oldThr;
* else { // zero initial threshold signifies using defaults
* newCap = DEFAULT_INITIAL_CAPACITY; 16
* newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 0.75*16
* }
* if (newThr == 0) {
* float ft = (float)newCap * loadFactor;
* newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
* (int)ft : Integer.MAX_VALUE);
* }
* threshold = newThr; 临界值赋值
* @SuppressWarnings({"rawtypes","unchecked"})
* Node<K, V>[] newTab = (Node<K,V>[])new Node[newCap]; 建立数组长度为16
* table = newTab;
* if (oldTab != null) {
* for (int j = 0; j < oldCap; ++j) {
* Node<K,V> e;
* if ((e = oldTab[j]) != null) {
* oldTab[j] = null;
* if (e.next == null)
* newTab[e.hash & (newCap - 1)] = e;
* else if (e instanceof TreeNode)
* ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
* else { // preserve order
* Node<K,V> loHead = null, loTail = null;
* Node<K,V> hiHead = null, hiTail = null;
* Node<K,V> next;
* do {
* next = e.next;
* if ((e.hash & oldCap) == 0) {
* if (loTail == null)
* loHead = e;
* else
* loTail.next = e;
* loTail = e;
* }
* else {
* if (hiTail == null)
* hiHead = e;
* else
* hiTail.next = e;
* hiTail = e;
* }
* } while ((e = next) != null);
* if (loTail != null) {
* loTail.next = null;
* newTab[j] = loHead;
* }
* if (hiTail != null) {
* hiTail.next = null;
* newTab[j + oldCap] = hiHead;
* }
* }
* }
* }
* }
* return newTab;
* }
*转为树形
* final void treeifyBin(Node<K,V>[] tab, int hash) {
* int n, index; Node<K,V> e;
* if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) 64 长度小于64扩容 ,大于64变为树
* 原来的长度不够长,元素都堆积在一起,扩容一下就可以
* resize(); 进行扩容
* else if ((e = tab[index = (n - 1) & hash]) != null) {
* TreeNode<K,V> hd = null, tl = null;
* do {
* TreeNode<K,V> p = replacementTreeNode(e, null);
* if (tl == null)
* hd = p;
* else {
* p.prev = tl;
* tl.next = p;
* }
* tl = p;
* } while ((e = e.next) != null);
* if ((tab[index] = hd) != null)
* hd.treeify(tab);
* }
* }
*
*
*
*HashMap的底层实现原理?以jdk7为例说明:
* * HashMap map = new HashMap():
* * 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
* * ...可能已经执行过多次put...
* * map.put(key1,value1):
* * 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
* * 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
* * 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
* * 的哈希值:
* * 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
* * 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
* * 如果equals()返回false:此时key1-value1添加成功。----情况3
* * 如果equals()返回true:使用value1替换value2。
* *
* * 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
* *
* * 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
* *
* * jdk8 相较于jdk7在底层实现方面的不同:
* * 1. new HashMap():底层没有创建一个长度为16的数组
* * 2. jdk 8底层的数组是:Node[],而非Entry[]
* * 3. 首次调用put()方法时,底层创建长度为16的数组
* * 4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
* * 4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
* 4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
* *
* * DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
* * DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
* * threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
* * TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
* * MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
* @author lenovo
*/
public class MapTest {
public static void main(String[] args) {
HashMap hashmap =new HashMap();
// 添加、删除、修改操作:
// Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
// void putAll(Map m):将m中的所有key-value对存放到当前map中
// Object remove(Object key):移除指定key的key-value对,并返回value
// void clear():清空当前map中的所有数据
// 元素查询的操作:
// Object get(Object key):获取指定key对应的value
// boolean containsKey(Object key):是否包含指定的key
// boolean containsValue(Object value):是否包含指定的value
// int size():返回map中key-value对的个数
// boolean isEmpty():判断当前map是否为空
// boolean equals(Object obj):判断当前map和参数对象obj是否相等
// 元视图操作的方法:
// Set keySet():返回所有key构成的Set集合
// Collection values():返回所有value构成的Collection集合
// Set entrySet():返回所有key-value对构成的Set集合
//
// *总结:常用方法:
// * 添加:put(Object key,Object value)
// * 删除:remove(Object key)
// * 修改:put(Object key,Object value)
// * 查询:get(Object key)
// * 长度:size()
// * 遍历:keySet() / values() / entrySet()
// *
/*
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
/*
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
*/
/*
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
*/
}
}
LinkedHashMap
/**
* LinkedHashMap
* public class LinkedHashMap<K,V>
* extends HashMap<K,V>
* implements Map<K,V>
* 其本质调用的是HashMap
* 区别在于调用上层put方法时 重写了Node方法
* Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
* LinkedHashMap.Entry<K,V> p =
* new LinkedHashMap.Entry<K,V>(hash, key, value, e);
* linkNodeLast(p);
* return p;
* }
*
* static class Entry<K,V> extends HashMap.Node<K,V> { 继承与HashMap
* Entry<K,V> before, after; 能够记录添加的元素的先后顺序
* Entry(int hash, K key, V value, Node<K,V> next) {
* super(hash, key, value, next);
* }
* }
* @author lenovo
*/
public class LinkedHashMapTest {
public static void main(String[] args) {
//LinkedHashMap;
}
}
Properties
Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key和 value 都是字符串类型
存取数据时,建议使用setProperty(String key,String value)方法和
getProperty(String key)方法
右击工程新建file或者Resource Bundle
import java.io.FileInputStream;
import java.util.Properties;
/**
* @author lenovo
* 配置信息
* 默认识别在当前工程下
*/
public class PropertiesTest {
public static void main(String[] args) throws Exception {
Properties pro =new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pro.load(fis);//加载流对应的文件
String name = pro.getProperty("name");
String password = pro.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
}
}
Collections
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
@Test
public void test2(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);
//报异常:IndexOutOfBoundsException("Source does not fit in dest")
// List dest = new ArrayList();
// Collections.copy(dest,list);
//正确的:
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);
System.out.println(dest);
/*
Collections 类中提供了多个 synchronizedXxx() 方法,
该方法可使将指定集合包装成线程同步的集合,从而可以解决
多线程并发访问集合时的线程安全问题
*/
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);
}