zoukankan      html  css  js  c++  java
  • JDK8--06:Stream流

    一、描述

    Stream流提供了筛选与切片、映射、排序、匹配与查找、归约、收集等功能

    筛选与切片:

      filter:接收lambda,从流中排除某些元素

      limit(n):截断流,使其元素不超过n

      skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补

      distinct:筛选,通过流所生成的元素的hashCode和equals去重

    映射:

      map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

      flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流

    排序:

      sorted()自然排序:comparable

      sorted(Comparator com)定制排序:comparator

    查找与匹配:

      allMatch:检查是否匹配所有元素

      anyMatch:检查是否至少匹配一个元素

      noneMatch:检查是否所有都不匹配

      findFirst:返回第一个元素

      findAny:返回当前流中任意元素

      count:返回当前流中元素个数

      max:返回当前流中最大值

      min:返回流中最小值

    归约:

      reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流

    收集:

      collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

    二、Strem语法

      Stream分为创建流、中间操作和终止操作三个部分

      (1)创建流有三种方式:

      第一种是使用通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象

            //1、通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象
            List list = new ArrayList();
            Stream stream = list.stream();
            Stream stream1 = list.parallelStream();

      第二种是通过数组中的静态方法stream()获取数组流

            //2、通过数组中的静态方法stream()获取数组流
            String[] arr = new String[0];
            Stream stream2 = Arrays.stream(arr);

      第三种是通过Stream中的静态方法of()创建

            List list = new ArrayList();//3、通过Stream中的静态方法of()创建
            Stream stream3 = Stream.of(list);

      除了上述三种,还有一种无限流的创建

      下面的迭代表示从0开始,每次加2,无限循环;生成就会无限生成随机数

            //无限流------迭代
            Stream stream4 = Stream.iterate(0, x->x+2);
            //无限流------生成
            Stream stream6 = Stream.generate(()->Math.random());

      (2)中间操作

      中间操作就是筛选切片、映射等都属于中间操作,操作后仍然返回一个流,以上面的无限流--迭代为例,可以使用截断流limit不让其无限循环,只要前10个

            Stream stream5 = stream4.limit(10);

      (3)终止操作,将流对象转换成数据对象的操作,以上述中间操作为例,加循环输出

            //加中间操作的终止操作
            stream4.limit(3).forEach(System.out::println);

      输出结果:0 2 4

    三、中间操作详解

      Stream的操作主要就是中间操作如何处理,接下来详细说明中间操作   

    1、筛选与切片

    为了代码演示需要,先创建一个Student类,和一个状态的枚举类

        public enum Status{
            FREE,
            BUSY,
            VOCATION
        }
    
    
    
    
        class Student{
            private String name;
            private Integer age;
            private Status status;
    
            public Student(String name,Integer age){
                this.name = name;
                this.age = age;
            }
    
            public Student(String name,Integer age, Status status){
                this.name = name;
                this.age = age;
                this.status = status;
            }
    
            public String getName() {
                return name;
            }
    
            public Integer getAge() {
                return age;
            }
    
            public Status getStatus(){
                return status;
            }
    
            public String toString(){
                return JSON.toJSONString(this);
            }
    
            /*public int hashCode(){
                int result = 1;
                result = this.getAge().hashCode() + this.getName().hashCode();
                return result;
            }
    
            @Override
            public boolean equals(Object obj) {
                if(obj instanceof Student){
                    if(this.name.equals(((Student) obj).getName()) && this.age == ((Student) obj).getAge()){
                        return true;
                    }
                }else {
                    return super.equals(obj);
                }
                return false;
            }*/
        }

    创建一个Student的集合

    List<Student> students1 = Arrays.asList(
                new Student("lcl1",18, Status.BUSY)
                ,new Student("lcl2",30, Status.VOCATION)
                ,new Student("lcl3",50, Status.FREE)
                ,new Student("lcl4",20, Status.FREE)
                ,new Student("lcl5",25, Status.BUSY)
                ,new Student("lcl6",8, Status.FREE)
                ,new Student("lcl2",30, Status.VOCATION));

    筛选与切片:

      filter:接收lambda,从流中排除某些元素

      limit(n):截断流,使其元素不超过n

      skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补

      distinct:筛选,通过流所生成的元素的hashCode和equals去重

    下面四个例子,分别对应四个中间操作的处理,操作内容分别为:

      只获取年龄小于10的Student后,将集合中Student对象输出;期望输出对象为:lcl5

      返回年龄大于10的前三个Student对象并输出:期望输出对象为:lcl1,lcl2,lcl3

      跳过两个Student对象后取前三个:期望输出对象为:lcl3,lcl4,lcl5

      输出集合中去重后的list对象:期望输出对象为集合中所有,包括两个lcl2,因为这里的去重操作是根据对象的hashCode和equals去判断是否重复的,由于两个lcl2都是new出来的,因此自然非同一个对象,但是如果我们重新hashCode和equals方法,按照名字和年龄一样,就会返回一样的hash码,且equals返回true(上述Student类中注释了重新的hashCode和equals方法,放开即可),则distinct会输出一个lcl2,下面演示输出按照重写hashCode和equals方法验证

        /**
         * 筛选与切片
         * filter:接收lambda,从流中排除某些元素
         * limit(n):截断流,使其元素不超过n
         * skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补
         * distinct:筛选,通过流所生成的元素的hashCode和equals去重
         */
        @Test
        public void tst2(){
            log.info("filter测试==============================");
            students1.stream().filter((x)->x.getAge() < 10).forEach(System.out::println);
    
            //惰性求值:所有中间操作不会做任何处理,只有在终止操作时,才会处理
            //内部迭代,StreamAPI自己处理的迭代处理
            //短路,只要找到满足的数据后,后面的流程就不再处理
            log.info("limit测试==============================");
            students1.stream().filter((x)->{log.info("filter");return x.getAge() > 10;}).limit(3).forEach(System.out::println);
    
            log.info("skip测试==============================");
            students1.stream().skip(2).limit(3).forEach(System.out::println);
    
            log.info("distinct测试==============================");
            students1.stream().distinct().forEach(System.out::println);
        }

    测试结果:

    2020-05-29 10:59:57.835  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter测试==============================
    {"age":8,"name":"lcl6","status":"FREE"}
    2020-05-29 10:59:58.003  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : limit测试==============================
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
    {"age":18,"name":"lcl1","status":"BUSY"}
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
    {"age":30,"name":"lcl2","status":"VOCATION"}
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
    {"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : skip测试==============================
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    2020-05-29 10:59:58.006  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : distinct测试==============================
    {"age":18,"name":"lcl1","status":"BUSY"}
    {"age":30,"name":"lcl2","status":"VOCATION"}
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    {"age":8,"name":"lcl6","status":"FREE"}

     可以发现,所有的输出都与我们期望的一致,根据测试结果,有两个概念需要说下,就是惰性求值和短路,其实在代码的注释中已经有了,这里结合测试结果再详细说明一下

      惰性求值:测试limit的时候,在中间操作中加了输出filter的操作,但是看测试结果,filter的输出和最终的结果输出一样,也是循环一次输出一次,而非通常想的会把所有的filter输出完之后,再循环输出测试结果,这就是惰性求值,即等输出结果时,在求值,而非过程中就求值

      短路:还是以limit测试为例,在使用filter过滤后,应该还有6个元素符合条件,但是在limit(3)之后,只输出了3个对象,最重要的是,连filter字符串都是只输出了三个,而非6个,这就是因为Stream中间操作的短路操作,即找到满足自己的数据后,就不再处理,这样其实也可以提升效率。

    2、映射:

      map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

      flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流

    为了做测试,先创建一个方法,传入一个字符串,返回一个单个字符的Stream流

        public static Stream<Character> filterChaacter(String str){
            List<Character> list = new ArrayList<>();
            for (Character c : str.toCharArray()) {
                list.add(c);
            }
            return list.stream();
        }

    demo需求:

      将数据组中的字符串变为大写

      输出所有学生的名字

      将数组中所有的字符串输出为单个字符

        @Test
        public void test3(){
    
            log.info("模拟map操作");
            List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
            list.stream().map((str)->str.toUpperCase()).forEach(System.out::println);
            students1.stream().map(Student::getName).forEach(System.out::println);
    
    
            log.info("模拟多重流操作");
            Stream<Stream<Character>> stream = list.stream().map(Jdk8StreamDemoTest::filterChaacter);
            stream.forEach(sm -> sm.forEach(System.out::println));
    
            log.info("模拟flatMap操作");
            list.stream().flatMap(Jdk8StreamDemoTest::filterChaacter).forEach(System.out::println);
        }

      上述代码中,模拟map操作块,即是对前两个demo需求的测试,没什么特别可说的,就是使用map从原有对象中获取我们所需要的对象;这里主要说明一下第三个demo需求,由于新增的filterChaacter方法返回的是一个Stream流,那么使用map的话就会得到一个Stream<Stream<Character>>的多重流,所以输出是,就需要双重循环(如模拟多重流代码块),非常的麻烦,因此就有了flatMap,他直接将返回的Stream流拼接成一个Stream流,这样输出的时候就只需要循环一次即可(如模拟flatMap操作)

      测试结果

    AAA
    BBB
    CCC
    DDD
    lcl1
    lcl2
    lcl3
    lcl4
    lcl5
    lcl6
    lcl2
    2020-05-29 10:58:18.557  INFO 9444 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 模拟多重流操作
    a
    a
    a
    b
    b
    b
    c
    c
    c
    d
    d
    d
    2020-05-29 10:58:18.557  INFO 9444 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 模拟flatMap操作
    a
    a
    a
    b
    b
    b
    c
    c
    c
    d
    d
    d

    3、排序:

      sorted()自然排序:comparable

      sorted(Comparator com)定制排序:comparator

        @Test
        public void test4(){
            //自然排序
            Arrays.asList("uio","fghf","dsf","ewre","gffjf","poiklk").stream().sorted().forEach(System.out::println);
            //定制排序
            students1.stream().sorted((s1,s2)->s1.getAge().compareTo(s2.getAge())).forEach(System.out::println);
            //倒序排序
            students1.stream().sorted((s1,s2)->-s1.getAge().compareTo(s2.getAge())).forEach(System.out::println);
        }

    测试结果:

    dsf
    ewre
    fghf
    gffjf
    poiklk
    uio
    {"age":8,"name":"lcl6","status":"FREE"}
    {"age":18,"name":"lcl1","status":"BUSY"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    {"age":30,"name":"lcl2","status":"VOCATION"}
    {"age":30,"name":"lcl2","status":"FREE"}
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":30,"name":"lcl2","status":"VOCATION"}
    {"age":30,"name":"lcl2","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":18,"name":"lcl1","status":"BUSY"}
    {"age":8,"name":"lcl6","status":"FREE"}

      这个没什么特别可说的,就是一个排序,空参的方法使用自然排序,传入排序方法的,按照穿的比较器进行排序

    4、查找与匹配:

      allMatch:检查是否匹配所有元素

      anyMatch:检查是否至少匹配一个元素

      noneMatch:检查是否所有都不匹配

      findFirst:返回第一个元素

      findAny:返回当前流中任意元素

      count:返回当前流中元素个数

      max:返回当前流中最大值

      min:返回流中最小值

    demo需求:

      判断集合中是否状态全为BUSY

      判断集合中是否存在状态为BUSY的数据

      判断集合中是否没有一个状态为BUSY的数据

      按照年龄排序后取第一个

      取任意一个元素

      获取集合中数据总数

      获取年龄最大的Student

      获取年龄最小的Student

        @Test
        public void test5(){
            log.info("allMatch结果{}",students1.stream().allMatch((e)-> e.getStatus() == Status.BUSY));
            log.info("anyMatch结果{}",students1.stream().anyMatch((e)-> e.getStatus() == Status.BUSY));
            log.info("noneMatch结果{}",students1.stream().noneMatch((e)-> e.getStatus() == Status.BUSY));
    
            Optional<Student> optionalStudent = students1.stream().sorted((e1, e2)->e1.getAge().compareTo(e2.getAge())).findFirst();
            if(optionalStudent.isPresent()) log.info("findFirst结果{}",optionalStudent.get());
    
            Optional<Student> optionalStudent1 = students1.stream().filter((e)->e.getStatus()==Status.FREE).findAny();
            if(optionalStudent1.isPresent()) log.info("findAny结果{}",optionalStudent1.get());
    
            log.info("count结果{}",students1.stream().count());
    
            Optional<Student> optionalStudent2 = students1.stream().max((e1,e2)->e1.getAge().compareTo(e2.getAge()));
            if(optionalStudent2.isPresent()) log.info("max结果{}",optionalStudent2.get());
    
            Optional<Student> optionalStudent3 = students1.stream().min((e1,e2)->e1.getAge().compareTo(e2.getAge()));
            if(optionalStudent3.isPresent()) log.info("min结果{}",optionalStudent3.get());
        }

      输出结果:

    2020-05-29 10:56:39.830  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : allMatch结果false
    2020-05-29 10:56:39.832  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : anyMatch结果true
    2020-05-29 10:56:39.832  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : noneMatch结果false
    2020-05-29 10:56:39.833  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : findFirst结果{"age":8,"name":"lcl6","status":"FREE"}
    2020-05-29 10:56:39.936  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : findAny结果{"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:56:40.025  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : count结果7
    2020-05-29 10:56:40.027  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : max结果{"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:56:40.028  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : min结果{"age":8,"name":"lcl6","status":"FREE"}

       上述代码示例中,有的地方的返回是个Optional,这个下一篇会说明

    5、归约:

      reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流

    demo需求:

      将一个集合中的数据,依次相加求和后再与100求和

      将集合中学生的年龄求和后再加10

        @Test
        public void test6(){
            List<Integer> list = Arrays.asList(1,2,3,4,5);
            log.info("reduce结果{}",list.stream().reduce(100, (x,y)->x+y));
            log.info("reduce计算年龄总和结果{}",students1.stream().map((x)->x.getAge()).reduce(10,(x,y)->x+y));
        }

      测试结果:

    2020-05-29 10:56:08.448  INFO 17680 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : reduce结果115
    2020-05-29 10:56:08.450  INFO 17680 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : reduce计算年龄总和结果191

    6、收集:

      collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,可以计算总和,最大值、最小值、平均值、分组、多重分组、分区和字符串操作等

      由于collect的内容比较多,所以分开一个一个说明

      (1)数据收集转换

      demo:

        将所有的名字获取,并添加到集合中

        将所有的名字获取,并添加到特殊集合中,例如HashSet

            List list = students1.stream().map(Student::getName).collect(Collectors.toList());
            log.info("收集:collect测试结果{}",JSON.toJSONString(list));
    
            Set set = students1.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));
            log.info("收集:特殊集合collect测试结果{}",JSON.toJSONString(set));

      测试结果:

    2020-05-29 10:44:37.478  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect测试结果["lcl1","lcl2","lcl3","lcl4","lcl5","lcl6","lcl2"]
    2020-05-29 10:44:37.481  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:特殊集合collect测试结果["lcl5","lcl6","lcl3","lcl4","lcl1","lcl2"]

       (2)计算总数、总和、最大值、最小值、平均值等数据收集操作

            log.info("收集:collect总数测试结果{}",students1.stream().collect(Collectors.counting()));
    
            log.info("收集:collect计算年龄平均值测试结果{}",students1.stream().collect(Collectors.averagingInt(Student::getAge)));
    
            log.info("收集:collect计算年龄总和测试结果{}",students1.stream().collect(Collectors.summarizingInt(Student::getAge)));
    
            log.info("收集:collect获取年龄最大测试结果{}",students1.stream().collect(Collectors.maxBy((s1,s2)->s1.getAge().compareTo(s2.getAge()))).get());
    
            log.info("收集:collect获取最小的年龄值测试结果{}",students1.stream().map(Student::getAge).collect(Collectors.minBy((s1,s2)->s1.compareTo(s2))).get());

    测试结果:

    2020-05-29 10:44:37.483  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect总数测试结果7
    2020-05-29 10:44:37.484  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect计算年龄平均值测试结果25.857142857142858
    2020-05-29 10:44:37.485  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect计算年龄总和测试结果IntSummaryStatistics{count=7, sum=181, min=8, average=25.857143, max=50}
    2020-05-29 10:44:37.488  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect获取年龄最大测试结果{"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:44:37.589  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect获取最小的年龄值测试结果8

     (3)分组

      需求:

        单一分组:按照状态分组

        多重分组:先按照状态分组,再按照年龄分组(以26为界,大于26为中老年,小于26为青少年)

            Map map3 = students1.stream().collect(Collectors.groupingBy(Student::getStatus));
            log.info("收集:collect按照Status分组测试结果{}", JSON.toJSONString(map3));
    
    
            Map<Status,Map<String,List<Student>>> map = students1.stream().collect(Collectors.groupingBy(Student::getStatus
                    ,Collectors.groupingBy((x)->{
                        if(((Student)x).getAge() > 26){
                            return "中老年";
                        }else{
                            return "青少年";
                        }
                    })
            ));
            log.info("收集:collect按照Status分组后按照年龄二次分组测试结果{}", JSON.toJSONString(map));

      测试结果:

    2020-05-29 10:44:37.591  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照Status分组测试结果{"VOCATION":[{"age":30,"name":"lcl2","status":"VOCATION"}],"FREE":[{"age":50,"name":"lcl3","status":"FREE"},{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"BUSY":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}
    2020-05-29 10:44:37.592  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照Status分组后按照年龄二次分组测试结果{"VOCATION":{"中老年":[{"age":30,"name":"lcl2","status":"VOCATION"}]},"FREE":{"中老年":[{"age":50,"name":"lcl3","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"青少年":[{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"}]},"BUSY":{"青少年":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}}

      (4)使用IntSummaryStatistics计算总数、总和、最大值、最小值、平均值等操作

            IntSummaryStatistics intSummaryStatistics = students1.stream().collect(Collectors.summarizingInt(Student::getAge));
            log.info("收集:collect按照年龄平均值{},总数{},最大值{},最小值{},总和{}", intSummaryStatistics.getAverage(),intSummaryStatistics.getCount(),intSummaryStatistics.getMax(),intSummaryStatistics.getMin(),intSummaryStatistics.getSum());

      测试结果:

    2020-05-29 10:44:37.595  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照年龄平均值25.857142857142858,总数7,最大值50,最小值8,总和181

      (5)字符串操作

            String s = students1.stream().map(Student::getName).collect(Collectors.joining("=="));
            log.info("收集:collect连接字符串测试结果{}", s);

      测试结果:

    2020-05-29 10:44:37.595  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect连接字符串测试结果lcl1==lcl2==lcl3==lcl4==lcl5==lcl6==lcl2
  • 相关阅读:
    Windows SDK编程(Delphi版) 之 应用基础,楔子
    一个小问题引发的论证思考
    Delphi 组件开发教程指南(7)继续模拟动画显示控件
    用PyInstaller将python转成可执行文件exe笔记
    使用 .Net Memory Profiler 诊断 .NET 应用内存泄漏(方法与实践)
    Microsof Office SharePoint 2007 工作流开发环境搭建
    How to monitor Web server performance by using counter logs in System Monitor in IIS
    LINQ之Order By
    window 性能监视器
    内存泄露检测工具
  • 原文地址:https://www.cnblogs.com/liconglong/p/12986255.html
Copyright © 2011-2022 走看看