Android14 Log.isLoggable判断的分析

news2025/3/20 23:25:43

Android14 Log.isLoggable判断的分析

文章目录

  • Android14 Log.isLoggable判断的分析
    • 一、前言
    • 二、答案和分析
      • 1、Log.isLoggable 设置成true
      • 2、Log.isLoggable 分析
        • (1)Log.java
        • (2)android_util_Log.cpp
        • (3)properties.cpp
        • (4)logd_write.c
    • 三、其他
      • 1、Log.isLoggable(TAG, Log.DEBUG) 小结
        • 设置Log.isLoggable 方法为true
        • 设置Log.isLoggable的应用场景
      • 2、Log.java 打印等级的值
      • 3、Log.java 旧版本的代码
      • 4、系统的Log.isLoggable(TAG, Log.DEBUG) 验证
      • 5、在cmd窗口查看手机的Log日志相关命令

一、前言

Android系统代码或者系统应用代码中经常有一些Dug打印,是可以在调试模式中查看的;
比如:if(Log.isLoggable(TAG, Log.DEBUG)) {XXX}
或者定义 boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
后面有些打印使用这个DEBUG属性来决定是否打印某些日志。

之前我不太懂,也有看过源码,但是看到了native再后面就追不下去了,就没管了。
后面各种搜索和研究后又有了新的发现。

本文介绍一下如何查看这个默认关闭的DEBUG信息日志。

其实不难,就是prop属性的设置和读取;
但是估计要调试模式才能手动设置这个日志开关;
系统权限应用也可以设置这个属性进行控制。

二、答案和分析

1、Log.isLoggable 设置成true

因为分析的过程比较麻烦,所以直接先说答案了。

    private static final String TAG = "WifiTracker";
    private static final boolean DBG() {
        return Log.isLoggable(TAG, Log.DEBUG);
    }
    //有些写成这样,比较简便:
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

如何让这个DBG() 方法或者 DEBUG 为true 呢?

下面两种方法:

1、在源码里面,把 Log.isLoggable(TAG, Log.DEBUG) 内容替换成 true,重新编译apk或者模块代码进行替换。

2、设置prop属性,log.tag.<TAG> 属性设置成D
比如上面这个类设置:setprop log.tag.WifiTracker D
重新加载这个类的时候,就会获取到 Log.isLoggable(TAG, Log.DEBUG) 为 true

第一种方法是最笨的方法,我之前经常是这样搞的。
第二种方法比较灵活,可以动态开启打印,关闭打印。

如果只要看Log.isLoggable的开启,那么到这里就可以了。

但是想看看系统代码是如何判断的可以往后看看。
Log.isLoggable开启后如何关闭?后面会提到!

2、Log.isLoggable 分析

下面分析的是Android14 的代码,不同系统版本可能有差异。

(1)Log.java

framework\base\core\java\android\util\Log.java

public final class Log {

...
    /**
     * Checks to see whether or not a log for the specified tag is loggable at the specified level.
     *
     *  The default level of any tag is set to INFO. This means that any level above and including
     *  INFO will be logged. Before you make any calls to a logging method you should check to see
     *  if your tag should be logged. You can change the default level by setting a system property:
     *      'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
     *  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, or ASSERT.
     *  You can also create a local.prop file that with the following in it:
     *      'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
     *  and place that in /data/local.prop.
     */
    @FastNative
    public static native boolean isLoggable(@Nullable String tag, @Level int level);
}

居然是native方法,那就就要看cpp代码了!
但是看上面注释的英文其实是有大致的说明了。

/**
*检查指定标记的日志是否可在指定级别记录。
*
*任何标签的默认级别都设置为INFO。这意味着,任何高于并包括以下级别的
*将记录信息。在调用日志方法之前,您应该检查以下内容
*如果你的标签应该被记录下来。您可以通过设置系统属性来更改默认级别:
*'setprop log.tag&lt;YOUR_LOG_TAG>&lt;级别>'
*其中级别为VERBOSE、DEBUG、INFO、WARN、ERROR或ASSERT。//从小到大
*您还可以创建一个local.prop文件,其中包含以下内容:
*'log.tag&lt;YOUR_LOG_TAG>=&lt;级别>'
*并将其放置在/data/local.prop中。
*/

上面大致的意思:

1、可以设置prop属性 setprop log.tag.XXTAG 设置打印等级
TAG一般是当前类,也有可能是其他字符串,有些是整个应用使用某个TAG。

2、setprop log.tag&lt;YOUR_LOG_TAG >&lt;级别>
当你设置的打印属性等级 >= 定义的等级,日志就会记录,也就是返回true
这里定义了DEBUG(缩写D),如果prop属性设置成DEBUG(D)或者ERRORE(缩写成V),Java代码就会返回true

3、属性文件位置:/data/local.prop
这个估计是很旧的代码目录了,Android14 上没看到这个文件!

总的来说就是设置属性:setprop log.tag.XXTAG 设置成D就可以看到打印。

但是第2个>=就有点问题了:

实际代码中尝试prop属性设置成 "E" 是不会显示日志的,设置成 "V" 会显示日志;
难道是注释里面写错了,实际要prop属性的打印等级 <= level级别 ?

继续往下追代码看看!

(2)android_util_Log.cpp

framework\base\core\jni\android_util_Log.cpp

static jboolean isLoggable(const char* tag, jint level) {
    return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}

这个就很难追下去了,__android_log_is_loggable 找不到实现?!

Android 源码framework、device下面搜了找不到 __android_log_is_loggable的实现,后面使用grep -nr 查看system目录下找到了!

(3)properties.cpp

system\logging\liblog\properties.cpp


int __android_log_is_loggable(int prio, const char* tag, int default_prio) {
  auto len = tag ? strlen(tag) : 0;//tag字符串的长度
  //四个参考:
  //1、传入的等级,这里是 Log.DEBUG
  //2、tag字符串
  //3、tag的字符串长度
  //4、默认等级,LOG_INFO,I
  return __android_log_is_loggable_len(prio, tag, len, default_prio);
}

//具体实现判断是在这里:
int __android_log_is_loggable_len(int prio, const char* tag, 
      size_t len, int default_prio) {
    //1、获取最小日志优先级和属性日志级别
    int minimum_log_priority = __android_log_get_minimum_priority();
    //2、这个级别通过系统属性prop配置的,过程在本类看起来很麻烦
    int property_log_level = __android_log_level(tag, len);

  if (property_log_level >= 0 && minimum_log_priority != ANDROID_LOG_DEFAULT) {
    
    //3、判断传入的 Log.DEBUG 是否大于 prop或者minimum_log_priority 最小值
    return prio >= std::min(property_log_level, minimum_log_priority);
  } else if (property_log_level >= 0) {
  
  //4、判断传入的 Log.DEBUG 是否大于 prop 值
    return prio >= property_log_level;
  } else if (minimum_log_priority != ANDROID_LOG_DEFAULT) {
    return prio >= minimum_log_priority;
  } else {
    return prio >= default_prio;
  }
}



上面第3点和第4点 都是有判断prop属性的值是否小于或者等于传入的等级值。

所以最终的实现是要prop定义的TAG值小于或者等于传入的Log.DEBUG 才会返回true。

所以对于isLoggable的示例判断:

Log.isLoggable(TAG, Log.DEBUG); 是否返回为true的判断是:
1、判断prop属性: log.tag.TAG 的 Log等级是否小于或者等于  Log.DEBUG;
2、Log的等级从小到大:VERBOSE、DEBUG、INFO、WARN、ERROR或ASSERT
3、所以 log.tag.TAG 等于 V 或者D 就会返回true

到这里简单代码就分析完了,很多复杂的过程在同级目录的其他文件,有兴趣的可以自己看看。

后面我从网上搜到的__android_log_is_loggable具体实现是在 logd_write.c;
但是我在Android13/14的代码上没有看到这个文件,发现是Android11 之前的代码才有;

(4)logd_write.c

但是Android9 和Android11 的代码 system/core/liblog/logd_write.c 文件中,以下是简化后的逻辑分析:

