本文主要介绍了集合的基本概念以及各种集合接口和它的实现类,重点是集合内部的数据结构,理解这些结构对我们在选择哪种集合作为容器有很大的帮助。
为什么要引入泛型:
为什么会有泛型呢? 早期的Object类型可以接收任意的对象类型,但是在实际的使用中, 会有类型转换的问题。也就存在这隐患,所以Java提供了泛型来解决 这个安全问题。
1.泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后 调用此方法时传递实参。
2.参数化类型,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数, 此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具 体的类型(类型实参)。
public class Demo1 <T,E>{ //泛型的本质是为了参数化类型,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型 T name; E age; public T getName() { return name; } public void setName(T name) { this.name = name; } public E getAge() { return age; } public void setAge(E age) { this.age = age; } public T play(E hour){ System.out.println("你今天已经玩了"+hour+"个小时"); return null; } public static void main(String[] args) { //如果不写泛型的类型,默认为object类型 Demo1<String,Integer>demo=new Demo1(); demo.play(100); System.out.println(demo.name); System.out.println(demo.age); } }
注意:
1、泛型的类型参数只能是类类型(包括自定义类 )
2、泛型的类型参数可以有多个。 如果没有定义具体类型,默认为Object。
public class CollectionDemo1 { public static void main(String[] args) { /*Collection c1=new ArrayList(); 如果没定义泛型,可以往集合里添加任意类型的元素,在后面的使用中可能会出现问题 c1.add("hello,world"); c1.add(1); c1.add(1.0);*/ Collection<Integer>c1=new ArrayList(); c1.add(1); //自动装箱,把1转换成包装类 c1.add(2); c1.add(3); // c1.clear(); 从此集合中删除所有元素(可选操作) //System.out.println(c1); /*System.out.println(c1.contains(1)); 如果此集合包含指定的元素,则返回true System.out.println(c1.isEmpty()); 如果此集合不包含元素,则返回 true System.out.println(c1.size()); 返回此集合中的元素数*/ Collection<Integer>c2=new ArrayList(); c2.add(1); c2.add(2); c2.add(9); //c1.addAll(c2); //将c2集合里面的全部元素加到c1中 //System.out.println(c1.containsAll(c2)); //如果此列表包含指定 集合的所有元素则返回true。 // c1.removeAll(c2); //把c1中和c2集合相同的元素删除 System.out.println(c1.retainAll(c2)); //在c1集合中仅保留和c2集合元素相同的元素,如果c1改变返回true,否则false; Object []sb=c1.toArray(); //从第一个到最后一个元素 返回一个包含此列表中所有元素的数组。 返回的数组是Object类型 Integer[]sb2= c1.toArray(new Integer[c1.size()]); //返回的数组的运行时类型是指定数组的运行时类型 System.out.println(c1); System.out.println(c2); System.out.println(Arrays.toString(sb2)); ArrayList<String>list=new ArrayList<>(); list.add("a"); } }
Linkedlist:
1.双向链表,优点,增加删除,用时间很短,但是因为没有索引,对索引的操作,比较麻烦,只能循环遍历,但是每次循环的时候,都会先判断一下,这个索引位于链表的前部分还是后部分,每次都会遍历链表的一半 ,而不是全部遍历。
2.都有一个previous和next, 链表最开始的部分都有一个fiest和last 指向第一个元素,和最后一个元素。增加和删除的时候,只需要更改一个previous和next,就可以实现增加和删除,所以说,LinkedList对于数据的删除和增加相当的方便。
3.在插入/删除的时候,默认在链尾操作。
ArrayList:
1.动态数组,查询快,增删慢(需要移动数组元素)也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。
2.每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作(grow),扩为原数组的1.5倍。 ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。
3.所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
Vector:
1、Vector的方法都是同步的,是线程安全的,而ArrayList的方法不是,由于线程的同步必然要影响性能;
2、当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加百分之五十的大小,这样,ArrayList就有利于节约内存空间。
Iterator遍历
ListIterator遍历
Iterator能遍历List或Set,ListIterator只能遍历List。
Iterotor对象可以调用三个方法:
1.hasNext()用来判断序列中接下来还有没有元素
2.next()用来得到下一个元素,如果是第一次调用,返回的是第一个元素
3.remove()用来移除当前遍历到的元素
ListIterator的接口是Iterator,除了有这三个方法,还增加了一些别的功能:
1.add()用来添加元素
2.set()用来替换元素
3.hasPrevious()用来判断有没有前一个元素
4.previous()得到前一个元素
5.nextIndex()得到下一个元素的索引
6.previousIndex()得到前一个元素的索引
所以,通过这些方法也可以看出,Iterator的遍历是单向的,只能向前遍历;ListIterator的遍历可以是双向的,可以向前或向后遍历。
public class ArrayListDemo3 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); /*for(int i = 0; i < list.size(); i++) { //普通for循环 if (list.get(i) == 1) { list.remove(i); } } System.out.println(list); for(Integer item: list){ //增强for循环 System.out.println(item); list.remove(item); 抛出ConcurrentModificationException 当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。 }*/ Iterator<Integer>it=list.iterator(); //迭代器遍历 while(it.hasNext()){ //hasNext(): 如果迭代具有更多元素,则返回 true Integer item=it.next(); //返回迭代中的下一个元素 if(item==1){ it.remove(); } else{ System.out.println(item);} } System.out.println(list); //next():返回迭代中的下一个元素 } } //Stream<Integer>s=list.stream(); 流遍历 /* s.forEach(new Consumer<Integer>() { @Override public void accept(Integer t) { System.out.println(t); } });*/ // s.forEach(t-> System.out.println(t));
public class HashSetDemo1 { public static void main(String[] args) { /*HashSet<Integer>set=new HashSet<>(); //无序不可重复, 哈希表+链表 set.add(1); set.add(5); set.add(10); set.add(6); set.add(15); set.add(1); set.add(1);*/ /* hashset添加时如何判断值是否重复: 添加时会调用hashCode(),equals()方法; 比较时既要保证内容是否相等,也要保证安全; 先调用hashCode()(速度快)计算哈希值,然后比较哈希值是否相同,如果相同在调用equals()方法比较(hashCode()方法可能不安全)*/ /* System.out.println(set); int a="通话".hashCode(); int b="重地".hashCode(); System.out.println(a==b); //hashCode()不安全,得出true*/ } }
public class TreeSetDemo1 { public static void main(String[] args) { //可以给Set集合中的元素进行指定方式的排序。存储的对象必须实现Comparable接口。 TreeSet底层数据结构是二叉树(红黑树是一种自平衡的二叉树) TreeSet<Integer>t1=new TreeSet<>(); t1.add(1); t1.add(2); t1.add(5); t1.add(4); t1.add(3); t1.add(1); System.out.println(t1); TreeSet<Student>t2=new TreeSet<>(); //Exception in thread "main" java.lang.ClassCastException: // com.ffyc.javacollection.day2.Student cannot be cast to java.lang.Comparable //没有实现Comparable接口,TreeSet底层是二叉树,需要排序,自定义类需要自己实现comparable接口; Student s1=new Student("张三",18); Student s2=new Student("李四",19); Student s3=new Student("王五",20); Student s4=new Student("张三",18); t2.add(s1); t2.add(s2); t2.add(s3); t2.add(s4); System.out.println(t2); for(Integer t:t1){ //增强for循环 System.out.print(t+" "); } System.out.println(); Iterator<Integer>it=t1.iterator(); while(it.hasNext()){ System.out.print(it.next()+" "); } System.out.println(); Stream<Integer>s=t1.stream(); /* s.forEach(new Consumer<Integer>() { @Override public void accept(Integer t) { System.out.print(t+" "); } }); System.out.println();*/ s.forEach(t-> System.out.println(t)); } }
1.对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成
2.TreeSet实现了SortedSet接口,它是一个有序的集合类,TreeSet的底层是通过TreeMap实现的。TreeSet并不是根据插入的顺序来排序,而是根据实际的值的大小来排序。TreeSet也支持两种排序方式:
1.自然排序
2.自定义排序
for(Integer t:t1){ //增强for循环 System.out.print(t+" "); } System.out.println(); Iterator<Integer>it=t1.iterator(); while(it.hasNext()){ System.out.print(it.next()+" "); } System.out.println(); Stream<Integer>s=t1.stream(); /* s.forEach(new Consumer<Integer>() { @Override public void accept(Integer t) { System.out.print(t+" "); } }); System.out.println();*/ s.forEach(t-> System.out.println(t));
HashMap底层实现:
TreeMap底层实现:
TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建时提供的Comparator进行排序
public class HashMapDemo1 { public static void main(String[] args) { /* Map接口常用方法 V put(K key,V value) V remove(Object key) void clear() boolean containsKey(Object key) boolean containsValue(Object value) boolean isEmpty() int size() V get(Object key) Collection<V> values() Set<K> keySet() Set<Map.Entry<K,V>> entrySet()*/ /* final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { HashMap.Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof HashMap.TreeNode) e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }*/ HashMap<String,String> myMap=new HashMap<>(); //HashMap中元素的key值不能重复, 排列顺序是不固定的,可以存储一个为null的键 //底层数据结构是哈希表+链表+红黑树 //用key算出哈希值,用哈希值计算元素在哈希表中的位置,将元素添加到对应的位置,当有重复位置的元素加进来时,以链表的 //的形式存储(避免哈希冲突的解决方法,拉链法) //jdk8以后,做了优化,当链表长度为8时,自动转换为红黑树 //哈希表默认初始长度是16.负载因子0.75 //每次处罚扩容机制时,扩容为原来的2倍 myMap.put("1","a"); myMap.put("2","a"); myMap.put("1","b"); //会替代前面1的建值 myMap.put("3","c"); System.out.println(myMap); myMap.remove("1"); //如果存在(从可选的操作),从该地图中删除一个键的映射。 System.out.println(myMap); // myMap.clear(); //从该地图中删除所有的映射(可选操作) System.out.println(myMap.containsKey("2")); //如果此映射包含指定键的映射,则返回 true 。 System.out.println(myMap.containsValue("d")); //如果此地图将一个或多个键映射到指定的值,则返回 true 。 System.out.println(myMap.isEmpty()); //如果此地图不包含键值映射,则返回 true 。 System.out.println(myMap.size()); System.out.println(myMap.get("5")); //返回到指定键所映射的值,如果键不存在返回null for(String t:myMap.keySet()){ System.out.println(t+":"+myMap.get(t)); } } }
public class TreeMapDemo1 { public static void main(String[] args) { //TreeMap 适用于按自然顺序或自定义顺序遍历键(key)。 // TreeMap根据key值排序,key值需要实现Comparable接口, 重写compareTo方法。 // TreeMap根据compareTo的逻辑,对 key进行排序。 // 键是红黑树结构,可以保证键的排序和唯一性 TreeMap<String,String>myTree=new TreeMap<>(); /* public V put(K key, V value) { TreeMap.Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new TreeMap.Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; TreeMap.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(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } TreeMap.Entry<K,V> e = new TreeMap.Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }*/ myTree.put("1","a"); myTree.put("2","b"); myTree.put("3","c"); myTree.put("1","d"); System.out.println(myTree); /* Map集合遍历 方式1:根据键找值 获取所有键的集合 遍历键的集合,获取到每一个键 根据键找值 方式2:根据键值对对象找键和值 获取所有键值对对象的集合 遍历键值对对象的集合,获取到每一个键值对对象 根据键值对对象找键和值*/ /*TreeMap中所有的元素都保持着某种固定的顺序 如果需要得到一个有序的Map就应该使用TreeMap key值所在类必须实现Comparable接口*/ Set<String> mySet=myTree.keySet(); for(String t: mySet){ System.out.print(t+":"+myTree.get(t)+" "); } System.out.println(); Set<Map.Entry<String,String>>myEntry=myTree.entrySet(); for(Map.Entry<String,String>t:myEntry){ System.out.print(t.getKey()+":"+t.getValue()+" "); } System.out.println(); myTree.forEach(new BiConsumer<String, String>() { @Override public void accept(String s, String s2) { //K V System.out.print(s+" "+s2+" "); } }); } }
HashTable跟HashMap的区别:
HashTable是线程安全的,即HashTable的方法都提供了同步机制;HashMap不是线程安全的,即不提供同步机制 ;HashTable不允许插入空值,HashMap允许
扩展:
1.StringBuffer是线程安全,而StringBuilder是线程不安全的
2.Vector的方法都是同步的,是线程安全的,而ArrayList的方法不是
线程安全:代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换,不会导致该接口的执行结果存在二义性,也就是不用考虑同步的问题。
线程不安全:是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
public class CollectionsDemo1 { public static void main(String[] args) { //Collections是集合类的工具类,与数组的工具类Arrays类似 /* addAll(Collection<? super T> c, T... elements); binarySearch(List<? extends Comparable<? super T>> l ist, T key) sort(List<T> l ist) sort(List<T> l ist, Comparator<? super T> c) swap(List<?> l ist, int i, int j) copy(List<? super T> dest, List<? extends T> src) ; 注意 dest size需大于等于src.size emptyList() 返回为空的集合,不能添加数据 fill(List<? super T> list, T obj) max(Collection<? extends T> coll) min(Col lection<? extends T> coll) replaceAl l(List<T> l ist, T oldVal, T newVal) reverse(List<?> l ist)*/ ArrayList<String>myList=new ArrayList<>(); Collections.addAll(myList,"d","b","c","a"); //可变长度的参数,本质是数组,一个参数列表只有一个 //必须放在参数列表的最后一个 Collections.sort(myList, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); System.out.println(myList); Collections.swap(myList,0,1); ArrayList<String>myList2=new ArrayList<>(); Collections.addAll(myList2,"a","a","a"); Collections.copy(myList,myList2); System.out.println(myList); List<Object> myList3=Collections.emptyList(); //返回空列表(immutable) 不能添加数据 Collections.fill(myList,"b"); System.out.println(myList); //用指定的元素代替指定列表的所有元素。 Collections.replaceAll(myList,"b","c"); //将列表中一个指定值的所有出现替换为另一个。 System.out.println(myList); System.out.println(Collections.max(myList)); //根据指定的比较器引发的顺序返回给定集合的最大元素。 Collections.addAll(myList,"d","e"); Collections.reverse(myList); System.out.println(myList); //反转指定列表中元素的顺序。 } }
注意:
1、java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
2、Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
本文的内容较多,重点是Collection接口和Map接口以及他们的实现类,对于本章内容我们应该借助集合底层的数据结构来加深理解,对不同集合之间的区别和联系应该重点掌握。