Java核心技术梳理-集合
2019-04-26 08:20:45来源:博客园 阅读 ()
一、前言
在日常开发中,我们经常会碰到需要在运行时才知道对象个数的情况,这种情况不能使用数组,因为数组是固定数量的,这个时候我们就会使用集合,因为集合可以存储数量不确定的对象。
集合类是特别有用的工具类,不仅可以存储数量不等的对象,还可以实现常用的数据结构,并且能够存储有映射关联的关联数组。
集合类和数组不一样,数据既可以存储基本类型,也可以存储对象,而集合只能存储对象(对象的引用变量)。
Java集合大致分为:
-
Set :无序,不可重复集合
-
List:有序,可重复集合
-
Map:具有映射关系集合
-
Queue:队列集合
Java的集合类主要是由两个接口派生出来:Collection 和Map 。
集合的框架可看此图:http://img.blog.csdn.net/20160124221843905
二、Collection 和 Iterator
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来确定位置的,但是从名字中可以看出,它还通过链表进行了插入次序的维护,也就说是遍历的时候可以是有顺序的,但是加入了排序意味着性能的降低。
3.2 TreeSet
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());
3.4 EnumSet
EnumSet是专门存储枚举的集合,所有的元素都必须是指定枚举类型的枚举值,EnumSet也是有序的,排序规则与枚举定义的顺序相同。
EnumSet在内部以位向量方式存储,存储非常紧凑、高效,运行效率也很好,EnumSet不允许加null。
3.5
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。
5.2 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);
7.3 同步控制
上面提过很多次可以使用Collections可以是集合变成线程安全,只要调用synchronizedXXX()便可以创建线程按照的集合
如:
Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>());
7.4 不可变集合
Collections提供了三类方法来获取不可变集合
-
emptyXXX():返回一个不可变的、空的集合对象
-
singletonXXX():返回一个只包含一个对象的,不可变的集合
-
unmodifiableXXX():返回指定集合的不可变视图
Collections.emptyList(); Collections.singletonList("原来是这样"); ArrayList<Integer> list = new ArrayList<>(); Collections.unmodifiableCollection(list);
集合的介绍和基本用法就是这样,当然这只是使用,后面还会进行源码的分析
原文链接:https://www.cnblogs.com/yuanqinnan/p/10770270.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:Spring的事务管理
下一篇:eureka服务搭建
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash