zoukankan      html  css  js  c++  java
  • 集合--(List、Set、Map)遍历、删除、比较元素时的小陷阱

     

    6,Map集合遍历的4中方法?

    5,List遍历时如何remove元素

    4、漏网之鱼-for循环递增下标方式遍历集合,并删除元素
    如果你用for循环递增下标方式遍历集合,在遍历过程中删除元素,你可能会遗漏了某些元素。

    3、ConcurrentModificationException异常-----Iterator遍历集合过程中用其他手段(或其他线程)操作元素

    2、Map集合操作陷阱;

    1、Set集合操作陷阱---一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2

    =====================

    6,Map集合遍历的4中方法?

    在Java中如何遍历Map对象

    在java中遍历Map有不少的方法。我们看一下最常用的方法及其优缺点。

    既然java中的所有map都实现了Map接口,以下方法适用于任何map实现(HashMap, TreeMap, LinkedHashMap, Hashtable, 等等)

    方法一 在for-each循环中使用entries来遍历

    这是最常见的并且在大多数情况下也是最可取的遍历方式。在键值都需要时使用。

    [java] view plain copy
     
    1. Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
    2.   
    3. for (Map.Entry<Integer, Integer> entry : map.entrySet()) {  
    4.   
    5.     System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
    6.   
    7. }  

    注意:for-each循环在Java 5中被引入所以该方法只能应用于java 5或更高的版本中。如果你遍历的是一个空的map对象,for-each循环将抛出NullPointerException,因此在遍历前你总是应该检查空引用。

    方法二 在for-each循环中遍历keys或values。

    如果只需要map中的键或者值,你可以通过keySet或values来实现遍历,而不是用entrySet。

    1. Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
    2.   
    3. //遍历map中的键  
    4.   
    5. for (Integer key : map.keySet()) {  
    6.   
    7.     System.out.println("Key = " + key);  
    8. }  
    9.   
    10. //遍历map中的值  
    11.   
    12. for (Integer value : map.values()) {  
    13.   
    14.     System.out.println("Value = " + value);  
    15.   
    16. }  

    该方法比entrySet遍历在性能上稍好(快了10%),而且代码更加干净。

    方法三使用Iterator遍历

    使用泛型:

    1. Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
    2.   
    3. Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();  
    4.   
    5. while (entries.hasNext()) {  
    6.   
    7.     Map.Entry<Integer, Integer> entry = entries.next();  
    8.   
    9.     System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
    10.   
    11. }  

    不使用泛型:

    1. Map map = new HashMap();  
    2.   
    3. Iterator entries = map.entrySet().iterator();  
    4.   
    5. while (entries.hasNext()) {  
    6.   
    7.     Map.Entry entry = (Map.Entry) entries.next();  
    8.   
    9.     Integer key = (Integer)entry.getKey();  
    10.   
    11.     Integer value = (Integer)entry.getValue();  
    12.   
    13.     System.out.println("Key = " + key + ", Value = " + value);  
    14.   
    15. }  

    你也可以在keySet和values上应用同样的方法。

    该种方式看起来冗余却有其优点所在。首先,在老版本java中这是惟一遍历map的方式。另一个好处是,你可以在遍历时调用iterator.remove()来删除entries,另两个方法则不能。根据javadoc的说明,如果在for-each遍历中尝试使用此方法,结果是不可预测的。

    从性能方面看,该方法类同于for-each遍历(即方法二)的性能。 

    方法四、通过键找值遍历(效率低)

    1. Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
    2.   
    3. for (Integer key : map.keySet()) {  
    4.   
    5.     Integer value = map.get(key);  
    6.   
    7.     System.out.println("Key = " + key + ", Value = " + value);  
    8.   
    9. }  

    作为方法一的替代,这个代码看上去更加干净;但实际上它相当慢且无效率。因为从键取值是耗时的操作(与方法一相比,在不同的Map实现中该方法慢了20%~200%)。如果你安装了FindBugs,它会做出检查并警告你关于哪些是低效率的遍历。所以尽量避免使用。

    总结

    如果仅需要键(keys)或值(values)使用方法二。如果你使用的语言版本低于java 5,或是打算在遍历时删除entries,必须使用方法三。否则使用方法一(键值都要)。

     5,List遍历时如何remove元素?

    public class RemoveElementDemo {
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("100012011");
    list.add("10001201s1");
    list.add("10001201s1");
    //解决方案:
    //1.i--操作
    /*for(int i = 0;i < list.size();i++){
    String b = list.get(i);
    if(b.equals("502323232")){
    list.remove(i);
    i--;
    }
    }*/
    //2.反向遍历
    /*for(int i = list.size() - 1;i >= 0;i--){
    String b = list.get(i);
    if(b.equals("502323232")){
    list.remove(i);
    }
    }*/
    //解决方案:调用Iterator的remove()方法安全删除元素,避免异常
    Iterator<String> iter = list.iterator();
    while(iter.hasNext()){
    String b = iter.next();
    if(b.equals("100012011")){
    iter.remove();
    }
    }
    for(String b : list){
    System.out.println(b);
    }
    }
    }

    ------------

    4、漏网之鱼-for循环递增下标方式遍历集合,并删除元素
        如果你用for循环递增下标方式遍历集合,在遍历过程中删除元素,你可能会遗漏了某些元素。

    public class ListTest_Unwork {
    public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    System.out.println("Original list : " + list);
    String temp = null;
    for (int i = 0; i < list.size(); i++) {
    temp = list.get(i);
    System.out.println("Check for " + temp);
    if ("3".equals(temp)) {
    list.remove(temp);
    }
    }
    System.out.println("Removed list : " + list);
    }
    }
    日志打印:
    Original list : [1, 2, 3, 4, 5]
    Check for 1
    Check for 2
    Check for 3
    Check for 5
    Removed list : [1, 2, 4, 5]
    如日志所见,其中值为4的元素并未经过判断,漏网之鱼。
    解决方法为以下两个(但一般不建议我们在遍历中用不是遍历本身的函数删除元素,见关于“ConcurrentModificationException”的内容):

    1、对于此情况,我一般都从后面开始遍历,以避免问题:

    public class ListTest_Work {
    public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    System.out.println("Original list : " + list);
    String temp = null;
    for (int i = list.size() - 1; i >= 0; i--) {
    temp = list.get(i);
    System.out.println("Check for " + temp);
    if ("3".equals(temp)) {
    list.remove(temp);
    }
    }
    System.out.println("Removed list : " + list);
    }
    }
    2、直接从新创建一个集合,重新摆放,但消耗内存,慎用:

    public class ListTest_Work2 {
    public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    System.out.println("Original list : " + list);
    System.out.println();

     

    List<String> tempList = new ArrayList<String>();
    for (String temp : list) {
    System.out.println("Check for " + temp);
    if (!"3".equals(temp)) {
    tempList.add(temp);
    }
    }
    System.out.println("Removed list : " + tempList);
    }
    }

    3、ConcurrentModificationException异常-----Iterator遍历集合过程中用其他手段(或其他线程)操作元素;
    ConcurrentModificationException是Java集合的一个快速报错(fail-fast)机制,防止多个线程同时修改同一个集合的元素。
    在用Iterator遍历集合时,如果你用其他手段(非Iterator自身手段)操作集合元素,就会报ConcurrentModificationException。
    用Iterator方式 或 --简写的for(Object o : list) {}方式,遍历集合,修改元素时会报异常:

    延伸个小问题,为什么for(Object o : list) {}方式遍历集合,现象和Iterator方式一样,都会报错呢?
    答:这是因为Java的糖语法,“for(Object o : list) {}方式”只是Java语言用“易用性糖衣”吸引你的手段,本质上,它也是Iterator。

    2、Map集合操作陷阱;

    方法1--- 在for-each循环中使用entries来遍历
    这是最常见的并且在大多数情况下也是最可取的遍历方式。在键值都需要时使用。

    Map<Integer, Integer> map = new HashMap<Integer, Integer>();

    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    System.out.println("Key="+entry.getKey()+",Value="+entry.getValue());
    }

    注意:for-each循环在Java 5中被引入所以该方法只能应用于java 5或更高的版本中。如果你遍历的是一个空的map对象,for-each循环将抛出NullPointerException,因此在遍历前你总是应该检查空引用。


    方法2 ---在for-each循环中遍历keys或values。
    如果只需要map中的键或者值,你可以通过keySet或values来实现遍历,而不是用entrySet。

    Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    //遍历map中的键
    for (Integer key : map.keySet()) {
    System.out.println("Key = " + key);
    }

    //遍历map中的值
    for (Integer value : map.values()) {
    System.out.println("Value = " + value);
    }
    该方法比entrySet遍历在性能上稍好(快了10%),而且代码更加干净。

    方法3--使用Iterator遍历

    使用泛型:
    Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
    while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
    System.out.print("Key="+entry.getKey()+",Value="+entry.getValue())
    }

    不使用泛型:
    Map map = new HashMap();
    Iterator entries = map.entrySet().iterator();

    while (entries.hasNext()) {
    Map.Entry entry = (Map.Entry) entries.next();
    Integer key = (Integer)entry.getKey();
    Integer value = (Integer)entry.getValue();
    System.out.println("Key = " + key + ", Value = " + value);

    }
    你也可以在keySet和values上应用同样的方法。

    该种方式看起来冗余却有其优点所在。首先,在老版本java中这是惟一遍历map的方式。
    另一个好处是,你可以在遍历时调用iterator.remove()来删除entries,另两个方法则不能。
    根据javadoc的说明,如果在for-each遍历中尝试使用此方法,结果是不可预测的。从性能方面看,该方法类同于for-each遍历(即方法二)的性能。


    方法4、通过键找值遍历(效率低)
     Map<Integer, Integer> map = new HashMap<Integer, Integer>();

    for (Integer key : map.keySet()) {

    Integer value = map.get(key);

    System.out.println("Key = " + key + ", Value = " + value);

    }

    作为方法一的替代,这个代码看上去更加干净;但实际上它相当慢且无效率。
    因为从键取值是耗时的操作(与方法一相比,在不同的Map实现中该方法慢了20%~200%)。如果你安装了FindBugs,它会做出检查并警告你关于哪些是低效率的遍历。所以尽量避免使用。

    1、Set集合操作陷阱--- 一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2


    public class SetTest2 {

    public static void main(String[] args) {
    Set<String> set = new HashSet<String>();
    set.add("a");
    set.add("b");
    set.add("c");
    set.add("d");
    set.add("e");

    /**
    * 遍历方法一,迭代遍历
    */
    for(Iterator<String> iterator = set.iterator();iterator.hasNext();){
    System.out.print(iterator.next()+" ");
    }

    /**
    * for增强循环遍历
    */
    for(String value : set){
    System.out.print(value+" ");
    }
    }
    }
    当然Set集合也可以使用for循环了。

    注意:这里Set集合中放入的是String类型,假如我们放入一个自己定义的类实例的时候,比如Person类实例,这时候我们要自己重写hashcode和equal方法,
    因为当使用HashSet时,会先比较对象的hashCode(),如果存储在集合中的对象的hashCode值是否与增加的对象的hash code值一致;如果不一致,直接加进去;如果一致,再进行equals方法的比较,equals方法如果返回true,表示对象已经加进去了,就不会再增加新的对象,否则加进去。

    ---Set集合的另外一个重要实现类TreeSet;
    TreeSet使用元素的自然顺序对元素进行排序,或者--根据创建 set时提供的Comparator 进行排序,具体取决于使用的构造方法。
    通俗一点讲,就是可以按照排序后的列表显示,也可以按照指定的规则排序;

    Set<String> set = new TreeSet<String>();
    set.add("f");
    set.add("a");
    set.add("b");
    set.add("c");
    set.add("d");
    set.add("e");
    System.out.println(set); 输出:[a, b, c, d, e, f]按照排序后输出。
    那么如果我们想让他倒序输出呢?当然方法很多。这里我采用指定一个规则让他倒序输出


    public class TreeSetTest3 {

    public static void main(String[] args) {
    Set<String> set = new TreeSet<String>(new MyComparator());
    set.add("a");
    set.add("b");
    set.add("c");
    set.add("d");
    set.add("e");
    set.add("A");
    for(Iterator<String> iterator = set.iterator();iterator.hasNext();){
    System.out.print(iterator.next()+" ");
    }
    }
    }

    class MyComparator implements Comparator<String>{
    @Override
    public int compare(String o1, String o2) {
    return o2.compareTo(o1);//降序排列
    }
    }
    输出:e d c b a A

    如果Set集合中放入的是我们自己定义的一个类类型呢?
    注意:一定要定义一个排序规则类实现Comparator接口,与上面的方法类似,因为TreeSet默认是自然排序,如果没有实现Comparator,那么TreeSet就识别不了。

    public class TreeSetTest2 {
    public static void main(String[] args) {

    Set<Person> set = new TreeSet<Person>(new PersonComparator());
    Person p1 = new Person(10);
    Person p2 = new Person(20);
    Person p3 = new Person(30);
    Person p4 = new Person(40);
    set.add(p1);
    set.add(p2);
    set.add(p3);
    set.add(p4);

    for(Iterator<Person> iterator = set.iterator();iterator.hasNext();){
    System.out.print(iterator.next().score+" ");
    }
    }
    }

    class Person{
    int score;

    public Person(int score){
    this.score = score;
    }

    public String toString(){
    return String.valueOf(this.score);
    }
    }

    class PersonComparator implements Comparator<Person>{

    @Override
    public int compare(Person o1, Person o2) {
    return o1.score - o2.score;
    }
    }
    输出:10 20 30 40
    如果按照一个人的分数的倒序排列,只需要更改compare方法中的o2.score-o1.score

  • 相关阅读:
    学习视频收集
    vscode 编译器插件
    vue2.0父子组件之间传值
    js 案例
    插件
    【转】30分钟掌握 C#6
    【初码干货】关于.NET玩爬虫这些事
    上机作业七 系统进程与计划任务管理
    客户端与服务器双向密钥对验证
    DHCP中继配置
  • 原文地址:https://www.cnblogs.com/awkflf11/p/6383569.html
Copyright © 2011-2022 走看看