• 读书笔记-《ON JAVA 中文版》-摘要24[第二十一章 数组]


    第二十一章 数组

    1. 数组特性

    将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据类型的能力。在 Java 中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。

    Java 提供了 Arrays.toString() 来将数组转换为可读字符串,然后可以在控制台上显示。

    2. 一等对象

    数组是保存指向其他对象的引用的对象,数组可以隐式地创建,也可以显式地创建,比如使用一个 new 表达式。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 length 成员函数,它能告诉你数组对象中可以存储多少元素。[ ] 语法是你访问数组对象的唯一方式。

    对象数组存储的是对象的引用,而基元数组则直接存储基本数据类型的值。

    package arrays;
    
    import java.util.Arrays;
    
    class BerylliumSphere {
        private static long counter;
        private final long id = counter++;
    
        @Override
        public String toString() {
            return "Sphere " + id;
        }
    }
    
    public class ArrayOptions {
        static <T> void show(String info, T[] array) {
            System.out.println(info + ":" + Arrays.toString(array));
        }
    
        public static void main(String[] args) {
            BerylliumSphere[] a;
            // 数组 a 是一个未初始化的本地变量,编译器不会允许你使用这个引用直到你正确地对其进行初始化。
    //        show("a",a);
    
            BerylliumSphere[] b = new BerylliumSphere[5];
            // 数组 b 被初始化,自动初始化为 null
            // 数值类型初始化为 0,char 型初始化为 (char)0,
            // 布尔类型初始化为 false。
            show("b", b);
    
            // 数组 c 展示了创建数组对象后给数组中各元素分配 BerylliumSphere 对象。
            BerylliumSphere[] c = new BerylliumSphere[4];
            for (int i = 0; i < c.length; i++) {
                if (c[i] == null) {
                    c[i] = new BerylliumSphere();
                }
            }
    
            // 数组 d 展示了创建数组对象的聚合初始化语法(隐式地使用 new 在堆中创建对象)
            BerylliumSphere[] d = {
                    new BerylliumSphere(),
                    new BerylliumSphere(),
                    new BerylliumSphere()
            };
    
            // 动态聚合初始化
            a = new BerylliumSphere[]{
                    new BerylliumSphere(), new BerylliumSphere(),
            };
    
            System.out.println("a.length = " + a.length);
            System.out.println("b.length = " + b.length);
            System.out.println("c.length = " + c.length);
            System.out.println("d.length = " + d.length);
    
            a = d;
            System.out.println("a.length = " + a.length);
    
            int[] e;
            int[] f = new int[5];
            System.out.println("f" + ":" + Arrays.toString(f));
            int[] g = new int[4];
            for (int i = 0; i < g.length; i++)
                g[i] = i * i;
            int[] h = {11, 47, 93};
            System.out.println("f.length = " + f.length);
            System.out.println("g.length = " + g.length);
            System.out.println("h.length = " + h.length);
            e = h;
            System.out.println("h-修改前:" + Arrays.toString(h));
            System.out.println("e-修改前:" + Arrays.toString(e));
            h[1] = 33;
            System.out.println("h-修改后:" + Arrays.toString(h));
            System.out.println("e-修改后:" + Arrays.toString(e));
            System.out.println("e.length = " + e.length);
            e = new int[]{1, 2};
            System.out.println("e.length = " + e.length);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    输出:

    b:[null, null, null, null, null]
    a.length = 2
    b.length = 5
    c.length = 4
    d.length = 3
    a.length = 3
    f:[0, 0, 0, 0, 0]
    f.length = 5
    g.length = 4
    h.length = 3
    h-修改前:[11, 47, 93]
    e-修改前:[11, 47, 93]
    h-修改后:[11, 33, 93]
    e-修改后:[11, 33, 93]
    e.length = 3
    e.length = 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3. 返回数组

    而在 Java 中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。

    4. 多维数组

    要创建多维的基元数组,你要用大括号来界定数组中的向量:

    package arrays;
    
    import java.util.Arrays;
    
    public class MultidimensionalPrimitiveArray {
        public static void main(String[] args) {
            int[][] a = {
                    // 每个嵌套的大括号都代表了数组的一个维度
                    { 1, 2, 3, },
                    { 4, 5, 6, },
            };
            System.out.println(Arrays.deepToString(a));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以使用 new 分配数组。这是一个使用 new 表达式分配的三维数组:

    package arrays;
    
    import java.util.Arrays;
    
    public class ThreeDWithNew {
        public static void main(String[] args) {
            int[][][] a = new int[2][2][4];
            System.out.println(Arrays.deepToString(a));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    输出:

    [[[0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0]]]
    
    • 1

    5. 泛型数组

    一般来说,数组和泛型并不能很好的结合。你不能实例化参数化类型的数组:

    Peel<Banana>[] peels = new Peel<Banana>[10]; // Illegal
    
    • 1

    类型擦除需要删除参数类型信息,而且数组必须知道它们所保存的确切类型,以强制保证类型安全。

    但是,可以参数化数组本身的类型:

    package arrays;
    
    class ClassParameter<T> {
        public T[] f(T[] arg) {
            return arg;
        }
    }
    
    class MethodParameter {
        public static <T> T[] f(T[] arg) {
            return arg;
        }
    }
    
    public class ParameterizedArrayType {
        public static void main(String[] args) {
            Integer[] ints = {1, 2, 3, 4, 5};
            Double[] doubles = {1.1, 2.2, 3.3, 4.4, 5.5};
            Integer[] ints2 =
                    new ClassParameter<Integer>().f(ints);
            Double[] doubles2 =
                    new ClassParameter<Double>().f(doubles);
            ints2 = MethodParameter.f(ints);
            doubles2 = MethodParameter.f(doubles);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    比起使用参数化类,使用参数化方法很方便。

    6. Arrays的fill方法

    Java 标准库 Arrays 类包括一个普通的 fill() 方法,该方法将单个值复制到整个数组,或者在对象数组的情况下,将相同的引用复制到整个数组:

    package arrays;
    
    import java.util.Arrays;
    
    public class FillingArrays {
        static <T> void show(String info, T[] array) {
            System.out.println(info + ":" + Arrays.toString(array));
        }
    
        public static void main(String[] args) {
            int size = 6;
            String[] a1 = new String[size];
    
            Arrays.fill(a1, "Hello");
            show("a1", a1);
    
            Arrays.fill(a1,1,3,"World");
            show("a1", a1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出:

    a1:[Hello, Hello, Hello, Hello, Hello, Hello]
    a1:[Hello, World, World, Hello, Hello, Hello]
    
    • 1
    • 2

    7. Arrays的setAll方法

    它的作用是根据给定的数组索引,使用指定的lambda表达式或方法引用来设置数组的值。

    package arrays;
    
    import java.util.Arrays;
    
    public class SimpleSetAll {
        public static final int SZ = 8;
        static int val = 1;
    
        public static void main(String[] args) {
            int[] ia = new int[SZ];
            Arrays.setAll(ia, n -> n);
            System.out.println(Arrays.toString(ia));
            Arrays.setAll(ia, n -> val++);
            System.out.println(Arrays.toString(ia));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出:

    [0, 1, 2, 3, 4, 5, 6, 7]
    [1, 2, 3, 4, 5, 6, 7, 8]
    
    • 1
    • 2

    8. 数组并行

    Arrays.parallelSetAll()

    效果与 SetAll() 一样,可并行

    9. Arrays工具类

    该类包含许多有用的 静态 程序方法:

    • asList(): 获取任何序列或数组,并将其转换为一个 列表集合 。

    • copyOf():以新的长度创建现有数组的新副本。

    • copyOfRange():创建现有数组的一部分的新副本。

    • equals():比较两个数组是否相等。

    • deepEquals():多维数组的相等性比较。

    • stream():生成数组元素的流。

    • hashCode():生成数组的哈希值。

    • deepHashCode(): 多维数组的哈希值。

    • sort():排序数组

    • parallelSort():对数组进行并行排序,以提高速度。

    • binarySearch():在已排序的数组中查找元素。

    • parallelPrefix():使用提供的函数并行累积(以获得速度)。基本上,就是数组的reduce()。

    • spliterator():从数组中产生一个Spliterator;这是本书没有涉及到的流的高级部分。

    • toString():为数组生成一个字符串表示。你在整个章节中经常看到这种用法。

    • deepToString():为多维数组生成一个字符串。

    10. 数组拷贝

    与使用for循环手工执行复制相比,copyOf()copyOfRange() 复制数组要快得多。这些方法被重载以处理所有类型。

    工具类:

    package arrays;
    
    import java.util.Arrays;
    import java.util.function.Supplier;
    
    public interface Count {
        class Integer
                implements Supplier<java.lang.Integer> {
            int i;
    
            @Override
            public java.lang.Integer get() {
                return i++;
            }
    
            public java.lang.Integer get(int n) {
                return get();
            }
    
            public java.lang.Integer[] array(int sz) {
                java.lang.Integer[] result =
                        new java.lang.Integer[sz];
                Arrays.setAll(result, n -> get());
                return result;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    测试类:

    package arrays;
    
    import java.util.Arrays;
    
    class Sup {
        // Superclass
        private int id;
    
        Sup(int n) {
            id = n;
        }
    
        @Override
        public String toString() {
            return getClass().getSimpleName() + id;
        }
    }
    
    class Sub extends Sup { // Subclass
        Sub(int n) {
            super(n);
        }
    }
    
    public class ArrayCopying {
        public static final int SZ = 10;
    
        static <T> void show(String info, T[] array) {
            System.out.println(info + ":" + Arrays.toString(array));
        }
    
        public static void main(String[] args) {
            Integer[] a1 = new Integer[SZ];
            Arrays.setAll(a1, new Count.Integer()::get);
            show("a1", a1);
    
            // 最基本的数组复制
            Integer[] a2 = Arrays.copyOf(a1, a1.length);
            // 把a1的所有元素都设为1,以证明a1的变化不会影响a2
            Arrays.fill(a1,1);
            show("a1", a1);
            show("a2", a2);
    
            a2 = Arrays.copyOf(a2, 8);
            show("a2", a2);
    
            // copyOfRange() 需要一个开始和结束索引
            Integer[] a3 = Arrays.copyOfRange(a2, 2, 5);
            show("a3", a3);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    输出:

    a1:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    a1:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    a2:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    a2:[0, 1, 2, 3, 4, 5, 6, 7]
    a3:[2, 3, 4]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    11. 数组比较

    数组 提供了 equals() 来比较一维数组,以及 deepEquals() 来比较多维数组。对于所有原生类型和对象,这些方法都是重载的。

    数组相等的含义:数组必须有相同数量的元素,并且每个元素必须与另一个数组中的对应元素相等,对每个元素使用 equals()

    package arrays;
    
    import java.util.Arrays;
    
    public class ComparingArrays {
        public static final int SZ = 5;
    
        public static void main(String[] args) {
            int[] a1 = new int[SZ], a2 = new int[SZ];
            Arrays.setAll(a1, new Count.Integer()::get);
            Arrays.setAll(a2, new Count.Integer()::get);
            System.out.println("a1 == a2: " + Arrays.equals(a1, a2));
            a2[3] = 11;
            System.out.println("a1 == a2: " + Arrays.equals(a1, a2));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出:

    a1 == a2: true
    a1 == a2: false
    
    • 1
    • 2

    12. 流和数组

    stream() 方法很容易从某些类型的数组中生成元素流。

    package arrays;
    
    import java.util.Arrays;
    
    public class StreamFromArray {
        public static void main(String[] args) {
            String[] s = {"abc", "dcd", "erb", "kkl", "232", "dfg", "abc"};
            Arrays.stream(s).skip(2)
                    .limit(4)
                    .map(n -> n + "!")
                    .forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出:

    erb!
    kkl!
    232!
    dfg!
    
    • 1
    • 2
    • 3
    • 4

    13. 数组排序

    Java有两种方式提供比较功能。第一种方法是通过实现 java.lang.Comparable 接口的原生方法。这是一个简单的接口,只含有一个方法 compareTo()。该方法接受另一个与参数类型相同的对象作为参数,如果当前对象小于参数,则产生一个负值;如果参数相等,则产生零值;如果当前对象大于参数,则产生一个正值。

    package arrays;
    
    import java.util.Arrays;
    import java.util.SplittableRandom;
    
    public class CompType implements Comparable<CompType> {
        private static int count = 1;
        private static SplittableRandom r = new SplittableRandom(47);
        int i;
        int j;
    
        public CompType(int n1, int n2) {
            i = n1;
            j = n2;
        }
    
        public static CompType get() {
            return new CompType(r.nextInt(100), r.nextInt(100));
        }
    
        static <T> void show(String info, T[] array) {
            System.out.println(info + ":" + Arrays.toString(array));
        }
    
        @Override
        public String toString() {
            String result = "[i = " + i + ", j = " + j + "]";
    
            return result;
        }
    
        public static void main(String[] args) {
            CompType[] a = new CompType[6];
            Arrays.setAll(a, n -> get());
            show("Before sorting", a);
            // 这里用的是重写的 compareTo() 进行排序
            Arrays.sort(a);
            show("After sorting", a);
        }
    
        @Override
        public int compareTo(CompType rv) {
            return i < rv.i ? -1 : (i == rv.i ? 0 : 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    输出:

    Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79], [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7]]
    After sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 48, j = 93], [i = 56, j = 68], [i = 70, j = 7], [i = 77, j = 79]]
    
    • 1
    • 2

    集合类包含一个方法 reverseOrder(),它生成一个来Comparator(比较器)反转自然排序顺序。

    package arrays;
    
    import java.util.Arrays;
    import java.util.Collections;
    
    public class Reverse {
        static <T> void show(String info, T[] array) {
            System.out.println(info + ":" + Arrays.toString(array));
        }
    
        public static void main(String[] args) {
            CompType[] a = new CompType[6];
            Arrays.setAll(a, n -> CompType.get());
            show("Before sorting", a);
            Arrays.sort(a, Collections.reverseOrder());
            show("After sorting", a);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出:

    Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79], [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7]]
    After sorting:[[i = 77, j = 79], [i = 70, j = 7], [i = 56, j = 68], [i = 48, j = 93], [i = 41, j = 20], [i = 35, j = 37]]
    
    • 1
    • 2

    您还可以编写自己的比较器。这个比较CompType对象基于它们的j值而不是它们的i值:

    package arrays;
    
    import java.util.Arrays;
    import java.util.Comparator;
    
    class CompTypeComparator implements Comparator<CompType> {
    
        @Override
        public int compare(CompType o1, CompType o2) {
            return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1));
        }
    }
    
    public class ComparatorTest {
        static <T> void show(String info, T[] array) {
            System.out.println(info + ":" + Arrays.toString(array));
        }
    
        public static void main(String[] args) {
            CompType[] a = new CompType[6];
            Arrays.setAll(a, n -> CompType.get());
            show("Before sorting", a);
            Arrays.sort(a, new CompTypeComparator());
            show("After sorting", a);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    输出:

    Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79], [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7]]
    After sorting:[[i = 70, j = 7], [i = 41, j = 20], [i = 35, j = 37], [i = 56, j = 68], [i = 77, j = 79], [i = 48, j = 93]]
    
    • 1
    • 2

    —PS:推荐大佬文章:Comparator和Comparable的区别

    Arrays.sort()的使用

    使用内置的排序方法,您可以对实现了 Comparable 接口或具有 Comparator 的任何对象数组或任何原生数组进行排序。

    并行排序

    如果排序性能是一个问题,那么可以使用 Java 8 parallelSort()

    parallelSort() 算法将大数组拆分成更小的数组,直到数组大小达到极限,然后使用普通的 Arrays.sort() 方法。然后合并结果。该算法需要不大于原始数组的额外工作空间。

    14. binarySearch二分查找

    一旦数组被排序,您就可以通过使用 Arrays.binarySearch() 来执行对特定项的快速搜索。但是,如果尝试在未排序的数组上使用 binarySearch(),结果是不可预测的。

    package arrays;
    
    import java.util.Arrays;
    import java.util.Random;
    
    public class ArraySearching {
        public static void main(String[] args) {
            Random random = new Random(47);
            int[] a = new int[15];
            for (int i = 0; i < a.length; i++) {
                a[i] = random.nextInt(100);
            }
            Arrays.sort(a);
            System.out.println("Sorted array:" + Arrays.toString(a));
    
            while (true) {
                int r = random.nextInt(100);
                int location = Arrays.binarySearch(a, r);
                if (location >= 0) {
                    System.out.println("Location of " + r + " is " + location + ", a["
                            + location + "] is " + a[location]);
                    break;
    
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    输出:

    Sorted array:[0, 7, 9, 22, 28, 29, 51, 55, 58, 61, 61, 68, 88, 89, 93]
    Location of 61 is 9, a[9] is 61
    
    • 1
    • 2

    15. 本章小结

    Java为固定大小的低级数组提供了合理的支持。在Java的最初版本中,固定大小的低级数组是绝对必要的,这不仅是因为Java设计人员选择包含原生类型(也考虑到性能),还因为那个版本对集合的支持非常少。因此,在早期的Java版本中,选择数组总是合理的。在Java的后续版本中,集合支持得到了显著的改进,现在集合在除性能外的所有方面都优于数组。正如本书其他部分所述,无论如何,性能问题通常不会出现在您设想的地方。

    所有这些问题都表明,在使用Java的最新版本进行编程时,应该“优先选择集合而不是数组”。只有当您证明性能是一个问题(并且切换到一个数组实际上会有很大的不同)时,才应该重构到数组。

    —PS:非必要不使用
    在这里插入图片描述
    (图网,侵删)

  • 相关阅读:
    Fritzing软件绘制Arduino面包板接线图传感器模块库文件287
    【golang】why 消费者定义接口,生产者返回实体struct?
    包含min函数栈
    Websocket
    【LeetCode热题100】--136.只出现一次的数字
    「尚硅谷与腾讯云官方合作」硅谷课堂项目视频发布
    MySQL 的几种碎片整理方案总结(解决delete大量数据后空间不释放的问题)
    CC导入UE5 人物眼球变白,睫毛粗解决办法思路提供
    xlua游戏热更新(C#访问lua)
    司空见惯 - 世界人口变化
  • 原文地址:https://blog.csdn.net/JustDI0209/article/details/132742580