int __android_log_is_loggable(int prio, const char* tag, int default_log_level) {
    if (prio >= ANDROID_LOG_WARN) {
        return 1;
    }

    char value[PROP_VALUE_MAX];
    if (tag != NULL) {
        char key[PROP_NAME_MAX];
        snprintf(key, sizeof(key), "log.tag.%s", tag);
        if (__system_property_get(key, value) > 0) {
            int prop_level = android_log_priority_to_int(value);
            if (prop_level >= 0) {
                //这里是判断出入的proi <= prop定义的等级,和最新的是相反的!
                return prio <= prop_level;
            } else if (strcmp(value, "SUPPRESS") == 0) {
                return 0;
            }
        }
    }

    //最新的系统代码是去除 log.default 的判断的!
    if (__system_property_get("log.default", value) > 0) {
        int prop_level = android_log_priority_to_int(value);
        if (prop_level >= 0) {
            return prio <= prop_level;
        }
    }

    return prio <= default_log_level;
}

从上面旧系统的代码逻辑看确实是判断 prop属性 log.tag. >= 传入的Log.DEBUG等级。

所以说旧系统代码和新系统代码逻辑是相反的;

Java代码里面的注释并没有进行修改!这个算是Google的一个小错误了。

三、其他

1、Log.isLoggable(TAG, Log.DEBUG) 小结

(1)Log.isLoggable默认都是返回false的
(2)Android 11 之前的版本设置prop属性 log.tag.TAG >= D 为true;
(3)Android 13 之后的版本设置prop属性 log.tag.TAG =< D 为true;
(4)不管什么系统版本设置成一样的等级最保险
(5)如果Android13 或者更新版本要关闭isLoggable的打印,设置大一点就行,比如E
(6)重启后设置的prop属性是不记忆的,要看打印要重新设置一次

设置Log.isLoggable 方法为true
串口设置:
setprop log.tag.XXTAG D //有些要这种V

系统/签名应用代码设置:
import android.os.SystemProperties;
SystemProperties.set("log.tag.XXXTAG", "V");

注意设置的属性,重启后是失效的,要重新设置。

如果prop属性是乱设置呢,比如 setprop log.tag.XXTAG XX
这个要看系统代码实现了,因为我试出了两种不同的结果:

Android14 的mtk方案,随便设置,isLoggable 是返回false的
Android13 的rk方案,随便设置,isLoggable 是返回true的

有兴趣的可以自己看看具体实现的cpp代码。

设置Log.isLoggable的应用场景

系统代码里面是有些 Log.isLoggable 的判断,可以用来查看某些默认隐藏的日志;

我们自己开发的系统应用也是可以这样操作:

普通的关键流程日志用Log.i正常打印,如果是比较多的数据,
比如wifi列表所有信息或者某个对象的具体信息,这种详细日志就用调试模式显示。

2、Log.java 打印等级的值

framework\base\core\java\android\util\Log.java
部分代码如下:

public final class Log {
    /** @hide */
    @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Level {}

    /**
     * Priority constant for the println method; use Log.v.
     */
    public static final int VERBOSE = 2;

    /**
     * Priority constant for the println method; use Log.d.
     */
    public static final int DEBUG = 3;

    /**
     * Priority constant for the println method; use Log.i.
     */
    public static final int INFO = 4;

    /**
     * Priority constant for the println method; use Log.w.
     */
    public static final int WARN = 5;

    /**
     * Priority constant for the println method; use Log.e.
     */
    public static final int ERROR = 6;

    /**
     * Priority constant for the println method.
     */
    public static final int ASSERT = 7;
...
}

这里可以看到 VERBOSE, DEBUG, INFO, WARN, ERROR, or ASSERT.
打印等级是2到7。越大表示越严重,V是最小的2,A 是最大的7.

ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE 翻译过来分别对应:
断言、错误、警告、信息、调试、详细,
断言是可能导致系统崩溃的日志,错误可能导致应用崩溃的日志;

3、Log.java 旧版本的代码

网招的,不清楚是哪个安卓版本的,这个版本的prop判断逻辑都在一个Java代码里面

