集合和数组中存放的都是对象的引用。html
看到array,就要想到角标。java
看到link,就要想到first,last。能够保证存入的有序性。算法
看到hash,就要想到hashCode,equals.apache
看到tree,能够按顺序进行排列,就要想到两个接口。Comparable(集合中元素实现这个接口,元素自身具有可比性),Comparator(比较器,传入容器构造方法中,容器具有可比性)。基于红黑树实现。编程
数组的长度是固定的,能够经过扩容扩大数组的长度,好比:api
package loader; import java.util.Arrays; public class Client { public static void main(String[] args) { Object[] objects = new Object[2]; System.out.println(objects.length); objects[0] = 1; objects[1] = 2; System.out.println(Arrays.toString(objects)); // 若是不扩容直接存入第三个值报数组越界(扩容方法一) objects = Arrays.copyOf(objects, 3); objects[2] = 3; System.out.println(Arrays.toString(objects)); // System.arraycopy(原数组名,起始下标,新数组名,起始下标,复制长度);(扩容方法二) Object[] objects2 = new Object[4]; System.arraycopy(objects, 0, objects2, 0, 3); objects2[3] = 4; System.out.println(Arrays.toString(objects2)); } }
结果:数组
2
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]安全
解释: (数组扩容的两种方法在org.apache.commons.lang.ArrayUtils普遍使用)网络
(1) 扩容方法一System.arraycopy解释数据结构
System.arraycopy(objects, 0, objects2, 0, 3); 是一个native方法,是将objects数组从下标0开始复制到objects2数组的下标0开始,复制3个数据(至关于所有复制objects)
/* * @param src the source array. * @param srcPos starting position in the source array. * @param dest the destination array. * @param destPos starting position in the destination data. * @param length the number of array elements to be copied. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception ArrayStoreException if an element in the <code>src</code> * array could not be stored into the <code>dest</code> array * because of a type mismatch. * @exception NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
(2) Arrays.copyOf (objects, 3)是至关于复制原来objects数组到一个新数组中,新数组的长度为3。查看源码内部也是调用System.arrayCopy, 以下:
public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
补充:class.getComponentType 获取的是数组的原始数据类型,而后经过反射的Array工具类能够建立1个数组对象
class.getComponentType 源码以下:
/** * Returns the {@code Class} representing the component type of an * array. If this class does not represent an array class this method * returns null. * * @return the {@code Class} representing the component type of this * class if this class is an array * @see java.lang.reflect.Array * @since JDK1.1 */ public native Class<?> getComponentType();
Array工具类的全部方法以下: (能够改变或获取数据对应下标的值)===结合apache的Arrayutils能够知足对数组的基本操做
例如:
package loader; import java.lang.reflect.Array; public class Constants { public static void main(String[] args) { String[] strings = { "1", "2" }; System.out.println(strings.getClass().getComponentType()); String[] newInstance = (String[]) Array.newInstance(strings.getClass().getComponentType(), 3); System.out.println(newInstance); System.out.println(newInstance.length); } }
结果:
class java.lang.String
[Ljava.lang.String;@15db9742
3
非数组对象调用getComponentType的返回值是null,直接获取到数组的class不能调用newInstance建立对象,没有构造方法(init是调用初始化方法)。
public static void main(String[] args) throws InstantiationException, IllegalAccessException { // 非数组对象获取不到原始数据类型 System.out.println(Object.class.getComponentType()); String[] strings = { "1", "2" }; String[] newInstance = strings.getClass().newInstance(); }
结果:报错以下
Exception in thread "main" java.lang.InstantiationException: [Ljava.lang.String;
at java.lang.Class.newInstance(Class.java:427)
at loader.Constants.main(Constants.java:10)
Caused by: java.lang.NoSuchMethodException: [Ljava.lang.String;.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 1 more
null
基于上面的理解建立的扩容工具类:
package loader; import java.lang.reflect.Array; import java.util.Arrays; public class Client { public static void main(String[] args) throws InstantiationException, IllegalAccessException { Object[] objects = new Object[2]; objects[0] = 1; objects[1] = 2; objects = expandCapacity(objects, 4); System.out.println(Arrays.toString(objects)); objects = expandCapacity(objects, 1); System.out.println(Arrays.toString(objects)); } private static <U> U[] expandCapacity(U[] objects, int newLength) { Class<?> componentType = objects.getClass().getComponentType(); U[] newInstance = (U[]) Array.newInstance(componentType, newLength); System.arraycopy(objects, 0, newInstance, 0, Math.min(objects.length, newLength)); return newInstance; } }
结果:
[1, 2, null, null]
[1]
存储对象的容器,面向对象语言对事物的体现都是以对象的形式,因此为了方便对多个对象的操做,存储对象,集合是存储对象最经常使用的一种方式。
集合的出现就是为了持有对象。集合中能够存储任意类型的对象, 并且长度可变。在程序中有可能没法预先知道须要多少个对象, 那么用数组来装对象的话, 长度很差定义, 而集合解决了这样的问题。
数组和集合类都是容器
数组长度是固定的,集合长度是可变的。数组中能够存储基本数据类型,集合只能存储对象。数组中存储数据类型是单一的,集合中能够存储任意类型的对象。
集合类的特色
用于存储对象,长度是可变的,能够存储不一样类型的对象。
存储类型单一的数据容器,操做复杂(数组一旦声明好不可变)CRUD
集合作什么
1:将对象添加到集合
2:从集合中删除对象
3: 从集合中查找一个对象
4:从集合中修改一个对象就是增删改查
注意:集合和数组中存放的都是对象的引用而非对象自己
Java工程师对不一样的容器进行了定义,虽然容器不一样,可是仍是有一些共性能够抽取最后抽取了一个顶层接口,那么就造成了一个集合框架。如何学习呢?固然是从顶层学起,顶层里边具备最共性,最基本的行为。具体的使用,就要选择具体的容器了。为何? 由于不断向上抽取的东西有多是不能建立对象的.抽象的可能性很大,而且子类对象的方法更多一些. 因此是看顶层,建立底层。那么集合的顶层是什么呢 叫作Collection
集合框架体系
---|Collection: 单列集合 ---|List: 有存储顺序, 可重复 ---|ArrayList: 数组实现, 查找快, 增删慢 因为是数组实现, 在增和删的时候会牵扯到数组增容, 以及拷贝元素. 因此慢。数组是能够直接按索引查找, 因此查找时较快。初始大小是10,增加为原来的1.5倍 ---|LinkedList: 双向链表实现, 增删快, 查找慢 因为链表实现, 增长时只要让前一个元素记住本身就能够, 删除时让前一个元素记住后一个元素, 后一个元素记住前一个元素. 这样的增删效率较高但查询时须要一个一个的遍历, 因此效率较低。 ---|Vector: 和ArrayList原理相同, 但线程安全, 效率略低 和ArrayList实现方式相同, 但考虑了线程安全问题, 因此效率略低。初始大小是10,增加为原来的2倍。 ---|Set: 无存储顺序, 不可重复。内部都是维护了一个map用于存储数据,咱们add进去的内容都是做为map的key,value是一个定值Object。 ---|HashSet--内部维护一个map,咱们存放的信息放在key,value就是一个固定的Object。HashMap的key不能够重复(阅读源码的put方法,key重复会替换value值),因此其set不能够重复(容许存null,且放在第一个位置)。当为HashSet的时候map实际类型为HashMap,当为LinkedHashSet的时候map为LinkedHashMap。 ---|TreeSet--内部经过TreeMap实现,因为TreeMap的key能够排序,所以Set能够作到有序。TreeMap不容许key为null,因此不能存null,null会直接报空指针异常。 ---|LinkedHashSet--继承hashSet,且增长了本身的方法。将父类HashSet中的map初始化为LinkedHashMap保存存入的顺序,其保证顺序的机制见LinkedHashMap的有序机制。容许值为null。 ---| Map: 键值对 ---|HashMap 线程非安全,初始化16个数组大小。成倍增加。容许一个key为null,多个value为null(key为null的时候存在数组的第一个位置table[0])。(HashMap的key不能够重复,阅读源码的put方法,key重复会替换value值) ---|TreeMap------能够按照key进行排序,前提是key元素实现Comparable接口或者给Treemap传入一个实现Comparator的比较器。红黑树实现。不容许key为null,容许value为null(由于key要进行compare比较)。 ---|HashTable---线程安全,初始化11个数组大小,增加时2*old+1。key和value都不能是null。 ---|LinkedHashMap 保存了记录的插入顺序,继承hashmap,实现Map接口。容许一个key为null,多个value为null。-----原理是其内部类 Entry维护了一个header和before记录顺序,没有重写put方法,重写了addEntry()方法,由于HashMap的put方法中调用addEntry方法()。 |
为何出现这么多集合容器,由于每个容器对数据的存储方式不一样,这种存储方式称之为数据结构(data structure)
注意
集合和数组中存放的都是对象的引用。
看到array,就要想到角标。
看到link,就要想到first,last。
看到hash,就要想到hashCode,equals.
看到tree,就要想到两个接口。Comparable,Comparator。
Collection |
咱们须要保存若干个对象的时候使用集合。 |
List
|
若是咱们须要保留存储顺序, 而且保留重复元素, 使用List. 若是查询较多, 那么使用ArrayList--基于数组实现 若是存取较多, 那么使用LinkedList--基于双向链表实现 若是须要线程安全, 那么使用Vector--基于数组实现,只是线程安全 |
Set
|
若是咱们不须要保留存储顺序, 而且须要去掉重复元素, 使用Set. 若是咱们须要将元素排序, 那么使用TreeSet--内部基于SortedMap实现 若是咱们不须要排序, 使用HashSet, HashSet比--内部基于HashMap实现 TreeSet效率高. 若是咱们须要保留存储顺序, 又要过滤重复元素, 那么使用LinkedHashSet--内部基于HashMap实现 |
1. Collection接口有两个子接口:
List(链表|线性表)
Set(集)
特色:
Collection中描述的是集合共有的功能(CRUD)
List可存放重复元素,元素存取是有序的
Set不能够存放重复元素,元素存取是无序的
java.util.Collection ---| Collection 描述全部接口的共性 ----| List接口 能够有重复元素的集合 ----| Set 接口 不能够有重复元素的集合 |
2. 学习集合对象
学习Collection中的共性方法,多个容器在不断向上抽取就出现了该体系。发现Collection接口中具备全部容器都具有的共性方法。查阅API时,就能够直接看该接口中的方法。并建立其子类对象对集合进行基本应用。当要使用集合对象中特有的方法,在查看子类具体内容。
查看api 文档Collection在在java.util 中(注意是大写Collection)
注意在现阶段遇到的 E T 之类的类型,须要暂时理解为object 由于涉及到了泛型.
3:建立集合对象,使用Collection中的List的具体实现类ArrayList
1:Collection coll=new Arraylist();
增长:
1:add() 将指定对象存储到容器中
add 方法的参数类型是Object 便于接收任意对象
2:addAll() 将指定集合中的元素添加到调用该方法和集合中
删除:
3:remove() 将指定的对象从集合中删除
4:removeAll() 将指定集合中的元素删除
修改
5:clear() 清空集合中的全部元素
判断
6:isEmpty() 判断集合是否为空
7:contains() 判断集合何中是否包含指定对象
8:containsAll() 判断集合中是否包含指定集合
使用equals()判断两个对象是否相等
获取: 9:int size() 返回集合容器的大小
转成数组10: toArray() 集合转换数组
public static void main(String[] args) { Collection list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); System.out.println(list); // [计算机网络, 现代操做系统, java编程思想] // 增长2 将list容器元素添加到list2容器中 Collection list2 = new ArrayList(); list2.add("java核心技术"); list2.addAll(list); list2.add("java语言程序设计"); System.out.println(list2); // [java核心技术, 计算机网络, 现代操做系统, java编程思想, java语言程序设计] }
// 删除1 remove boolean remove = list2.remove("java核心技术"); System.out.println(remove); // true System.out.println(list2); // // 删除2 removeAll() 将list中的元素删除 boolean removeAll = list2.removeAll(list); System.out.println(removeAll);// true System.out.println(list2);// [java语言程序设计]
public static void main(String[] args) { Collection list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // 修改 clear() 清空集合中的全部元素 list.clear(); System.out.println(list); //[] }
public static void main(String[] args) { Collection list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); boolean empty = list.isEmpty(); System.out.println(empty);// false boolean contains = list.contains("java编程思想"); System.out.println(contains);// true Collection list2 = new ArrayList(); list2.add("水许传"); boolean containsAll = list.containsAll(list2); System.out.println(containsAll);// false }
public static void main(String[] args) { Collection list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // 获取 集合容器的大小 int size = list.size(); System.out.println(size); }
---| Iterable 接口
Iterator iterator()
----| Collection 接口
------| List 接口 元素能够重复,容许在指定位置插入元素,并经过索引来访问元素
1:增长 void add(int index, E element) 指定位置添加元素 boolean addAll(int index, Collection c) 指定位置添加集合 2:删除 E remove(int index) 删除指定位置元素 3:修改 E set(int index, E element) 返回的是须要替换的集合中的元素 4:查找: E get(int index) 注意: IndexOutOfBoundsException int indexOf(Object o) // 找不到返回-1 lastIndexOf(Object o) 5:求子集合 List<E> subList(int fromIndex, int toIndex) // 不包含toIndex
public static void main(String[] args) { List list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // add,在0角标位置添加一本书 list.add(0, "舒克和贝塔"); System.out.println(list); // 在list2集合的1角标位置添加list集合元素 List list2 = new ArrayList(); list2.add("史记"); list2.add("资治通鉴"); list2.add("全球通史"); boolean addAll = list2.addAll(1, list); System.out.println(addAll); //true System.out.println(list2); }
public static void main(String[] args) { List list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // 删除0角标元素 Object remove = list.remove(0); System.out.println(remove); }
public static void main(String[] args) { List list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // 修改2角标位置的书,返回的原来2角标位置的书 Object set = list.set(2, "边城"); System.out.println(set); //java编程思想 System.out.println(list); }
public static void main(String[] args) { List list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); list.add("java编程思想"); System.out.println(list); // 查找: E get(int index) 注意角标越界 Object set = list.get(list.size() - 1); System.out.println(set); // java语言程序设计 System.out.println(list); // list.get(list.size()); //IndexOutOfBoundsException // indexOf(Object o) 返回第一次出现的指定元素的角标 int indexOf = list.indexOf("java编程思想"); System.out.println(indexOf); // 2 // 没有找到,返回-1 int indexOf2 = list.indexOf("三国志"); System.out.println(indexOf2); // -1 // lastIndexOf 返回最后出现的指定元素的角标 int lastIndexOf = list.lastIndexOf("java编程思想"); System.out.println(lastIndexOf); // 5 }
---------------------S 重写equals和hashcode的Person方法-------------------------------
1:若是不重写,调用Object类的equals方法,判断内存地址,为false
1:若是是Person类对象,而且姓名和年龄相同就返回true
2:若是不重写,调用父类hashCode方法
1:若是equals方法相同,那么hashCode也要相同,须要重写hashCode方法
3:重写toString方法
1:不重写,直接调用Object类的toString方法,打印该对象的内存地址
package CollectionTest; public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int hashCode() { // TODO Auto-generated method stub return this.name.hashCode()+age; } @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) { return false; } Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } protected Person(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
---------------------E 重写equals和hashcode的Person方法-------------------------------
--| Iterable
----| Collection
------| List
---------| ArrayList 底层采用数组实现,默认10。每次增加50%, 查询快,增删慢。
---------| LinkedList 双向链表实现。查询慢,增删快
ArrayList:实现原理:
数组实现, 查找快, 增删慢
数组为何是查询快?由于数组的内存空间地址是连续的.
ArrayList底层维护了一个Object[] 用于存储对象,默认数组的长度是10。能够经过 new ArrayList(20)显式的指定用于存储对象的数组的长度。
当默认的或者指定的容量不够存储对象的时候,容量自动增加为原来的容量的1.5倍。
因为ArrayList是数组实现, 在增和删的时候会牵扯到数组增容, 以及拷贝元素. 因此慢。数组是能够直接按索引查找, 因此查找时较快
能够考虑,假设向数组的0角标未知添加元素,那么原来的角标位置的元素须要总体日后移,而且数组可能还要增容,一旦增容,就须要要将老数组的内容拷贝到新数组中.因此数组的增删的效率是很低的.
public static void main(String[] args) { ArrayList arr = new ArrayList(); Person p1 = new Person("jack", 20); Person p2 = new Person("rose", 18); Person p3 = new Person("rose", 18); arr.add(p1); arr.add(p2); arr.add(p3); System.out.println(arr); // [Person [name=jack, age=20], Person [name=rose, age=18], Person [name=rose, age=18]] ArrayList arr2 = new ArrayList(); for (int i = 0; i < arr.size(); i++) { Object obj = arr.get(i); Person p = (Person) obj; if (!(arr2.contains(p))) { arr2.add(p); } } System.out.println(arr2); // [Person [name=jack, age=20], Person [name=rose, age=18]] }
--| Iterable ----| Collection ------| List ---------| ArrayList 底层采用数组实现,默认10。每次增加50% 查询快,增删慢。 ---------| LinkedList 底层采用双向链表实现,增删快,查询慢。
LinkedList:链表实现, 增删快, 查找慢
因为LinkedList:在内存中的地址不连续,须要让上一个元素记住下一个元素.因此每一个元素中保存的有下一个元素的位置.虽然也有角标,可是查找的时候,须要从头往下找,显然是没有数组查找快的.可是,链表在插入新元素的时候,只须要让前一个元素记住新元素,让新元素记住下一个元素就能够了.因此插入很快.
因为链表实现, 增长时只要让前一个元素记住本身就能够, 删除时让前一个元素记住后一个元素, 后一个元素记住前一个元素. 这样的增删效率较高。
但查询时须要一个一个的遍历, 因此效率较低。
特有方法
1:方法介绍 addFirst(E e) addLast(E e) getFirst() getLast() removeFirst() removeLast() 若是集合中没有元素,获取或者删除元 素抛:NoSuchElementException 2:数据结构 1:栈 (1.6) 先进后出 push() pop() 2:队列(双端队列1.5) 先进先出 offer() poll() 3:返回逆序的迭代器对象 descendingIterator() 返回逆序的迭代器对象
基本方法:
package CollectionTest; import java.util.LinkedList; @SuppressWarnings("all") public class LinkedListTest { public static void main(String[] args) { LinkedList list = new LinkedList(); list.add("西游记"); list.add("三国演义"); list.add("石头记"); list.add("水浒传"); list.add("全球通史"); // 往头顶添加 list.addFirst("史记"); // 往最后添加 list.addLast("呐喊"); // list.addFirst(null); // list.addLast(null); System.out.println(list); // 获取指定位置处的元素。 String str = (String) list.get(0); // 返回此列表的第一个元素。 String str2 = (String) list.getFirst(); System.out.println(str.equals(str2)); // 获取指定位置处的元素。 String str3 = (String) list.get(list.size() - 1); // 返回此列表的最后一个元素。 String str4 = (String) list.getLast(); System.out.println(str3.equals(str4)); // 获取但不移除此列表的头(第一个元素)。 Object element = list.element(); System.out.println(element); int size = list.size(); System.out.println(size); } }
结果:
[史记, 西游记, 三国演义, 石头记, 水浒传, 全球通史, 呐喊]
true
true
史记
7
@Test public void test1(){ LinkedList list = new LinkedList(); list.add("西游记"); list.add("三国演义"); list.add("石头记"); list.add("水浒传"); list.add("全球通史"); Iterator it = list.iterator(); while (it.hasNext()) { String next = (String) it.next(); System.out.println(next); } }
/** * 逆袭迭代集合 */ @Test public void test2(){ LinkedList list = new LinkedList(); list.add("aa"); list.add("bb"); list.add("cc"); Iterator dit = list.descendingIterator(); while (dit.hasNext()) { System.out.println(dit.next()); } }
/** * 模拟做为栈结构(后进先出) */ @Test public void test3(){ LinkedList list = new LinkedList(); // 压栈,先进后出 list.push("西游记"); list.push("三国演义"); list.push("石头记"); list.push("水浒传"); System.out.println(list); // 弹栈(获取到元素而且删除元素) String str1 = (String) list.pop(); System.out.println(str1); String str2 = (String) list.pop(); System.out.println(str2); String str3 = (String) list.pop(); System.out.println(str3); String str4 = (String) list.pop(); System.out.println(str4); System.out.println(list.size());// 0 System.out.println(list); //[] }
[水浒传, 石头记, 三国演义, 西游记]
水浒传
石头记
三国演义
西游记
0
[]
/** * 做为队列的使用(先进先出) */ @Test public void test4() { LinkedList list = new LinkedList(); // 队列,先进先出 list.offer("西游记"); list.offer("三国演义"); list.offer("石头记"); list.offer("水浒传"); System.out.println(list); // 出队列 System.out.println(list.poll()); System.out.println(list.poll()); System.out.println(list.poll()); System.out.println(list.poll()); System.out.println(list.size()); System.out.println(list.peek()); // 获取队列的头元素,可是不删除 System.out.println(list.peekFirst()); // 获取队列的头元素,可是不删除 System.out.println(list.peekLast()); // 获取队列的最后一个元素可是不删除 }
[西游记, 三国演义, 石头记, 水浒传]
西游记
三国演义
石头记
水浒传
0
null
null
null
ArrayList 和 LinkedList的存储查找的优缺点:
一、ArrayList 是采用动态数组来存储元素的,它容许直接用下标号来直接查找对应的元素。可是,可是插入元素要涉及数组元素移动及内存的操做。总结:查找速度快,插入操做慢。
二、LinkedList 是采用双向链表实现存储,按序号索引数据须要进行前向或后向遍历,可是插入数据时只须要记录本项的先后项便可,因此插入速度较快
问题:有一批数据要存储,要求存储这批数据不能出现重复数据,ArrayList、LinkedList都无法知足需求。解决办法:使用 set集合。
Vector: 描述的是一个线程安全的ArrayList。
ArrayList: 单线程效率高
Vector : 多线程安全的,因此效率低
特有的方法:
void addElement(E obj) 在集合末尾添加元素 E elementAt( int index) 返回指定角标的元素 Enumeration elements() 返回集合中的全部元素,封装到Enumeration对象中 Enumeration 接口: boolean hasMoreElements() 测试此枚举是否包含更多的元素。 E nextElement() 若是此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。
public static void main(String[] args) { Vector v = new Vector(); v.addElement("aaa"); v.addElement("bbb"); v.addElement("ccc"); System.out.println( v ); System.out.println( v.elementAt(2) ); // ccc // 遍历Vector遍历 Enumeration ens = v.elements(); while ( ens.hasMoreElements() ) { System.out.println( ens.nextElement() ); } Iterator iterator = v.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } }
为了方便的处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫作迭代器(Iterator).
对 Collection 进行迭代的类,称其为迭代器。仍是面向对象的思想,专业对象作专业的事情,迭代器就是专门取出集合元素的对象。可是该对象比较特殊,不能直接建立对象(经过new),该对象是之内部类的形式存在于每一个集合类的内部。
如何获取迭代器?Collection接口中定义了获取集合类迭代器的方法(iterator()),因此全部的Collection体系集合均可以获取自身的迭代器。
正是因为每个容器都有取出元素的功能。这些功能定义都同样,只不过实现的具体方式不一样(由于每个容器的数据结构不同)因此对共性的取出功能进行了抽取,从而出现了Iterator接口。而每个容器都在其内部对该接口进行了内部类的实现。也就是将取出方式的细节进行封装。
Jdk1.5以后添加的新接口, Collection的父接口. 实现了Iterable的类就是可迭代的.而且支持加强for循环。该接口只有一个方法即获取迭代器的方法iterator()能够获取每一个容器自身的迭代器Iterator。(Collection)集合容器都须要获取迭代器(Iterator)因而在5.0后又进行了抽取将获取容器迭代器的iterator()方法放入到了Iterable接口中。Collection接口进程了Iterable,因此Collection体系都具有获取自身迭代器的方法,只不过每一个子类集合都进行了重写(由于数据结构不一样)
Iterator iterator() 返回该集合的迭代器对象
该类主要用于遍历集合对象,该类描述了遍历集合的常见方法 1:java.lang. Itreable ---| Itreable 接口 实现该接口可使用加强for循环 ---| Collection 描述全部集合共性的接口 ---| List接口 能够有重复元素的集合 ---| Set接口 不能够有重复元素的集合
public interface Iterable<T>
Itreable 该接口仅有一个方法,用于返回集合迭代器对象。
1: Iterator<T> iterator() 返回集合的迭代器对象
Iterator接口定义的方法
Itreator 该接口是集合的迭代器接口类,定义了常见的迭代方法 1:boolean hasNext() 判断集合中是否有元素,若是有元素能够迭代,就返回true。 2: E next() 返回迭代的下一个元素,注意: 若是没有下一个元素时,调用next元素会抛出NoSuchElementException 3: void remove() 从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操做)。
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { // Racy but within spec, since modifications are checked // within or after synchronization in next/previous return cursor != elementCount; } public E next() { synchronized (Vector.this) { checkForComodification(); int i = cursor; if (i >= elementCount) throw new NoSuchElementException(); cursor = i + 1; return elementData(lastRet = i); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); synchronized (Vector.this) { checkForComodification(); Vector.this.remove(lastRet); expectedModCount = modCount; } cursor = lastRet; lastRet = -1; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
解释:cursor默认为0。返回元素以后,cursor加一,同时将未加一的cursor赋值给lastRet。删除的时候也是根据上次返回的删除,因此调用删除以前必须先执行next()方法。cursor始终比lastRet大一。
好比第一次访问next()以后: cursor为1,lastRet为0,返回下标为lastRet的元素(返回第一个元素)。调用remove()方法的时候删除下标为lastRet的元素,删除第一个元素。
next():将cursor赋值给lastRet,将cursor加一,返回元素的时候返回下标为lastRet的元素。
1. while循环
public static void main(String[] args) { ArrayList list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); Iterator it = list.iterator(); while (it.hasNext()) { String next = (String) it.next(); System.out.println(next); } }
2. for循环
public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); for (Iterator it = list.iterator(); it.hasNext();) { //迭代器的next方法返回值类型是Object,因此要记得类型转换。 String next = (String) it.next(); System.out.println(next); } } }
须要取出全部元素时,能够经过循环,java 建议使用for 循环。由于能够对内存进行一下优化。
3:使用迭代器清空集合
public class Demo1 { public static void main(String[] args) { Collection coll = new ArrayList(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); System.out.println(coll); Iterator it = coll.iterator(); while (it.hasNext()) { it.next(); it.remove(); } System.out.println(coll); } }
细节一:
若是迭代器的指针已经指向了集合的末尾,那么若是再调用next()会返回NoSuchElementException异常
细节二:
若是调用remove以前没有调用next是不合法的,会抛出IllegalStateException
4:迭代器原理
查看ArrayList源码
private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } }
5. 注意在对集合进行迭代过程当中,不容许出现迭代器之外的对元素的操做,由于这样会产生安全隐患,java会抛出异常并发修改异常(ConcurrentModificationException),普通迭代器只支持在迭代过程当中的删除动做。
注意: ConcurrentModificationException: 当一个集合在循环中即便用引用变量操做集合又使用迭代器操做集合对象, 会抛出该异常。
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo1 { public static void main(String[] args) { Collection coll = new ArrayList(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); System.out.println(coll); Iterator it = coll.iterator(); while (it.hasNext()) { it.next(); it.remove(); coll.add("abc"); // 出现了迭代器之外的对元素的操做 } System.out.println(coll); } }
若是是List集合,想要在迭代中操做元素可使用List集合的特有迭代器ListIterator,该迭代器支持在迭代过程当中,添加元素和修改元素。
public interface ListIterator extends Iterator
ListIterator<E> listIterator()
---| Iterator hasNext() next() remove() ------| ListIterator Iterator子接口 List专属的迭代器 add(E e) 将指定的元素插入列表(可选操做)。该元素直接插入到 next 返回的下一个元素的前面(若是有) void set(E o) 用指定元素替换 next 或 previous 返回的最后一个元素 hasPrevious() 逆向遍历列表,列表迭代器有多个元素,则返回 true。 previous() 返回列表中的前一个元素。
Iterator在迭代时,只能对元素进行获取(next())和删除(remove())的操做。
对于 Iterator 的子接口ListIterator 在迭代list 集合时,还能够对元素进行添加
(add(obj)),修改set(obj)的操做。
import java.util.ArrayList; import java.util.ListIterator; public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // 获取List专属的迭代器 ListIterator lit = list.listIterator(); while (lit.hasNext()) { String next = (String) lit.next(); System.out.println(next); } } }
倒序遍历
import java.util.ArrayList; import java.util.ListIterator; public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); // 获取List专属的迭代器 ListIterator lit = list.listIterator(); while (lit.hasNext()) { String next = (String) lit.next(); System.out.println(next); } System.out.println("***************"); while (lit.hasPrevious()) { String next = (String) lit.previous(); System.out.println(next); } } }
Set方法:用指定元素替换 next或 previous返回的最后一个元素
import java.util.ArrayList; import java.util.ListIterator; public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); ListIterator lit = list.listIterator(); lit.next(); // 计算机网络 lit.next(); // 现代操做系统 System.out.println(lit.next()); // java编程思想 //用指定元素替换 next 或 previous 返回的最后一个元素 lit.set("平凡的世界");// 将java编程思想替换为平凡的世界 System.out.println(list); } }
add方法将指定的元素插入列表,该元素直接插入到 next 返回的元素的后
public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增长:add() 将指定对象存储到容器中 list.add("计算机网络"); list.add("现代操做系统"); list.add("java编程思想"); list.add("java核心技术"); list.add("java语言程序设计"); System.out.println(list); ListIterator lit = list.listIterator(); lit.next(); // 计算机网络 lit.next(); // 现代操做系统 System.out.println(lit.next()); // java编程思想 // 将指定的元素插入列表,该元素直接插入到 next 返回的元素的后 lit.add("平凡的世界");// 在java编程思想后添加平凡的世界 System.out.println(list); } }
Set:注重独一无二的性质,该体系集合能够知道某物是否已近存在于集合中,不会存储重复的元素。原理是内部基于Map实现,咱们的元素添加在map的key上,value是一个固定的Object。
以HashSet为例分析不能重复的原理:
(1)HashSet初始化的时候建立一个hashmap:
private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); }
(2)添加元素的时候将咱们传下来的元素做为map的key保存到map中,value是一个Object常量。
public boolean add(E e) { return map.put(e, PRESENT)==null; }
(3)清空的时候调用map的clear()方法:
public void clear() { map.clear(); }
用于存储无序(存入和取出的顺序不必定相同)元素,值不能重复。
对象的相等性
引用到堆上同一个对象的两个引用是相等的。若是对两个引用调用hashCode方法,会获得相同的结果,若是对象所属的类没有覆盖Object的hashCode方法的话,hashCode会返回每一个对象特有的序号(java是依据对象的内存地址计算出的此序号),因此两个不一样的对象的hashCode值是不可能相等的。
若是想要让两个不一样的Person对象视为相等的,就必须覆盖Object继下来的hashCode方法和equals方法,由于Object hashCode方法返回的是该对象的内存地址,因此必须重写hashCode方法,才能保证两个不一样的对象具备相同的hashCode,同时也须要两个不一样对象比较equals方法会返回true
该集合中没有特有的方法,直接继承自Collection。
---| Itreable 接口 实现该接口可使用加强for循环 ---| Collection 描述全部集合共性的接口 ---| List接口 能够有重复元素的集合 ---| ArrayList ---| LinkedList ---| Set接口 不能够有重复元素的集合
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo4 { public static void main(String[] args) { //Set 集合存和取的顺序不一致。 Set hs = new HashSet(); hs.add("世界军事"); hs.add("兵器知识"); hs.add("舰船知识"); hs.add("汉和防务"); System.out.println(hs); // [舰船知识, 世界军事, 兵器知识, 汉和防务] Iterator it = hs.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } }
---| Itreable 接口实现该接口可使用加强for循环 ---| Collection 描述全部集合共性的接口 ---| List接口 能够有重复元素的集合 ---| ArrayList ---| LinkedList ---| Set接口 不能够有重复元素的集合 ---| HashSet 线程不安全,存取速度快。底层是以哈希表实现的。 |
HashSet
哈希表边存放的是哈希值。HashSet存储元素的顺序并非按照存入时的顺序(和List显然不一样)是按照哈希值来存的因此取数据也是按照哈希值取得。
HashSet不存入重复元素的规则.使用hashcode和equals
因为Set集合是不能存入重复元素的集合。那么HashSet也是具有这一特性的。HashSet如何检查重复?HashSet会经过元素的hashcode()和equals方法进行判断元素师否重复。
当你试图把对象加入HashSet时,HashSet会使用对象的hashCode来判断对象加入的位置。同时也会与其余已经加入的对象的hashCode进行比较,若是没有相等的hashCode,HashSet就会假设对象没有重复出现。
简单一句话,若是对象的hashCode值是不一样的,那么HashSet会认为对象是不可能相等的。
所以咱们自定义类的时候须要重写hashCode,来确保对象具备相同的hashCode值。
若是元素(对象)的hashCode值相同,是否是就没法存入HashSet中了? 固然不是,会继续使用equals 进行比较.若是 equals为true 那么HashSet认为新加入的对象重复了,因此加入失败。若是equals 为false那么HashSet 认为新加入的对象没有重复.新元素能够存入.
总结:
元素的哈希值是经过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,若是哈希值同样,接着会比较equals方法若是 equls结果为true ,HashSet就视为同一个元素。若是equals 为false就不是同一个元素。
哈希值相同equals为false的元素是怎么存储呢,就是在一样的哈希值下顺延(能够认为哈希值相同的元素放在一个哈希桶中)。也就是哈希同样的存一列。
hashtable
HashSet:经过hashCode值来肯定元素在内存中的位置。一个hashCode位置上能够存放多个元素。
当hashcode() 值相同equals() 返回为true 时,hashset 集合认为这两个元素是相同的元素.只存储一个(重复元素没法放入)。调用原理:先判断hashcode 方法的值,若是相同才会去判断equals 若是不相同,是不会调用equals方法的。
HashSet究竟是如何判断两个元素重复。
经过hashCode方法和equals方法来保证元素的惟一性,add()返回的是boolean类型
判断两个元素是否相同,先要判断元素的hashCode值是否一致,只有在该值一致的状况下,才会判断equals方法,若是存储在HashSet中的两个对象hashCode方法的值相同equals方法返回的结果是true,那么HashSet认为这两个元素是相同元素,只存储一个(重复元素没法存入)。
注意:HashSet集合在判断元素是否相同先判断hashCode方法,若是相同才会判断equals。若是不相同,是不会调用equals方法的。
HashSet 和ArrayList集合都有判断元素是否相同的方法,
boolean contains(Object o)
HashSet使用hashCode和equals方法,ArrayList使用了equals方法
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo4 { public static void main(String[] args) { // Set 集合存和取的顺序不一致。 Set hs = new HashSet(); hs.add("世界军事"); hs.add("兵器知识"); hs.add("舰船知识"); hs.add("汉和防务"); // 返回此 set 中的元素的数量 System.out.println(hs.size()); // 4 // 若是此 set 还没有包含指定元素,则返回 true boolean add = hs.add("世界军事"); // false System.out.println(add); // 返回此 set 中的元素的数量 System.out.println(hs.size());// 4 Iterator it = hs.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } }
package cn.itcast.gz.map; import java.util.HashSet; import java.util.Iterator; public class Demo4 { public static void main(String[] args) { HashSet hs = new HashSet(); hs.add(new Person("jack", 20)); hs.add(new Person("rose", 20)); hs.add(new Person("hmm", 20)); hs.add(new Person("lilei", 20)); hs.add(new Person("jack", 20)); Iterator it = hs.iterator(); while (it.hasNext()) { Object next = it.next(); System.out.println(next); } } } class Person { private String name; private int age; Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int hashCode() { System.out.println("hashCode:" + this.name); return this.name.hashCode() + age * 37; } @Override public boolean equals(Object obj) { System.out.println(this + "---equals---" + obj); if (obj instanceof Person) { Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } else { return false; } } @Override public String toString() { return "Person@name:" + this.name + " age:" + this.age; } }
import java.util.TreeSet; public class Demo5 { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add("ccc"); ts.add("aaa"); ts.add("ddd"); ts.add("bbb"); System.out.println(ts); // [aaa, bbb, ccc, ddd] } }
---| Itreable 接口实现该接口可使用加强for循环 ---| Collection 描述全部集合共性的接口 ---| List接口 有序,能够重复,有角标的集合 ---| ArrayList ---| LinkedList ---| Set接口 无序,不能够重复的集合 ---| HashSet 线程不安全,存取速度快。底层是以hash表实现的。 ---| TreeSet 红-黑树的数据结构,默认对元素进行天然排序(String)。若是比较的时候两个对象返回值为0,那么元素重复。 |
红-黑树
红黑树是一种特定类型的二叉树
红黑树算法的规则: 左小右大。
既然TreeSet能够天然排序,那么TreeSet一定是有排序规则的。
1:让存入的元素自定义比较规则。
2:给TreeSet指定排序规则。
方式一:元素自身具有比较性
元素自身具有比较性,须要元素实现Comparable接口,重写compareTo方法,也就是让元素自身具有比较性,这种方式叫作元素的天然排序也叫作默认排序。
方式二:容器具有比较性
当元素自身不具有比较性,或者自身具有的比较性不是所须要的。那么此时可让容器自身具有。须要定义一个类实现接口Comparator,重写compare方法,并将该接口的子类实例对象做为参数传递给TreeMap集合的构造方法。
注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主;
注意:在重写compareTo或者compare方法时,必需要明确比较的主要条件相等时要比较次要条件。(假设姓名和年龄一直的人为相同的人,若是想要对人按照年龄的大小来排序,若是年龄相同的人,须要如何处理?不能直接return 0,由于可能姓名不一样(年龄相同姓名不一样的人是不一样的人)。此时就须要进行次要条件判断(须要判断姓名),只有姓名和年龄同时相等的才能够返回0.)
经过return 0来判断惟一性。
问题:为何使用TreeSet存入字符串,字符串默认输出是按升序排列的?由于字符串实现了一个接口,叫作Comparable 接口.字符串重写了该接口的compareTo 方法,因此String对象具有了比较性.那么一样道理,个人自定义元素(例如Person类,Book类)想要存入TreeSet集合,就须要实现该接口,也就是要让自定义对象具有比较性.
存入TreeSet集合中的元素要具有比较性.
比较性要实现Comparable接口,重写该接口的compareTo方法
TreeSet属于Set集合,该集合的元素是不能重复的,TreeSet如何保证元素的惟一性
经过compareTo或者compare方法中的来保证元素的惟一性。
添加的元素必需要实现Comparable接口。当compareTo()函数返回值为0时,说明两个对象相等,此时该对象不会添加进来。
比较器接口
----| Comparable compareTo(Object o) 元素自身具有比较性 ----| Comparator compare( Object o1, Object o2 ) 给容器传入比较器 |
TreeSet集合排序的两种方式:
一,让元素自身具有比较性。
也就是元素须要实现Comparable接口,覆盖compareTo 方法。
这种方式也做为元素的天然排序,也可称为默认排序。
年龄按照搜要条件,年龄相同再比姓名。
import java.util.TreeSet; public class Demo4 { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add(new Person("aa", 20, "男")); ts.add(new Person("bb", 18, "女")); ts.add(new Person("cc", 17, "男")); ts.add(new Person("dd", 17, "女")); ts.add(new Person("dd", 15, "女")); ts.add(new Person("dd", 15, "女")); System.out.println(ts); System.out.println(ts.size()); // 5 } } class Person implements Comparable { private String name; private int age; private String gender; public Person() { } public Person(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public int hashCode() { return name.hashCode() + age * 37; } public boolean equals(Object obj) { System.err.println(this + "equals :" + obj); if (!(obj instanceof Person)) { return false; } Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } public String toString() { return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]"; } @Override public int compareTo(Object obj) { Person p = (Person) obj; System.out.println(this+" compareTo:"+p); if (this.age > p.age) { return 1; } if (this.age < p.age) { return -1; } return this.name.compareTo(p.name); } }
二,让容器自身具有比较性,自定义比较器。
需求:当元素自身不具有比较性,或者元素自身具有的比较性不是所需的。
那么这时只能让容器自身具有。
定义一个类实现Comparator 接口,覆盖compare方法。
并将该接口的子类对象做为参数传递给TreeSet集合的构造函数。
当Comparable比较方式,及Comparator比较方式同时存在,以Comparator
比较方式为主。
import java.util.Comparator; import java.util.TreeSet; public class Demo5 { public static void main(String[] args) { TreeSet ts = new TreeSet(new MyComparator()); ts.add(new Book("think in java", 100)); ts.add(new Book("java 核心技术", 75)); ts.add(new Book("现代操做系统", 50)); ts.add(new Book("java就业教程", 35)); ts.add(new Book("think in java", 100)); ts.add(new Book("ccc in java", 100)); System.out.println(ts); } } class MyComparator implements Comparator { public int compare(Object o1, Object o2) { Book b1 = (Book) o1; Book b2 = (Book) o2; System.out.println(b1+" comparator "+b2); if (b1.getPrice() > b2.getPrice()) { return 1; } if (b1.getPrice() < b2.getPrice()) { return -1; } return b1.getName().compareTo(b2.getName()); } } class Book { private String name; private double price; public Book() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Book(String name, double price) { this.name = name; this.price = price; } @Override public String toString() { return "Book [name=" + name + ", price=" + price + "]"; } }
会保存插入的顺序。
看到array,就要想到角标。
看到link,就要想到first,last。
看到hash,就要想到hashCode,equals.
看到tree,就要想到两个接口。Comparable,Comparator。
若是程序中存储了几百万个学生,并且常常须要使用学号来搜索某个学生,那么这个需求有效的数据结构就是Map。Map是一种依照键(key)存储元素的容器,键(key)很像下标,在List中下标是整数。在Map中键(key)可使任意类型的对象。Map中不能有重复的键(Key),每一个键(key)都有一个对应的值(value)。一个键(key)和它对应的值构成map集合中的一个元素。
Map中的元素是两个对象,一个对象做为键,一个对象做为值。键不能够重复,可是值能够重复。
看顶层共性方法找子类特有对象.
Map与Collection在集合框架中属并列存在
Map存储的是键值对
Map存储元素使用put方法,Collection使用add方法
Map集合没有直接取出元素的方法,而是先转成Set集合,在经过迭代获取元素
Map集合中键要保证惟一性
也就是Collection是单列集合, Map 是双列集合。
总结:
Map一次存一对元素, Collection 一次存一个。Map 的键不能重复,保证惟一。
Map 一次存入一对元素,是以键值对的形式存在.键与值存在映射关系.必定要保证键的惟一性.
查看api文档:
interface Map<K,V>
K - 此映射所维护的键的类型
V - 映射值的类型
概念
将键映射到值的对象。一个映射不能包含重复的键;每一个键最多只能映射到一个值。
特色
Key和Value是1对1的关系,如:门牌号:家 老公:老婆
双列集合
Map学习体系: ---| Map 接口 将键映射到值的对象。一个映射不能包含重复的键;每一个键最多只能映射到一个值。 ---| HashMap 采用哈希表实现,因此无序 ---| TreeMap 能够对健进行排序 |
---|Hashtable: 底层是哈希表数据结构,线程是同步的,不能够存入null键,null值。 效率较低,被HashMap 替代。 ---|HashMap: 底层是哈希表数据结构,线程是不一样步的,能够存入null键,null值。 要保证键的惟一性,须要覆盖hashCode方法,和equals方法。 ---| LinkedHashMap: 该子类基于哈希表又融入了链表。能够Map集合进行增删提升效率。 ---|TreeMap: 底层是二叉树数据结构。(红黑树)能够对map集合中的键进行排序。须要使用Comparable或者Comparator 进行比较排序。return 0,来判断键的惟一性。 |
常见方法
一、添加: 一、V put(K key, V value) (能够相同的key值,可是添加的value值会覆 盖前面的,返回值是前一个,若是没有就返回null) 二、putAll(Map<? extends K,? extends V> m) 从指定映射中将全部映射关 系复制到此映射中(可选操做)。 二、删除 一、remove() 删除关联对象,指定key对象 二、clear() 清空集合对象 三、获取 1:value get(key); 能够用于判断键是否存在的状况。当指定的键不存在的时候,返 回的是null。
三、判断: 一、boolean isEmpty() 长度为0返回true不然false 二、boolean containsKey(Object key) 判断集合中是否包含指定的key 三、boolean containsValue(Object value) 判断集合中是否包含指定的value 四、长度: Int size()
|
添加:
该案例使用了HashMap,创建了学生姓名和年龄之间的映射关系。并试图添加剧复的键。
import java.util.HashMap; import java.util.Map;
public class Demo1 { public static void main(String[] args) { // 定义一个Map的容器对象 Map<String, Integer > map1 = new HashMap<String, Integer >(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); // 添加剧复的键值(值不一样),会返回集合中原有(重复键)的值, System.out.println(map1.put("jack", 30)); //20
Map<String, Integer> map2 = new HashMap<String, Integer>(); map2.put("张三丰", 100); map2.put("虚竹", 20); System.out.println("map2:" + map2); // 从指定映射中将全部映射关系复制到此映射中。 map1.putAll(map2); System.out.println("map1:" + map1); // } }
|
删除:
// 删除: // remove() 删除关联对象,指定key对象 // clear() 清空集合对象
Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); // 指定key,返回删除的键值对映射的值。 System.out.println("value:" + map1.remove("java")); map1.clear(); System.out.println("map1:" + map1); |
获取:
// 获取: // V get(Object key) 经过指定的key对象获取value对象 // int size() 获取容器的大小 Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); // V get(Object key) 经过指定的key对象获取value对象 // int size() 获取容器的大小 System.out.println("value:" + map1.get("jack")); System.out.println("map.size:" + map1.size()); |
判断:
// 判断: // boolean isEmpty() 长度为0返回true不然false // boolean containsKey(Object key) 判断集合中是否包含指定的key // boolean containsValue(Object value)
Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); System.out.println("isEmpty:" + map1.isEmpty()); System.out.println("containskey:" + map1.containsKey("jack")); System.out.println("containsvalues:" + map1.containsValue(100)); |
遍历Map的方式:
一、将map 集合中全部的键取出存入set集合。 Set<K> keySet() 返回全部的key对象的Set集合 再经过get方法获取键对应的值。 二、 values() ,获取全部的值. Collection<V> values()不能获取到key对象 三、 Map.Entry对象 推荐使用 重点 Set<Map.Entry<k,v>> entrySet() 将map 集合中的键值映射关系打包成一个对象 Map.Entry对象经过Map.Entry 对象的getKey, getValue获取其键和值。 |
|
第一种方式:使用keySet
将Map转成Set集合(keySet()),经过Set的迭代器取出Set集合中的每个元素(Iterator)就是Map集合中的全部的键,再经过get方法获取键对应的值。
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;
public class Demo2 { public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "aaaa"); map.put(2, "bbbb"); map.put(3, "cccc"); System.out.println(map);
// // 获取方法: // 第一种方式: 使用keySet // 须要分别获取key和value,没有面向对象的思想 // Set<K> keySet() 返回全部的key对象的Set集合
Set<Integer> ks = map.keySet(); Iterator<Integer> it = ks.iterator(); while (it.hasNext()) { Integer key = it.next(); String value = map.get(key); System.out.println("key=" + key + " value=" + value); } } }
|
第二种方式: 经过values 获取全部值,不能获取到key对象
public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "aaaa"); map.put(2, "bbbb"); map.put(3, "cccc"); System.out.println(map); // 第二种方式: // 经过values 获取全部值,不能获取到key对象 // Collection<V> values()
Collection<String> vs = map.values(); Iterator<String> it = vs.iterator(); while (it.hasNext()) { String value = it.next(); System.out.println(" value=" + value); } } |
第三种方式: Map.Entry(重要)
public static interface Map.Entry<K,V>
经过Map中的entrySet()方法获取存放Map.Entry<K,V>对象的Set集合。
Set<Map.Entry<K,V>> entrySet()
面向对象的思想将map集合中的键和值映射关系打包为一个对象,就是Map.Entry,将该对象存入Set集合,Map.Entry是一个对象,那么该对象具有的getKey,getValue得到键和值。
public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "aaaa"); map.put(2, "bbbb"); map.put(3, "cccc"); System.out.println(map); // 第三种方式: Map.Entry对象 推荐使用 重点 // Set<Map.Entry<K,V>> entrySet()
// 返回的Map.Entry对象的Set集合 Map.Entry包含了key和value对象 Set<Map.Entry<Integer, String>> es = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = es.iterator();
while (it.hasNext()) {
// 返回的是封装了key和value对象的Map.Entry对象 Map.Entry<Integer, String> en = it.next();
// 获取Map.Entry对象中封装的key和value对象 Integer key = en.getKey(); String value = en.getValue();
System.out.println("key=" + key + " value=" + value); } } |
底层是哈希表数据结构,线程是不一样步的,能够存入null键,null值。要保证键的惟一性,须要覆盖hashCode方法,和equals方法。
案例:自定义对象做为Map的键。
package cn.itcast.gz.map;
import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set;
public class Demo3 { public static void main(String[] args) { HashMap<Person, String> hm = new HashMap<Person, String>(); hm.put(new Person("jack", 20), "1001"); hm.put(new Person("rose", 18), "1002"); hm.put(new Person("lucy", 19), "1003"); hm.put(new Person("hmm", 17), "1004"); hm.put(new Person("ll", 25), "1005"); System.out.println(hm); System.out.println(hm.put(new Person("rose", 18), "1006"));
Set<Entry<Person, String>> entrySet = hm.entrySet(); Iterator<Entry<Person, String>> it = entrySet.iterator(); while (it.hasNext()) { Entry<Person, String> next = it.next(); Person key = next.getKey(); String value = next.getValue(); System.out.println(key + " = " + value); } } }
class Person { private String name; private int age;
Person() {
}
public Person(String name, int age) {
this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override public int hashCode() {
return this.name.hashCode() + age * 37; }
@Override public boolean equals(Object obj) { if (obj instanceof Person) { Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } else { return false; } }
@Override public String toString() {
return "Person@name:" + this.name + " age:" + this.age; }
} }
|
TreeMap的排序,TreeMap能够对集合中的键进行排序。如何实现键的排序?
方式一:元素自身具有比较性
和TreeSet同样原理,须要让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具有比较性,这种方式叫作元素的天然排序也叫作默认排序。
方式二:容器具有比较性
当元素自身不具有比较性,或者自身具有的比较性不是所须要的。那么此时可让容器自身具有。须要定义一个类实现接口Comparator,重写compare方法,并将该接口的子类实例对象做为参数传递给TreeMap集合的构造方法。
注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主;
注意:在重写compareTo或者compare方法时,必需要明确比较的主要条件相等时要比较次要条件。(假设姓名和年龄一直的人为相同的人,若是想要对人按照年龄的大小来排序,若是年龄相同的人,须要如何处理?不能直接return 0,觉得可能姓名不一样(年龄相同姓名不一样的人是不一样的人)。此时就须要进行次要条件判断(须要判断姓名),只有姓名和年龄同时相等的才能够返回0.)
经过return 0来判断惟一性。
import java.util.TreeMap;
public class Demo4 { public static void main(String[] args) { TreeMap<String, Integer> tree = new TreeMap<String, Integer>(); tree.put("张三", 19); tree.put("李四", 20); tree.put("王五", 21); tree.put("赵六", 22); tree.put("周七", 23); tree.put("张三", 24); System.out.println(tree); System.out.println("张三".compareTo("李四"));//-2094 } } |
自定义元素排序
package cn.itcast.gz.map;
import java.util.Comparator; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap;
public class Demo3 { public static void main(String[] args) { TreeMap<Person, String> hm = new TreeMap<Person, String>( new MyComparator()); hm.put(new Person("jack", 20), "1001"); hm.put(new Person("rose", 18), "1002"); hm.put(new Person("lucy", 19), "1003"); hm.put(new Person("hmm", 17), "1004"); hm.put(new Person("ll", 25), "1005"); System.out.println(hm); System.out.println(hm.put(new Person("rose", 18), "1006"));
Set<Entry<Person, String>> entrySet = hm.entrySet(); Iterator<Entry<Person, String>> it = entrySet.iterator(); while (it.hasNext()) { Entry<Person, String> next = it.next(); Person key = next.getKey(); String value = next.getValue(); System.out.println(key + " = " + value); } } }
class MyComparator implements Comparator<Person> {
@Override public int compare(Person p1, Person p2) { if (p1.getAge() > p2.getAge()) { return -1; } else if (p1.getAge() < p2.getAge()) { return 1; } return p1.getName().compareTo(p2.getName()); }
}
class Person implements Comparable<Person> { private String name; private int age;
Person() {
}
public Person(String name, int age) {
this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override public int hashCode() {
return this.name.hashCode() + age * 37; }
@Override public boolean equals(Object obj) { if (obj instanceof Person) { Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } else { return false; } }
@Override public String toString() {
return "Person@name:" + this.name + " age:" + this.age; }
@Override public int compareTo(Person p) {
if (this.age > p.age) { return 1; } else if (this.age < p.age) { return -1; } return this.name.compareTo(p.name); }
} |
注意:
Set的元素不可重复,Map的键不可重复,若是存入重复元素如何处理
Set元素重复元素不能存入add方法返回false
Map的重复健将覆盖旧键,将旧值返回。
集合框架中的工具类:特色:该工具类中的方法都是静态的。
Collections:常见方法: 1, 对list进行二分查找: 前提该集合必定要有序。 int binarySearch(list,key); //必须根据元素天然顺序对列表进行升级排序 //要求list 集合中的元素都是Comparable 的子类。 int binarySearch(list,key,Comparator); 2,对list集合进行排序。 sort(list); //对list进行排序,其实使用的事list容器中的对象的compareTo方法 sort(list,comaprator); //按照指定比较器进行排序 3,对集合取最大值或者最小值。 max(Collection) max(Collection,comparator) min(Collection) min(Collection,comparator) 4,对list集合进行反转。 reverse(list); 5,对比较方式进行强行逆转。 Comparator reverseOrder(); Comparator reverseOrder(Comparator); 6,对list集合中的元素进行位置的置换。 swap(list,x,y); 7,对list集合进行元素的替换。若是被替换的元素不存在,那么原集合不变。 replaceAll(list,old,new); 8,能够将不一样步的集合变成同步的集合。 Set synchronizedSet(Set<T> s) Map synchronizedMap(Map<K,V> m) List synchronizedList(List<T> list) 9. 若是想要将集合变数组: 可使用Collection 中的toArray 方法。注意:是Collection不是Collections工具类 传入指定的类型数组便可,该数组的长度最好为集合的size。
Arrays:用于对数组操做的工具类
1,二分查找,数组须要有序 binarySearch(int[]) binarySearch(double[]) 2,数组排序 sort(int[]) sort(char[])…… 1, 将数组变成字符串。 toString(int[]) 2, 复制数组。 copyOf(); 3, 复制部分数组。 copyOfRange(): 4, 比较两个数组是否相同。 equals(int[],int[]); 5, 将数组变成集合。 List asList(T[]); 这样能够经过集合的操做来操做数组中元素, 可是不可使用增删方法,add,remove。由于数组长度是固定的,会出现 UnsupportOperationExcetion。 可使用的方法:contains,indexOf。。。 若是数组中存入的基本数据类型,那么asList会将数组实体做为集合中的元素。 若是数组中的存入的引用数据类型,那么asList会将数组中的元素做为集合中 的元素。
import java.util.ArrayList; import java.util.Collections; import java.util.Arrays; import java.util.List; class Demo1 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(4); list.add(3); list.add(1); list.add(2); list.add(3); // 排序 Collections.sort(list); // 折半查找的前提是排序好的元素 System.out.println( Collections.binarySearch( list , 8 ) ); // 找不到返回-插入点-1 // 反序集合输出 Collections.reverse( list ); System.out.println( list ); // 求最值 System.out.println( Collections.max( list ) ); // 4 // fill() 使用指定的元素替换指定集合中的全部元素 // Collections.fill( list, 5 ); System.out.println( list ); // 将数组转换为集合 Integer is[] = new Integer[]{6,7,8}; List<Integer> list2 = Arrays.asList(is); list.addAll( list2 ); System.out.println( list ); // 将List转换为数组 Object [] ins = list.toArray(); System.out.println( Arrays.toString( ins ) ); } }
List<String> list = new ArrayList<String>(); list.add("China"); list.add("Switzerland"); list.add("Italy"); list.add("France"); String [] countries = list.toArray(new String[list.size()]);
String[] countries = {"China", "Switzerland", "Italy", "France"};
List list = Arrays.asList(countries);
List<Value> list = new ArrayList<Value>(map.values());
String [] countries = {"India", "Switzerland", "Italy"}; Set<String> set = new HashSet<String>(Arrays.asList(countries)); System.out.println(set);
Map<Integer, String> sourceMap = createMap(); Set<String> targetSet = new HashSet<>(sourceMap.values());
List<String> datas = getHibernateTemplate().find(hql); Set result = new HashSet<String>(datas);
补充:
在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中咱们总能看到modCount的身影,modCount字面意思就是修改次数,但为何要记录modCount的修改次数呢?
你们发现一个公共特色没有,全部使用modCount属性的全是线程不安全的,这是为何呢?说明这个玩意确定和线程安全有关系喽,那有什么关系呢
在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中咱们总能看到modCount的身影,modCount字面意思就是修改次数,但为何要记录modCount的修改次数呢?
你们发现一个公共特色没有,全部使用modCount属性的全是线程不安全的,这是为何呢?说明这个玩意确定和线程安全有关系喽,那有什么关系呢
private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // next entry to return int expectedModCount; // For fast-fail int index; // current slot Entry<K,V> current; // current entry HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }
由以上代码能够看出,在一个迭代器初始的时候会赋予它调用这个迭代器的对象的mCount,若是在迭代器遍历的过程当中,一旦发现这个对象的mcount和迭代器中存储的mcount不同那就抛异常
好的,下面是这个的完整解释
Fail-Fast 机制
咱们知道 java.util.HashMap 不是线程安全的,所以若是在使用迭代器的过程当中有其余线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是经过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增长这个值,那么在迭代器初始化过程当中会将这个值赋给迭代器的 expectedModCount。在迭代过程当中,判断 modCount 跟 expectedModCount 是否相等,若是不相等就表示已经有其余线程修改了 Map:注意到 modCount 声明为 volatile,保证线程之间修改的可见性。
想要将map中的key直接所有加上"prefix-",例如:{1=1}变为{prefix-1=1}
1.使用迭代器遍历keySet进行先添加元素后删除元素,报错
package cn.qlq.test.test; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @Author: qlq * @Description * @Date: 21:12 2018/9/30 */ public class ForTest { private static final String prefix = "prefix-"; public static void main(String[] args) throws InterruptedException { // 原来的Map HashMap<String, Object> oriMap = new HashMap<String, Object>(); oriMap.put("key1", "111"); oriMap.put("key2", "222"); // 2替换map中的key disposeMap(oriMap); System.out.println(oriMap); } /** * 直接处理报错(java.util.ConcurrentModificationException) * * @param oriMap */ private static void disposeMap(HashMap<String, Object> oriMap) { Set<String> strings = oriMap.keySet(); for(String key:strings){ Object o = oriMap.get(key); oriMap.put(prefix+key,o); oriMap.remove(key); } } }
编译后代码:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package cn.qlq.test.test; import java.util.HashMap; import java.util.Iterator; import java.util.Set; public class ForTest { private static final String prefix = "prefix-"; public ForTest() { } public static void main(String[] args) throws InterruptedException { HashMap<String, Object> oriMap = new HashMap(); oriMap.put("key1", "111"); oriMap.put("key2", "222"); disposeMap(oriMap); System.out.println(oriMap); } private static void disposeMap(HashMap<String, Object> oriMap) { Set<String> strings = oriMap.keySet(); Iterator i$ = strings.iterator(); while(i$.hasNext()) { String key = (String)i$.next(); Object o = oriMap.get(key); oriMap.put("prefix-" + key, o); oriMap.remove(key); } } }
报错:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(HashMap.java:922) at java.util.HashMap$KeyIterator.next(HashMap.java:956) at cn.qlq.test.test.ForTest.disposeMap(ForTest.java:32) at cn.qlq.test.test.ForTest.main(ForTest.java:21)
原理:其实第一次是遍历的时候能够删除元素与增长元素,可是第二次就会报错。(也就是map只有一个元素的时候是能够采用这种方法操做)
执行nextEntry()方法的时候会验证 modCount 是否等于 expectedModCount,expectedModCount是一个定值(大小等于map元素个数),当modCount!=expectedModCount的时候会抛出异常,每次对元素进行put操做和remove操做以后modCount +1 ,因此上面一轮遍历以后modCount = 4,而expectedModCount=2因此抛出异常。
package cn.qlq.test.test; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @Author: qlq * @Description * @Date: 21:12 2018/9/30 */ public class ForTest { private static final String prefix = "prefix-"; public static void main(String[] args) throws InterruptedException { // 原来的Map Map<String, Object> oriMap = new HashMap<String, Object>(); oriMap.put("key1", "111"); oriMap.put("key2", "222"); // 2替换map中的key oriMap = disposeMap2(oriMap); System.out.println(oriMap); } /** * 从新建立一个map进行修改(有效,浪费资源) * * @param oriMap */ private static Map disposeMap2(Map<String, Object> oriMap) { Map result = new HashMap(); Set<String> keys = oriMap.keySet(); for (String key : keys) { Object value = oriMap.get(key); result.put("prefix-" + key, value); } return result; } }
结果:
{prefix-key1=111, prefix-key2=222}
此处有点浪费内存,没有找到更好的办法。。。。。。。
需求是咱们但愿根据集合map的某个key相同的进行合并,好比:
[{key2=222, key1=value00}, {key1=value00, key22=key22}, {key02=value02, key1=value01}] 合并为:
[{key2=222, key1=value00, key22=key22}, {key02=value02, key1=value01}]
代码以下:(思路:遍历须要合并的map,内部遍历合并后的map,若是指定key的value相同就将外层map合并到内层,而且进行下层循环,若是内层没找到就将map添加到合并后的map集合中)
package cn.qlq.test; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * * @author Administrator * */ @SuppressWarnings("all") public class Test2 { public static void main(String[] args) throws InterruptedException { // 原来的Map Map<String, Object> map1 = new HashMap<String, Object>(); map1.put("key1", "value00"); map1.put("key2", "222"); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("key1", "value00"); map2.put("key22", "key22"); Map<String, Object> map3 = new HashMap<String, Object>(); map3.put("key1", "value01"); map3.put("key02", "value02"); List<Map<String, Object>> list = new ArrayList(); list.add(map1); list.add(map2); list.add(map3); System.out.println(list); list = disposeLise(list); System.out.println(list); } /** * 根据某个字段合并map的算法 * * @param list * @return */ private static List<Map<String, Object>> disposeLise(List<Map<String, Object>> list) { List<Map<String, Object>> result = new ArrayList<>(); outer: for (int i = 0; i < list.size(); i++) { Map map = list.get(i); String str = (String) map.get("key1"); if (result.size() == 0) { Map map2 = new HashMap(); map2.put("key1", str); result.add(map); } inner: for (int j = 0; j < result.size(); j++) { Map<String, Object> map2 = result.get(j); if (str.equals(map2.get("key1"))) { map2.putAll(map); continue outer; } } result.add(map); } return result; } }
其实每一个Entry内部维护了一个before和一个after,所以变为有序的。
可是第一个元素和最后一个元素是如何肯定的?
先看下面HashMap和LinkedHashMap的结构:
代码:
package cn.xm.exam.test; import java.util.HashMap; import java.util.LinkedHashMap; public class test { public static void main(String[] args) { HashMap map1 = new HashMap<>(); map1.put("张三", 22); map1.put("lisi", 22); map1.put("赵六", 22); map1.put("aldf", 22); System.out.println(map1.keySet()); LinkedHashMap map2 = new LinkedHashMap<>(); map2.put("张三", 22); map2.put("lisi", 22); map2.put("赵六", 22); map2.put("aldf", 22); System.out.println(map2.keySet()); } }
结果:
[aldf, 赵六, 张三, lisi]
[张三, lisi, 赵六, aldf]
Debug查看两个map结构:
map1是普通的hashmap,没什么特殊之处。
aldf和赵六的hash值相等,可是先添加的赵六,最后用aldf占用赵六的bucket,而且赵六自身做为aldf的next元素。知足数组+链表
Map2与上面的结构同样,只是内部多维护了一个before和after,因此变为有序的。(JDK7查看的结构)
JDK7header元素的after和before分别维护了第一个元素和最后一个元素:
JDK8再次查看LinedHashMap的结构:(其内部有一个head和一个tail,分别记录头元素和尾元素)
补充:高度注意 Map 类集合 K/V 能不能存储 null 值的状况
LinkedHashMap继承HashMap,所以二者同样。
补充:基于TreeMap和TreeSet中的元素或者比较器返回0的时候致使元素丢失:
今天遇到一个TreeSet中传入的比较器返回0的状况致使元素丢失,以下:
public class User implements Comparable<User> { private int age; private String username; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public int compareTo(User o) { if (o.getAge() < this.age) { return -1; } if (o.getAge() > this.age) { return 1; } return 0; } public User(int age, String username) { super(); this.age = age; this.username = username; } @Override public String toString() { return "User [age=" + age + ", username=" + username + "]"; } }
上面按年龄逆序排序,若是年龄相等返回0不排序。
将User做为TreeMap的key查看,以下
import java.util.Map; import java.util.TreeMap; public class Client { public static void main(String[] args) { Map<Object, Object> map = new TreeMap<>(); User user1 = new User(26, "26"); User user2 = new User(25, "25"); User user3 = new User(27, "27"); User user4 = new User(26, "262"); map.put(user1, 1); map.put(user2, 2); map.put(user3, 3); map.put(user4, 4); System.out.println(map); } }
结果:
{User [age=27, username=27]=3, User [age=26, username=26]=4, User [age=25, username=25]=2}
能够看到虽然存进去4个值,可是两个age为26的值只存了一个,并且是第一个存进去的26,value被替换为第二个26的4.
查看TreeMap的put(K,V)源码:
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; 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); } else { if (key == null) throw new NullPointerException(); 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; }
能够看到当比较的值返回0的时候是将值进行替换 t.setValue(value)。因此当集合是TreeMap的时候(TreeSet内部也是维护TreeMap)若是比较器返回的是0会替换值,也就形成值丢失。
最后:
关于数据结构能够查看以下网站: