草庐IT

java Stream流练习

XIAOW 2023-04-18 原文

1.遍历/匹配(foreach/find/match)

Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 7, 8, 9, 0, 100);
// find使用,查找第一个元素
Optional<Integer> first = list.stream().findFirst();
log.info(first.get().toString());

// match使用,判断是否存在某个值
boolean b1 = list.stream().anyMatch(value -> value >= 100);
boolean b2 = list.stream().anyMatch(value -> value > 10);
log.info(String.valueOf(b1));
log.info(String.valueOf(b2));

// foreach使用,遍历输出元素
list.stream().filter(value -> value > 4).forEach(System.out::print);
System.out.println();
list.forEach(System.out::print);

2.筛选(filter)

筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。

// 数字筛选
List<Integer> list = Arrays.asList(1, 3, 4, 5, 6, 7, 8, 9, 10, 20);

ArrayList<Integer> arrayList1 = new ArrayList<>();
list.stream().filter(value -> value > 4).forEach(value -> arrayList1.add(value));
log.info(arrayList1.toString());

ArrayList<Integer> arrayList2 = new ArrayList<>();
list.stream().filter(value -> value <= 4).forEach(arrayList2::add);
log.info(arrayList2.toString());

// 对象筛选
List<User> userList1 = Arrays.asList(
        new User(1, "xw", "男"),
        new User(2, "zgx", "男"),
        new User(3, "gg", "男"),
        new User(4, "whb", "男"),
        new User(5, "yda", "男"),
        new User(6, "bhm", "女")
);

List<User> userList2 = new ArrayList<>();
userList1.stream().filter(user -> user.getId() > 2).forEach(userList2::add);
log.info(userList2.toString());

userList1.stream().filter(user -> 					         user.getName().equals("xw")).forEach(System.out::println);

HashMap<String, Optional<User>> userHashMap = new HashMap<>();
Optional<User> man = userList1.stream().filter(user -> user.getSex().equals("男")).findFirst();
        userHashMap.put("man", man);
        log.info(userHashMap.toString());

3.聚合(max/min/count)

maxmincount这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。

max、min、count

 // max & min & count
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 89, 9, 0, 10, 20, 30);

Optional<Integer> max = list.stream()
        .max(Comparator.comparing(Integer::intValue));
log.info(String.format("最大值是:%d", max.get()));

Optional<Integer> min = list.stream()
        .min(Comparator.comparing(value -> value.intValue()));
log.info(String.format("最小值是:%d", min.get()));

Integer count1 = Math.toIntExact(list.stream().count());
log.info(String.format("list总元素量1为:%d", count1));

Integer count2 = Math.toIntExact(list.stream().filter(value -> value > 5).count());
log.info(String.format("list元素值大于5的个数:%d", count2));


List<User> userList = Arrays.asList(
        new User(1, "xw", "男", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 22),
        new User(7, "lwn", "女", 22)
);
Optional<User> ageMax = userList.stream().max(Comparator.comparing(value -> value.getAge()));
log.info(String.format("年龄最大的是:%s", ageMax.get()));

Optional<User> ageMin = userList.stream().filter(user -> user.getSex().equals("男")).min(Comparator.comparing(User::getAge));
log.info(String.format("性别为男且年龄最小的:%s", ageMin.get()));

Integer count3 = Math.toIntExact(userList.stream().filter(user -> user.getAge() > 22).count());
log.info(String.format("年龄大于22的用户数量为:%d", count3));