/**
 * Checks to see whether or not a log for the specified tag is loggable at the specified level.
 * <p>
 * The default level of any tag is set to INFO. This means that any level above and including
 * INFO will be logged. Before you make any calls to a logging method you should check to see
 * if your tag should be logged. You can change the default level by setting a system property:
 * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
 * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
 * turn off all logging for your tag. You can also create a local.prop file that with the
 * following in it:
 * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
 * and place that in /data/local.prop.
 * </p>
 *
 * @param tag The tag to check.
 * @param level The level to check.
 * @return Whether or not that this is allowed to be logged.
 * @throws IllegalArgumentException is thrown if the tag.length() > 23.
 */
public static boolean isLoggable(String tag, int level) {
    if (tag.length() > LOG_TAG_MAX_LENGTH) {
        throw new IllegalArgumentException("Log tag " + tag + " exceeds limit of " + LOG_TAG_MAX_LENGTH + " characters");
    }

    // 如果级别大于等于 WARN ,直接返回 true
    if (level >= Log.WARN) {
        return true;
    }

    // 获取系统属性 log.tag.<TAG> 的值
    String key = "log.tag." + tag;
    String value = SystemProperties.get(key);
    if (value != null) {
        value = value.toUpperCase();
        // 解析系统属性值对应的日志级别
        int i = LogLevel.levelToInt(value, -1); //获取prop属性对应的调试等级int值
        if (i >= 0) {
            //这里也是prop属性的级别,大于或者等于输入的定义基本返回true
            return level <= i; 
        } else if (value.equals("SUPPRESS")) {
            return false;
        }
    }

    // 如果没有设置系统属性,则检查全局的日志级别
    value = SystemProperties.get("log.default");
    if (value != null) {
        value = value.toUpperCase();
        int i = LogLevel.levelToInt(value, -1);
        if (i >= 0) {
            return level <= i;
        }
    }

    return false;
}

上面的代码逻辑熟悉Java的大致可以看懂。
上面的Java代码逻辑大致和Android11 之前的系统Log等级判断是一致的。

不同系统的Log.isLoggable 后面具体逻辑有差异是正常的;

4、系统的Log.isLoggable(TAG, Log.DEBUG) 验证

Android系统中有很多这样的代码,比如热点开关打印的代码:

Settings\src\com\android\settings\network\TetherEnabler.java

public class TetherEnabler implements... {
        
