集合的由来:html
面向对象语言对事物都是以对象的形式来体现,为了方便对多个对象的操做,就须要将对象进行存储,集合就是存储对象最经常使用的一种方式。java
集合特色:面试
1,用于存储对象的容器。(容器自己就是一个对象,存在于堆内存中,里面存的是对象的地址)
2,集合的长度是可变的。
3,集合中不能够存储基本数据类型值。 (只能存对象)api
小问题:想用集合存基本数据类型怎么办? 数组
装箱、拆箱。 例:al.add(5); // 至关于al.add(new Integer(5));安全
集合和数组的区别:数据结构
数组虽然也能够存储对象,但长度是固定的,集合长度是可变的。app
数组中能够存储基本数据类型,集合只能存储对象。框架
集合框架的构成及分类:(虚线为接口)ide
下面分别整理集合框架中的几个顶层接口。
Collection子接口以及经常使用实现类:
Collection接口
|--List接口:有序(存入和取出的顺序一致),元素都有索引(角标),元素能够重复。
|--Vector:内部是 数组 数据结构,是同步的。增删,查询都很慢!100%延长(几乎不用了)
|--ArrayList:内部是 数组 数据结构,是不一样步的。替代了Vector,查询的速度快,增删速度慢。50%延长。(查询时是从容器的第一个元素日后找,因为数组的内存空间是连续的,因此查询快;增删的话全部元素内存地址都要改变,因此增删慢。)
|--LinkedList:内部是 链表 数据结构,是不一样步的。增删元素的速度很快。(同理,链表的内存空间是不连续的,因此查询慢;增删时只需改变单个指针的指向,因此快;)
|--Set接口:无序,元素不能重复。Set接口中的方法和Collection一致。
|--HashSet: 内部数据结构是哈希表 ,是不一样步的。
|--LinkedHashSet:内部数据结构是哈希表和链表,是有顺序的HashSet。
|--TreeSet:内部数据结构是有序的二叉树,它的做用是提供有序的Set集合,是不一样步的。
List接口:
有一个最大的共性特色就是均可以操做角标,因此LinkedList也是有索引的。list集合能够完成对元素的增删改查。
Set和List的区别:
1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,能够重复的元素 <最本质区别>。
2. Set检索效率低下,删除和插入效率高,插入和删除不会引发元素位置改变 。
3. List和数组相似,能够动态增加,根据实际存储的数据的长度自动增加List的长度。查找元素效率高,插入删除效率低,由于会引发其余元素位置改变 。
ArryList和Vector可变长度数组的原理:
当默认长度的数组不够存储时,会创建一个新数组。将原来数组的内容拷贝到新的数组当中,并将新增长的元素追加到拷贝完的数组尾,若是仍然不够重复上述动做。其中,ArryList的增长是以原来50%长度进行增长,而Vector是按照100%延长。
ArryList是线程不安全的,Vector是安全的:
因为是否有锁的判断将影响效率,故Arrylist效率远远高于Vector。并且只要是经常使用的容器就不是同步的,由于同步效率比较低。
ArryList存取对象的一个小例子:
Person p1 = new Person("lisi1",21); ArrayList al = new ArrayList(); al.add(p1); al.add(new Person("lisi2",22)); al.add(new Person("lisi3",23)); al.add(new Person("lisi4",24)); Iterator it = al.iterator(); while(it.hasNext()){ // System.out.println(((Person) it.next()).getName()+"::"+((Person) it.next()).getAge()); //错误方式:不能这样取,next()一次指针会移动一次,会输出“lisi1::22 lisi3::24” // 正确方式:拿到一个Person对象,而后取属性。 Person p = (Person) it.next(); System.out.println(p.getName()+"--"+p.getAge()); }
如何保证HashSet的元素惟一性呢?
是经过对象的hashCode和equals方法来完成对象惟一性的:
->若是对象的hashCode值不一样,那么不用判断equals方法,就直接存储到哈希表中。
->若是对象的hashCode值相同,那么要再次判断对象的equals方法是否为true:
若是为true,视为相同元素,不存;若是为false,那么视为不一样元素,就进行存储。
记住:若是对象要存储到HashSet集合中,该对象必须覆盖hashCode方法和equals方法。
通常状况下,若是定义的类会产生不少对象,好比人,学生,书,一般都须要覆盖equals,hashCode方法,以创建对象判断是否相同的依据。
例:往HashSet集合中存储Person对象。若是姓名和年龄相同,视为同一我的,视为相同元素。
import java.util.HashSet; import java.util.Iterator; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public int hashCode() { // System.out.println(this+".......hashCode"); return name.hashCode() + age * 27; // 乘以一个任意数,防止加了年龄之后HashCode仍相同 } @Override public boolean equals(Object obj) { // 健壮性判断 if (this == obj) return true; if (!(obj instanceof Person)) throw new ClassCastException("类型错误"); // System.out.println(this+"....equals....."+obj); Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.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; } public String toString() { return name + ":" + age; } } public class HashSetTest { public static void main(String[] args) { HashSet hs = new HashSet(); /* * HashSet集合数据结构是哈希表,因此存储元素的时候, * 使用的元素的hashCode方法来肯定位置,若是位置相同,在经过元素的equals来肯定是否相同。 * */ hs.add(new Person("lisi4", 24)); hs.add(new Person("lisi7", 27)); hs.add(new Person("lisi1", 21)); hs.add(new Person("lisi9", 29)); hs.add(new Person("lisi7", 27)); Iterator it = hs.iterator(); while (it.hasNext()) { Person p = (Person) it.next(); System.out.println(p); } } }
运行结果:
lisi1:21 lisi9:29 lisi4:24 lisi7:27
TreeSet默认判断元素惟一性的方式:
根据Conpare接口的比较方法conpareTo的返回结果是不是0,是0,就是相同元素,不存。
下面,咱们给出两种自定义判断元素惟一性的方式:
方式一:
让元素自身具有比较功能,即根据元素中的属性来比较。采用这种方式须要元素实现Comparable接口,覆盖compareTo方法。
例:往TreeSet集合中存储Person对象。若是姓名和年龄相同,视为同一我的,视为相同元素。
import java.util.Iterator; import java.util.TreeSet; class Person implements Comparable { public String name; public int age; public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } public String toString() { return name + ":" + age; } @Override public int compareTo(Object o) { Person p = (Person) o; /* 敲黑板划重点,代码简洁方式 */ int temp = this.age - p.age; return temp == 0 ? this.name.compareTo(p.name) : temp; // 上面这两句至关于底下这一段的简洁形式 // if (this.age > p.age) // return 1; // if (this.age < p.age) // return -1; // else { // return this.name.compareTo(p.name); // } } public static void main(String[] args) { TreeSet<Person> ts = new TreeSet<Person>(); ts.add(new Person("zhangsan", 22)); ts.add(new Person("lisi", 27)); ts.add(new Person("wangermazi", 21)); ts.add(new Person("zhaosi", 25)); Iterator it = ts.iterator(); while (it.hasNext()) { Person person = (Person) it.next(); System.out.println(person.toString()); } } }
运行结果:
wangermazi:21 zhangsan:22 zhaosi:25 lisi:27
能够看到,复写compareTo方法后,元素根据age这个属性进行了排序。
方式二:(开发用这个,掌握比较器的用法)
让集合自身具有比较功能。本身写一个比较器,先定义一个类实现Comparator接口,覆盖compare方法。而后将该类对象做为参数传递给TreeSet集合的构造函数。
再也不须要元素实现Conparable接口。
step1-新建比较器类ComparedByName.java,覆盖compare方法:
import java.util.Comparator; public class ComparedByName implements Comparator { @Override public int compare(Object o1, Object o2) { // TODO Auto-generated method stub Person p1 = (Person) o1; Person p2 = (Person) o2; int temp = p1.name.compareTo(p2.name); return temp == 0 ? p1.age - p2.age : temp; } }
step2-将比较器类类对象做为参数传递给TreeSet集合的构造函数:
import java.util.Iterator; import java.util.TreeSet; class Person implements Comparable { public String name; public int age; public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } public String toString() { return name + ":" + age; } @Override public int compareTo(Object o) { Person p = (Person) o; /* 敲黑板划重点,代码简洁方式 */ int temp = this.age - p.age; return temp == 0 ? this.name.compareTo(p.name) : temp; // 上面这两句至关于底下这一段的简洁形式 // if (this.age > p.age) // return 1; // if (this.age < p.age) // return -1; // else { // return this.name.compareTo(p.name); // } } public static void main(String[] args) { TreeSet<Person> ts = new TreeSet<Person>(new ComparedByName()); ts.add(new Person("zhangsan", 22)); ts.add(new Person("lisi", 27)); ts.add(new Person("wangermazi", 21)); ts.add(new Person("zhaosi", 25)); Iterator it = ts.iterator(); while (it.hasNext()) { Person person = (Person) it.next(); System.out.println(person.toString()); } } }
运行结果:
lisi:27 wangermazi:21 zhangsan:22 zhaosi:25
此次咱们的比较器是根据元素属性name进行排序的,复写的compareTo方法是根据age进行排序的。
能够看到,当两种方法同时存在时,是按照比较器的方法来排序的。
思考:如何经过这种方式实现先进先出和先进后出?
让比较器直接返回1或-1便可。
对 Collection 进行迭代的迭代器,即对全部的Collection容器进行元素取出的公共接口。
该迭代器对象依赖于具体容器,由于每个容器的数据结构都不一样,因此该迭代器对象是在具体容器中进行内部实现的。(内部类,能够看具体容器的源码)
对于使用容器者而言,具体的实现方法不重要,只要经过具体容器获取到该实现的迭代器的对象便可,也就是iterator()方法,而不用new。(Iterator<String> ite=list.iterator();)
小知识点:使用迭代器过程当中while和for的区别
1 第一种
2 Iterator<String> ite=list.iterator(); 3 while(ite.hasNext())//判断下一个元素以后有值 4 { 5 System.out.println(ite.next()); 6 } 7 第二种 8 Iterator<String> ite=list.iterator(); 9 for(Iterator it = coll.iterator(); it.hasNext(); ){ 10 System.out.println(it.next()); 11 }
第一种方法while循环结束后迭代器对象还在内存中存在,还能继续使用迭代器对象。
第二种方法for循环结束后迭代器对象就消失了,清理了内存,开发中第二种经常使用。
Iterator的一个子接口
|--ListIterator接口(列表迭代器)
应用场景:
顾名思义,只能用于List的迭代器。
在使用迭代器迭代的过程当中须要使用集合中的方法操做元素,出现ConcurrentModificationException异常时,具体看下面的例子。
出现异常状况代码:
Iterator it = list.iterator(); while(it.hasNext()){ Object obj = it.next();//java.util.ConcurrentModificationException //在使用迭代器的过程当中使用集合中的方法add()操做元素,出现异常。 //可使用Iterator接口的子接口ListIterator来完成在迭代中对元素进行更多的操做。 if(obj.equals("abc2")){ list.add("abc9"); } else System.out.println("next:"+obj); } System.out.println(list);
解决办法代码:
public static void main(String[] args) { List list = new ArrayList(); list.add("abc1"); list.add("abc2"); list.add("abc3"); System.out.println("list:"+list); ListIterator it = list.listIterator();//获取列表迭代器对象 //它能够实如今迭代过程当中完成对元素的增删改查。 //注意:只有list集合具有该迭代功能. while(it.hasNext()){ Object obj = it.next(); if(obj.equals("abc2")){ it.add("abc9"); //ListIterator提供了add方法 } }
Map接口与Set相似,能够对照着来学,好比比较器在TreeMap中也适用。
Map: 一次添加一对元素,Collection 一次添加一个元素。
Map也称为双列集合,Collection集合也称为单列集合。
其实map集合中存储的就是键值对,map集合中必须保证键的惟一性。
经常使用方法:
1,添加
value put(key,value):返回前一个和key关联的值,若是没有返回null.
2,删除
void clear():清空map集合。
value remove(key):根据指定的key翻出这个键值对。
3,判断
boolean containsKey(key):是否包含该key
boolean containsValue(value):是否包含该value
boolean isEmpty();是否为空
4,获取
value get(key):经过键获取值,若是没有该键返回null。固然,能够经过是否返回null,来判断是否包含指定键。
int size(): 获取键值对的个数。
Map经常使用的子类:(*HashMap与Hashtable的区别,面试常问*,详见这个博客。)
|--Hashtable :内部结构是哈希表,是同步的。不容许null做为键,null做为值。
|--Properties:用来存储键值对型的配置文件的信息,能够和IO技术相结合。
|--HashMap : 内部结构是哈希表,不是同步的。容许null做为键,null做为值。
|--TreeMap : 内部结构是二叉树,不是同步的。能够对Map集合中的键进行排序。
Map的迭代方法:
Map自己没有迭代器。
方法一:利用Map接口的values()方法,返回此映射中包含的值的 Collection
(值不惟一),
而后经过Collecion的迭代器进行迭代。(只须要Value,不须要Key的时候)
public class MapDemo { public static void main(String[] args) { Map<Integer,String> map = new HashMap<Integer,String>(); method_2(map); } public static void method_2(Map<Integer,String> map){ map.put(8,"zhaoliu"); map.put(2,"zhaoliu"); map.put(7,"xiaoqiang"); map.put(6,"wangcai"); Collection<String> values = map.values(); Iterator<String> it2 = values.iterator(); while(it2.hasNext()){ System.out.println(it2.next()); } } }
方法二:经过keySet方法获取map中全部的键所在的Set集合(Key和Set的都具备惟一性),
再经过Set的迭代器获取到每个键,再对每个键经过Map集合的get方法获取其对应的值便可。
Set<Integer> keySet = map.keySet(); Iterator<Integer> it = keySet.iterator(); while(it.hasNext()){ Integer key = it.next(); String value = map.get(key); System.out.println(key+":"+value); }
方法三:利用Map的内部接口Map.Entry<K,V>使用iterator。
经过Map的entrySet()方法,将键和值的映射关系做为对象存储到Set集合中。
这个映射关系的类型就是Map.Entry类型(结婚证)。
再经过Map.Entry对象的getKey和getValue获取其中的键和值。
Set<Map.Entry<Integer, String>> entrySet = map.entrySet(); Iterator<Map.Entry<Integer, String>> it = entrySet.iterator(); while(it.hasNext()){ Map.Entry<Integer, String> me = it.next(); Integer key = me.getKey(); String value = me.getValue(); System.out.println(key+":"+value); }
方法四:经过Map.entrySet()方法遍历key和value(推荐,尤为是容量大时)
for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); }
百度考到过HashMap中怎么按value来排序。
和Set中比较器的用法相似,这里咱们用内部类的形式来实现比较器。简单的例子涵盖了不少知识点。
1 public class HashMapTest { 2 // 将内部内修改成静态,直接能够在main函数中建立内部类实例 3 private static class ValueComparator implements Comparator<Map.Entry<Character, String>> { 4 @Override 5 public int compare(Map.Entry<Character, String> entryA, Map.Entry<Character, String> entryB) { 6 // 复写的方法是compare,String类的方法是compareTo,不要记混。 7 return entryA.getValue().compareTo(entryB.getValue()); 8 } 9 } 10 11 public static void main(String[] args) { 12 Map<Character, String> map = new HashMap<>(); 13 map.put('c', "3"); 14 map.put('a', "5"); 15 map.put('b', "1"); 16 map.put('d', "2"); 17 System.out.println("Before Sort:"); 18 for (Map.Entry<Character, String> mapping : map.entrySet()) { 19 System.out.println(mapping.getKey() + ":" + mapping.getValue()); 20 } 21 22 List<Map.Entry<Character, String>> list = new ArrayList<>(map.entrySet()); 23 // 或者list.addAll(map.entrySet()); 24 ValueComparator vc = new ValueComparator(); 25 Collections.sort(list, vc); 26 27 System.out.println("After Sort:"); 28 for (Map.Entry<Character, String> mapping : list) { 29 System.out.println(mapping.getKey() + ":" + mapping.getValue()); 30 } 31 } 32 }
Collections是集合框架的工具类,里面的方法都是静态的。
例1:根据字符串长度的正序和倒序排序。
用到比较器的地方均可以用Collections.reverseOrder()。
static void |
reverse(List<?> list) 反转指定列表中元素的顺序。 |
|
static
|
reverseOrder() 返回一个比较器,它强行逆转实现了 Comparable 接口的对象 collection 的天然顺序。 |
|
static
|
reverseOrder(Comparator<T> cmp) 返回一个比较器,它强行逆转指定比较器的顺序。 |
比较器ComparatorByLength.java:
import java.util.Comparator; public class ComparatorByLength implements Comparator<String> { @Override public int compare(String o1, String o2) { int temp = o1.length() - o2.length(); return temp==0?o1.compareTo(o2): temp; } }
Demo:
public static void demo_3() { // reverse实现原理 /* * TreeSet<String> ts = new TreeSet<String>(new Comparator<String>() { @Override public int compare(String o1, String o2) { int temp = o2.compareTo(o1); return temp; } }); */ TreeSet<String> treeset = new TreeSet<String>(new ComparatorByLength()); treeset.add("abc"); treeset.add("hahaha"); treeset.add("zzz"); treeset.add("aa"); treeset.add("cba"); System.out.println(treeset); TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(new ComparatorByLength()));//都是静态方法,直接类名调用 ts.add("abc"); ts.add("hahaha"); ts.add("zzz"); ts.add("aa"); ts.add("cba"); System.out.println("after reverse:\t" + ts); } public static void main(String[] args) { demo_3(); }
[aa, abc, cba, zzz, hahaha]
after reverse: [hahaha, zzz, cba, abc, aa]
例2:用工具类Collections.sort()进行排序:
public static void demo_2() { List<String> list = new ArrayList<String>(); list.add("abcde"); list.add("cba"); list.add("aa"); list.add("zzz"); list.add("cba"); list.add("nbaa"); System.out.println(list); Collections.sort(list); System.out.println("after sort:\n" + list); Collections.sort(list, Collections.reverseOrder()); System.out.println("after reverse sort:\n" + list); int index = Collections.binarySearch(list, "cba"); System.out.println("index=" + index); // 获取最大值。 String max = Collections.max(list, new ComparatorByLength()); System.out.println("maxLength=" + max); } public static void main(String[] args) { demo_2(); }
[abcde, cba, aa, zzz, cba, nbaa] after sort: [aa, abcde, cba, cba, nbaa, zzz] after reverse sort: [zzz, nbaa, cba, cba, abcde, aa] index=2 maxLength=abcde
例3:给非同步的集合加锁,方法太多就不一一列举了,本身查看API。(掌握,面试会问到)
static
|
synchronizedCollection(Collection<T> c) 返回指定 collection 支持的同步(线程安全的)collection。 |
|
static
|
synchronizedList(List<T> list) 返回指定列表支持的同步(线程安全的)列表。 |
|
static
|
synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全的)映射。 |
|
static
|
synchronizedSet(Set<T> s) 返回指定 set 支持的同步(线程安全的)set。 |
简单说一下给集合加锁的思想。
List list = new ArrayList();// 非同步的list。 list=MyCollections.synList(list);// 返回一个同步的list. class MyCollections{ /** * 返回一个加锁的List * */ public static List synList(List list){ return new MyList(list); } // 内部类 private class MyList implements List{ private List list; private static final Object lock = new Object(); MyList(List list){ this.list = list; } public boolean add(Object obj){ synchronized(lock) { return list.add(obj); } } public boolean remove(Object obj){ synchronized(lock) { return list.remove(obj); } } } }
例4:将集合转成数组,Arrays.asList()方法 (掌握)
应用场景:数组方法有限,须要使用集合中的方法操做数组元素时。
注意1:
数组的长度是固定的,因此对于集合的增删方法(add()和remove())是不能使用的。
Demo:
public static void demo_1() { String[] arr = { "abc", "haha", "xixi" }; List<String> list = Arrays.asList(arr); boolean b1 = list.contains("xixi"); System.out.println("list contains:" + b1); // list.add("hiahia");//引起UnsupportedOperationException System.out.println(list); }
list contains:true [abc, haha, xixi]
注意2:
若是数组中的元素是对象(包装器类型),那么转成集合时,直接将数组中的元素做为集合中的元素进行集合存储。(好比上面那个Demo)
若是数组中的元素是基本数据类型,那么会将该*数组*做为集合中的元素进行存储。(好比下面这个Demo)
Demo:
public static void demo_2() { /* * 若是数组中的元素是对象,那么转成集合时,直接将数组中的元素做为集合中的元素进行集合存储。 * * 若是数组中的元素是基本类型数值,那么会将该数组做为集合中的元素进行存储。 * */ int[] arr = { 31, 11, 51, 61 }; List<int[]> list = Arrays.asList(arr); System.out.println(list); System.out.println("数组的长度为:" + list.size()); }
[[I@659e0bfd]
数组的长度为:1
由结果能够看出,当数组中的元素时int类型时,集合中存的元素是整个数组,集合的长度为1而不是4。
例5:将数组转成集合,List.toArray()方法
Object[] |
toArray()
Returns an array containing all of the elements in this list in proper sequence (from first to last element).
|
<T> T[] |
toArray(T[] a)
Returns an array containing all of the elements in this list in proper sequence (from first to last element); the runtime type of the returned array is that of the specified array.
|
应用场景:对集合中的元素操做的方法进行限定,不容许对其进行增删时。
注意:toArray方法须要传入一个指定类型的数组,数组的长度如何定义呢?
若是定义的数组长度小于集合的size,那么该方法会建立一个同类型并和集合相同size的数组。
若是定义的数组长度大于集合的size,那么该方法就会使用指定的数组,存储集合中的元素,其余位置默认为null。
因此,通常将数组的长度定义为集合的size。
Demo:
public class ToArray { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("abc1"); list.add("abc2"); list.add("abc3"); String[] arr = list.toArray(new String[list.size()]); System.out.println(Arrays.toString(arr)); } }
例6:foreach语句
应用场景:遍历数组或Collection单列集合。
对数组的遍历若是仅仅是获取数组中的元素用foreach能够简化代码,若是要对数组的角标进行操做建议使用传统for循环。
格式:
for(类型 变量 :Collection集合|数组)
{}
Demo:
public class ForEachDemo { public static void main(String[] args) { // 遍历数组 int[] arr = { 3, 1, 5, 7, 4 }; for (int i : arr) { System.out.println(i); } //遍历List List<String> list = new ArrayList<String>(); list.add("abc1"); list.add("abc2"); list.add("abc3"); for (String s : list) { System.out.println(s); } // 遍历map // 可使用高级for遍历map集合吗?不能直接用,可是将map转成单列的set,就能够用了。 Map<Integer, String> map = new HashMap<Integer, String>(); map.put(3, "zhagsan"); map.put(1, "wangyi"); map.put(7, "wagnwu"); map.put(4, "zhagsansan"); for (Integer key : map.keySet()) { String value = map.get(key); System.out.println(key + "::" + value); } for (Map.Entry<Integer, String> me : map.entrySet()) { Integer key = me.getKey(); String value = me.getValue(); System.out.println(key + ":" + value); } // 老式的迭代器写法 Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } }