在平常开发中,咱们常常会碰到须要在运行时才知道对象个数的状况,这种状况不能使用数组,由于数组是固定数量的,这个时候咱们就会使用集合,由于集合能够存储数量不肯定的对象。算法
集合类是特别有用的工具类,不只能够存储数量不等的对象,还能够实现经常使用的数据结构,而且可以存储有映射关联的关联数组。数组
集合类和数组不同,数据既能够存储基本类型,也能够存储对象,而集合只能存储对象(对象的引用变量)。安全
Java集合大体分为:数据结构
Set :无序,不可重复集合多线程
List:有序,可重复集合框架
Map:具备映射关系集合函数
Queue:队列集合工具
Java的集合类主要是由两个接口派生出来:Collection 和Map 。性能
集合的框架可看此图:http://img.blog.csdn.net/20160124221843905spa
Collection collection = new ArrayList(); //添加 collection.add("晚安"); collection.add(9); //返回长度 System.out.println(collection.size()); //移除 collection.remove(9); //是否包含 System.out.println(collection.contains("晚安")); //是否为空 System.out.println(collection.isEmpty()); Collection books = new HashSet(); books.add("晚安"); books.add("愿长夜无梦"); books.add("在全部夜晚安眠"); //去掉collection 包含的元素 books.removeAll(collection); System.out.println(books); books.add("晚安"); //保留二者都有的数据 books.retainAll(collection); System.out.println(books);
Collection 继承了Iterable接口,Java 8为Iterable提供了forEach方法,且这个方法的参数是一个函数式接口,咱们能够经过这个方法进行集合遍历,而且可使用Lambda表达式。
books.forEach(p -> System.out.println(p));
//获取迭代器 Iterator iterator = books.iterator(); //判断是否遍历完成 while (iterator.hasNext()){ //获取集合中的下一个元素,返回的Object对象,须要强制转换 String text = (String)iterator.next(); System.out.println(text); //这里的删除对象是迭代器中删除,删除的是上一个next返回的方法,并且不会真正删除books中的内容 iterator.remove(); //会报错 books.remove(text); }
咱们看到这里有一个删除方法,可是删除的并非books的内容,并且若是修改了其中的内容,实际的内容也不会改变,这里咱们就能够得出结论:集合并非把自己传给了迭代器,而是将集合中的元素传递给了迭代器
迭代器采用的是快速失败机制,一旦在迭代过程当中发现集合被改变,当即抛出错误,这样能够避免共享了资源而致使数据不一致问题。
咱们也能够直接经过forEachRemaining 来遍历,这也是一个函数式接口
iterator.forEachRemaining(p-> System.out.println(p));
for (Object s : books) { System.out.println(s); }
与迭代器相同,这里循环的也不是集合自己,而是元素,而且也不能修改。
books.removeIf(p -> ((String) p).length() > 5);
这个Predicate 咱们能够充分的利用,它能够充分简化集合运算,如:
public static int count(Predicate predicate, Collection collection) { int total = 0; for (Object object : collection) { //判断是否知足条件 if (predicate.test(object)) { total++; } } return total; }
System.out.println(count(p -> ((String) p).length() > 5, books));
Collection 还有一个Stream()流式API,流式API在JQuery中经常会用到,主要分为中间方法和末端方法,顾名思义,中间方法就是容许继续调用后续方法,而末端方法是最终的操做。Stream的引入极大的丰富了集合的操做。
经常使用的中间方法有
filter(Predicate<? super T> predicate) :过滤不符合条件的集合
sorted:排序
limit(long maxSize) :对数量进行控制,通常是排序以后的操做
distinct():去重
经常使用的末端方法有
forEach(Consumer<? super T> action):遍历
toArray():转换成数据
min(Comparator<? super T> comparator):获取最小值
max(Comparator<? super T> comparator) :获取最大值
count() :总数
咱们能够很方便的组合这些API,而对集合进行操做,简单的例子以下:
System.out.println(books.stream().filter(p->((String) p).contains("夜")).count());
在平时的开发咱们能够慢慢熟悉这些写法。
顾名思义,HashSet是按照Hash算法来存储集合中的元素,所以具备很好的存储和查找性能,Hashset不是线程安全的,在多线程状况下,咱们须要经过代码来保证其同步,HashSet元素值能够是null。
HashSet是经过判断两个对象equals()相等而且hashCode()的返回值也相等来决定这两个对象是否为同一对象的。
那这个时候就有些问题了,
若是两个对象的equals()为true,可是hashCode()的返回值不相等,那这个时候HashSet认为这两个对象不等,都会保存,可是其实与咱们的指望就不同了。
若是两个对象的hashCode()返回值相等,可是equals()为false,这个时候也会保存,可是会保存在同一个位置,并经过链式结构来保存,这样会对性能产生影响。
因此咱们要将对象保存到HashSet中,咱们就要尽可能保证两个对象在equals()为true时,其返回的hashCode()的值也要相等。
LinkedHashSet是HashSet的子类,LinkedHashSet也是经过hashCode来肯定位置的,可是从名字中能够看出,它还经过链表进行了插入次序的维护,也就说是遍历的时候能够是有顺序的,可是加入了排序意味着性能的下降。
TreeSet是SortedSet的实现类,这就意味着TreeSet能够确保集合元素处于排序状态,既然须要排序,那就有排序规则,TreeSet有两个排序方法:天然排序和定制排序。
天然排序:TreeSet是调用compareTo方法来比较元素之间的大小。
定制排序:定制排序就是咱们按照咱们制定的规则来进行排序。
TreeSet treeSet=new TreeSet((o1,o2)-> { String m1 = (String)o1; String m2=(String)o2; return m1.length()>m2.length()?-1:0; });
因为要进行排序,因此TreeSet添加的必须是同一个类元素,不然会报错。
由于增长了排序,因此相应的也增长了一些方法:
TreeSet<Integer> treeSet1 = new TreeSet<>(); treeSet1.add(1); treeSet1.add(2); treeSet1.add(3); //以前的一个元素 System.out.println(treeSet1.lower(2)); //后一个元素 System.out.println(treeSet1.higher(2)); //第一个元素 System.out.println(treeSet1.first()); //最后一个元素 System.out.println(treeSet1.last());
EnumSet是专门存储枚举的集合,全部的元素都必须是指定枚举类型的枚举值,EnumSet也是有序的,排序规则与枚举定义的顺序相同。
EnumSet在内部以位向量方式存储,存储很是紧凑、高效,运行效率也很好,EnumSet不容许加null。
List list = new ArrayList(); list.add("晚安"); list.add("愿路途遥远"); list.add("都有人陪在身边"); list.forEach(p-> System.out.println(p)); list.remove(1); //在索引处添加数据 list.add(1, "愿路途遥远"); //获取指定索引位置元素 System.out.println(list.get(2)); System.out.println(list.size()); //设置索引位置的数据,index必须在现有的长度以内 list.set(2, "想要说的话还没说完"); //返回fromIndex(包含),到toIndex(不包含)集合至新集合 List list1 = list.subList(0, 2); //排序,比较函数 list.sort((o1, o2) -> ((String) o1).length() - ((String) o2).length()); //将字符串长度做为新的集合元素替换原来的集合 list.replaceAll(p -> ((String) p).length()); list.forEach(p-> System.out.println(p));
ArrayList 、Vector、LinkedList 是list的三个实现类,彻底支持前面list接口实现的所有功能。
ArrayList 、Vector 是基于数组实现的,内部封装了一个动态的、容许再分配的Object[] 数组,初始化是经过initialCapacity参数肯定初始长度,若是不指定的话默认是10,当咱们能肯定数组的长度时,咱们能够给出,这样能够减小从新分配的次数,而提升性能。
ArrayList 、Vector在使用上彻底相同,而Vector出现的较早,全部其中的一些方法名较长,然后改为List接口的方法后增长了一些方法,可是与其以前的方法有一些重复,咱们通常都喜欢使用新东西的嘛,虽然Vector 线程安全,但若是咱们使用Collections工具类一样可使ArrayList 线程安全,因此总结就是使用ArrayList 就完事了。
LinkedList的内部实现与ArrayList 、Vector彻底不一样,它的内部实现是经过链表来存储的,而且它还继承了Deque接口,也便是能够当作双端队列来使用,因而可知它功能的强大。
LinkedList<String> linkedList = new LinkedList(); //将字符串放入队列尾部 linkedList.offer("队列尾部字符串"); //将字符放入栈顶部 linkedList.push("栈顶部字符串"); //将字符串放入到队列的头部 linkedList.offerFirst("队列头部字符串"); linkedList.forEach(p-> System.out.println(p)); //访问不删除栈顶元素 System.out.println(linkedList.peekFirst()); //访问不删除队列的最后一个元素 System.out.println(linkedList.peekLast()); //弹出栈顶元素 System.out.println(linkedList.pop()); //访问并删除队列的最后一个元素 System.out.println(linkedList.pollLast());
Queue 用于模拟队列这种数据结构,也就是先进先出的容器,队列简单理解就是排队打饭,先排队的人先吃饭,后来的就到队列尾部,队列一般不容许随机访问数据(这样就至关于插队了)。有如下方法:add(E e)
add(E e) :添加元素到尾部。
offer(E e):也是添加元素到尾部,不过在使用容量有限制的队列时,效率比add要高。
remove():获取头部元素并删除。
poll():获取尾部元素并删除。
element():获取头部元素,但不删除。
peek():获取头部元素,但不删除,队列为空返回null
Queue接口有PriorityQueue 实现类,除此以外,Queue 还有一个Deque 子接口,是一个双端队列,能够从两端来添加和删除元素,这样Deque实现类既能够当队列使用,也能够当栈使用,上面的LinkedList就是其实现子类,另外还有一个ArrayDeque。
ArrayDeque实现的是Deque,也就是说它是双端队列,简单理解就是既能够当队列使用,又能够当栈使用,当咱们须要栈这种数据结构时,推荐使用ArrayDeque,Stack是古老的集合,不推荐使用。
咱们分别将ArrayDeque 当作栈和队列来使用下:
栈:
ArrayDeque<String> stack = new ArrayDeque(); stack.push("晚安"); stack.push("愿路途遥远"); stack.push("都有人陪在身边"); System.out.println(stack); //访问第一个元素,但不弹出 System.out.println(stack.peek()); //访问第一个元素,而且弹出 System.out.println(stack.pop()); System.out.println(stack);
队列:
ArrayDeque<String> queue=new ArrayDeque<>(); queue.offer("晚安"); queue.offer("愿长夜无梦"); queue.offer("在每一个夜晚安眠"); System.out.println(queue); //访问队列头部元素,但不删除 System.out.println(queue.peek()); //访问队列头部元素,而且删除 System.out.println(queue.poll()); System.out.println(queue);
Map用于存储具备映射关系的数据,也就是键值对,Map集合保存着两组值,一组存key,另外一组存value,这两组数据能够是任何应用类型的数据,key不容许重复,key和value存在单向的一对一关系。
Map中key 组合起来是一个Set集合,key没有顺序,也不能重复,Map中有个keySet()方法就是获取key集合。
Map的一些经常使用方法以下:
HashMap<Integer, String> map = new HashMap<>(); //放入数据 map.put(1,"宋江"); map.put(2,"卢俊义"); map.put(3,"吴用"); //若是原先位置存在数据时会返回原先的数据 System.out.println(map.put(3,"武松")); //是否存在某key System.out.println(map.containsKey(2)); //是否存在某value System.out.println(map.containsValue("武松")); //是否为空 System.out.println(map.isEmpty()); //获取长度 System.out.println(map.size()); //循环key值 for (Object key: map.keySet()) { //经过key值直接获取value System.out.println(map.get(key)); } //根据key移除元素 System.out.println(map.remove(3)); //新的循环方式 map.forEach((key,value)-> System.out.println(key+":"+value)); //获取value,不存在则返回默认值 map.getOrDefault(8,"查无此人"); //只是替换,不会新增 map.replace(2,"林冲"); //清空数据 map.clear();
HashMap与Hashtable都是Map接口的典型实现类,他们关系相似ArrayList与Vector,Hashtable早出现且线程安全,可是实现并很差,HashMap性能更好但线程不安全,Hashtable的key和value不容许为空,可是HashMap能够,咱们通常也是推荐使用HashMap,即便须要线程安全也可使用Collections工具类。
咱们要正确的存储key,就要让做为key的对象必须实现hashCode()和equals()方法,那咱们判断两个key值是否相等,也是和HashSet相同,必须hashCode()相等,equals()返回为true。
除了key值以外,咱们有时候也要比较value值是否相等containsValue(),这里判断的话只须要equals()返回为true便可。
TreeMap是一个红黑树数据结构,每个key-value即为红黑树的一个节点,存储时根据key进行节点排序,TreeMap保证key-value处于有序状态,也是两个排序机制,天然排序和定制排序,跟以前讲的相似。
由于TreeMap是有序的,那么就会提供一些访问前一个,后一个,第一个,最后一个这种方法,具体方法参考API文档。
ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(8); list.add(5); list.add(10); list.add(7); System.out.println("----天然排序----"); //天然排序 Collections.sort(list); list.forEach(p-> System.out.println(p)); System.out.println("----反转----"); //反转 Collections.reverse(list); list.forEach(p-> System.out.println(p)); System.out.println("----随机排序----"); //随机排序,至关于洗牌 Collections.shuffle(list); list.forEach(p-> System.out.println(p)); System.out.println("----定制排序规则----"); //定制排序规则 Collections.sort(list,(o1,o2)->(o1-o2)); list.forEach(p-> System.out.println(p)); System.out.println("----定制排序规则----"); //调换list中指定位置的顺序 Collections.swap(list,2,4); list.forEach(p-> System.out.println(p)); System.out.println("----将list最后的两个元素移到前面----"); //将list最后的两个元素移到前面 Collections.rotate(list,2); list.forEach(p-> System.out.println(p)); System.out.println("----将list最后的两个元素移到前面----"); //将list中前面的两个元素移到后面 Collections.rotate(list,-2); list.forEach(p-> System.out.println(p));
ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(8); list.add(5); list.add(10); list.add(7); list.add(7); //天然排序 Collections.sort(list); //二分法查找list,带入的参数为value,返回的为索引值(必须是排序以后) System.out.println(Collections.binarySearch(list,10)); //最大值 System.out.println(Collections.max(list)); //最小值 System.out.println(Collections.min(list)); //出现的次数 System.out.println(Collections.frequency(list,8)); //新值替换全部的旧值 Collections.replaceAll(list,8,6); list.forEach(p-> System.out.println(p)); //所有替换 Collections.fill(list,8);
上面提过不少次可使用Collections能够是集合变成线程安全,只要调用synchronizedXXX()即可以建立线程按照的集合
如:
Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>());
Collections提供了三类方法来获取不可变集合
emptyXXX():返回一个不可变的、空的集合对象
singletonXXX():返回一个只包含一个对象的,不可变的集合
unmodifiableXXX():返回指定集合的不可变视图
Collections.emptyList(); Collections.singletonList("原来是这样"); ArrayList<Integer> list = new ArrayList<>(); Collections.unmodifiableCollection(list);
集合的介绍和基本用法就是这样,固然这只是使用,后面还会进行源码的分析