Java(八)——String类

文章目录

  • String类
    • String的构造及内存分布
      • 构造
      • 内存分布
    • 常用方法
      • 判等
      • 比较
      • 查找
      • 转化
      • 替换
      • 拆分
      • 截取
    • 字符串的不可变性
    • StringBuilder和StringBuffer

String类

C语言中没有专门的字符串类型,一般使用字符数组或字符指针表示字符串,而字符串的函数需要包含头文件才能使用,这种数据与方法分离的做法不符合面向对象的思想。

Java中提供了String类来表示字符串


String的构造及内存分布

构造

字符串的构造方式主要有三种:

  1. 常量串构造

    String str1 = "hello";
    
  2. 直接new String对象

    String str2 = new String("hello");
    
  3. 使用字符数组进行构造

    char[] ch = new char[]{'h', 'e', 'l', 'l', 'o'};
    String str3 = new String(ch);
    

内存分布

核心就是 字符串常量池

JDK6及以前,字符串常量池存放在在永久代(内存的永久保存区域)

JDK7之后,字符串常量池存放在

字符串常量构造字符串时,Java在字符串常量池中寻找该字符串的内存地址,找到则将地址赋值给变量,如果没有找到,则在常量池开辟空间并将这块空间赋值该字符串,将这块地址赋值给变量。

new构造字符串时,Java先在堆区开辟一块空间,存放String对象,然后在字符串常量池中寻找字符串的内存地址,找到则将该地址的引用传给堆区的对象,如果没有找到,则在常量池开辟空间并将这块空间赋值该字符串,将地址的引用传给堆区的对象,最后,将堆区对象的地址赋值给变量。

如下代码及内存分布:

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        String s4 = new String("hello");
    }

在这里插入图片描述

我们在IDEA中观察String的源码:

在这里插入图片描述

我们可以看到,String类中包含很多的成员变量,我们仅关注value数组即可,它存放的就是字符串常量池的某个字符串的地址,其内存分布简单表示如下图:

String str1 = new String("hello");

在这里插入图片描述

字符串非常重要,我们接下来介绍一下Java中字符串的常用方法。


常用方法

判等

字符串的判等区分两种:

  1. ==:判断是否引用同一个对象
  2. boolean equals():判断字符串的内容是否相等

字符串类型是引用类型,==判断两个字符串的地址,而equals()方法判断两个字符串的内容是否相等(无关地址)

我们看下面一段代码,体会==equals()的区别:

public class Test {

    public static void main(String[] args) {
        //new了三个不同的对象
        String str1 = new String("hello");
        String str2 = new String("HELLO");
        String str3 = new String("hello");

        System.out.println(str1 == str2);//不同对象的地址不同
        System.out.println(str1 == str3);//不同对象的地址不同
        System.out.println(str1.equals(str2));//两个字符串的内容不同
        System.out.println(str1.equals(str3));//两个字符串的内容相同

        System.out.println("=============");
        str2 = str1;//将引用变量str1的值赋给str2,此时str1和str2指向同一个对象

        System.out.println(str1 == str2);//指向同一个对象后,地址相同
        System.out.println(str1.equals(str2));//指向同一个对象,内容自然也相同
    }
}

打印观察:

在这里插入图片描述


我们再来看一段代码:

    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "HELLO";
        String str3 = "hello";
        
        System.out.println(str1 == str2);//直接指向了字符串常量池的不同字符串,地址当然不同
        System.out.println(str1 == str3);//直接指向了字符串常量池的同一字符串,地址相同
        System.out.println(str1.equals(str2));//内容不同
        System.out.println(str1.equals(str3));//内容相同

        System.out.println("=============");
        str2 = str1;

        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));
    }

打印结果是:
在这里插入图片描述

前后两次区别在于构造字符串的方式不同,第一次是使用new构造,第二次是直接使用常量串构造;

第一次不同对象指向字符串常量池的同一地址,==结果是false;而第二次相同的常量串构造,==结果是true,这刚好验证了我们上面介绍的内存分布情况。


比较

如下两种:

  1. int compareTo()
  2. int compareToIgnoreCase()

【compareTo】

前面介绍的equals()的返回值是boolean类型且只能判断是否相等,而compareTo()返回值是int类型,不同的比较结果返回不同特征的值。

compareTo()比较规则如下:

  1. 先按照字典序大小依次比较,如果出现不等的字符,直接返回两个字符的大小差值
  2. 如果前n个字符都相等(n为要比较的两个字符串的长度较小值),返回两个字符串长度差值
  3. 按照上面的比较规则,如果>就返回一个正数;如果<就返回一个负数;如果==就返回0

例如:

    public static void main(String[] args) {

        String s1 = new String("hello");
        String s2 = new String("Hello");
        String s3 = new String("zero");
        String s4 = new String("hello");

        System.out.println(s1.compareTo(s2));//第一个字符'h'与'H'比较,'h' > 'H',所以返回正值
        System.out.println(s1.compareTo(s3));//第一个字符'h'与'z'比较,'h' < 'z',所以返回负值
        System.out.println(s1.compareTo(s4));//所有字符依次比较,全部相等,返回0
    }

在这里插入图片描述


【compareToIgnoreCase】

compareTo()的区别是,compareToIgnoreCase()比较时忽略大小写,如:

    public static void main(String[] args) {
        String s1 = "HELLO";
        String s2 = "hello";

        System.out.println(s1.compareTo(s2));
        System.out.println(s1.compareToIgnoreCase(s2));
    }

在这里插入图片描述

注意:

  • compareTo()compareToIgnoreCase()比较时的主体都是调用方法的那个字符串而非传入的参数

查找

char charAt()

int indexOf()及其重载方法

int lastIndexOf()及其重载方法


【charAt】

charAt()方法传入一个int类型的值,这个值即下标值,它将返回指定下标位置的字符(char类型)

    public static void main(String[] args) {
        String s1 = "hello";
        System.out.println(s1.charAt(0));
        System.out.println(s1.charAt(1));
        System.out.println(s1.charAt(2));
        System.out.println(s1.charAt(3));
        System.out.println(s1.charAt(4));
    }

在这里插入图片描述


【indexOf及其重载方法】

  1. int indexOf(int ch):返回 ch 第一次出现的位置,没有则返回 -1

        public static void main(String[] args) {
            String s1 = new String("hello");
    
            System.out.println(s1.indexOf('h'));
            System.out.println(s1.indexOf('l'));
            System.out.println(s1.indexOf('i'));
        }
    

    在这里插入图片描述

  2. int indexOf(int ch, int fromIndex):从fromIndex(下标值)位置开始找,返 ch 第一次出现的位置,没有找到则返回 -1

        public static void main(String[] args) {
            String s1 = "abcabcdabcde";
    
            System.out.println(s1.indexOf('c', 4));
            System.out.println(s1.indexOf('f', 0));
        }
    

    在这里插入图片描述

  3. int indexOf(String str):返回 str 第一次出现的位置,没有找到则返回 -1

        public static void main(String[] args) {
            String s1 = new String("hello");
    
            System.out.println(s1.indexOf("ll"));
            System.out.println(s1.indexOf("lll"));
        }
    

    在这里插入图片描述

  4. int indexOf(String str, int fromIndex):从fromIndex(下标值)的位置开始找,返回 str 第一次出现的位置,没有则返回 -1

        public static void main(String[] args) {
            String s1 = "abcabcdabcde";
            System.out.println(s1.indexOf("abc", 4));
            System.out.println(s1.indexOf("abcf", 2));
        }
    

    在这里插入图片描述


【lastIndexOf】

与前面indexOf()的区别是:查找方向不同lastIndexOf()从后往前找

4种方式:

  • int lastIndexOf(int ch):从后往前找,返回 ch 第一次出现的位置,没有则返回 -1;
  • int lastIndexOf(int ch, int fromIndex):从fromIndex的位置开始从后往前找,返回 ch 第一次出现的位置,没有则返回 -1;
  • int lastIndexOf(String str):从后往前找,返回 str 第一次出现的位置,没有则返回 -1;
  • int lastIndexOf(String str, int fromIndex):从fromIndex的位置开始从后往前找,返回 str 第一次出现的位置,没有则返回 -1;
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "abcabcdabcde";

        System.out.println(s1.lastIndexOf('l'));
        System.out.println(s1.lastIndexOf('l', 2));
        System.out.println(s2.lastIndexOf("abc"));
        System.out.println(s2.lastIndexOf("abc", 6));
    }

在这里插入图片描述


转化

转化部分介绍:

  • 数值和字符串转化
  • 大小写转化
  • 字符串转数组
  • 格式化

【数值和字符串转化】

数值转化为字符串:

String String.valueOf()

字符串转化为数值:

包装类对应类型 包装类.valueOf(String str)

当然不是所有的转化都会成功

注意:需要使用类名调用valueOf()方法,这是因为valueOf()方法被static修饰

在这里插入图片描述

    public static void main(String[] args) {
        String s1 = String.valueOf(123);
        int i1 = Integer.valueOf("123");
        int i2 = Integer.parseInt("123");
        int i3 = Integer.valueOf("123");
        double d1 = Double.valueOf("12.2");
        System.out.println(s1);
        System.out.println(i1);
        System.out.println(i2);
        System.out.println(i3);
        System.out.println(d1);
    }

在这里插入图片描述

上图中穿插了一个parseInt()方法,其与valueOf()没有差别,观察valueOf()方法:

发现,valueOf()底层就调用了parseInt()方法


【大小写转化】

转大写:

String toUpperCase()

转小写

String toLowerCase()

public static void main(String[] args) {
    String s1 = "hello";
    String s2 = s1.toUpperCase();
    System.out.println(s2);
    System.out.println(s2.toLowerCase());
}

在这里插入图片描述


【字符串转数组】

char[] toCharArray()

    public static void main(String[] args) {
        String s1 = "hello";
        char[] chars = s1.toCharArray();

        for (char tmp : chars) {
            System.out.println(tmp);
        }
    }

在这里插入图片描述


【格式化】

String format()

需要了解占位符的知识,Java较少使用,但学过C语言的应该不陌生

    public static void main(String[] args) {
        String s1 = String.format("%d-%d-%d", 2024, 5, 31);
        System.out.println(s1);
    }

在这里插入图片描述


替换

String replace()

String replaceAll(String regex, String replacement):替换所有指定内容

String replaceFirst(String regex, String replacement):替换首个指定内容

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = s1.replace('l', 'o');
        String s3 = s1.replace("ll", "oo");
        String s4 = s1.replaceAll("l", "o");
        String s5 = s1.replaceFirst("ll", "oo");
        
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
        System.out.println(s5);
    }

在这里插入图片描述


拆分

  1. String[] split(String regex):以regex为分割符拆分字符串,返回一个字符串数组

  2. String[] split(String regex, int limit):将字符串以指定的格式,拆分为limit


String[] split(String regex):以regex为分割符拆分字符】

    public static void main(String[] args) {
        String s1 = "hello world!";
        String[] strings1 = s1.split(" ");
        for (String tmp : strings1) {
            System.out.print(tmp + " ");
        }
        //换行
        System.out.println();
        //检验分割后返回的数组
        System.out.println(Arrays.deepToString(strings1));
    }

在这里插入图片描述

如果有多个分割符可以使用|,如:

public static void main(String[] args) {
    String s1 = "I am@OK!";
    String[] strings = s1.split(" |@");
    System.out.println(Arrays.deepToString(strings));
}

在这里插入图片描述


String[] split(String regex, int limit):将字符串以指定的格式,拆分为limit组】

    public static void main(String[] args) {
        String s1 = "i am a bit";
        String[] strings = s1.split(" ", 2);//仅被分为两组
        System.out.println(Arrays.deepToString(strings));
    }

在这里插入图片描述


有些特殊字符作为分割符可能无法正确切分,需要加上转义

  • 字符|*,+.都得加上转义字符,即\\
  • 如果想以\为分割符,就得写成\\\\;单独的\不能出现在字符串中,必须以\\表示\
  • 如果一个字符串中有多个分割符,可以使用|作为连字符,上面演示过了
    public static void main(String[] args) {
        String s1 = "2024.5.31";
        String[] strings1 = s1.split("\\.");
        System.out.println(Arrays.deepToString(strings1));

        String s2 = "2024+5+31";
        strings1 = s2.split("\\+");
        System.out.println(Arrays.deepToString(strings1));

        String s3 = "2024\\5\\31";
        strings1 = s3.split("\\\\");
        System.out.println(Arrays.deepToString(strings1));
    }

在这里插入图片描述


截取

String substring(int beginIndex):从beginIndex下标位置截取到结尾

String substring(int beginIndex, int endIndex):从beginIndex下标位置截取到endIndex - 1下标位置,也就是说[beginIndex, endIndex)


    public static void main(String[] args) {
        String s1 = "hello world!";
        String s2 = s1.substring(6);
        System.out.println(s2);

        String s3 = s1.substring(6, 11);
        System.out.println(s3);
    }

在这里插入图片描述


【补充】

String trim():去掉字符串中的左右空格,保留中间空格

    public static void main(String[] args) {
        String s1 = new String("  hello world   ");
        String s2 = s1.trim();
        System.out.println(s2);
    }

在这里插入图片描述


字符串的不可变性

String是一种不可变对象。字符串不可修改,我们从String类源码观察:

在这里插入图片描述

如图:

  • 第一个final表示String类不能被继承;

  • 第二个final表示value不能指向新的对象,但是其内容可以改变,我们可以验证这一点:

    public static void main(String[] args) {
        final String[] strings = new String[]{"hello", " world", "!"};
        //修改内容,不报错
        strings[1] = "bit";
        
        //修改strings指向的对象,报错!
        strings = new String[10];
    }

在这里插入图片描述
在这里插入图片描述

  • 真正决定String不可被修改的是蓝框里的private修饰符,valueprivate修饰,意味着value只能在当前的String类中使用,而String类没有提供valuegetset方法,所以我们没有办法拿到value值,自然就不能修改字符串了。反过来,只要给value提供getset方法,我们就可以修改字符串了。

基于字符串的不可变性,我们补充一点注意点:

上面我们介绍的所有的方法,都不是在原字符串上进行操作的,都是重新创建了一个新的字符串返回


【字符串的追加(拼接)操作】

使用+即可

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = s1 + " world";
        System.out.println(s2);
    }

在这里插入图片描述

此操作容易被误认为修改了字符串,其实并不是。

这段代码实际上如此:

    public static void main(String[] args) {
        
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("hello");
        stringBuilder.append(" world");
        String s2 = stringBuilder.toString();
        System.out.println(s2);
    }

并不是在原字符串上修改的,而是使用了StringBuilder类,这是个什么类呢?我们接下来讲!


StringBuilder和StringBuffer

Java中字符串的不可变性使得字符串的操作要不断地new新的对象,使得便捷性与效率并不理想。

为了方便字符串的修改,Java提供了StringBuilderStringBuffer两个类String调用这两个类的构造方法或append方法即可实现转化,这两个类中有非常多的操作字符串的方法(包含String类方法并比String类更丰富),如上面展示过的append()追加方法、reverse()反转方法,调用这些方法时无需创建新对象,直接在它们的类对象上操作即可,若要转换成字符串,只需调用toString()方法并接收即可

我们演示一下:

    public static void main(String[] args) {
        //创建StringBuilder类对象
        StringBuilder stringBuilder = new StringBuilder("hello");
        //直接操作
        stringBuilder.reverse();
        //转换为字符串
        String str = stringBuilder.toString();
        //打印
        System.out.println(stringBuilder);
        System.out.println(str);
    }

在这里插入图片描述

以后我们想要对字符串执行追加操作时,要尽量使用StringBuilderStringBuffer两个类的追加方法,它们的运行效率更高!

StringBuilderStringBuffer类与String类最大的区别就是String类的内容无法修改,而StringBuilderStringBuffer的内容可以修改。


StringBuilderStringBuffer的区别】

  • StringBuffer采用同步处理,属于线程安全操作,用于保障多线程安全
  • StringBuilder未采用同步处理,属于线程不安全操作,用于单线程

那么,我们以后都使用StringBuffer?

不是的。虽然StringBuffer可以保证多线程安全,但是相比StringBuilder多了一把"锁",“开锁” 和 “上锁” 也是会有损耗的,其效率会较低。对于现在的我们,只需要记住,具体场景具体分析,合理选择。


以上就是对 String的所有介绍了
接下来就是异常了,异常结束后,JavaSE的语法部分就基本结束了,后续会将所有的知识整合在一起,发一篇SE语法全集

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/669499.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】二叉树:简约和复杂的交织之美

专栏引入&#xff1a; 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累…

PWN-栈迁移

栈迁移 题目&#xff1a;BUUCTF在线评测 (buuoj.cn) 知识点&#xff1a;栈迁移 使用情况&#xff1a;题目中有栈溢出&#xff0c;但是 栈溢出的范围 有限&#xff0c;导致构造的ROP链不能完全写入到栈中&#xff0c;此时需要进行栈迁移&#xff0c;将栈迁移到能接受更多数据的…

GD32F系列MCU片上Flash中Code区和Data区使用解密

GD32F系列MCU产品片上Flash分Code区和Data区&#xff0c;以GD32F303系列为例&#xff0c;从GD32F303xx Datasheet中可以获取code区和data区大小&#xff0c;那Code区和Data区在代码执行上有什么差别呢&#xff1f; Code区代码运行0等待&#xff0c;一般用于存放实时性要求高的代…

STM32(八):独立看门狗 (标准库函数)

前言 上一篇文章介绍了STM32单片机中的USART串口通信&#xff0c;这篇文章我们来介绍一下如何用STM32单片机中的独立看门狗来实现检测按键点灯的程序。 一、实验原理 单片机系统会由于受到外界的干扰&#xff0c;而造成程序执行紊乱&#xff0c;系统无法正常运行。为了防止这…

混合模型方差分析

文章目录 一、说明二、受试者“间”因素和受试者“内”因素的意思&#xff1f;三、混合模型方差分析回答 3 件事四、混合模型方差分析的假设 一、说明 在本文中&#xff0c;我将讨论一种称为混合模型方差分析的方差分析变体&#xff0c;也称为具有重复测量的 2 因素方差分析。…

python替换占位符为变量,实现读取配置文件

文章目录 背景1、定义正则表达式2、替换变量占位符3、实现功能 背景 使用python编写小工具&#xff0c;有一个配置文件&#xff0c;希望实现类似shell命令的&#xff0c;定义变量并且使用${}或者$来引用。如果有好的建议欢迎讨论。 配置文件示例内容如下: D:\project\test\pr…

word里面没有Acrobat选项

加载项被禁止&#xff0c;选择项里面&#xff0c;没有Acrobat选项 文件-》选项 加载项-》com加载项-》转到 添加Acrobat 出现Acrobat选项

阿里云部署nodejs

目录 1、安装node.js 1-1 进入opt/software 1-2 下载node.js安装包 1-3 解压 2 配置环境变量 2-1 vim中配置环境变量 2-2 命令行中保存环境变量 2-3 检查安装版本 2-3 更换镜像 3、上传node.js项目 1-1 启动项目 1-2 配置对应的安全组 ​编辑 4、pm2启动多个node项…

《PNAS》和《Nature Communications》仿章鱼和蜗牛的粘液真空吸附,赋予了机器人吸盘新的“超能力”

想象一下&#xff0c;如果机器人能够像章鱼一样牢牢吸附在粗糙崎岖的岩石上&#xff0c;或者像蜗牛那样在墙面上悠然负重爬行&#xff0c;那会是多么神奇的一幕&#xff01;近日&#xff0c;布里斯托大学机器人实验室的Jonathan Rossiter教授课题组就为我们带来了这样的“超能力…