    private static final String TAG = "TetherEnabler";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    void updateState(@Nullable String[] tethered) {
        int state = getTetheringState(tethered);
        if (DEBUG) {
            Log.d(TAG, "updateState: " + state);//热点开关状态打印,0关闭,1开启
        }
...
    }

这个热点开关状态的打印是在:原生Settings一级界面刚进入二级界面"WLAN、热点"显示界面会执行updateState方法;
三级界面返回二级界面也是会执行updateState方法;
后续热点开关这里是没有执行updateState方法;

console:/ # logcat | grep TetherEnabler  
//没日志!
console:/ # setprop log.tag.TetherEnabler D //设置prop属性
console:/ # 
console:/ # logcat | grep TetherEnabler 
03-18 20:12:40.290  1006  1006 D TetherEnabler: updateState: 0 //热点关闭状态
03-18 20:12:40.374  1006  1006 D TetherEnabler: updateState: 0      

上面日志可以看到设置prop属性后就可以看到 DEBUG 的日志。

上面这个TetherEnabler 的打印是 Settings 的wifi服务正常启动才会进入 TetherEnabler的逻辑,否则不会调用 TetherEnabler 的方法。

有些类设置了prop属性,但是还是没有相关的打印日志,有可能是:

1、prop属性未设置正确
2、等级未设置正确
Log.isLoggable(TAG, Log.VERBOSE) 的等级,prop只能设置成V
3、代码逻辑确实不经过那里

5、在cmd窗口查看手机的Log日志相关命令

//格式1:打印默认日志数据
adb logcat 

//格式2:需要打印日志详细时间的简单数据
adb logcat -v time

//格式3:需要打印级别为Error的信息
adb logcat *:E

//格式4:需要打印时间和级别是Error的信息
adb logcat -v time *:E

//格式5:将日志保存到电脑固定的位置,比如D:\log.txt
adb logcat -v time >D:\log.txt

https://blog.csdn.net/wenzhi20102321/article/details/81058196

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mac:JMeter 下载+安装+环境配置(图文详细讲解)

&#x1f4cc; 下载JMeter 下载地址&#xff1a;https://jmeter.apache.org/download_jmeter.cgi &#x1f4cc; 无需安装 Apache官网下载 JMeter 压缩包&#xff0c;无需安装&#xff0c;下载解压后放到自己指定目录下即可。 按我自己的习惯&#xff0c;我会在用户 jane 目…

Python IP解析器 ip2region使用

说明&#xff1a;最近需要在python项目内使用IP定位所在城市的需求&#xff0c;没有采用向外部ISP服务商API请求获取信息的方案&#xff0c;则翻了翻&#xff0c;在搞Java时很多的方案&#xff0c;在Python端反而可选择范围很小。 # 示例查询 ips ["106.38.188.214"…

labview与西门子1500plc进行S7通讯(仿真效果)

环境&#xff1a; 1.博图V16 2.S7-PLCSIM Advanced V3.0 3.labview2020 4.HslCommunication的dll文件 运行效果图 通过使用HslCommunication的库文件来对西门子plc进行通讯 labview代码 代码打包 通过网盘分享的文件&#xff1a;labview进行s7通讯测试.rar 链接: https:/…

Oracle 公布 Java 的五大新功能

Java 增强提案包括语言增强和性能优化&#xff0c;从 JDK 25 中的稳定值 API 开始。 随着JDK&#xff08;Java 开发工具包&#xff09;24刚刚全面上市&#xff0c;Oracle 提前透露了不久的将来即将推出的 Java 功能&#xff0c;包括增强原始装箱到空限制值类类型。 3 月 18 日…

台式机电脑组装---电脑机箱与主板接线

台式机电脑组装—电脑机箱与主板接线 1、机箱连接主板的跳线一般主要有USB 2.0、USB 3.0、前置音频接口(HD_AUDIO)以及POWER SW、RESET SW、POWER LED、HDD LED四个主板跳线&#xff0c;这些跳线分别的含义如下。 RESET SW&#xff1a;机箱重启按键&#xff1b;注&#xff1a…

动作捕捉手套如何让虚拟现实人机交互 “触手可及”?

在虚拟与现实逐渐交融的当下&#xff0c;动作捕捉技术正以前所未有的速度革新着多个领域。 动作捕捉技术&#xff0c;简称“动捕”&#xff0c;已经从早期的影视特效制作&#xff0c;逐步拓展到游戏开发、虚拟现实、机器人控制等多个领域。 而mHandPrO数据手套作为这一领域的…

笔记本电脑关不了机是怎么回事 这有解决方法

在快节奏的现代生活中&#xff0c;笔记本电脑已成为我们工作、学习和娱乐的得力助手。在使用电脑的过程中&#xff0c;笔记本电脑突然关不了机了&#xff0c;怎么回事&#xff1f;下面驱动人生就来讲一讲笔记本电脑不能正常关机的解决方法&#xff0c;有需要的可以来看看。 一、…

给管理商场消防安全搭建消防安全培训小程序全过程

一、需求沟通 “我是管理商场消防安全的嘛&#xff0c;做这个的作用呢&#xff0c;1是商场的所有商户员工可以看平面或者视频随时自学&#xff0c; 2是我们定期培训必修课程、考试&#xff0c;这个需要留存他们的手签字的签到表确认我们讲给他们听了&#xff08;免责很重要&am…

Flutter:页面滚动,导航栏背景颜色过渡动画

记录&#xff1a;导航默认透明&#xff0c;页面发生滚动后&#xff0c;导航背景色由0-1&#xff0c;过渡到白色背景。 view import package:ducafe_ui_core/ducafe_ui_core.dart; import package:flutter/material.dart; import package:get/get.dart; import package:redo…

VSCode + CMake

参考文献&#xff1a; 如何用 GCC, CMake 和 Make 编译C/C代码Windows 上的 Linux 子系统&#xff1a;WSLWSL&#xff1a;桌面 UI 远程连接 RDP 配置 VScode 文章目录 CMake 配置VSCode 配置launch.jsontask.jsonc_cpp_properties.json CMake 配置 编写如下的 CmakeLists.t…

Docker进阶篇1:什么是Docker数据卷?为什么需要Docker数据卷?Docker数据卷3种类型介绍

大家好我是木木&#xff0c;在当今快速发展的云计算与云原生时代&#xff0c;容器化技术蓬勃兴起&#xff0c;Docker 作为实现容器化的主流工具之一&#xff0c;为开发者和运维人员带来了极大的便捷 。下面我们一起开始进阶第1篇&#xff1a;什么是Docker数据卷&#xff1f;为什…

(2025|ICLR|华南理工,任务对齐,缓解灾难性遗忘,底层模型冻结和训练早停)语言模型持续学习中的虚假遗忘

Spurious Forgetting in Continual Learning of Language Models 目录 1. 引言 2. 动机&#xff1a;关于虚假遗忘的初步实验 3. 深入探讨虚假遗忘 3.1 受控实验设置 3.2 从性能角度分析 3.3 从损失景观角度分析 3.4 从模型权重角度分析 3.5 从特征角度分析 3.6 结论 …

从两指到三指:Robotiq机器人自适应夹持器技术解析

工业自动化离不开高效工具的支持。Robotiq机器人工具凭借其模块化设计和智能化编程技术&#xff0c;提升了设备的灵活性和操作效率。Robotiq机器人工具精准的传感器和自适应夹持器技术&#xff0c;能够满足多样化的应用需求&#xff0c;为制造业、物流和科研等领域提供可靠的解…

【css酷炫效果】纯CSS实现悬浮弹性按钮

【css酷炫效果】纯CSS实现悬浮弹性按钮 缘创作背景html结构css样式完整代码效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492020 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚看到csdn出活动了&…

力扣222. 完全二叉树的节点个数(Java实现)

222. 完全二叉树的节点个数 1. 思路 这个题最简单的做法就是暴力遍历&#xff0c;时间复杂度为O(n)。 我们现在用低于O(n)的做法解决问题。 对于一棵满二叉树&#xff0c;它的节点数 2 h - 1 (h 是指树一共有多少层) 头节点不断遍历左孩子直至为null&#xff0c;得到树高…

element-ui pagination 组件源码分享

pagination 分页组件源码分享&#xff0c;主要从以下三个方面&#xff1a; 1、pagination 组件页面结构。 2、pagination 组件属性。 3、pagination 组件方法。 一、组件页面结构。 二、组件属性。 2.1 small 是否使用小型分页样式&#xff0c;类型为 boolean&#xff0c;…

【css酷炫效果】纯CSS实现火焰文字特效

【css酷炫效果】纯CSS实现火焰文字特效 缘创作背景html结构css样式完整代码基础版进阶版(冰霜版) 效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492005 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚…

【java面型对象进阶】------继承实例

继承结构下的标准Javabean 代码如下&#xff1a; package demo10;//定义员工父类 public class Employee {private String id;private String name;private double salary;//构造方法public Employee(){}public Employee(String id,String name,double salary){this.idid;thi…

【数据分享】1999—2023年地级市固定资产投资和对外经济贸易数据(Shp/Excel格式)

在之前的文章中&#xff0c;我们分享过基于2000-2024年《中国城市统计年鉴》整理的1999-2023年地级市的人口相关数据、染物排放和环境治理相关数据、房地产投资情况和商品房销售面积相关指标数据、社会消费品零售总额和年末金融机构存贷款余额、各类用地面积、地方一般公共预算…

数据结构——串、数组和广义表

串、数组和广义表 1. 串 1.1 串的定义 串(string)是由零个或多个字符组成的有限序列。一般记为 S a 1 a 2 . . . a n ( n ≥ 0 ) Sa_1a_2...a_n(n\geq0) Sa1​a2​...an​(n≥0) 其中&#xff0c;S是串名&#xff0c;单引号括起来的字符序列是串的值&#xff0c; a i a_i a…