Collection coon = new ArrayList(); Iterator iter = coon.iterator(); while(iter.hasNext()) { Object obj = iter.next(); System.out.println(obj); }
Iterator 对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
Iterator 仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Collection接口:单列集合,用来存储一个一个的对象
List接口:存储有序的、可重复的数据,动态数组
ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[] elementsData存储
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层双向链表存储
Vector:作为List接口的古老实现类,线程安全,效率低底层使用Object[] elementsData存储
jdk 7 情况下
ArrayList list = new ArrayList();// 底层创建了长度是10的Object[]数组elementData list.add(123);// elementData = new Integer(123) list.add(112); // 如果此次添加导致底层elementData数组容量不够,则扩容,默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中
建议使用带参的构造器: ArrayList list = new ArrayList(int Capacity)
jdk 8 的变化
ArrayList list = new ArrayList(); // 底层Object[] elementData初始化为{},并没有创建长度为10的数组 list.add(123); // 第一次调用add()时,底层才创建l长度为10的数组,并添加数据到elemnetData中 // 后续的添加和扩容操作与jdk 7 一样
小结:jdk7中的 ArrayList的对象的创建类似于单例的饿汉式,而jdk8 中的类似于懒汉式。
LinkedList list = new LinkedList(); // 创建LinkedList /* LinkedList 是使用java中链表结构来存储数据的,一个结点Node包含prev、next分别指向前驱和后驱 */ // Node 结点 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } // 链表结构 class LinkedList<E> { Node<E> first; Node<E> last; // 记录首个和尾结点,用于遍历查找 public MyLinkList() { } public int size() { return size; } // 让e最后一个结点 void linkLast(E e) { final Node<E> l = last; // 记录尾结点 final Node<E> newNode = new Node<>(l, e, null);// 创造新节点 last = newNode;// 最后的尾结点是newNode if (l == null) // 开始链表为空 first = newNode;// 首结点和尾结点都是newNode else l.next = newNode;// 否则最后一个结点向后指向新的尾结点 size++; modCount++; } // 添加元素 add(E e) public boolean add(E e) { linkLast(e); // 添加元素即向链表尾部 加元素 return true; } // 同理,向首部添加元素 private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) // 初始链表为空 last = newNode;// 首部和尾部都指向newNode else f.prev = newNode;// f(不为空)指向第一个结点,则前驱结点指向newNode size++; modCount++; } // 向链表中间添加元素, 在第index元素后面添加element public void add(int index, E element) { checkPositionIndex(index); // 检查index是否越界 if (index == size) // 在最后一个元素(尾部)添加 linkLast(element); else linkBefore(element, node(index)); // node返回的第index个元素的后一个元素,所以要在这之前插入给定元素 } // 在结点之前插入元素 void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; } }
jdk 7 和 jdk 8 中通过Vector( ) 构造器创建对象时,底层创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍。
下面分析 jdk14的 Vector 源码
/* vector 也采用Object[] 数组存储数据和扩容机制 */ public class Vector extends AbstractList{ protected Object[] elementData; // Object数组 protected int elementCoune; // 有效元素的个数 protected int capacityIncrement;// 每次扩容的容量大小z //构造函数 // - 有参构造(初始容量,自定义每次扩容大小) public Vector(int initialCapacity,int Increment) { super(); // 父类的构造哈函数 if(容量 < 0 ) 抛出异常 throw new Exception; this.elementData = new Object[initialCapacity]; this.capacityIncrement = Increment } public Vector(int initialCapacity) { this(initialCapacity,0); // 默认扩容为0 } public Vector() { this(10); // 默认容量为10 } // 返回vector的容量 public synchronized int capacity() { return elementData.length; } // 返回vector中元素的数量 public synchronized int size() { return elementCount; }
扩容机制(扩容之后返回的复制后的新数组)
// 1、grow() 无参数 // 添加元素 public synchronized boolean add(E e) { modCount++; add(e, elementData, elementCount); // 往数组最后面加元素 // 数组的个数比下标大1 return true; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) // 越界了 elementData = grow(); // 开始扩容 elementData[s] = e; elementCount = s + 1; // 元素的个数加1 } // 2、扩容,调用有参的grow() private Object[] grow() { return grow(elementCount + 1); // 要多加1个元素,所以函数参数表示新数组的长度 } // 3、扩容核心代码 // - minCapacity 最少需要的数组长度 private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ capacityIncrement > 0 ? capacityIncrement : oldCapacity /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } /* oldLength 现在数组的长度length minGrowth 数组的最小增量 prefGrowth 数组首选zeng'zahng */ public static int newLength(int oldLength, int minGrowth, int prefGrowth) { // assert oldLength >= 0 // assert minGrowth > 0 int newLength = Math.max(minGrowth, prefGrowth) + oldLength; // 如果增长因子 > 0,vector增加max(minGrowth,capacityIncrement) // 增长因子 < 0,vector容量加倍或者max(minGrowth,oldCapacity)+oldLength if (newLength - MAX_ARRAY_LENGTH <= 0) { return newLength; } return hugeLength(oldLength, minGrowth); // 上面都不行则返回整数的最大值作为新数组的长度 }
如果增长因子 > 0,vector增加
max(minGrowth,capacityIncrement)
增长因子 < 0,vector容量加倍
或者max(minGrowth,oldCapacity)+oldLength
List除继承集合Collection中的方法外,还添加了一些根据索引操作元素的方法
index从0开始
void add(int index,Object ele)
在index位置插入元素boolean addAll(int index,Collection eles)
从index位置插入结合的索引元素Object get(int index)
int indexOf(Object obj)
返回obj在集合中首次出现的位置int lastIndexOf(Object obj)
返回obj在集合中最后出现的位置Object remove(int index)
移除指定index位置的元素,返回该元素Object set(int index,Object ele)
设定指定index位置的元素为eleList subList(int fromIndex, int toIndex)
返回范围的子集合(左闭右开)/* List遍历的三种方式 */ @Test public void test3() { ArrayList list = new ArrayList(); list.add(123); list.add(456); list.add("Tom"); // 方式一 Iterator iter = list.iterator(); while(iter.hasNext()) { Object obj = iter.next(); System.out.println(obj); } // 方式二 for(Object obj : list) { System.out.println(obj); } // 方式三 for(int i = 0;i < list.size();i++) { System.out.println(list.get(i)); } }
equals()
方法Set接口: 存储无序的、不可重复的数据
HashSet:Set接口的主要实现类;线程不安全;可以存储null值;
LinkedHashSet:HashSet的子类,遍历内部数据时,可以按照添加的顺序的遍历,遍历的效率要高于HashSet
TreeSet:可以按照添加对象的属性,进行排序
无序性和不可重复性?
无序性不等于随机性,存储的数据在底层数组不是按照数组索引的顺序添加。而是根据数据的hash值决定
不可重复性:Set中不能有重复的数据元素(使用equals() 方法判断),如果是重复的类则这个类需要重写 equals() 和 hashCode( )方法
HashSet 是 Set 接口的典型实现,大多数时候使用Set集合时,都使用这个实现类
HashSet 按照 Hash 算法存储集合中的元素,存取、查找、删除性能
HashSet :不能 保证元素的排列顺序
底层是数组,初始容量16,当如果使用率超过0.75,就会扩容为原来的2倍。
HashSet 集合判断两个元素相等:
hashCode()
比较相等equals()
方法返回值相等对于放在Set容器中的对象,对应的类一定要重写 equals()
和hashCode(Object obj)
方法,以实现对象相等规则。“相等的对象必须具有相等的散列码”
添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()
方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置
(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素
,则元素a添加成功。 --->情况1
如果此位置上有其他元素
b(或以链表形式存在的多个元素),则比较元素a与元素b 的hash值:
如果hash值不相同
,则元素a添加成功。--->情况2
如果hash值相同
,进而需要调用元素a所在类的equals()
方法:
equals()返回true
,元素a添加失败
equals()返回false
,则元素a添加成功
。--->情况3
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构
LinkedHashSet
是HashSet
的子类LinkedHashSet
根据元素的hasCode
值决定元素的存储位置,同时用双向链表维护元素的次序,使得元素看起来是以插入顺序保存,的。LinkedHashSet
插入性能低于HashSet
,但在迭代(频繁遍历)访问Set 里的全部元素时有很好的性能TreeSet不能添加不同类的对象
TreeSet
是SortedSet
接口的实现类,TreeSet
可以确保集合元素处于排序状态TreeSet
底层使用红黑树结构存储数据(TreeMap也是)List
快两种排序方法: 自然排序 定制排序,默认采用自然排序
TreeSet
调用集合元素的compareTo(Object obj)
方法,两个对象通过compareTo()方法的返回值比较大小Comparable
的典型实现
BigDecimal
、BigInteger
以及所有的数值型对应的包装类:按数值大小比较Character
:按字符的unicode值来进行比较Boolean
:true对应的包装类实例大于false对应的包装类实例String
:按字符串中字符的unicode值进行比较Data、Time
:时间和日期从小到大向TreeSet
中添加元素时,只有第一个元素无须比较compareTo()
方法,后面添加的所有元素都会调用compareTo()
方法进行比较。Comparable
接口,通过Comparator
接口来实现,重写compare(T o1,T o2)
方法Comparator
接口的实例作为形参传递给TreeSet
的构造器。@Test public void test3() { Comparator com = new Comparator() { // 采用实现类实例化 // 匿名内部类一定要实现内部所有的抽象方法 @Override public int compare(Object o1, Object o2) { if(o1 instanceof User && o2 instanceof User) { User u1 = (User) o1; User u2 = (User) o2; return Integer.compare(u1.getAge(),u2.getAge()); }else { throw new RuntimeException("类型不一致"); } } }; TreeSet set2 = new TreeSet(com); set2.add(new User("Tom",12)); set2.add(new User("Jerry",32)); set2.add(new User("Jim",2)); set2.add(new User("Mike",65)); set2.add(new User("Jack",33)); set2.add(new User("Jack",56)); Iterator iterator = set2.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); } }
Map :双列数据,存储 key-value 对的数据
HashMap : 作为Map 的主要实现类;线程不安全,效率高,存储null的key和value
- LinkedHashMap :保证在遍历 map 元素时,可以按照添加的顺序进行遍历 (原因:在原有的HashMap底层结构上,添加了一对指针,指向前一个和后一个元素,对于频繁的遍历操作,效率较高)
TreeMap :保证按照添加的 key-value对 进行排序,实现排序遍历,此时考虑key的自然排序或定制排序(底层使用红黑树)
Hashtable : 作为古老实现类;线程安全,效率低,不能存储null 的 key 和 value(过时了)
- Properties :常用来存储配置文件。key 和 value 都是 String类型
HashMap的底层:数组+链表 (jdk7及之前) * 数组+链表+红黑树 (jdk 8)
key
用 set
存放,不允许重复,即同一个 Map
对象所对应的类,须重写 hashCode()
和equals()
方法HashMap
是Map
接口使用频率最高的实现类Map结构的理解
Map中的key: 无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals( )和 hashCode() (以HashMap为例)
HashMap的底层原理? 在实例化以后,底层创建了长度是16的一维数组Entry[] table 以jdk7为例: 首先,调用key1所在类的 hashCode()计算key1 哈希值,此哈希值经过某种算法计算后,得到在Entry数组中的存放位置 i 如果此位置上的数据为空,此时的key1-value1 添加成功。 ----情况1 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据,链表形式),比较key1和已经存在的一个或多个数据的哈希值: - 比较哈希值(存储位置相同不代表哈希值相同) 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。 -----情况2 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2) - 比较equals() 判断key1的对象内容是否相等 如果equals()返回false:此时key1-value1添加成功。 -----情况3 如果equals()返回true:使用value1替换value2。 补充--关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储 扩容:在不断添加的过程中,会涉及到扩容问题,默认的扩容方式:扩容到原来的2倍,并将原有的数据复制过来 --- jdk8 相较于jdk7在底层实现方面的不同: 1. new HashMap():底层没有创建一个长度16的数组 2. jdk 8 底层的数组是:Node[],而非Entry[] 3. 首次调用put()方法时,底层才创建长度为16的数组 4. jdk7 只有数组+链表。 jdk8 底层结构: 数组+链表+红黑树 当数组的某一个索引位置上的元素以链表形式存在的数据个数 >= 8 且当前数组的长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储
HashMap 源码中的重要常量
DEFAULT_INITIAL_CAPACITY
: HashMap的默认容量,16`DEFAULT_LOAD_FACTOR`:HashMap的默认加载因子:0.75
`threshold`:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
`TREEIFY_THRESHOLD`:Bucket中链表长度大于该默认值,转化为红黑树:8
`MIN_TREEIFY_CAPACITY`:桶中的Node被树化时最小的hash表容量:64
添加、删除、修改
Object put(Object key,Object value)
:添加key-value,返回valuevoid putAll(Map m)
:将m中所有key-value对存到当前map中Object remove(Object key)
:移除指定的key-value,返回valuevoid clear()
:清空当前map中的所有数据查询
Object get(Object key)
:获取指定key对应的valueboolean containsKey(Object key)
boolean containsValue(Object value)
int size()
返回map中key-vaue对的个数boolean isEmpty()
: 判断是否为空booean equals(Object obj)
: 判断当前map 和 参数对象 obj是否相等元视图操作
Set keySet()
:返回所有key构成的set集合Collection values()
:返回所有value构成的Collection集合Set entrySet()
:返回所有key-value 对的集合public void test2() { /** * 元视图操作的方法: * Set keySet():返回所有key构成的Set集合 * Collection values():返回所有value构成的Collection集合 * Set entrySet():返回所有key-value对构成的Set集合 */ Map map = new HashMap(); map.put("AA",123); map.put(45,1234); map.put("BB",56); // 遍历所有的key集:keySet() Set set = map.keySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("-----------"); // 遍历所有的values集:values() Collection values = map.values(); for(Object obj : values) { System.out.println(obj); } System.out.println("********"); // 遍历所有的key-values // 方式一 直接遍历entrySet(存储键值对) Set entrySet = map.entrySet(); Iterator iterator1 = entrySet.iterator(); while (iterator1.hasNext()) { Object obj = iterator1.next(); Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey()+"--->"+entry.getValue()); } System.out.println("/"); // 方式二 Set keySet = map.keySet(); // 获得所有的key Iterator iterator2 = keySet.iterator(); while (iterator2.hasNext()) { Object key = iterator2.next(); Object value = map.get(key); System.out.println(key + "====" +value); } }
LinkedHashMap
是HashMap
的子类HashMap
存储结构的基础下,使用一对双向链表来记录添加元素的顺序HashMap
中的内部类:Node
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node <K,V> next; }
LinkedHashMap
中的内部类:Entry
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before,after; Entry(int hash,K key,V value,Node <K,V> next) { super(hash,key,value,next); } }
TreeMap
存储Key-Value
对时,需要根据key-value
对进行排序。TreeMap
可以保证所有的Key-Value
对处于有序状态。key
的排序
TreeMap
的所有key
必须实现Comparable
接口,而且所有的key
要是同一个类的对象TreeMap
时,传入一个Comparator
对象,此时不需要Map
的key
实现Comparable
接口TreeMap
判断两个key
相等的标准:通过compareTo()
和compare
方法返回0Properties
类是Hashtable
的子类,用于处理属性文件key和value
都是字符串类型setProperty(String key,String value)
和getProperty(String key)
方法Collections
是一个操作Set
、List
和Map
等集合的工具类
Collections
中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作:static
方法 以下操作会直接影响到list的元素内容
reverse(List)
:颠倒顺序shuffle(List)
:随机排序,任意顺序sort(List)
:根据元素的自然顺序对指定List 集合元素按升序排序sort(List,Comparator)
:定制排序 Comparatorswap(List,int,int)
:list集合中交换元素(下标)Object max(Collection)
:根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object)
:返回指定集合中指定元素的出现次数
void copy(List dest,List src)
:将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal)
:使用新值替换 List 对象的所有旧值
public class CollectionsTest { @Test public void tets() { Collection list = new ArrayList(); list.add(123); list.add(140); list.add(150); Collections.reverse((List<?>) list); System.out.println("原始数据"+list); System.out.println("颠倒后"+list); Collections.shuffle((List<?>) list); System.out.println("随机排序"+list); Collections.swap((List<?>) list,1,2); System.out.println("交换元素后"+list); // 自然排序:排序的类要实现Comparable接口,重写compareTo方法 LinkedList linkedList = new LinkedList(); linkedList.add(new User("Tom",12)); linkedList.add(new User("Jerry",14)); linkedList.add(new User("Kack",8)); linkedList.add(new User("Asia",15)); linkedList.add(new User("Bobo",9)); Collections.sort(linkedList); System.out.println(linkedList); } @Test public void test3() { Comparator com = new Comparator() { @Override public int compare(Object o1, Object o2) { if(o1 instanceof User && o2 instanceof User) { User u1 = (User) o1; User u2 = (User) o2; return u1.getName().compareTo(u2.getName()); // name从小到大 }else throw new RuntimeException("传入类型不一致"); } }; // 定制排序 LinkedList linkedList = new LinkedList(); linkedList.add(new User("Tom",12)); linkedList.add(new User("Jerry",14)); linkedList.add(new User("Kack",8)); linkedList.add(new User("Asia",15)); linkedList.add(new User("Bobo",9)); Collections.sort(linkedList,com); System.out.println(linkedList); } } 原始数据[150, 140, 123] 颠倒后[150, 140, 123] 随机排序[150, 140, 123] 交换元素后[150, 123, 140] [User{name='Tom', age=12}, User{name='Kack', age=8}, User{name='Jerry', age=14}, User{name='Bobo', age=9}, User{name='Asia', age=15}] // 定制排序 [User{name='Asia', age=15}, User{name='Bobo', age=9}, User{name='Jerry', age=14}, User{name='Kack', age=8}, User{name='Tom', age=12}]