Java集合容器
一、Java集合框架
java集合概述:
1.面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。但使用Arrays(数组)存储对象有一些弊端,而java集合就像一种容器,可以动态的把多个对象的引用放入容器中。
2.java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数据。
之前使用数组存储相同各类型的数据,但是数组的缺点:
- 数组的长度不能够任意扩充。集合的长度是可变的。
- 同一个数组不能存放不同类型的数据【集合可以,但是一般开发中并不推荐同一个集合中存放不同类型的数据】
java集合框架:
1.单列集合java.util.Collection,Collection:是单列集合的最高父接口,它有两个重要的子接口,分别是java.util.List
和java.util.Set
。实际开发中,list比set用的多。
- Set:随机散列存储不可重复的元素(元素无序、不可重复的集合)
Set`接口的主要实现类有`java.util.HashSet`和`java.util.TreeSet
- List:先进先出的存储方法,可以存放相同的元素(元素有序、可重复的集合)
List`接口的主要实现类有`java.util.ArrayList`和`java.util.LinkedList
2.双列集合java.util.Map,Map:是键值对的最高父接口,具有映射关系"key-value对"的集合,
键值对就像我们手机的通讯录一样,一个键,对应一个值。
Key value
张三 ---------13456789109
李四 ---------13812345678
键值对的主要作用在于查找,通过key 找到 value。
二、Collection API
package com.wanbangee.collection;
import java.util.ArrayList;
import java.util.List;
public class CollectionDemo01 {
public static void main(String[] args) {
//JDK1.5之前,Collection(java集合)会丢失容器中所有对象的数据类型,把所有对象都当成object类型处理。
List<Integer> list = new ArrayList<>();
//JDK1.5之后,使用泛型定义集合的存储的数据类型,表示此集合只能存储Integer类型对象,JDK1.7后后面<>中内容可省略
//泛型像一个占位符一样表示“未知的某个数据类型”,在真正调用时传入这个数据类型。
list.add(new Integer(10));
//list.add(new Double(10.100));
}
}
Collection接口中常用的操作方法。(子接口list和set继承这些方法)
* public boolean add(E e)
:往集合中添加指定对象。
* public void clear()
:清空集合中所有的元素。
* public boolean remove(E e)
: 移除集合中指定的对象。
//移除不是删除,把这个对象从容器中移除出去,但这个对象还在。
//容器中存的是对象的引用地址,移除只是把对象的引用地址从容器中移除,这个对象还是存在的
* public boolean contains(E e)
: 判断集合中是否存在指定的对象。
* public boolean isEmpty()
: 判断当前集合是否为空。
* public int size()
: 返回集合中元素(对象)的个数。
* public Object[] toArray()
: 将集合转换成数组,数组中保存这个集合中的所有对象。
import java.util.ArrayList;
import java.util.Collection;
public class Demo1Collection {
public static void main(String[] args) {
// 创建集合对象
// 使用多态形式
Collection<String> coll = new ArrayList<String>();
// 使用方法
// 添加功能 boolean add(String s)
coll.add("小李广");
coll.add("扫地僧");
coll.add("石破天");
System.out.println(coll);
// boolean contains(E e) 判断o是否在集合中存在
System.out.println("判断 扫地僧 是否在集合中"+coll.contains("扫地僧"));
//boolean remove(E e) 删除在集合中的o元素
System.out.println("删除石破天:"+coll.remove("石破天"));
System.out.println("操作之后集合中元素:"+coll);
// size() 集合中有几个元素
System.out.println("集合中有"+coll.size()+"个元素");
// Object[] toArray()转换成一个Object数组
Object[] objects = coll.toArray();
// 遍历数组
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
// void clear() 清空集合
coll.clear();
System.out.println("集合中内容为:"+coll);
// boolean isEmpty() 判断是否为空
System.out.println(coll.isEmpty());
}
}
有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。
三、Iterator 迭代器
Iterator对象称为迭代器对象,是23种设计模式之一的迭代器模式,主要用于遍历Collection集合中的元素。所有Collection的子接口的实现类,都有一个方法iterator(),这个方法返回了一个Iterator接口的对象。Iterator接口仅用于遍历集合,本身不提供存储对象的功能,如果要创建一个Iterator对象,必须有一个被迭代的集合。
1.Iterator接口
开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。Iterator
接口也是Java集合中的一员,但它与Collection
、Map
接口有所不同,Collection
接口与Map
接口主要用于存储元素,而Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素。
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式称为迭代。
Iterator接口的常用方法如下:
* public E next()
:返回迭代的下一个元素。
* public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
package com.wanbangee.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo02 {
public static void main(String[] args) {
// 使用多态方式创建对象
Collection<String> coll = new ArrayList<String>();//定义集合对象
// 添加元素到集合
coll.add("张三");
coll.add("李四");
coll.add("王五");
coll.add("赵六");
coll.add("田七");
//使用迭代器遍历,每个集合对象都有自己的迭代器
Iterator<String> it = coll.iterator();//获取迭代器对象
//泛型指的是迭代出元素的数据类型
while(it.hasNext()) { //判断是否有迭代元素
String s = it.next();//获取迭代出的元素,获取下一个元素
System.out.println(s);
}
}
}
在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
迭代器的实现原理:
当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
集合的遍历除了迭代器之外,还可以使用for循环,但是迭代器的效率更高。
2.增强for
增强for循环(也称for each循环)是JDK1.5以后出来的一个**高级for循环,专门用来遍历数组和集合的。**它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
for(元素的数据类型 变量 : Collection集合or数组名){
//写操作代码
}
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
练习1:遍历数组
public class NBForDemo1 {
public static void main(String[] args) {
int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
System.out.println(a);
}
}
}
练习2:遍历集合
public class NBFor {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("小河神");
coll.add("老河神");
coll.add("神婆");
//使用增强for遍历
for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
System.out.println(s);
}
}
}
增强for循环必须有被遍历的目标。目标只能是Collection或者是数组。增强for仅仅作为遍历操作出现。
package com.wanbangee.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionDemo02 {
public static void main(String[] args) {
List<String> coll = new ArrayList<String>();//定义集合对象
coll.add("张三");
coll.add("李四");
coll.add("王五");
coll.add("赵六");
coll.add("田七");
coll.add("王八");
System.out.println("------------使用迭代器输出集合--------------");
Iterator<String> it = coll.iterator();//获取迭代器对象
while(it.hasNext()) {
String s = it.next();//获取下一个元素
System.out.println(s);
}
System.out.println("---------使用增强型for循环输出集合-------------");
for (String string : coll) {
System.out.println(string);
}
System.out.println("----------普通for循环---------------------");
for (int i = 0; i < coll.size(); i++) {
String s = coll.get(i);//获取指定位置的元素,Collection中并没有此方法,而是子接口添加上的
System.out.println(s);
}
}
}
四、List接口
List集合可以存放相同元素,而且也是按照加入的顺序有序存放,集合中的每个元素都有其对应的顺序的索引,索引从0开始,到size()-1结束,List接口常用的实现类有三个:ArrayList,LinkedList,Vector。
List接口特点:
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、 22、33的顺序完成的)。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List接口中常用方法:
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:
1.public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。
2.public E get(int index) :返回集合中指定位置的元素。
3.public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
4.public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回的是更新前的元素
package com.wanbangee.collection;
import java.util.ArrayList;
import java.util.List;
public class ListDemo01 {
public static void main(String[] args) {
List<Person> persons = new ArrayList<>(); //主集合
persons.add(new Person("张大炮",20)); //向集合中添加元素 Collection接口中方法
persons.add(new Person("张益达",20));//向集合中添加元素 Collection接口中方法
persons.add(new Person("snake",20));//向集合中添加元素 Collection接口中方法
List<Person> personsC = new ArrayList<>();//用来作为方法参数的集合
personsC.add(new Person("张大炮",20));//向集合中添加元素 Collection接口中方法
personsC.add(new Person("张益达",20));//向集合中添加元素 Collection接口中方法
System.out.println("---------原始的persons集合----------");
for (Person person : persons) {
System.out.println(person);
}
persons.addAll(personsC);//将personsC集合中的所有元素添加到persons集合,Collection提供的方法
System.out.println("---------添加personsC集合之后的persons----------");
for (Person person : persons) {
System.out.println(person);
}
System.out.println(persons.contains(new Person("张益达",20)));//是否存在指定的元素,Collection接口中提供的方法
System.out.println(persons.containsAll(personsC));//persons中是否存在personsC集合中所有的元素,Collection接口中提供的方法
System.out.println(persons.equals(new Object()));//对象与对象的比较,Collection接口中的方法
//persons.clear();//移除集合中的所有元素 Collection接口中提供的方法
System.out.println(persons.isEmpty());//集合是否没有元素,没有元素返回true,有则返回false Collection接口中提供的方法
//size()方法获取集合的元素个数
System.out.println(persons.size() == 0);//判断集合中是否存在元素,不存在返回true,存在返回false Collection接口中提供的方法
// persons.remove(new Person("张益达",20));//移除指定元素【只移除最早加入集合的对象】 Collection接口中提供的方法
// System.out.println("---------移除指定元素后的persons----------");
// for (Person person : persons) {
// System.out.println(person);
// }
//
// persons.removeAll(personsC);//将personsC集合中存在的元素 从 persons集合中移除【移除所有匹配的对象】,Collection接口中提供的方法
// System.out.println("---------移除指定personsC集合中元素后的persons----------");
// for (Person person : persons) {
// System.out.println(person);
// }
persons.retainAll(personsC);//persons集合保留和personsC相同元素,其他都移除掉,Collection接口提供的方法
System.out.println("---------persons.retainAll(personsC)后的persons----------");
for (Person person : persons) {
System.out.println(person);
}
System.out.println("---------persons转换成数组后的数组输出----------");
Object pers[] = persons.toArray();//将集合转换成数组,Collection接口中提供的方法
for (Object person : pers) {
System.out.println(person);
}
persons.add(3, new Person("吕小布",21));//在集合的指定索引位置添加指定元素,List接口提供的方法
System.out.println("---------在指定位置添加指定元素后的persons----------");
for (Person person : persons) {
System.out.println(person);
}
List<Person> persons3 = new ArrayList<Person>();
persons3.add(new Person("曾贤儿",22));
persons3.add(new Person("胡一菲",21));
persons.addAll(2,persons3);//将集合中的元素 添加到persons集合的指定索引位置,List接口提供的方法
System.out.println("---------在指定位置添加指定元素后的persons----------");
for (Person person : persons) {
System.out.println(person);
}
System.out.println("---------普通循环:输出persons集合----------");
for (int i = 0;i < persons.size();i++) {
Person person = persons.get(i);//获取集合中指定索引位置的元素,List接口提供的方法
System.out.println(person);
}
System.out.println(persons.indexOf(new Person("张益达",20)));//集合中是否存在指定的元素,有则返回第一次出现的索引,否则返回-1 List接口提供的方法
Person person1 = new Person("张益达",20);
if(persons.indexOf(person1) != -1) {//表示存在,实际开发中,indexOf 结合if语句使用较多
//...
}else {
//...
}
System.out.println(persons.lastIndexOf(new Person("张益达",20)));集合中是否存在指定的元素,有则返回最后一次出现的索引,否则返回-1 List接口提供的方法
persons.remove(6);//移除指定位置的元素,List集合中提供的方法
System.out.println("---------移除指定位置的元素后的persons----------");
for (Person person : persons) {
System.out.println(person);
}
persons.set(3, new Person("关谷",19));//在指定的索引位置设置为指定元素【相当于修改】,List集合中提供的方法
System.out.println("---------移除指定位置的元素后的persons----------");
for (Person person : persons) {
System.out.println(person);
}
List<Person> personsNew = persons.subList(2, 4);//集合的截取,从第formIndex 到 toIndex-1 结束,List集合中提供的方法
System.out.println("---------输出截取后的personsNew集合----------");
for (Person person : personsNew) {
System.out.println(person);
}
}
}
实现方式一:ArrayList集合
1.java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢,查找快,可以通过索引查找。
2.线程不安全的,Vector是线程安全的,但是即使在多线程的情况下,也不建议使用Vector。
3.占用内存为元素本身的1.5倍
数组长度是有限的,而ArrayList能存放任意数量的对象,长度不受限制,它是怎么实现的呢?
底层采用数组扩容原理,再定义个更长的数组,把旧的内容复制过来,新的内容依次往后放。
旧的数组被垃圾回收掉,ArrayList的Object数组初始化长度为10,如果需要存放11个对象,就会定义新的长度更大的数组,并将原数组内容和新的元素一起加入到新数组内,源码如下:>>1右移一位,就是除2,新数组为15,新数组长度为:旧数组长度+旧数组长度一半。
增删改效率低,因为要增删改的元素后面所有元素都跟着要动,增删改原理也是数组拷贝。
下面是移除的源码:数组拷贝。
自定义实现个ArrayList集合:
package com.wanbangee.collection;
/*
* 自定义实现一个ArrayList,体会底层原理
*/
public class MppArrayList {
private Object[] elementData;//核心数组,存储内容
private int size;//表示数组中有多少个元素
private static final int DEFALT_CAPACITY = 10;
public MppArrayList() {
elementData = new Object[DEFALT_CAPACITY];//默认数组长度为10
}
public MppArrayList(int capacity) {//传数字,自定义数组长度
elementData = new Object[capacity];
}
public void add(Object obj) {
elementData[size++] = obj;
}
@Override
public String toString() {
//return elementData.toString();
StringBuilder sb = new StringBuilder();
sb.append("[");
// for (Object obj : elementData) {
// sb.append(obj + ",");
// }
for(int i=0;i<size;i++) {
sb.append(elementData[i]+",");
}
//sb.append("]");
sb.setCharAt(sb.length()-1, ']');
//方法setCharAt里第二个参数是char,得用单引号括起单个字符,不能用双引号括起字符串
return sb.toString();
}
//打印的是类名加地址,希望可视化重写toString方法
public static void main(String[] args) {
MppArrayList m1 = new MppArrayList(20);
m1.add("aa");
m1.add("bb");
System.out.println(m1);
}
}
增加泛型:
package com.wanbangee.collection;
/*
* 自定义实现一个ArrayList,体会底层原理
* 增加泛型
*/
public class MppArrayList<E> {
private Object[] elementData;//核心数组,存储内容
private int size;//表示数组中有多少个元素
private static final int DEFALT_CAPACITY = 10;
public MppArrayList() {
elementData = new Object[DEFALT_CAPACITY];//默认数组长度为10
}
public MppArrayList(int capacity) {//传数字,自定义数组长度
elementData = new Object[capacity];
}
public void add(E element) {
elementData[size++] = element;
}
@Override
public String toString() {
//return elementData.toString();
StringBuilder sb = new StringBuilder();
sb.append("[");
// for (Object obj : elementData) {
// sb.append(obj + ",");
// }
for(int i=0;i<size;i++) {
sb.append(elementData[i]+",");
}
//sb.append("]");
sb.setCharAt(sb.length()-1, ']');
//方法setCharAt里第二个参数是char,得用单引号括起单个字符,不能用双引号括起字符串
return sb.toString();
}
//打印的是类名加地址,希望可视化重写toString方法
public static void main(String[] args) {
MppArrayList m1 = new MppArrayList(20);
m1.add("aa");
m1.add("bb");
System.out.println(m1);
}
}
增加数组扩容:
package com.wanbangee.collection;
/*
* 自定义实现一个ArrayList,体会底层原理
* 增加数组扩容
*/
public class MppArrayList<E> {
private Object[] elementData;//核心数组,存储内容
private int size;//表示数组中有多少个元素
private static final int DEFALT_CAPACITY = 10;
public MppArrayList() {
elementData = new Object[DEFALT_CAPACITY];//默认数组长度为10
}
public MppArrayList(int capacity) {//传数字,自定义数组长度
elementData = new Object[capacity];
}
//扩容就是数组拷贝,先创建个大数组,把旧数组内容拷贝到新数组来
public void add(E element) {
//什么时候扩容?
if(size == elementData.length) {
//怎么扩容?(扩容操作)
//Object[] newArray = new Object[elementData.length<<1];
//左移1就是*2,扩大2倍,扩容要占空间的不要随意扩很大
Object[] newArray = new Object[elementData.length+(elementData.length>>1)];
//写一个功能调试一次,不要等所有功能完成再调试那样很打断点或打印输出
//Object[] newArray = new Object[elementData.length+elementData.length>>1];
//这里是优先级的问题,+的优先级高于位运算符,10+10再除2容量还是10没变
//>>1右移一位,就是除2,新数组为15,扩容大小参照JDK源码
//拷贝,用系统提供的底层函数:原数组,从哪个位置拷贝从0索引位置开始拷贝,考到哪个数组新数组,考到新数组0位置开始考,拷贝所有的元素
System.arraycopy(elementData, 0, newArray, 0, elementData.length);
elementData = newArray; //把新数组的引用地址赋值给它,老数组内容被垃圾回收掉
}
elementData[size++] = element;
}
@Override
public String toString() {
//return elementData.toString();
StringBuilder sb = new StringBuilder();
sb.append("[");
// for (Object obj : elementData) {
// sb.append(obj + ",");
// }
for(int i=0;i<size;i++) {
sb.append(elementData[i]+",");
}
//sb.append("]");
sb.setCharAt(sb.length()-1, ']');
//方法setCharAt里第二个参数是char,得用单引号括起单个字符,不能用双引号括起字符串
return sb.toString();
}
//打印的是类名加地址,希望可视化重写toString方法
public static void main(String[] args) {
MppArrayList m1 = new MppArrayList(20);
for(int i=0;i<30;i++) {
m1.add("666" +i); //会报错,数组长度不够,得扩容
}
System.out.println(m1);
}
}
增加get和set方法
增加数组边界检查
package com.wanbangee.collection;
/*
* 自定义实现一个ArrayList,体会底层原理
* 增加数组扩容
* 增加get和set方法
* 增加数组边界检查
*/
public class MppArrayList<E> {
private Object[] elementData;//核心数组,存储内容
private int size;//表示数组中有多少个元素
private static final int DEFALT_CAPACITY = 10;
public MppArrayList() {
elementData = new Object[DEFALT_CAPACITY];//默认数组长度为10
}
public MppArrayList(int capacity) {//传数字,自定义数组长度
if(capacity<0) { //增加容器容量判断,让程序更加严谨
throw new RuntimeException("容器的容量不能为负数");
}else if(capacity == 0) {
elementData = new Object[DEFALT_CAPACITY];
}else {
elementData = new Object[capacity];
}
}
//扩容就是数组拷贝,先创建个大数组,把旧数组内容拷贝到新数组来
public void add(E element) {
//什么时候扩容?
if(size == elementData.length) {
//怎么扩容?(扩容操作)
//Object[] newArray = new Object[elementData.length<<1];
//左移1就是*2,扩大2倍,扩容要占空间的不要随意扩很大
Object[] newArray = new Object[elementData.length+(elementData.length>>1)];
//写一个功能调试一次,不要等所有功能完成再调试那样很打断点或打印输出
//Object[] newArray = new Object[elementData.length+elementData.length>>1];
//这里是优先级的问题,+的优先级高于位运算符,10+10再除2容量还是10没变
//>>1右移一位,就是除2,新数组为15,扩容大小参照JDK源码
//拷贝,用系统提供的底层函数:原数组,从哪个位置拷贝从0索引位置开始拷贝,考到哪个数组新数组,考到新数组0位置开始考,拷贝所有的元素
System.arraycopy(elementData, 0, newArray, 0, elementData.length);
elementData = newArray; //把新数组的引用地址赋值给它,老数组内容被垃圾回收掉
}
elementData[size++] = element;
}
public E get(int index) {
checkRange(index);
return (E) elementData[index];
}
public void set(E element,int index) {
checkRange(index);
elementData[index] = element;
}
public void checkRange(int index) {
//检查索引是否合法[0,size)
if(index<0 || index>size-1) {
//不合法
//System.out.println("给个提示,索引不合法");
throw new RuntimeException("给个提示,索引不合法"+index);
}
}
@Override
public String toString() {
//return elementData.toString();
StringBuilder sb = new StringBuilder();
sb.append("[");
// for (Object obj : elementData) {
// sb.append(obj + ",");
// }
for(int i=0;i<size;i++) {
sb.append(elementData[i]+",");
}
//sb.append("]");
sb.setCharAt(sb.length()-1, ']');
//方法setCharAt里第二个参数是char,得用单引号括起单个字符,不能用双引号括起字符串
return sb.toString();
}
//打印的是类名加地址,希望可视化重写toString方法
public static void main(String[] args) {
MppArrayList m1 = new MppArrayList(20);
for(int i=0;i<30;i++) {
m1.add("666" +i); //会报错,数组长度不够,得扩容
}
m1.set("555", 10);
//m1.set("555", -10);
//System.out.println(m1.get(30));
System.out.println(m1);
System.out.println(m1.get(10));
}
}
增加remove方法,remove方法的2种实现,根据索引移除,根据内容移除
增加size方法、isEmpty方法
package com.wanbangee.collection;
/*
* 自定义实现一个ArrayList,体会底层原理
* 增加数组扩容
* 增加get和set方法
* 增加数组边界检查
* 增加remove方法,remove方法的2种实现,根据索引移除,根据内容移除
* 增加size方法、isEmpty方法
*/
public class MppArrayList<E> {
private Object[] elementData;//核心数组,存储内容
private int size;//表示数组中有多少个元素
private static final int DEFALT_CAPACITY = 10;
public MppArrayList() {
elementData = new Object[DEFALT_CAPACITY];//默认数组长度为10
}
public MppArrayList(int capacity) {//传数字,自定义数组长度
if(capacity<0) { //增加容器容量判断,让程序更加严谨
throw new RuntimeException("容器的容量不能为负数");
}else if(capacity == 0) {
elementData = new Object[DEFALT_CAPACITY];
}else {
elementData = new Object[capacity];
}
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0?true:false;
}
//扩容就是数组拷贝,先创建个大数组,把旧数组内容拷贝到新数组来
public void add(E element) {
//什么时候扩容?
if(size == elementData.length) {
//怎么扩容?(扩容操作)
//Object[] newArray = new Object[elementData.length<<1];
//左移1就是*2,扩大2倍,扩容要占空间的不要随意扩很大
Object[] newArray = new Object[elementData.length+(elementData.length>>1)];
//写一个功能调试一次,不要等所有功能完成再调试那样很打断点或打印输出
//Object[] newArray = new Object[elementData.length+elementData.length>>1];
//这里是优先级的问题,+的优先级高于位运算符,10+10再除2容量还是10没变
//>>1右移一位,就是除2,新数组为15,扩容大小参照JDK源码
//拷贝,用系统提供的底层函数:原数组,从哪个位置拷贝从0索引位置开始拷贝,考到哪个数组新数组,考到新数组0位置开始考,拷贝所有的元素
System.arraycopy(elementData, 0, newArray, 0, elementData.length);
elementData = newArray; //把新数组的引用地址赋值给它,老数组内容被垃圾回收掉
}
elementData[size++] = element;
}
public E get(int index) {
checkRange(index);
return (E) elementData[index];
}
public void set(E element,int index) {
checkRange(index);
elementData[index] = element;
}
public void checkRange(int index) {
//检查索引是否合法[0,size)
if(index<0 || index>size-1) {
//不合法
//System.out.println("给个提示,索引不合法");
throw new RuntimeException("给个提示,索引不合法"+index);
}
}
public void remove(E element) {
//element,将它和所有元素挨个比较,获得第一个比较为true的返回结果
for(int i=0;i<size;i++) {
if(element.equals(get(i))) {//容器中所有的比较操作都是用equals()方法而不是==
//将该元素从此处移除
remove(i);
}
}
}
public void remove(int index) {
//a,b,c,d,e,f,g,h
//a,b,c,e,f,g,h 移除操作也是数组拷贝
int numMoved = elementData.length-index-1;
if(numMoved>0) {
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
// elementData[size-1] = null;
// size--;
elementData[--size] = null;
}
@Override
public String toString() {
//return elementData.toString();
StringBuilder sb = new StringBuilder();
sb.append("[");
// for (Object obj : elementData) {
// sb.append(obj + ",");
// }
for(int i=0;i<size;i++) {
sb.append(elementData[i]+",");
}
//sb.append("]");
sb.setCharAt(sb.length()-1, ']');
//方法setCharAt里第二个参数是char,得用单引号括起单个字符,不能用双引号括起字符串
return sb.toString();
}
//打印的是类名加地址,希望可视化重写toString方法
public static void main(String[] args) {
MppArrayList m1 = new MppArrayList(20);
for(int i=0;i<30;i++) {
m1.add("666" +i); //会报错,数组长度不够,得扩容
}
m1.set("555", 10);
//m1.set("555", -10);
//System.out.println(m1.get(30));
System.out.println(m1);
System.out.println(m1.get(10));
m1.remove(3);//按索引移除
m1.remove("6665");//按内容移除
System.out.println(m1);
System.out.println(m1.size);
System.out.println(m1.isEmpty());
}
}
实现方式二:LinkedList集合
java.util.LinkedList 集合数据存储的结构是链表结构(双向链表)。元素新增和删除的效率较高,因为只需要修改指针,不需要移动数据,但是查找的效率较低,因为必须从链表的头开始查找。线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有2个指针,分别指向前一个节点和后一个节点。所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
Node节点(Entry单元格),每个节点由3部分组成:上一节点、自己数据、下一节点。如删a2,让a1指向a3,a3指向a1,不涉及大量元素拷贝。
LinkedList中额外的提供了基于链表的操作方法:
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的后一个元素。
public boolean isEmpty() :如果列表不包含元素,则返回true。
自定义一个链表:
package com.wanbangee.collection;
public class Node {
Node previous;//上一节点
Node next;//下一节点
Object element;//元素数组
public Node(Node previous, Node next, Object element) {
super();
this.previous = previous;
this.next = next;
this.element = element;
}
public Node(Object element) {
super();
this.element = element;
}
}
package com.wanbangee.collection;
/**
* 自定义一个链表
* @author teacher
*/
public class MyLinkedList {
private Node first;
private Node last;
private int size;
//['a','b','c']
public void add(Object obj) {
Node node = new Node(obj);
if(first == null) {
// node.previous = null;
// node.next = null;
first = node;
last = node;
}else {
node.previous = last;
node.next = null;
last.next = node;
last = node;
}
size++;
}
@Override
public String toString() {
//[a,b,c] first==a last=c
//a,b,c
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while(temp != null) {
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
}
}
增加get查询、节点遍历:
package com.wanbangee.collection;
/**
* 自定义一个链表
* 增加get查询、节点遍历
* @author teacher
*
*/
public class MyLinkedList {
private Node first;
private Node last;
private int size;
//['a','b','c'] 2 //first.next.next
public Object get(int index) {
if(index<0 || index>size-1) {
throw new RuntimeException("索引数字不合法:"+index);
}
// Node temp = first;
// for(int i=0;i<index;i++) {
// temp = temp.next;//从头找到尾效率低
// }
Node temp = null;
if(index<=(size>>1)) {//size>>1 相当于除以2
temp = first;
for(int i=0;i<index;i++) {
temp = temp.next;
}
}else {
temp = last;
for(int i=size-1;i>index;i--) {
temp = temp.previous;
}
}
return temp.element;
}
//['a','b','c']
public void add(Object obj) {
Node node = new Node(obj);
if(first == null) {
// node.previous = null;
// node.next = null;
first = node;
last = node;
}else {
node.previous = last;
node.next = null;
last.next = node;
last = node;
}
size++;
}
@Override
public String toString() {
//[a,b,c] first==a last=c
//a,b,c
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while(temp != null) {
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
System.out.println(list.get(2));
}
}
remove移除节点:
package com.wanbangee.collection;
/**
* 自定义一个链表
* 增加get查询、节点遍历
* remove移除节点
* 如删a2,让a1指向a3,a3指向a1,不涉及大量元素拷贝。
*/
public class MyLinkedList {
private Node first;
private Node last;
private int size;
public void remove(int index) {
//根据索引返回一个节点
Node temp = getNode(index);
if(temp!=null) {
Node up = temp.previous;
Node down = temp.next;
if(up!=null) {
up.next = down;
}
if(down!=null) {
down.previous = up;
}
//被删除的元素是第一个元素时
if(index==0) {
first = down;
}
//被删除的元素是最后一个元素时
if(index == size-1) {
last = up;
}
size--;
}
}
public Node getNode(int index) {
Node temp = null;
if(index<=(size>>1)) {//size>>1 相当于除以2
temp = first;
for(int i=0;i<index;i++) {
temp = temp.next;
}
}else {
temp = last;
for(int i=size-1;i>index;i--) {
temp = temp.previous;
}
}
return temp;
}
//['a','b','c'] 2 //first.next.next
public Object get(int index) {
if(index<0 || index>size-1) {
throw new RuntimeException("索引数字不合法:"+index);
}
// Node temp = first;
// for(int i=0;i<index;i++) {
// temp = temp.next;//从头找到尾效率低
// }
Node temp = getNode(index);
return temp !=null?temp.element:null;
}
//['a','b','c']
public void add(Object obj) {
Node node = new Node(obj);
if(first == null) {
// node.previous = null;
// node.next = null;
first = node;
last = node;
}else {
node.previous = last;
node.next = null;
last.next = node;
last = node;
}
size++;
}
@Override
public String toString() {
//[a,b,c] first==a last=c
//a,b,c
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while(temp != null) {
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
//System.out.println(list.get(2));
list.remove(0);
System.out.println(list);
}
}
插入节点,是否为空,首节点怎么处理,末节点怎么处理:
package com.wanbangee.collection;
/**
* 自定义一个链表
* 增加get查询、节点遍历
* remove移除节点
* 如删a2,让a1指向a3,a3指向a1,不涉及大量元素拷贝。
* 插入节点,是否为空,首节点怎么处理,末节点怎么处理
*/
public class MyLinkedList {
private Node first;
private Node last;
private int size;
public void add(int index,Object obj) {
//是否为空,首节点怎么处理,末节点怎么处理
Node newNode = new Node(obj);
Node temp = getNode(index);
if(temp!=null) {
Node up = temp.previous;
up.next = newNode;
newNode.previous = up;
newNode.next = temp;
temp.previous = newNode;
}
}
public void remove(int index) {
//根据索引返回一个节点
Node temp = getNode(index);
if(temp!=null) {
Node up = temp.previous;
Node down = temp.next;
if(up!=null) {
up.next = down;
}
if(down!=null) {
down.previous = up;
}
//被删除的元素是第一个元素时
if(index==0) {
first = down;
}
//被删除的元素是最后一个元素时
if(index == size-1) {
last = up;
}
size--;
}
}
public Node getNode(int index) {
Node temp = null;
if(index<=(size>>1)) {//size>>1 相当于除以2
temp = first;
for(int i=0;i<index;i++) {
temp = temp.next;
}
}else {
temp = last;
for(int i=size-1;i>index;i--) {
temp = temp.previous;
}
}
return temp;
}
//['a','b','c'] 2 //first.next.next
public Object get(int index) {
if(index<0 || index>size-1) {
throw new RuntimeException("索引数字不合法:"+index);
}
// Node temp = first;
// for(int i=0;i<index;i++) {
// temp = temp.next;//从头找到尾效率低
// }
Node temp = getNode(index);
return temp !=null?temp.element:null;
}
//['a','b','c']
public void add(Object obj) {
Node node = new Node(obj);
if(first == null) {
// node.previous = null;
// node.next = null;
first = node;
last = node;
}else {
node.previous = last;
node.next = null;
last.next = node;
last = node;
}
size++;
}
@Override
public String toString() {
//[a,b,c] first==a last=c
//a,b,c
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while(temp != null) {
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
//System.out.println(list.get(2));
// list.remove(0);
// System.out.println(list);
list.add(1, "555");
System.out.println(list);
}
}
增加小的封装、增加泛型:
package com.wanbangee.collection;
/**
* 自定义一个链表
* 增加get查询、节点遍历
* remove移除节点
* 如删a2,让a1指向a3,a3指向a1,不涉及大量元素拷贝。
* 插入节点,是否为空,首节点怎么处理,末节点怎么处理
* 增加小的封装、增加泛型
*/
public class MyLinkedList<E> {
private Node first;
private Node last;
private int size;
public void add(int index,E element) {
checkRange(index);
//是否为空,首节点怎么处理,末节点怎么处理
Node newNode = new Node(element);
Node temp = getNode(index);
if(temp!=null) {
Node up = temp.previous;
up.next = newNode;
newNode.previous = up;
newNode.next = temp;
temp.previous = newNode;
}
}
public void remove(int index) {
checkRange(index);
//根据索引返回一个节点
Node temp = getNode(index);
if(temp!=null) {
Node up = temp.previous;
Node down = temp.next;
if(up!=null) {
up.next = down;
}
if(down!=null) {
down.previous = up;
}
//被删除的元素是第一个元素时
if(index==0) {
first = down;
}
//被删除的元素是最后一个元素时
if(index == size-1) {
last = up;
}
size--;
}
}
private Node getNode(int index) {//不希望外部知道设为私有
checkRange(index);
Node temp = null;
if(index<=(size>>1)) {//size>>1 相当于除以2
temp = first;
for(int i=0;i<index;i++) {
temp = temp.next;
}
}else {
temp = last;
for(int i=size-1;i>index;i--) {
temp = temp.previous;
}
}
return temp;
}
//['a','b','c'] 2 //first.next.next
public E get(int index) {
checkRange(index);
// Node temp = first;
// for(int i=0;i<index;i++) {
// temp = temp.next;//从头找到尾效率低
// }
Node temp = getNode(index);
return temp !=null?(E)temp.element:null;
}
private void checkRange(int index) {//不希望外部知道设为私有
if(index<0 || index>size-1) {
throw new RuntimeException("索引数字不合法:"+index);
}
}
//['a','b','c']
public void add(E element) {
Node node = new Node(element);
if(first == null) {
// node.previous = null;
// node.next = null;
first = node;
last = node;
}else {
node.previous = last;
node.next = null;
last.next = node;
last = node;
}
size++;
}
@Override
public String toString() {
//[a,b,c] first==a last=c
//a,b,c
StringBuilder sb = new StringBuilder("[");
Node temp = first;
while(temp != null) {
sb.append(temp.element+",");
temp = temp.next;
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}
public static void main(String[] args) {
MyLinkedList<String> list = new MyLinkedList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
//System.out.println(list.get(2));
// list.remove(0);
// System.out.println(list);
list.add(1, "555");
System.out.println(list);
}
}
实现方式三:Vector,开发中几乎不用
- 线程安全的
- 非常古老,从JDK1.0开始就存在
- 大多数操作和ArrayList相同,区别之处在于Vector是线程安全的
- 当插入,删除的时候总是比较慢,每个元素占用的内存是元素本身的2倍
- 除了继承自Collection中的方法外,还有额外的提供了其他的方法,我们能够用的到的就是枚举。
public Enumeration elements():获得集合枚举对象,迭代器没出现之前都用枚举
线程安全就是排队上车,线程不安全就是一窝蜂上车。
Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全、效率低”。线程安全意味着效率低。(笔试会考,用的少)
如indexOf()方法就增加了synchronized同步标记。
Public synchronized int indexOf(Object o,int index){//代码省略}
如何选用ArrayList(线程不安全),LinkedList(线程不安全),Vector(线程安全)?
1.需要线程安全用Vector
2.不存在线程安全问题,并且查找较多用ArrayList(一般使用它)
3.不存在线程安全问题,增加或删除元素较多用LinkedList
总结:
ArrayList【最常用的list接口实现类】是基于动态数组的结构:对于查找来说,比较方便,可以从数组的任意位置开始查找,通过索引查找,但是对于删除和新增操作来说,比较复杂,因为需要移动位置。线程不安全。
LinkedList是基于双向链表的数据结构:对于查找来说,效率比ArrayList低,因为链表必须从头开始,但是新增和删除效率比ArrayList高,因为LinkedList只需要修改指针,不需要移动数据。线程不安全。
Vector和ArrayList一样,但是出现很早,使用很少,ArrayList非线程安全的,而Vector是线程安全的,Vector的执行的效率比较低,ArrayList集合中元素的占据的内存空间是元素本来大小的1.5倍,而Vector是2倍。相对于更高的操作效率来说,线程安全几乎可以省略,Vector既可以使用迭代器,也可以使用枚举。
五、Set接口
Set接口:
1.Set接口是Collection的子接口,set接口没有提供额外的方法。
2.Set 接口中元素无序,元素不出现重复。
3.Set判断两个对象是否相同不是使用==运算符,而是根据equals方法。
4.Set集合不允许有重复元素,如果试图添加元素的话,会判断是否有存在的元素,没有的则添加,有的话则将添加元素覆盖已有的元素。
Set集合的两个常用实现类,java.util.HashSet 、 java.util.LinkedHashSet(是 java.util.HashSet的子类)。
Set接口大部分都是用HashSet实现类。
Set接口实现类之一:HashSet
java.util.HashSet 是 Set 接口的一个实现类,存储的元素无序不重复。
大多数使用Set集合时都使用这个实现类。java.util.HashSet 底层的实现是一个 java.util.HashMap 支持。
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置(HashSet按Hash算法来存储集合中的元素),因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。
HashSet 特点:
1.不能保证元素的排列顺序。
2.HashSet 不是线程安全的。
3.集合元素可以是null
当向HashSet 集合中存放一个元素时,HashSet 会调用该对象的HashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet 中存储的位置。
HashSet集合判断两个元素相等的标准:两个对象通过HashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一 。
HashSet集合存储数据的结构(哈希表)
哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。 但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈 希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,减少了查找时间。
Set接口实现类之二 :LinkedHashSet
LinkedHashSet是HashSet的子类,它是链表和哈希表组合的一个数据存储结构。有序不重复。
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的,这样就使得元素插入和删除效率比较高,但是查找的效率会低一些,因为每次查找都要从链表头开始。LinkedHashSet插入性能低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
Set set = new LinkedHashSet<>();
Set接口实现类之三 :TreeSet
Sorted接口是Set接口的子接口,而TreeSet接口是SortedSet接口的实现类,TreeSet可以确保集合中的元素处于排序的状态。默认对集合中的元素进行升序排列。
TreeSet实现类提供了额外的操作方法:
public E first() : 取得集合的第一个元素
public E last() :取得集合的最后一个元素
六、Map接口
java.util.Map 接 口,存放有对象映射关系的对象。
Collection 中的集合,向集合中存储元素采用一个个元素的方式存储。
Map 中的集合,元素是成对存在的,每个元素由键与值两部分组成,通过键可以找对所对应的值。
Collection 中的集合称为单列集合, Map 中的集合称为双列集合。
需要注意的是, Map 中的集合不能包含重复的键,值可以重复;键唯一,值可重复,每个键只能对应一个值。
键唯一不能重复,值可重复。如果键重复,则新的覆盖旧的。
Map接口是键值对的最高的父接口,键值对也可以认为是集合,存储数据的每个元素除了值之外,还存在键,是一个key value结构。
Map接口中的操作方法大都是基于键的
Key value
张三 ---------13456789109
李四 ---------13812345678
键唯一,值不唯一可重复,都是无序的,通过键查找值。
Map常用实现类 :常用的HashMap集合(用的多)、LinkedHashMap集合
Map接口实现类之一:HashMap
HashMap:存储数据采用的哈希表结构,哈希表的基本结构是“数组+链表”。元素的存取顺序不能保证一致就是无序。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
- HashMap是Map接口使用频率最高的实现类
- HashMap允许使用null 键和null值,和HashSet一样,不保证存放的顺序
- HashMap如何判断两个key是否相等的标准是,两个key 通过equals方法返回true,通过hashCode方法返回的哈希码值也相等
- HashMap判断两个value的值相等表示是两个value通过equals方法返回为true
哈希表的本质是“数组+链表”,哈希表结合了数组和链表的优势(即查询快、增删效率高)。
1.数组:占用空间连续,寻址容易,查询速度快,但是增加删除效率低。
2.链表:占用空间不连续,寻址困难,查询速度慢,但是增加删除效率高。
Map接口实现之二:LinkedHashMap
LinkedHashMap:存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的 hashCode()方法、equals()方法。 - LinkedHashMap是HashMap的子类
- 和LinkedHashSet类似,使用链表进行迭代,这个时候迭代顺序和插入的顺序一致
Map接口实现之三:TreeMap(底层是红黑二叉树)
- TreeMap是SortedMap接口的实现类,默认根据key进行排序,排序的规则为从小到大。
TreeMap和HashMap实现了接口Map,HashMap效率高于TreeMap,在需要排序的Map时才用TreeMap。
Map接口实现之四:HashTable
- HashTable是个比较古老的Map接口的实现类,是线程安全的
- 与HashMap不同的是,HashTable不允许null作为key和value
- HashTable和HashMap一样,不保证在Map中存放的顺序
- HashTable和HashMap一样,判断key和value的值
HashMap和Hashtable的区别和联系
相同点:
实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用
不同点:
1、Hashtable是早期提供的接口,HashMap是新版JDK提供的接口
2、Hashtable继承Dictionary类,HashMap实现Map接口
3、Hashtable线程安全,HashMap线程非安全
4、Hashtable不允许null值,HashMap允许null值
Map接口的实现之五:Properties
- 使用相对较少,是HashTable的子类
- 可以读取文件中配置的key和value,读取文件的扩展名必须是properties
Map接口中的常用方法
public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。
public V remove(Object key) : 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
public Set keySet() : 获取Map集合中所有的键,存储到Set集合中。
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
void clear():清除键值对中所有的元素
V put(K key,V value):往键值对中添加元素
V remove(Object key):通过key移除键值对中的一个元素
void putAll(Map<? extends K,? extends V> m):往一个键值对中添加一个新的键值对m的所有的元素
default V replace(K key, V value):修改键值对中key对应的value
V get(Object key):通过键取得对应的值
boolean containsKey(Object key):判断key是否存在
boolean containsValue(Object value):判断value是否存在
boolean isEmpty():判断是否存在元素,存在返回false
int size():返回键值对的长度
Set<K> keySet():将键值对中所有的key保存到一个新的Set集合中
Set<Map.Entry<K,V>> entrySet():将键值对中的key-value保存到一个新的Set集合中
Collection<V> values():将键值对中所有的value保存到一个新的集合中
操作方法实例:
package com.wanbangee.mapdmeo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class TestMap {
public static void main(String[] args) {
//Map集合存放的是电话本
Map<String,String> maps = new HashMap<>();
maps.put("张三", "1234567");//往集合中添加元素
maps.put("李四", "2345678");//往集合中添加元素
maps.put("赵六", "3456789");//往集合中添加元素
// maps.remove("李四");//根据key 移除元素
//
// maps.remove("张三", "1234567");//根据 key 和value 移除元素
//重新定义的Map集合
Map<String,String> maps2 = new HashMap<>();
maps2.put("AA", "AA123");
maps2.put("赵六","BB123");
// maps.clear();//清除Map集合中所有的元素
maps.putAll(maps2);//将maps2中的所有的键值对放入 maps
//注意点:如果加入到Map集合中的元素的key 和 已经存在的元素的key是相同的,后加入的覆盖前加入的。
Set<String> keys = maps.keySet();//获取Map中所有的key,并且返回一个set集合
Iterator<String> keyIts = keys.iterator();
while(keyIts.hasNext()) {
String key = keyIts.next();
String value = maps.get(key);
System.out.println(key + "----->" + value);
}
System.out.println("-----------------");
Collection<String> values = maps.values();//获取Map中所有的value,并且返回一个Collection集合
for (String value : values) {
System.out.println(value);
}
System.out.println("-----------------");
Set<Map.Entry<String,String>> set2 = maps.entrySet();//返回Set集合,类型是Map.Entry类型
Iterator<Map.Entry<String,String>> it2 = set2.iterator();
while(it2.hasNext()) {
Map.Entry<String, String> entry = it2.next();
System.out.println(entry.getKey() + "----->" + entry.getValue());
}
System.out.println(maps.containsKey("AA"));//Map集合中的元素 key 是否有AA存在,有则返回true,否则返回false
System.out.println(maps.containsValue("BB123"));//Map集合中的元素 value 是否有BB123存在,有则返回true,否则返回false
}
}
七、Collections工具类
Collections是一个操作Set、List、Map集合的工具类,提供了一系列的静态的方法对集合元素进行排序、查询、修改等等一些列的方法。
public static boolean addAll(Collection c, T… elements) :往集合中添加一些元素。
public static void shuffle(List<?> list) 打乱顺序 :打乱集合顺序。
public static void sort(List list) :将集合中元素按照默认规则排序。
public static void sort(List list,Comparator<? super T> ) :将集合中元素按照指定规则排序。
public static void reverse(List<?> list):反转List集合的元素存储顺序
public static void shuffle(List<?> list) : 对list集合进行随机排序
public static void swap(List<?> list,int i,int j):将list集合的i索引元素和j索引元素调换位置
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll):根据自然排序的规则,返回给定集合中最大的元素
public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll):根据自然排序的规则,返回给定集合中最小的元素
public static int frequency(Collection<?> c, Object o):返回指定元素在指定集合中出现的次数
public static <T> void copy(List<? super T> dest,List<? extends T> src):将src集合中的元素复制到dest集合
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal):使用新的元素newVal 替代list集合中的所有的oldVal
八、枚举类
枚举是迭代器中最古老的版本。对应的接口是Enumeration
常用的操作方法:
boolean hasMoreElements() : 是否有更多的元素
E nextElement() : 获取下一个元素
使用Vector实现类实现List接口:
package com.wanbangee.collection;
import java.util.Enumeration;
import java.util.Vector;
public class ListDemo04 {
public static void main(String[] args) {
Vector<String> list = new Vector<>();//JDK1.5之后,集合框架记住了所有对象的数据类型
list.add("女儿国王"); //往集合中添加元素
list.add("玉兔精");//往集合中添加元素
list.add("嫦娥仙子");//往集合中添加元素
list.add("紫霞仙子");//往集合中添加元素
list.add("青霞仙子");//往集合中添加元素
list.add("至尊宝");//往集合中添加元素
Enumeration<String> en = list.elements();//获得枚举对象
while(en.hasMoreElements()) {
String s = en.nextElement();
System.out.println(s);
}
}
}
九、Arrays工具类
Arrays工具类我们已经用过,Arrays提供的静态方法sort()可以对数组进行排序。
Arrays类中有一个方法:public static List asList(T… a) ,将T类型的数组转换成List集合,泛型为T。
package com.wanbangee.mapdmeo;
import java.util.Arrays;
import java.util.List;
public class ArraysDemo01 {
public static void main(String[] args) {
Person persons[] = new Person[] {new Person("张三",20),new Person("李四",21),new Person("王五",22)};
List<Person> personsList = Arrays.asList(persons);//将数组转换成List集合
System.out.println(personsList);
}
}
总结:
集合中内容是否相同
• 通过equals进行内容比较,而不是==引用比较。
理解面向接口编程
• List list = new ArrayList();
• ArrayList list = new ArrayList();
List 有序重复
List的遍历方法 • for • for-each • Iterator迭代器
Set 无序不重复
Set的遍历方法 • for-each • Iterator迭代器 • 无法使用for进行遍历(因为无序,所以没有get(i))
HashSet、HashMap或Hashtable中对象唯一性判断
• 重写其hashCode()和equals()方法
Map键值对,key-value
键唯一,值不唯一可重复,都是无序的,通过键查找值。
HashMap
• Key无序 唯一 (Set)
• Value 无序 不唯一 (Collection)
容器
Java中集合主要分为两种:Collection和Map。Collection是List和Set接口的父接口;ArrayList和LinkedList是List的实现类;HashSet和TreeSet是Set的实现类;LinkedHashSet是HashSet的子类。HashMap和TreeMap是Map的实现类;LinkedHashMap是HashMap的子类。
Vector和ArrayList的区别和联系:
相同点:
1)实现原理相同—底层都使用数组
2)功能相同—实现增删改查等操作的方法相似
3)都是长度可变的数组结构,很多情况下可以互用
不同点:
1)Vector是早期JDK版本提供,ArrayList是新版本替代Vector的
2)Vector线程安全,ArrayList重速度轻安全,线程非安全。长度需增长时,Vector默认增长一倍,ArrayList增长50%
ArrayList和LinkedList的区别和联系:
HashMap和Hashtable的区别和联系
相同点:
实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用
不同点:
1、Hashtable是早期提供的接口,HashMap是新版JDK提供的接口
2、Hashtable继承Dictionary类,HashMap实现Map接口
3、Hashtable线程安全,HashMap线程非安全
4、Hashtable不允许null值,HashMap允许null值
HashSet的使用和原理(hashCode()和equals())
1)哈希表的查询速度特别快,时间复杂度为O(1)。
2)HashMap、Hashtable、HashSet这些集合采用的是哈希表结构,需要用到hashCode哈希码,hashCode是一个整数值。
3)系统类已经覆盖了hashCode方法 自定义类如果要放入hash类集合,必须重写hashcode。如果不重写,调用的是Object的hashcode,而Object的hashCode实际上是地址。
4)向哈希表中添加数据的原理:当向集合Set中增加对象时,首先集合计算要增加对象的hashCode码,根据该值来得到一个位置用来存放当前对象,如在该位置没有一个对象存在的话,那么集合Set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,在进行一次散列,将该对象放到散列后计算出的新地址里。如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。
5)在哈希表中判断两个元素是否重复要使用到hashCode()和equals()。hashCode决定数据在表中的存储位置,而equals判断是否存在相同数据。
6) Y=K(X) :K是函数,X是哈希码,Y是地址
TreeSet的原理和使用(Comparable和comparator)
1)TreeSet集合,元素不允许重复且有序(自然顺序)
2)TreeSet采用树结构存储数据,存入元素时需要和树中元素进行对比,需要指定比较策略。
3)可以通过Comparable(外部比较器)和Comparator(内部比较器)来指定比较策略,实现了Comparable的系统类可以顺利存入TreeSet。自定义类可以实现Comparable接口来指定比较策略。
4)可创建Comparator接口实现类来指定比较策略,并通过TreeSet构造方法参数传入。这种方式尤其对系统类非常适用。
集合和数组的比较(为什么引入集合)
数组不是面向对象的,存在明显的缺陷,集合完全弥补了数组的一些缺点,比数组更灵活更实用,可大大提高软件的开发效率而且不同的集合框架类可适用于不同场合。具体如下:
1)数组的效率高于集合类.
2)数组能存放基本数据类型和对象,而集合类中只能放对象。
3)数组容量固定且无法动态改变,集合类容量动态改变。
4)数组无法判断其中实际存有多少元素,length只告诉了array的容量。
5)集合有多种实现方式和不同的适用场合,而不像数组仅采用顺序表方式。
6)集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性调用即可实现各种复杂操作,大大提高软件的开发效率。
Collection和Collections的区别
1)Collection是Java提供的集合接口,存储一组不唯一,无序的对象。它有两个子接口List和Set。
2)Java中还有一个Collections类,专门用来操作集合类 ,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
HashMap与Hashtable实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用
两者的主要区别如下:
1、Hashtable是早期JDK提供的接口,HashMap是新版JDK提供的接口
2、Hashtable继承Dictionary类,HashMap实现Map接口
3、Hashtable线程安全,HashMap线程非安全
4、Hashtable不允许null值,HashMap允许null值
HashSet与HashMap的区别:
1、HashSet底层是采用HashMap实现的。HashSet 的实现比较简单,HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。
2、HashMap的key就是放进HashSet中对象,value是Object类型的。
3、当调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的那个对象,该行的value就是一个Object类型的常量
- Map的实现类有HashMap,LinkedHashMap,TreeMap
- HashMap是有无序的,LinkedHashMap和TreeMap都是有序的(LinkedHashMap记录了添加数据的顺序;TreeMap默认是自然升序)
- LinkedHashMap底层存储结构是哈希表+链表,链表记录了添加数据的顺序
- TreeMap底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性
- LinkedHashMap有序性能比较高,因为底层数据存储结构采用的哈希表
TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会 回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections 工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是是通过接口注入比较元素大小的算法,也是对回调模式的应用。
分析:Java中的java.util.Map的实现类
1、HashMap
2、Hashtable
3、LinkedHashMap
4、TreeMap
说出ArrayList、Vector、LinkedList 的存储性能和特性?
ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized 方法(线程安全),通常性能上较ArrayList 差,而LinkedList 使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,其实对内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。Vector属于遗留容器(早期的JDK中使用的容器,除此之外Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),现在已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果需要多个线程操作同一个容器,那么可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这其实是装潢模式最好的例子,将已有对象传入另一个类的构造器中创建新的对象来增加新功能)。
补充:遗留容器中的Properties类和Stack类在设计上有严重的问题,Properties是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个Hashtable并将其两个泛型参数设置为String类型,但是Java API中的Properties直接继承了Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是HAS-A关系而不是IS-A关系,另一方面容器都属于工具类,继承工具类本身就是一个错误的做法,使用工具类最好的方式是HAS-A关系(关联)或USE-A关系(依赖) 。同理,Stack类继承Vector也是不正确的。
List、Map、Set 三个接口,存取元素时,各有什么特点?
List以特定索引来存取元素,可有重复元素。
Set不能存放重复元素(用对象的equals()方法来区分元素是否重复) 。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。Set和Map容器都有基于哈希存储和排序树(红黑树)的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。
TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型 (需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是是通过接口注入比较元素大小的算法,也是对回调模式的应用。