前言
集合就是存储数据的,和数组一样,但是数组存在一些弊端,
->数据是内存中连续的空间,存储的数据有限,
->数组大小一旦确定无法修改
->有序可重复,对于无需不可重复没有办法
->没有提供直接操作集合中元素的方法。
一、Collection(单列集合)
1、UML图
2、List接口常用方法
3、list接口(有序可重复)
①、有序可重复,在Collection的基础上加了可以通过下标操作集合中的元素(插入,删除,获取(元素get,下标indexOf),修改)
②、读取效率高,插入删除效率低
1)ArrayList(变长数组,非线程安全)
1、内部使用数组存储,读取效率高,插入删除效率低,
2、源码:
①JDK1.7
构造方法:无参构造方法初始化容量为10,
添加元素方法:当容量不够时,会进行数组扩容,扩容的大小是原来的1.5倍(原来数组大小左移一位加上原来数组大小),然后将原来数组内容copy到新的数组中
②JDK1.8
里面定义了一个默认的空数组的常量,初始化时不会创建初始化数组大小,而是设置为{},当第一次添加时,通过数据扩容的方式初始化数组容量大小为10和上图一样的方法
/**
* list:有序可重复,在自身中又将父类方法定义了一遍???
* 理解:变长数组,1.8默认是0,插入元素时扩容10,1.7是默认10大小
* public ArrayList() {
* this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//{}
* }
*
* list方法:新增了根据下标操作的集合
* void add(int,Object):指定位置插入元素,<=size,插入
* Object get(int):指定下标获取元素,<size
* int indexOf(Object):获取指定元素的第一个的下标,
* int lastIndexOf(Object):获取该元素的最后一个的下标
* Object remove(int):删除指定下标元素,<size
* Object set(int,Object):设置指定下标元素为obj
* List subList(int,int):返回指定下标到指定下标的元素,不包括最后一个下标
*
* ArrayList测试list方法
* ArrayList:数组存储,读写很快,插入删除
*
*
*/
public class MyArrayList {
public static void main(String[] args) {
List list = new ArrayList();
list.add(12);
list.add(0,list.size());//不能超出size大小
list.add(0,0);//不能超出size大小
System.out.println(list);
list.set(0,Integer.MAX_VALUE);
System.out.println(list.remove(2));
System.out.println(list.subList(0,2));//不包括最后一个下标
System.out.println(list);
}
}
2)LindedList(双向链表,非线程安全)
1、双向链表,内有头节点和尾节点(节点存储节点信息和前后节点)
2、节点操作插入删除效率高,读取效率低
3、新增节点的操作方法
4、源码:
内部有头部节点和尾部节点,
构造方法:初始化容量大小为0
add方法:尾插法,第一次插入时,头节点和尾节点相同
void linkLast(E e) {
LinkedList.Node<E> l = this.last;
LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);
this.last = newNode;
if (l == null) {
this.first = newNode;
} else {
l.next = newNode;
}
++this.size;
++this.modCount;
}
/**
* 实现类LinkedList:单列集合,有序可重复
* 理解:插入删除很快,双向链表(内部有头结点和尾节点),读写慢
* 常用方法:(本类新增),只有一个时,头尾节点是一样的。
* void addFirst(Object)头插法
* void addLast(Object)尾插法
* Object getFirst()
* Object getLast()
* Object removeFirst()
* Object removeLast()
*
*
*
*/
Collection list=new LinkedList();//接口的上转型。也可以指定类型插入,也可以插入自定义类型,但是比较方法需要重写。
String str="qwer";//直接指向常量池的
//增
System.out.println("插入元素");
list.add("fds");
list.add(new Integer(3));
list.add(new String("fds"));
list.add(34);//自动装箱。
list.add(str);
//查(for)需要转型,才能获取到正确的数据类型。
System.out.println("当前数组中含有的元素:");
for(Object obj:list){
System.out.println(obj);
}
//删
System.out.printf("删除元素'fds':");
boolean success=list.remove(new String("fds"));//调用String的equals方法
System.out.println((success)?"删除成功":"删除失败");
//查(迭代)
System.out.println("当前数组中含有的元素:");
Iterator it=list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//判断是否为空
boolean empty=list.isEmpty();
System.out.println(empty?"数组为空":"数组不为空");
/*list.clear();//清空数组
System.out.println("清空数组");
empty = list.isEmpty();
System.out.println(empty?"数组为空":"数组不为空");*/
//是否包含某个元素,调用的是String的equals方法;//比较的是这个变量的值啊。
boolean contains=list.contains(new String("qwer"));
System.out.println(contains);
System.out.println(contains?"包含元素'fds'":"不包含元素'fds'");
//
// System.out.println(list.get(0));
节点操作
public class TestLinkedList {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add(1);
System.out.println("头结点:" + list.getFirst());
System.out.println("尾结点:" + list.getLast());
list.add(3);
list.addFirst(2);
System.out.println("头结点:" + list.getFirst());
System.out.println("尾结点:" + list.getLast());
list.addLast(4);
System.out.println(list);//2,1,3,4
}
}
3)Vector(向量,线程安全)
线程安全不常用
/**
* Vector:线程安全,数组,和ArrayList类似
* 常用方法:新增
* void addElement(Object);
* void insertElement(Object,int);
* void setElement(Object,int);
* void removeElement(Object);
* void removeAllElements();
*/
public class MyVector {
public static void main(String[] args) {
Vector vector = new Vector();
vector.addElement(1);
vector.add(0,2);
System.out.println(vector);
}
}
4、set接口(无序不可重复)
1)HashSet(HashMap的key部分,哈希和比较)
存储自定义类型需要重写hashcode和equals方法,不重写其实也可以,但是数组的散列不够均匀而且比较大小有问题。利用HashMap的key部分,可以存储null
/**
*
* Set:无序不可重复(采用map集合的key部分,equals判断),没有新增方法
* 实现类HashSet: 实则是采用HashMap的key部分(key是不可重复的,HashCode确定数组下标,equals判断是否相等,不相等添加链表元素,相等替换),
* 判断是否相等:元素hashcode值相同,并且equals相同
* 存储实体类应该重写equals方法和hashcode
*/
public class MyHashSet {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(128);
set.add(129);
set.add(130);
set.add(1);
System.out.println(set.add(128));//插入失败
System.out.println(set);//[128, 129, 1, 130]无序排列
Integer i ;
}
}
2)TreeSet(TreeMap的key部分,排序)
实际上是利用了TreeMap的key实现,只能存储一种类型,存储实体类还需要设置排序规则(自然排序,定制排序)。比较时也是通过排序进行比较
/**
*
* TreeSet:实现了SortedSet接口(可排序:自然排序,定制排序),红黑树,实际就是TreeMap的key部分
* 排序:
* ①自定义类需要实现comparable接口,并且实现comparaTo(Object obj)方法,不然插入报错(java.lang.ClassCastException: com.zy.collection.set.Student cannot be cast to java.lang.Comparable)
* 基本数据类型都是比较值,常用类String比较unicode的值,常用类date比较时间大小
* ②定制排序,实体类实现Comparator接口,重写int compare(o1,o2),1:o1>o2,0,-1
* 每插入一个元素就会和之前的元素比较,并且按照升序排序(第一个除外)
* 插入的元素应该是同一种类型,不然无法比较
* 常用方法:
* Comparator comparator();
* Object first();
* Object last();
* Object lower(Object);
* Object higher(Object);
* SortedSet subSet(Object toElement,Object endElement)
* SortedSet headSet(Object end);头结点到指定元素的集合,不包括结尾
* SortedSet tailSet(Object from);
*
*/
public class MySortedSet {
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(1);
set.add(new Integer(2));
set.add(new Integer(5));
set.add(new Integer(4));
set.add(new Integer(3));
System.out.println(set.first());
//set = (TreeSet) set.headSet(3);//[1,2]
//set = (TreeSet) set.tailSet(-1);//[1, 2, 3, 4, 5]
System.out.println(set);//
TreeSet<Student> treeSet = new TreeSet();
treeSet.add(new Student(1,"2"));
treeSet.add(new Student(2,"3"));
treeSet.add(new Student(2,"3"));//相等
System.out.println(treeSet);
}
}
二、Map(双列集合,KV键值对存储)
1、UML
2、 Map接口
1)基础方法
KV存储,无序不可重复,可以存储null值,
/**
* Map接口:KV键值对
* 常用方法:
* Object put(Object,Object):KV存储
* boolean putAll(Map):存储map中的所有KV
* Object remove(Object key):删除指定key,返回value
* void clear():清空所有KV
*
* Object get(Object):获取指定key的value
* boolean containsKey(Object):包含key
* boolean containsValue(Object):包含value
* int size():数据长度
* boolean isEmpay():是否为空
* boolean equals():比较是否相等???
*
* Set keySet():返回所有key的Set集合
* Set<Map.Entry<Object,Object>> entrySet:返回存储KV信息的entrySet的set集合
* Collection values():返回所有values的collection集合
*
*
*
*/
public class MyMap {
public static void main(String[] args) {
Map<String,Object> map = new HashMap();
map.put("k1","v1");
map.put("k1","v1");
map.put("k2","v2");
System.out.println("大小:"+map.size());
System.out.println("k1对应的value:"+map.get("k1"));//equals方法
System.out.println("是否包含Key:k2->"+map.containsKey("k2"));//hashcode和equals相等
System.out.println("是否包含Value:v2->"+map.containsValue("v2"));//比较equals
System.out.println("是否为空:"+map.isEmpty());
System.out.println("删除k1:"+map.remove("k1"));
System.out.println("大小:"+map.size());
System.out.println("清空map所有元素");
map.clear();
System.out.println("最终大小:"+map.size());
}
}
2)操作方法
①Map.computeIfAbsent,computeIfPresent,compute,不存在key/存在key/无论如何都会进入方法,方法体内的对象作为返回值,并且写入对应key的value
2、HashMap(数组+链表+红黑树(链表大于8))
1、基本常量:
①数组容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
②加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
③最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
④扩容:static final int MIN_TREEIFY_CAPACITY = 64;
2、变量:
①KV键值对元素:transient Set<Map.Entry<K,V>> entrySet;
②存储链表的数组:transient Node<K,V>[] table;
③扩容和结构改变的次数:transient int modCount;
当链表长度大于该值转为红黑树:static final int TREEIFY_THRESHOLD = 8;
当红黑树节点小于该值转为链表:static final int UNTREEIFY_THRESHOLD = 6;
3、存储过程:根据存储类型的hashcode值经过计算得到hashmap中对应的数组下标,如果当前位置没有元素,那么这个元素直接被添加到链表的头部,如果有值,那么会比较hashcode是否相等,如果不等直接插入,如果相等,再比对equals是否相等,如果不等,插入,如果相等也就是有k存在,那么当前key的value会替换掉之前的value值
4、关于比较:比较的是hashcode和equals,(区别于list集合一个一个比较效率很高)
5、jdk1.7和jdk1.8:
JDK1.8之前,new对象时设置容量大小,JDK1.8put时设置容量大小,并且数组类型为Node,以前是entry,新增红黑树结构
5、代码测试
/**
* key允许为null
*/
public class MyHashMap {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put(null,null);
map.put(null,"1");//替换value
map.put(1,2);
System.out.println(map);[null=1,1=2]
}
}
3、TreeMap(红黑树)
1、实现了SortedMap,所以可排序。对于基本数据类型比较值(对应的包装类型都实现了接口)和常用类String比较unicode的值,Date比较毫秒数。自定义类需要实现排序接口(Comparator,Comparable)。
2、只能存储一种类型,存储多种类型比较时会出现强转错误
3、put源码:
首先判断是否第一次添加数据,
—>是则初始化,直接添加第一个数据
–>不是,获取全局比较器
---->不为空,利用全局比较比较,左小右大
—>为空,利用key的实现比较器比较,左小右大
public V put(K key, V value) {
Entry<K,V> t = root;
//在第一次添加数据时初始化
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//通过new对象指定的Comparator比较器规则比较
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//比较器规则为空,则通过Key实现的比较器进行比较,左小右大
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
4、Hashtable(线程安全)
和HashMap算法一样,只是是线程安全的,key和value不允许为null
/**
/**
* key,value均不允许为null
*/
public class MyHashtable {
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
//hashtable.put(null,null);
// hashtable.put(null,1);
hashtable.put(1,null);
}
}
5、Properties(IO操作,只能存储String类型)
1、只能存储string,String类型
2、多用于IO流读写配置文件
3、常用方法
①load(InputStream)读取KV键值对
②store(OutputStream)写入KV键值对
public class PropertiesTest {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
FileOutputStream fos = new FileOutputStream("test.txt");
FileInputStream fis = new FileInputStream("test.txt");
properties.setProperty("name","properties");
properties.setProperty("age","12");
properties.store(fos,"");
properties.clear();//清空数组内容
properties.load(fis);
System.out.println("name:"+properties.getProperty("name")+","+"age:"+properties.getProperty("age"));
fos.close();
fis.close();
}
}
6、LinkedHashMap(有序遍历输出)
在添加数据时,利用双向链表指针指向下一个元素,遍历时通过指针顺序输出
public class LinkedMap {
public static void main(String[] args) {
LinkedHashMap map = new LinkedHashMap();
map.put(2,1);
map.put(1,1);
map.put(5,1);
map.put(3,1);
HashMap map2 = new HashMap(map);
System.out.println(map);//2,1,5,3
System.out.println(map2);//1,2,3,5
}
}
7、关于HashMap存储引用数据类型时的比较
存储自定义类总是需要重写hashcode(决定数组下标)和equals方法(决定对象是否相等)
重写规则:
①两个相同对象无论调用多少次hashcode方法都是相等的
②两个不同的对象如果equals相等,那么hashcode也应该是相同的
当对于存储自定义数据类型时,应该具有一定的逻辑关系,比如学生类,判断的是学号是否相同,hashcode应该与学号有关,所以与equals有关的field都应该和hashcode影响(在使用IDEA工具重写时,可以看到重写两个方法时,与equals相关的field也必须影响hashcode),所以我们需要同时重写hashcode和equals方法
8、关于TreeMap的Comparator和Comparable
定制排序:自定义类实现Comparator接口,然后将对象传入到map的构造方法中,
自然排序:map存储的实体类实现Comparable接口,重写排序方法,
调用时,首先是获取定制排序Comparator进行排序的,没有才会去找存储类Comparable接口的比较方法
三、Iterator迭代器
1、主要用来遍历Collection集合中的元素,Collection继承了Iteratable接口,通过实现类的iterator方法可以返回迭代器Iterator对象
2、过程解读
public class Test {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(-1);
list.add(0);
Iterator<Integer> iterator = list.iterator();//获取迭代器对象,指针指向第一个元素之前
System.out.println(list);
while(iterator.hasNext()){//是否到达数组末尾
int a = iterator.next();//获取下一个数据,并且指针后移
if(a==0) iterator.remove();//删除集合中所有的0
}
System.out.println(list);
}
}
四、Collections工具类
1、操作java中的集合(map和collection)
2、将线程非安全的集合转为线程安全的
/**
*常用方法:
* void reverse(List<?>):顺序反转
* void shuffle(List<?>):随机排序
* sort(List):根据元素的自然顺序排序
* sort(List,Comparator):根据定制排序排序
* Object max/min(Collection):根据自然排序获取元素中最大值/最小值
* Object max/min(Collection,Comparator):指定排序规则获取最大值/最小值
* void copy(List dest,list src):将src复制到dest中
* boolean replaceAll(List,Object oldVal,Object newVal)
* XXX synchronizedXXX(XXX):将指定的集合转换成线程安全的并且返回
*
*
*/
public class Test {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(1);
System.out.println(list);
Collections.reverse(list);//顺序反转
//Collections.shuffle(list);//随机排序
System.out.println("反转list:"+list);//3,1,2
Collections.swap(list,0,1);//交换指定两个下标的元素。
System.out.println("交换0,1"+list);
Collections.sort(list);
System.out.println("自然排序:"+list);
System.out.println("元素最大值:"+Collections.max(list));
Collections.replaceAll(list,1,0);
System.out.println("替换所有的1为0"+list);
List list2 = Collections.synchronizedList(list);
}
}
五、练习
1)list去重
public class work08_RemoveSame {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(1);
list.add(2);
System.out.println(list);
Set set = new TreeSet();
set.addAll(list);//去除重复
list = new ArrayList(set);
System.out.println(list);
}
}
2)
总结
1、集合只能存储引用数据类型(基本数据类型自动装箱),在map中,存储之后不要修改其属性(与equals和hashcode相关),修改后的hashcode值改变了,再和之前的自己比较就是不同的了,也就是对于HashMap比较的是hashcode和equals
2、Set只能存储自定义类:
HashSet需要重写hashcode和equals
TreeSet需要确定排序规则(Comparator,Comparable)
set集合只能存储一种数据类型,不然比较时会出错
3、数组转listArrays.asList(),这样也可以撑开list的大小,不用自己一一add,也别是在使用Collections.copy()方法时。