4.映射(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为mapflatMap

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

map

// map
List<Integer> list1 = Arrays.asList(1, 3, 5, 6, 7, 8, 0, 10, 20, 22, 39);
List<Integer> collect1 = list1.stream().filter(value -> value > 7).collect(Collectors.toList());
log.info(String.format("list1元素值大于7的有: %s", collect1));

List<String> list2 = Arrays.asList("xw", "sjdk", "sf", "jk", "hoksh", "shdfj", "jhgkj");
List<String> collect2 = list2.stream().map(String::toUpperCase).collect(Collectors.toList());
log.info(String.format("list2元素值全转大写,结果:%s", collect2));

List<User> userList1 = Arrays.asList(
        new User(1, "xw", "男", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 22),
        new User(7, "lwn", "女", 22),
        new User(8, "ksj", "女", 22)
);
List<User> userList2 = userList1.stream()
        .map(user -> {
            if (user.getSex().equals("女")) {
                user.setAge(user.getAge() - 2);
            }
            user.setName(user.getName().toUpperCase());
            return user;
        })
        .filter(user -> user.getAge() > 19 && user.getSex().equals("女"))
        .collect(Collectors.toList());
log.info(String.format("修改结果为:%s", userList2));

flatMap

// flatMap
List<String> stringList = userList1.stream()
        .flatMap(user -> {
            Stream<String> stream = Arrays.stream(user.toString().split("="));
            return stream;
        })
        .collect(Collectors.toList());
log.info(String.format("flatMap处理前:%s", userList1));
log.info(String.format("flatMap转换结果:%s", stringList));

5.归约(reduce)

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和求乘积求最值操作。

// 求和
List<Integer> list1 = Arrays.asList(1, 3, 5, 2, 1, 5, 89, 23, 89, 23, 34);
Integer sum = list1.stream().reduce(0, Integer::sum);
log.info(String.format("list1中各元素之和:%d", sum));

// 求积
List<Integer> list2 = Arrays.asList(1, 2, 4);
Optional<Integer> product = list2.stream().reduce((x, y) -> x * y);
log.info(String.format("list中2各元素之积:%d", product.get()));

// 求最大值1
Optional<Integer> max1 = list1.stream().reduce(Integer::max);
log.info(String.format("list1中的最大值是:%d", max1.get()));

// 求最大/小值2
Optional<Integer> min1 = list1.stream().reduce((x, y) -> x < y ? x : y);
log.info(String.format("list1中的最小值:%d", min1.get()));
List<User> userList1 = Arrays.asList(
        new User(1, "xw", "男", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);

Integer maxAge1 = userList1.stream().reduce(0, (maxAge, user) -> maxAge > user.getAge() ? maxAge : user.getAge(), Integer::max);
log.info(String.format("年龄最大是:%d", maxAge1));

Optional<Integer> max2 = userList1.stream().map(User::getAge).reduce(Integer::max);
Optional<Integer> max3 = userList1.stream().map(User::getAge).reduce((x, y) -> x > y ? x : y);
log.info(String.format("年龄最大是:%d", max2.get()));
log.info(String.format("年龄最大是:%d", max3.get()));

Integer stringMaxLength = userList1.stream()
        .filter(user -> user.getAge() > 22 && user.getAge() < 25)
        .flatMap(user -> {
            Stream<String> newStream = Arrays.stream(user.toString().split("="));
            return newStream;
        })
        .collect(Collectors.toList())
        .stream().map(String::toUpperCase)
        .reduce(0, (maxLength, string) -> maxLength > string.length() ? maxLength : string.length(), Integer::max);

log.info(String.format("最大字符串长度为:%s", stringMaxLength));

6.收集(collect)

collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

collect主要依赖java.util.stream.Collectors类内置的静态方法。

6.1归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用,另外还有toCollectiontoConcurrentMap等复杂一些的用法。

toList

List<Integer> list1 = Arrays.asList(1, 2, 8, 0, 9, 0, 1, 23, 32, 37, 49, 48);
List<Integer> collect1 = list1.stream().filter(value -> value > 5).collect(Collectors.toList());
log.info(String.format("list1中收集>5的结果为:%s", collect1));

toSet

Set<Integer> collect2 = list1.stream().filter(value -> value < 5).collect(Collectors.toSet());
log.info(String.format("list1中收集<5的结果为:%s", collect2));
collect2.forEach(value -> {System.out.print(value + " ");

toMap

List<User> userList1 = Arrays.asList(
        new User(1, "xww", "男", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);

Map<String, User> userMap = userList1.stream()
         .filter(user -> user.getSex().equals("女"))
         .collect(Collectors.toMap(User::getName, user -> user));
log.info(String.format("性别为女的用户转map:%s", userMap));

6.2 统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

  • 计数:count
  • 平均值:averagingIntaveragingLongaveragingDouble
  • 最值:maxByminBy
  • 求和:summingIntsummingLongsummingDouble
  • 统计以上所有:summarizingIntsummarizingLongsummarizingDouble
List<Integer> list = Arrays.asList(1, 2, 3, 2, 5, 3, 9, 8, 7, 6, 29, 10, 22);

// count
long count = list.stream().filter(value -> value > 10).count();
log.info(String.format("list中元素>10的个数为:%d", count));

// average
Double average = list.stream().filter(value -> value > 1).collect(Collectors.averagingInt(Integer::intValue));
log.info(String.format("list中元素>1的元素平均值为:%.2f", average));

List<User> userList1 = Arrays.asList(
        new User(1, "xww", "女", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);

Double averageAge = userList1.stream()
        .filter(user -> user.getSex().equals("男"))
        .map(User::getAge)
        .collect(Collectors.averagingInt(Integer::intValue));
log.info(String.format("男用户的平均年龄为:%d 岁", averageAge.intValue()));

// mapToInt
int ageSum = userList1.stream()
        .filter(user -> user.getSex().equals("女"))
        .mapToInt(User::getAge)
        .sum();
log.info(String.format("女用户的年龄之和为:%d", ageSum));

// summarizingInt 统计 计数、总和、最小值、平均值、最大值
IntSummaryStatistics recording = userList1.stream()
        .filter(user -> user.getSex().equals("男"))
        .collect(Collectors.summarizingInt(User::getAge));
log.info(String.format("记录所有男用户的年龄各项值,结果为:%s", recording));

6.3 分组(partitioningBy/groupingBy)

  • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
  • 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
partitioningBy
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 20, 37, 49, 243, 30);

// partitioningBy
Map<Boolean, List<Integer>> collect1 = list.stream()
        .collect(Collectors.partitioningBy(value -> value > 20));
log.info(String.format("元素值是否大于20进行分组,结果为:%s", collect1));

collect1.forEach((key, value) -> {
    log.info(String.format("元素值是否大于20进行分组,结果为:%s:%s", key, value));
});

groupingBy

List<User> userList = Arrays.asList(
        new User(1, "xww", "女", 22),
        new User(2, "zgx", "男", 21),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);

// groupingBy
Map<String, List<User>> collect2 = userList.stream()
        .collect(Collectors.groupingBy(User::getSex));
log.info(String.format("根据性别对用户进行分组,结果为:%s", collect2));

collect2.forEach((key, user) -> {
    log.info(String.format("根据性别对用户进行分组,结果为:%s:%s", key, user));
});

6.4 接合(joining)

joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 20, 37, 49, 243, 30);
String collect = list.stream()
        .map(Object::toString)
        .collect(Collectors.joining("——"));
log.info(String.format("joining测试结果为:%s", collect));

7.排序(sorted)

sorted,中间操作。有两种排序:

  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):Comparator排序器自定义排序
List<User> userList1 = Arrays.asList(
        new User(1, "xw", "女", 22),
        new User(2, "zgx", "男", 21),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);

// sorted
List<User> userList2 = userList1.stream()
        .sorted(Comparator.comparing(User::getAge))
        .collect(Collectors.toList());
log.info(String.format("按照年龄排序,结果为:%s", userList2));

// 从小到大,正序
List<String> userName1 = userList1.stream()
        .sorted(Comparator.comparing(User::getAge))
        .map(User::getName)
        .collect(Collectors.toList());
log.info(String.format("根据年龄从小到大排序:%s", userName1));

// 从大到小,倒序
List<String> userName2 = userList1.stream()
        .filter(user -> user.getSex().equals("男"))
        .sorted(Comparator.comparing(User::getAge).reversed())
        .map(User::getName)
        .collect(Collectors.toList());
log.info(String.format("男用户根据年龄从大到小排序:%s", userName2));

8.提取/组合

流也可以进行合并去重限制跳过等操作。

1.去重排序

List<Integer> list = Arrays.asList(1, 2, 4, 4, 10, 9, 6, 8, 6, 2, 3, 7, 5);
List<Integer> collect = list
        .stream()
        .distinct()
        .sorted(Comparator.comparing(Integer::intValue))
        .collect(Collectors.toList());
collect.forEach(x -> System.out.print(x+" ")); // 1 2 3 4 5 6 7 8 9 10

存在重复数据的问题,这里使用stream流的衍生功能,去除一个对象中的部分元素的重复如下:

List<User> userList = Arrays.asList(
        new User(1, "xw", "女", 21),
        new User(2, "zgx", "男", 21),
        new User(3, "whb", "男", 23),
        new User(4, "gag", "男", 30),
        new User(4, "gbg", "男", 30),
        new User(4, "gcg", "女", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);
ArrayList<User> collect1 = userList.stream().collect(Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(
                                Comparator.comparing(
                                        User::getId))), ArrayList::new));

多个字段或者多个条件去重

ArrayList<User> collect2 = userList.stream().collect(Collectors.collectingAndThen(
                Collectors.toCollection(() -> new TreeSet<>(
                        Comparator.comparing(user->user.getName() + ";" + user.getId()))), ArrayList::new)

以上使用到了collectingAndThen()根据属性进行去重的操作,进行结果集的收集,收集到结果集之后再进行下一步的处理。在这个去重操作中还用到了toCollection、TreeSet两个操作。

public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,Function<R,RR> finisher)

看源码中需要传的参数有两个,第一个参数是Collector的子类,所以Collectors类中的大多数方法都能使用,比如:toList(),toSet(),toMap()等,当然也包括collectingAndThen()。第二个参数是一个Function函数,也是去重的关键,用到的ArrayList::new调用到了ArrayList的有参构造。Function函数是R apply(T t),在第一个参数downstream放在第二个参数Function函数的参数里面,将结果设置为t。对于toCollection是一个通用的方法,满足treeSet收集集合,再传入需要根据某个属性进行比较的比较器,就能达到去重的效果。

2.限制长度(limit)

List<Integer> list = Arrays.asList(1, 2, 7, 3, 2, 2, 3, 4, 5, 2, 5, 6, 7, 8, 9, 0, 12);
List<Integer> collect1 = list
        .stream()
        .distinct()
        .sorted(Comparator.comparing(Integer::intValue))
        .limit(6)
        .collect(Collectors.toList());
collect1.forEach(x -> System.out.print(x + " "));

3.跳过(skip)

// 跳过前几项
List<Integer> list = Arrays.asList(1, 2, 7, 3, 2, 2, 3, 4, 5, 2, 5, 6, 7, 8, 9, 0, 12);
List<Integer> collect2 = list
        .stream()
        .distinct()
        .sorted(Comparator.comparing(Integer::intValue))
        .skip(3)
        .limit(6)
        .collect(Collectors.toList());
collect2.forEach(x -> System.out.print(x + " ")); 

有关java Stream流练习的更多相关文章

  1. 牛客网专项练习30天Pytnon篇第02天 - 2

    1.在Python3中,下列关于数学运算结果正确的是:(B)a=10b=3print(a//b)print(a%b)print(a/b)A.3,3,3.3333...B.3,1,3.3333...C.3.3333...,3.3333...,3D.3.3333...,1,3.3333...解析:    在Python中,//表示地板除(向下取整),%表示取余,/表示除(Python2向下取整返回3)2.如下程序Python2会打印多少个数:(D)k=1000whilek>1:    print(k)k=k/2A.1000 B.10C.11D.9解析:    按照题意每次循环K/2,直到K值小于等

  2. ruby-on-rails - Rails for Zombies Lab 4 > 练习 3 - 2

    我在第三个练习中停留在第四个RailsforZombies实验室。这是我的任务:创建将创建新僵尸的操作,然后重定向到创建的僵尸的显示页面。我有以下参数数组:params={:zombie=>{:name=>"Greg",:graveyard=>"TBA"}}我写了下面的代码作为解决方案:defcreate@zombie=Zombie.create@zombie.name=params[:zombie[:name]]@zombie.graveyard=params[:zombie[:graveyard]]@zombie.saveredirect_to(create_zombie_path

  3. javascript - Eloquent JavaScript 2nd Edition 递归练习解答 - 2

    我试图解决在线书籍eloquentjavascript2ndedition的递归练习:问题是这样的:We’veseenthat%(theremainderoperator)canbeusedtotestwhetheranumberisevenoroddbyusing%2tocheckifit’sdivisiblebytwo.Here’sanotherwaytodefinewhethera(positive,whole)numberisevenorodd:Zeroiseven.Oneisodd.ForanyothernumberN,itsevennessisthesameasN-2.De

  4. javascript - 对javascript练习的困惑 - 2

    我刚拿到DouglasCrockford的Javascript:TheGoodParts,我在理解他关于原型(prototype)的示例之一时遇到了一些困难。书中代码如下:if(typeofObject.create!=="function"){Object.create=function(o){varF=function(){}F.prototype=o;returnnewF;};}我假设此代码用于定位函数的原型(prototype)。但为什么要使用如此复杂的方法呢?为什么不直接使用variable.prototype?Crockford是Javascript方面的领先专家,因此我确

  5. javascript - 练习 Javascript 的最佳环境 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭11年前。我目前有Notepad++和AptanaStudio。是否有任何其他开发环境可以简化javascript代码的编写?谢谢。

  6. Javascript 练习 - 反转二维数组 - 2

    反转二维数组的值,可以扩展n次。[1,[2,[3,...[n,null]]]]给定:所有数组的长度始终为2列表中的最后一个数组将包含一个null索引1示例:[1,[2,[3,null]]]将输出[3,[2,[1,null]]][1,[2,[3,[4,null]]]]会输出[4,[3,[2,[1,null]]]]我不确定我描述的是否正确,但我今天遇到了这个练习并想出了一个相当明显的解决方案。varars=[1,[2,[3,null]]],rev=null;functionr(x){rev=(rev==null)?[x[0]]:[x[0],rev];if(x[1]!==null)r(x[1

  7. 华为eNSP网络配置综合练习一(vlan +MSTP+VLANif+VRRP+ 静态路由+单臂路由+STP+BFD) - 2

    综合练习一题目要求:实验范图实现PC机之间互通配置思路:配置过程:配置终端设备及3700交换机实现此案例需要按照如下步骤进行。1)配置PC的IP地址和网关2)配置SW1/5/6的vlan为10/20/30,交换机之间的链路为Trunk,与PC间为Access3)配置SW2/3/7的vlan为40/50,交换机之间的链路为Trunk,与PC间为Access4)配置SW4/8/9的vlan为60/70/80,交换机之间的链路为Trunk,与PC间为Access5)配置R1/R2/R3的接口IP地址6)配置每个VLAN的网关接口IP地址SW1为vlan10/20/30的网关设备:interfacev

  8. 智能合约学习笔记一 、——{Solidity语言详解——(1—2)小练习} - 2

    1.要求:1.根据提示,在指定位置写出编译版本,要求使用^符号,版本要求在0.6.0及以上。2.根据提示,在指定位置写出所定义的合约名称。3.为了查看程序的效果,我们使用在线Solidity开发工具RemixIDE编译和运行Solidity程序。中文在线版:在浏览器打开下方链接: Remix-中文版-智谷星图。第1步–在文件浏览器选项卡下,新建一个Firstapp.sol文件,把我们补充完整的代码直接复制过来。第2步–在SOLIDITY编译器选项卡下,选择0.6.5的那个编译器版本并单击 编译Firstapp.sol 按钮,开始编译。编译成功后会根据本地客户端和版本内容弹出提示,可以不用处理。

  9. javascript - 我需要一些初级程序员的简单逻辑/编程练习 - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion我目前正在教员工ECMA脚本,因为维护我们使用的工作流系统需要它,我需要一些挑战作为练习。我们已经涵盖了大部分语言,他现在非常熟悉语法,所以我只需要他开始使用它。我需要给他提供练习,让他进行逻辑思考。例如,他了解什么是if和switch

  10. 软件测试Selenium-API 操作(上机练习文档)分享 - 2

    目录目标一、元素定位目标1. 如何进行元素定位?2. 浏览器开发者工具2.1 如何使用浏览器开发者工具3. 元素定位方式3.1 id 定位3.2 name 定位3.3 class_name 定位3.4 tag_name 定位3.5 link_text 定位3.6 partial_link_text 定位4. 定位一组元素 4.1 find_elements_by_xxx()4.2 案例4.3 示例代码二、XPath、CSS 定位目标为什么要学习XPath、CSS 定位?1. 什么是XPath?2. XPath 定位策略(方式)2.1 路径定位(绝对路径、相对路径)2.2 利用元素属性2.3 属

随机推荐