Java_Mybatis

Mybatis是一款优秀的持久层框架&#xff0c;用户简化JDBC(使用Java语言操作关系型数据库的一套API)开发 使用Mybatis查询所有用户数据&#xff1a; 代码演示&#xff1a; UserMapper&#xff1a; Mapper //被调用时会通过动态代理自动创建实体类&#xff0c;并放入IOC容器中…

一文了解JVM面试篇(上)

Java内存区域 1、如何解释 Java 堆空间及 GC? 当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建 堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一 个进程,回收无效对象的内存用于将来的分配。 2、JVM 的主要组成…

JVM学习-详解类加载器(一)

类加载器 类加载器是JVM执行类加载机制的前提 ClassLoader的作用 ClassLoader是Java的核心组件&#xff0c;所有的Class都是由ClassLoader进行加载的&#xff0c;ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部&#xff0c;转换为一个与目标类型对应的ja…

什么是TLAB?

这个得从内存申请说起。 一般而言生成对象需要向堆中的新生代申请内存空间&#xff0c;而堆又是全局共享的&#xff0c;像新生代内存又是规整的&#xff0c;是通过一个指针来划分的。 内存是紧凑的&#xff0c;新对象创建指针就右移对象大小size即可&#xff0c;这叫指针加法…

Linux 的权限

目录 Linux 的用户 root 用户 和 普通用户 如何新建普通用户&#xff1f; 如何切换用户&#xff1f; 一开始是以 root 用户登录&#xff1a; 一开始以普通用户登录&#xff1a; 如何删除用户&#xff1f; Linux文件权限 什么是读权限&#xff08; r &#xff09;&#…

数据结构学习笔记

1. 数组 (Array) 定义 数组是一种线性数据结构&#xff0c;用于存储固定大小的相同类型元素集合。每个元素都有一个索引&#xff0c;用于快速访问。 特点 优点&#xff1a;访问速度快&#xff0c;通过索引直接访问O(1)时间复杂度。缺点&#xff1a;大小固定&#xff0c;插入…

Unity 自定义房间布局系统 设计与实现一个灵活的房间放置系统 ——自定义房间区域功能

自定义房间区域功能 效果&#xff1a; 功能&#xff1a; 能够自定义房间的大小一键生成放置区域可控的放置网格点当物体放置到区域内可自动吸附物体是否可放置&#xff0c;放置时如果与其他物体交叉则不可放置&#xff08;纯算法计算&#xff09;管理房间内的物体&#xff0c…

MySQL(二)-基础操作

一、约束 有时候&#xff0c;数据库中数据是有约束的&#xff0c;比如 性别列&#xff0c;你不能填一些奇奇怪怪的数据~ 如果靠人为的来对数据进行检索约束的话&#xff0c;肯定是不行的&#xff0c;人肯定会犯错~因此就需要让计算机对插入的数据进行约束要求&#xff01; 约…

下载HF AutoTrain 模型的配置文件

下载HF AutoTrain 模型的配置文件 一.在huggingface上创建AutoTrain项目二.通过HF用户名和autotrain项目名,拼接以下url,下载模型列表(json格式)到指定目录三.解析上面的json文件、去重、批量下载模型配置文件(权重以外的文件) 一.在huggingface上创建AutoTrain项目 二.通过HF用…

电脑没电关机,wsl和docker又挂了,附解决过程

如题&#xff0c;开了个会没带笔记本电源&#xff0c;点啊弄关机后docker打不开&#xff0c;我以为是docker坏了&#xff0c;结果docker报错&#xff1a; An unexpected error occurred while executing a WSL command. Either shut down WSL down with wsl --shutdown, and/or…

植被变化趋势线性回归以及可视化

目录 植被变化线性回归ee.Reducer.linearFit().reduce()案例:天水市2004-2023年EVI线性回归趋势在该图中,使用了从红色到蓝色的渐变来表示负趋势到正趋势。红色代表在某段时间中,植被覆盖减少,绿色表示持平,蓝色表示植被覆盖增加。 植被变化线性回归 该部分参考Google…