精华内容
下载资源
问答
  • 对于mk文件理解

    万次阅读 2018-04-10 21:54:06
    对于今天早上学习到的makefile 文件:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)PRODUCT_COPY_FILES += $(LOCAL_PATH)/+目录和文件比如 就是拷贝文件到:文件原来位置PRODUCT_COPY_FILES += $(LOCAL_PATH)/...
    对于今天早上学习到的makefile 文件:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    PRODUCT_COPY_FILES += $(LOCAL_PATH)/+目录和文件比如 就是拷贝文件到:文件原来位置
    PRODUCT_COPY_FILES += $(LOCAL_PATH)/libs/armeabi/sdkcore.so:system/lib/sdkcore.so

    #================================================

    LOCAL_MODULE_TAGS := optional 制定编译版本 user debug option (所有的版本都编译)等

    LOCAL_CERTIFICATE := platform 是平台的签名

    LOCAL_PROGUARD_ENABLED := disabled 是否混淆

    LOCAL_SRC_FILES := $(call all-java-files-under, src) src的文件路径

    LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res res 的文件路径

    LOCAL_PACKAGE_NAME :=so 文件的名字

    # 所引用的java library 这个时候会去找其中的关联文件,如果关联文件存在就正常关联
    #不存在的话就报错,这个时候如果下面有定义就可以使用,没有定义并且是去找,但是如果是tests那#么就报错。

    一般应该提前编译 java library 忘记是检测之后编译还是没有编译的话就报错了。

    LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 \
    #区别于不同的包的不同的引用,如果这个是_What_FOR_ANDROID_L那么使用 org.apache.http.legacy这个文件
    ifneq ($(strip $(What_FOR_ANDROID_L)), yes)
    LOCAL_JAVA_LIBRARIES += org.apache.http.legacy
    endif
    #是否使用Jack  
    LOCAL_JACK_ENABLED := disabled

    #LOCAL_REQUIRED_MODULES := coreSDK
    LOCAL_REQUIRED_MODULES的作用当编译整个android源码时,如果这个模块在编译路径中
    则会自动编译coreSDK,并且打包到system.img,如果不是编译整个源码,只是mm。则不会编译coreSDK直接使用生成相应的文件。 所以这个时候可能需要去使用mma 去编译。


    #LOCAL_JNI_SHARED_LIBRARIES := coreSDk
    变量主要是用在JNI的编译中,如果你要在你的Java代码中引用JNI中的共享库*.so,此变量就是共享库的名字。

    include $(BUILD_PACKAGE)
    #这个时候表示所有的需要的链接都一定说明完整

    include $(CLEAR_VARS)

    LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=coreSDK:/libs/coreSDK.jar \
    #LOCAL_REQUIRED_MODULES := coreSDK 和这个对应 只有在LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES 定义之后才可以使用
    include $(BUILD_MULTI_PREBUILT)# 编译多个 prebuilt
    #prebuilt.mk就是prebuilt的具体实现,它是针对独立一个文件的操作,multi_prebuilt.mk 可以针对###个文件的,它对多个文件进行判断,然后调用prebuilt对独立一个文件进行处理。
    #预编译so ,jar 文件


    include $(CLEAR_VARS)
    LOCAL_MODULE := libweibosdkcore.so
    LOCAL_MODULE_CLASS := SHARED_LIBRARIES
    LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)
    #用这二个确定最后所在的文件列表的目录


    LOCAL_SRC_FILES := libs/arm64-v8a/sdkcore.so
    #所在的文件目录
    include $(BUILD_PREBUILT)

    #====================================================

    #====================================================
    对于so文件的定义等。
    #include $(CLEAR_VARS)
    #LOCAL_MODULE := sdkcore.so
    #LOCAL_MODULE_CLASS := SHARED_LIBRARIES
    #LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)
    #LOCAL_SRC_FILES := libs/armeabi-v7a/sdkcore.so
    #include $(BUILD_PREBUILT)

    include $(call all-makefiles-under,$(LOCAL_PATH))
    #去call 了一下列表下面的所有文件
    define all-makefiles-under
    $(wildcard $(1)/*/Android.mk)
    endef

    1.只include Android.mk文件,叫其他名字的mk文件,不include.
    2.只include这个$(LOCAL_PATH)一级目录下的Android.mk文件,而不是所有子目录以及子目录下的Android.mk文件
    应该是为了保证目录下面的mk文件都可以被识别到然后可以编译。

    $(shell $(LOCAL_PATH)/distribute.sh)
    最后的结束语,不明所以

    首先,这个mk文件应该分成3个部分。第一个部分分成,LOCAL_PATH := $(call my-dir)---include $(BUILD_PACKAGE) 第二个部分为 include $(CLEAR_VARS)---include $(BUILD_MULTI_PREBUILT) 第三个部分为:include $(CLEAR_VARS)---include $(BUILD_PREBUILT)。
    对于第一个部分来说,这个过程定义了编译的各个属性,和相关的依赖关系:
    include $(CLEAR_VARS) :清除所所设置的属性,避免上一次编译的属性使用到这一次的编译过程中。
    LOCAL_MODULE_TAGS := optional
    LOCAL_MODULE_TAGS :=user eng tests optional
    user: 指该模块只在user版本下才编译
    eng: 指该模块只在eng版本下才编译
    tests: 指该模块只在tests版本下才编译
    optional:指该模块在所有版本下都编译

    LOCAL_CERTIFICATE := platform
    这个功能和 android:sharedUserId和LOCAL_CERTIFICATE 相关。
    都是用系统的签名来对apk 进行签名。其中签名的文件是:build\target\product\security",下面的platform.pk8和platform.x509.pem两个文件
    签名的工具是:
    Android提供的Signapk工具来签名,signapk的源代码是在"build\tools\signapk"下,用法为"signapk platform.x509.pem platform.pk8 input.apk output.apk"
    其中通过 android:sharedUserId 也已可应用共享同一个进程,但是如果签名不一样是没有办法共享的,其中签名问题还设计到很多,之后可以补充。
    其中这个属性还可以有其他的赋值比如:
    LOCAL_CERTIFICATE := PRESIGNED 表示 这个apk已经签过名了,系统不需要再次 签名;
    LOCAL_CERTIFICATE := platform 表示为系统签名
     LOCAL_CERTIFICATE := media
      用于指定签名时使用的KEY,如果不指定,默认使用testkey,LOCAL_CERTIFICATE可设置的值如下:
        LOCAL_CERTIFICATE := platform 该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system
        LOCAL_CERTIFICATE := shared 该APK需要和home/contacts进程共享数据。
        LOCAL_CERTIFICATE := media 该APK是media/download系统中的一环。
     LOCAL_CERTIFICATE := testkey 普通APK,默认情况下使用。、
        对于使用eclipse编译的apk,其实 是用testkey 来签的名;我们也可以用signapk.jar来手动进行签名,其源码在build/tools/signapk下,编译后在out/host/linux-x86/framework/signapk.jar,也可以从网上下载。使用方法,例如:把签名修改为platformJava -jar ./signapk platform.x509.pem platform.pk8 input.apk output.apk  (platform.x509.pem platform.pk8在build/target/product/security获取)。build/target/product/security获取)。

    LOCAL_PROGUARD_ENABLED := disabled
    是否混淆
    LOCAL_SRC_FILES := $(call all-java-files-under, src)
    确定一个src文件
    LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res
    定义一个res文件
    LOCAL_PACKAGE_NAME := name
    给这个package 确定name


    展开全文
  • 从一个class文件深入理解Java字节码结构

    万次阅读 多人点赞 2018-05-15 10:01:56
    我们都知道,Java程序最终是转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容呢,这篇文章带你深入理解Java字节码中的结构。 1.Demo源码 首先,...

    前言

    我们都知道,Java程序最终是转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容呢,这篇文章带你深入理解Java字节码中的结构。

    1.Demo源码

    首先,编写一个简单的Java源码:

    package com.april.test;
    
    public class Demo {
        private int num = 1;
    
        public int add() {
            num = num + 2;
            return num;
        }
    }
    

    这段代码很简单,只有一个成员变量num和一个方法add()

    2.字节码

    要运行一段Java源码,必须先将源码转换为class文件,class文件就是编译器编译之后供虚拟机解释执行的二进制字节码文件,可以通过IDE工具或者命令行去将源码编译成class文件。这里我们使用命令行去操作,运行下面命令:

    javac Demo.java
    

    就会生成一个Demo.class文件。

    我们打开这个Demo.class文件看下。这里用到的是Notepad++,需要安装一个HEX-Editor插件。
    1.字节码-完整版.png

    3.class文件反编译java文件

    在分析class文件之前,我们先来看下将这个Demo.class反编译回Demo.java的结果,如下图所示:
    源码与class转java对比.png
    可以看到,回编译的源码比编写的代码多了一个空的构造函数this关键字,为什么呢?先放下这个疑问,看完这篇分析,相信你就知道答案了。

    4.字节码结构

    从上面的字节码文件中我们可以看到,里面就是一堆的16进制字节。那么该如何解读呢?别急,我们先来看一张表:

    类型 名称 说明 长度
    u4 magic 魔数,识别Class文件格式 4个字节
    u2 minor_version 副版本号 2个字节
    u2 major_version 主版本号 2个字节
    u2 constant_pool_count 常量池计算器 2个字节
    cp_info constant_pool 常量池 n个字节
    u2 access_flags 访问标志 2个字节
    u2 this_class 类索引 2个字节
    u2 super_class 父类索引 2个字节
    u2 interfaces_count 接口计数器 2个字节
    u2 interfaces 接口索引集合 2个字节
    u2 fields_count 字段个数 2个字节
    field_info fields 字段集合 n个字节
    u2 methods_count 方法计数器 2个字节
    method_info methods 方法集合 n个字节
    u2 attributes_count 附加属性计数器 2个字节
    attribute_info attributes 附加属性集合 n个字节

    这是一张Java字节码总的结构表,我们按照上面的顺序逐一进行解读就可以了。

    首先,我们来说明一下:class文件只有两种数据类型:无符号数。如下表所示:

    数据类型 定义 说明
    无符号数 无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值。 其中无符号数属于基本的数据类型。
    以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节
    表是由多个无符号数或其他表构成的复合数据结构。 所有的表都以“_info”结尾。
    由于表没有固定长度,所以通常会在其前面加上个数说明。

    实际上整个class文件就是一张表,其结构就是上面的表一了。

    那么我们现在再来看表一中的类型那一列,也就很简单了:

    类型 说明 长度
    u1 1个字节 1
    u2 2个字节 2
    u4 4个字节 4
    u8 8个字节 8
    cp_info 常量表 n
    field_info 字段表 n
    method_info 方法表 n
    attribute_info 属性表 n

    上面各种具体的表的数据结构后面会详细说明,这里暂且不表。

    好了,现在我们开始对那一堆的16进制进行解读。
    b.jpg

    4.1 魔数

    从上面的总的结构图中可以看到,开头的4个字节表示的是魔数,其值为:
    2.字节码-魔数.png
    嗯,其值为0XCAFE BABE。CAFE BABE??What the fxxk?
    好了,那么什么是魔数呢?魔数就是用来区分文件类型的一种标志,一般都是用文件的前几个字节来表示。比如0XCAFE BABE表示的是class文件,那么为什么不是用文件名后缀来进行判断呢?因为文件名后缀容易被修改啊,所以为了保证文件的安全性,将文件类型写在文件内部可以保证不被篡改。
    再来说说为什么class文件用的是CAFE BABE呢,看到这个大概你就懂了。
    java.jpg

    4.2 版本号

    紧跟着魔数后面的4位就是版本号了,同样也是4个字节,其中前2个字节表示副版本号,后2个字节
    表示主版本号。再来看看我们Demo字节码中的值:
    3.字节码-版本号.png
    前面两个字节是0x0000,也就是其值为0;
    后面两个字节是0x0034,也就是其值为52.
    所以上面的代码就是52.0版本来编译的,也就是jdk1.8.0

    4.3 常量池

    4.3.1 常量池容量计数器

    接下来就是常量池了。由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值。Demo的值为:
    4.字节码-常量池容量计数值.png
    其值为0x0013,掐指一算,也就是19。
    需要注意的是,这实际上只有18项常量。为什么呢?

    通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了。这是为了在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。

    Class文件中只有常量池的容量计数是从1开始的,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。

    4.3.2 字面量和符号引用

    在对这些常量解读前,我们需要搞清楚几个概念。
    常量池主要存放两大类常量:字面量符号引用。如下表:

    常量 具体的常量
    字面量 文本字符串
    声明为final的常量值
    符号引用 类和接口的全限定名
    字段的名称和描述符
    方法的名称和描述符

    4.3.2.1 全限定名

    com/april/test/Demo这个就是类的全限定名,仅仅是把包名的".“替换成”/",为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。

    4.3.2.2 简单名称

    简单名称是指没有类型和参数修饰的方法或者字段名称,上面例子中的类的add()方法和num字段的简单名称分别是addnum

    4.3.2.3 描述符

    描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表:

    标志符 含义
    B 基本数据类型byte
    C 基本数据类型char
    D 基本数据类型double
    F 基本数据类型float
    I 基本数据类型int
    J 基本数据类型long
    S 基本数据类型short
    Z 基本数据类型boolean
    V 基本数据类型void
    L 对象类型,如Ljava/lang/Object

    对于数组类型,每一维度将使用一个前置的[字符来描述,如一个定义为java.lang.String[][]类型的二维数组,将被记录为:[[Ljava/lang/String;,,一个整型数组int[]被记录为[I

    用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“( )”之内。如方法java.lang.String toString()的描述符为( ) LJava/lang/String;,方法int abc(int[] x, int y)的描述符为([II) I

    4.3.3 常量类型和结构

    常量池中的每一项都是一个表,其项目类型共有14种,如下表格所示:

    类型 标志 描述
    CONSTANT_utf8_info 1 UTF-8编码的字符串
    CONSTANT_Integer_info 3 整形字面量
    CONSTANT_Float_info 4 浮点型字面量
    CONSTANT_Long_info 5 长整型字面量
    CONSTANT_Double_info 6 双精度浮点型字面量
    CONSTANT_Class_info 7 类或接口的符号引用
    CONSTANT_String_info 8 字符串类型字面量
    CONSTANT_Fieldref_info 9 字段的符号引用
    CONSTANT_Methodref_info 10 类中方法的符号引用
    CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
    CONSTANT_NameAndType_info 12 字段或方法的符号引用
    CONSTANT_MethodHandle_info 15 表示方法句柄
    CONSTANT_MothodType_info 16 标志方法类型
    CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

    这14种类型的结构各不相同,如下表格所示:
    常量池中常量项的结构总表.png

    注:此表格的类型的单位不对,不是bit,应该是byte(字节)。后面的同理。

    从上面的表格可以看到,虽然每一项的结构都各不相同,但是他们有个共同点,就是每一项的第一个字节都是一个标志位,标识这一项是哪种类型的常量。

    4.3.4 常量解读

    好了,我们进入这18项常量的解读,首先是第一个常量,看下它的标志位是啥:
    5.字节码-第一个常量的标志位.png
    其值为0x0a,即10,查上面的表格可知,其对应的项目类型为CONSTANT_Methodref_info,即类中方法的符号引用。其结构为:
    CONSTANT_Methodref_info的结构.png
    即后面4个字节都是它的内容,分别为两个索引项:
    6.字节码-第一个常量的项目.png
    其中前两位的值为0x0004,即4,指向常量池第4项的索引;
    后两位的值为0x000f,即15,指向常量池第15项的索引。
    至此,第一个常量就解读完毕了。
    我们再来看下第二个常量:
    7.字节码-第二个常量.png
    其标志位的值为0x09,即9,查上面的表格可知,其对应的项目类型为CONSTANT_Fieldref_info,即字段的符号引用。其结构为:
    CONSTANT_Fieldref_info的结构.png
    同样也是4个字节,前后都是两个索引。分别指向第3项的索引和第16项的索引。

    后面还有16项常量就不一一去解读了,因为整个常量池还是挺长的:
    8.字节码-所有常量.png

    你看,这么长的一大段16进制,看的我都快瞎了:
    你说什么,我没带眼镜听不清.jpg

    实际上,我们只要敲一行简单的命令:

    javap -verbose Demo.class
    

    其中部分的输出结果为:

    Constant pool:
       #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#16         // com/april/test/Demo.num:I
       #3 = Class              #17            // com/april/test/Demo
       #4 = Class              #18            // java/lang/Object
       #5 = Utf8               num
       #6 = Utf8               I
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               add
      #12 = Utf8               ()I
      #13 = Utf8               SourceFile
      #14 = Utf8               Demo.java
      #15 = NameAndType        #7:#8          // "<init>":()V
      #16 = NameAndType        #5:#6          // num:I
      #17 = Utf8               com/april/test/Demo
      #18 = Utf8               java/lang/Object
    

    你看,一家大小,齐齐整整,全都出来了。
    但是,通过我们手动去分析才知道这个结果是怎么出来的,要知其然知其所以然嘛~

    4.4 访问标志

    常量池后面就是访问标志,用两个字节来表示,其标识了类或者接口的访问信息,比如:该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final等等。各种访问标志如下所示:

    标志名称 标志值 含义
    ACC_PUBLIC 0x0001 是否为Public类型
    ACC_FINAL 0x0010 是否被声明为final,只有类可以设置
    ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真
    ACC_INTERFACE 0x0200 标志这是一个接口
    ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
    ACC_SYNTHETIC 0x1000 标志这个类并非由用户代码产生
    ACC_ANNOTATION 0x2000 标志这是一个注解
    ACC_ENUM x4000 标志这是一个枚举

    再来看下我们Demo字节码中的值:
    9.字节码-访问标志.png
    其值为:0x0021,是0x00200x0001的并集,即这是一个Public的类,再回头看看我们的源码。
    确认过眼神,我遇上对的了。

    4.5 类索引、父类索引、接口索引

    访问标志后的两个字节就是类索引;
    类索引后的两个字节就是父类索引;
    父类索引后的两个字节则是接口索引计数器。
    通过这三项,就可以确定了这个类的继承关系了。

    4.5.1 类索引

    我们直接来看下Demo字节码中的值:
    10.字节码-类索引.png
    类索引的值为0x0003,即为指向常量池中第三项的索引。你看,这里用到了常量池中的值了。
    我们回头翻翻常量池中的第三项:

       #3 = Class              #17            // com/april/test/Demo
    

    通过类索引我们可以确定到类的全限定名。

    4.5.2 父类索引

    从上图看到,父类索引的值为0x0004,即常量池中的第四项:

       #4 = Class              #18            // java/lang/Object
    

    这样我们就可以确定到父类的全限定名。
    可以看到,如果我们没有继承任何类,其默认继承的是java/lang/Object类。
    同时,由于Java不支持多继承,所以其父类只有一个。

    4.5.3 接口计数器

    从上图看到,接口索引个数的值为0x0000,即没有任何接口索引,我们demo的源码也确实没有去实现任何接口。

    4.5.4 接口索引集合

    由于我们demo的源码没有去实现任何接口,所以接口索引集合就为空了,不占地方,嘻嘻。
    可以看到,由于Java支持多接口,因此这里设计成了接口计数器和接口索引集合来实现。

    4.6 字段表

    接口计数器或接口索引集合后面就是字段表了。
    字段表用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。

    4.6.1 字段表计数器

    同样,其前面两个字节用来表示字段表的容量,看下demo字节码中的值:
    11.字节码-字段表容量计数器.png
    其值为0x0001,表示只有一个字段。

    4.6.2 字段表访问标志

    我们知道,一个字段可以被各种关键字去修饰,比如:作用域修饰符(public、private、protected)、static修饰符、final修饰符、volatile修饰符等等。因此,其可像类的访问标志那样,使用一些标志来标记字段。字段的访问标志有如下这些:

    标志名称 标志值 含义
    ACC_PUBLIC 0x0001 字段是否为public
    ACC_PRIVATE 0x0002 字段是否为private
    ACC_PROTECTED 0x0004 字段是否为protected
    ACC_STATIC 0x0008 字段是否为static
    ACC_FINAL 0x0010 字段是否为final
    ACC_VOLATILE 0x0040 字段是否为volatile
    ACC_TRANSTENT 0x0080 字段是否为transient
    ACC_SYNCHETIC 0x1000 字段是否为由编译器自动产生
    ACC_ENUM 0x4000 字段是否为enum

    4.6.3 字段表结构

    字段表作为一个表,同样有他自己的结构:

    类型 名称 含义 数量
    u2 access_flags 访问标志 1
    u2 name_index 字段名索引 1
    u2 descriptor_index 描述符索引 1
    u2 attributes_count 属性计数器 1
    attribute_info attributes 属性集合 attributes_count

    4.6.4 字段表解读

    我们先来回顾一下我们demo源码中的字段:

        private int num = 1;
    

    由于只有一个字段,还是比较简单的,直接看demo字节码中的值:
    12.字节码-字段表.png
    访问标志的值为0x0002,查询上面字段访问标志的表格,可得字段为private
    字段名索引的值为0x0005,查询常量池中的第5项,可得:

       #5 = Utf8               num
    

    描述符索引的值为0x0006,查询常量池中的第6项,可得:

       #6 = Utf8               I
    

    属性计数器的值为0x0000,即没有任何的属性。

    确认过眼神,我遇上对的了。

    至此,字段表解读完成。

    4.6.5 注意事项

    1. 字段表集合中不会列出从父类或者父接口中继承而来的字段。
    2. 内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
    3. 在Java语言中字段是无法重载的,两个字段的数据类型,修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的.

    4.7 方法表

    字段表后就是方法表了。

    4.7.1 方法表计数器

    前面两个字节依然用来表示方法表的容量,看下demo字节码中的值:
    13.字节码-方法表容量计数器.png
    其值为0x0002,即有2个方法。

    4.7.2 方法表访问标志

    跟字段表一样,方法表也有访问标志,而且他们的标志有部分相同,部分则不同,方法表的具体访问标志如下:

    标志名称 标志值 含义
    ACC_PUBLIC 0x0001 方法是否为public
    ACC_PRIVATE 0x0002 方法是否为private
    ACC_PROTECTED 0x0004 方法是否为protected
    ACC_STATIC 0x0008 方法是否为static
    ACC_FINAL 0x0010 方法是否为final
    ACC_SYHCHRONRIZED 0x0020 方法是否为synchronized
    ACC_BRIDGE 0x0040 方法是否是有编译器产生的方法
    ACC_VARARGS 0x0080 方法是否接受参数
    ACC_NATIVE 0x0100 方法是否为native
    ACC_ABSTRACT 0x0400 方法是否为abstract
    ACC_STRICTFP 0x0800 方法是否为strictfp
    ACC_SYNTHETIC 0x1000 方法是否是有编译器自动产生的

    4.7.3 方法表结构

    方法表的结构实际跟字段表是一样的,方法表结构如下:

    类型 名称 含义 数量
    u2 access_flags 访问标志 1
    u2 name_index 方法名索引 1
    u2 descriptor_index 描述符索引 1
    u2 attributes_count 属性计数器 1
    attribute_info attributes 属性集合 attributes_count

    4.7.4 属性解读

    还是先回顾一下Demo中的源码:

        public int add() {
            num = num + 2;
            return num;
        }
    

    只有一个自定义的方法。但是上面方法表计数器明明是2个,这是为啥呢?
    这是因为它包含了默认的构造方法,我们来看下下面的分析就懂了,先看下Demo字节码中的值:
    14.字节码-方法表1.png
    这是第一个方法表,我们来解读一下这里面的16进制:
    访问标志的值为0x0001,查询上面字段访问标志的表格,可得字段为public;

    方法名索引的值为0x0007,查询常量池中的第7项,可得:

       #7 = Utf8               <init>
    

    这个名为<init>的方法实际上就是默认的构造方法了。

    描述符索引的值为0x0008,查询常量池中的第8项,可得:

       #8 = Utf8               ()V
    

    注:描述符不熟悉的话可以回头看看4.3.2.3的内容。

    属性计数器的值为0x0001,即这个方法表有一个属性。
    属性计数器后面就是属性表了,由于只有一个属性,所以这里也只有一个属性表。
    由于涉及到属性表,这里简单说下,下一节会详细介绍。
    属性表的前两个字节是属性名称索引,这里的值为0x0009,查下常量池中的第9项:

       #9 = Utf8               Code
    

    即这是一个Code属性,我们方法里面的代码就是存放在这个Code属性里面。相关细节暂且不表。下一节会详细介绍Code属性。

    先跳过属性表,我们再来看下第二个方法:
    16.字节码-方法表2.png
    访问标志的值为0x0001,查询上面字段访问标志的表格,可得字段为public;

    方法名索引的值为0x000b,查询常量池中的第11项,可得:

      #11 = Utf8               add
    

    描述符索引的值为0x000c,查询常量池中的第12项,可得:

      #12 = Utf8               ()I
    

    属性计数器的值为0x0001,即这个方法表有一个属性。
    属性名称索引的值同样也是0x0009,即这是一个Code属性。
    可以看到,第二个方法表就是我们自定义的add()方法了。

    4.7.5 注意事项

    1. 如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现父类的方法。
    2. 编译器可能会自动添加方法,最典型的便是类构造方法(静态构造方法)<client>方法和默认实例构造方法<init>方法。
    3. 在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中。

    4.8 属性表

    前面说到了属性表,现在来重点看下。属性表不仅在方法表有用到,字段表和Class文件中也会用得到。本篇文章中用到的例子在字段表中的属性个数为0,所以也没涉及到;在方法表中用到了2次,都是Code属性;至于Class文件,在末尾时会讲到,这里就先不说了。

    4.8.1 属性类型

    属性表实际上可以有很多类型,上面看到的Code属性只是其中一种,下面这些是虚拟机中预定义的属性:

    属性名称 使用位置 含义
    Code 方法表 Java代码编译成的字节码指令
    ConstantValue 字段表 final关键字定义的常量池
    Deprecated 类,方法,字段表 被声明为deprecated的方法和字段
    Exceptions 方法表 方法抛出的异常
    EnclosingMethod 类文件 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
    InnerClass 类文件 内部类列表
    LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
    LocalVariableTable Code属性 方法的局部变量描述
    StackMapTable Code属性 JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
    Signature 类,方法表,字段表 用于支持泛型情况下的方法签名
    SourceFile 类文件 记录源文件名称
    SourceDebugExtension 类文件 用于存储额外的调试信息
    Synthetic 类,方法表,字段表 标志方法或字段为编译器自动生成的
    LocalVariableTypeTable 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
    RuntimeVisibleAnnotations 类,方法表,字段表 为动态注解提供支持
    RuntimeInvisibleAnnotations 表,方法表,字段表 用于指明哪些注解是运行时不可见的
    RuntimeVisibleParameterAnnotation 方法表 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
    RuntimeInvisibleParameterAnnotation 方法表 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
    AnnotationDefault 方法表 用于记录注解类元素的默认值
    BootstrapMethods 类文件 用于保存invokeddynamic指令引用的引导方式限定符

    4.8.2 属性表结构

    属性表的结构比较灵活,各种不同的属性只要满足以下结构即可:

    类型 名称 数量 含义
    u2 attribute_name_index 1 属性名索引
    u2 attribute_length 1 属性长度
    u1 info attribute_length 属性表

    即只需说明属性的名称以及占用位数的长度即可,属性表具体的结构可以去自定义

    4.8.3 部分属性详解

    下面针对部分常见的一些属性进行详解

    4.8.3.1 Code属性

    前面我们看到的属性表都是Code属性,我们这里重点来看下。
    Code属性就是存放方法体里面的代码,像接口或者抽象方法,他们没有具体的方法体,因此也就不会有Code属性了。

    4.8.3.1.1 Code属性表结构

    先来看下Code属性表的结构,如下图:

    类型 名称 数量 含义
    u2 attribute_name_index 1 属性名索引
    u4 attribute_length 1 属性长度
    u2 max_stack 1 操作数栈深度的最大值
    u2 max_locals 1 局部变量表所需的存续空间
    u4 code_length 1 字节码指令的长度
    u1 code code_length 存储字节码指令
    u2 exception_table_length 1 异常表长度
    exception_info exception_table exception_length 异常表
    u2 attributes_count 1 属性集合计数器
    attribute_info attributes attributes_count 属性集合

    可以看到:Code属性表的前两项跟属性表是一致的,即Code属性表遵循属性表的结构,后面那些则是他自定义的结构。

    4.8.3.1.2 Code属性解读

    同样,解读Code属性只需按照上面的表格逐一解读即可。
    我们先来看下第一个方法表中的Code属性:
    15.字节码-Code属性表1.png
    属性名索引的值为0x0009,上面也说过了,这是一个Code属性;
    属性长度的值为0x00000026,即长度为38,注意,这里的长度是指后面自定义的属性长度,不包括属性名索引和属性长度这两个所占的长度,因为这哥俩占的长度都是固定6个字节了,所以往后38个字节都是Code属性的内容;
    max_stack的值为0x0002,即操作数栈深度的最大值为2;
    max_locals的值为0x0001,即局部变量表所需的存储空间为1;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位。
    code_length的值为0x00000000a,即字节码指令的10;
    code的值为0x2a b7 00 01 2a 04 b5 00 02 b1,这里的值就代表一系列的字节码指令。一个字节代表一个指令,一个指令可能有参数也可能没参数,如果有参数,则其后面字节码就是他的参数;如果没参数,后面的字节码就是下一条指令。
    这里我们来解读一下这些指令,文末最后的附录附有Java虚拟机字节码指令表,可以通过指令表来查询指令的含义。

    1. 2a 指令,查表可得指令为aload_0,其含义为:将第0个Slot中为reference类型的本地变量推送到操作数栈顶。
    2. b7 指令,查表可得指令为invokespecial,其含义为:将操作数栈顶的reference类型的数据所指向的对象作为方法接受者,调用此对象的实例构造器方法、private方法或者它的父类的方法。其后面紧跟着的2个字节即指向其具体要调用的方法。
    3. 00 01,指向常量池中的第1项,查询上面的常量池可得:#1 = Methodref #4.#15 // java/lang/Object."<init>":()V 。即这是要调用默认构造方法<init>
    4. 2a 指令,同第1个。
    5. 04 指令,查表可得指令为iconst_1,其含义为:将int型常量值1推送至栈顶。
    6. b5 指令,查表可得指令为putfield,其含义为:为指定的类的实例域赋值。其后的2个字节为要赋值的实例。
    7. 00 02,指向常量池中的第2项,查询上面的常量池可得:#2 = Fieldref #3.#16 // com/april/test/Demo.num:I。即这里要将num这个字段赋值为1。
    8. b1 指令,查表可得指令为return,其含义为:返回此方法,并且返回值为void。这条指令执行完后,当前的方法也就结束了。

    所以,上面的指令简单点来说就是,调用默认的构造方法,并初始化num的值为1。
    同时,可以看到,这些操作都是基于栈来完成的。

    如果要逐字逐字的去查每一个指令的意思,那是相当的麻烦,大概要查到猴年马月吧。实际上,只要一行命令,就能将这样字节码转化为指令了,还是javap命令哈:

     javap -verbose Demo.class 
    

    截取部分输出结果:

       public com.april.test.Demo();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: iconst_1
             6: putfield      #2                  // Field num:I
             9: return
          LineNumberTable:
            line 7: 0
            line 8: 4
    

    看看,那是相当的简单。关于字节码指令,就到此为止了。继续往下看。

    exception_table_length的值为0x0000,即异常表长度为0,所以其异常表也就没有了;

    attributes_count的值为0x0001,即code属性表里面还有一个其他的属性表,后面就是这个其他属性的属性表了;
    所有的属性都遵循属性表的结构,同样,这里的结构也不例外。
    前两个字节为属性名索引,其值为0x000a,查看常量池中的第10项:

      #10 = Utf8               LineNumberTable
    

    即这是一个LineNumberTable属性。LineNumberTable属性先跳过,具体可以看下一小节。

    再来看下第二个方法表中的的Code属性:
    17.字节码-Code属性表2.png
    属性名索引的值同样为0x0009,所以,这也是一个Code属性;
    属性长度的值为0x0000002b,即长度为43;
    max_stack的值为0x0003,即操作数栈深度的最大值为3;
    max_locals的值为0x0001,即局部变量表所需的存储空间为1;
    code_length的值为0x00000000f,即字节码指令的15;
    code的值为0x2a 2a b4 20 02 05 60 b5 20 02 2a b4 20 02 ac,使用javap命令,可得:

      public int add();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: aload_0
             2: getfield      #2                  // Field num:I
             5: iconst_2
             6: iadd
             7: putfield      #2                  // Field num:I
            10: aload_0
            11: getfield      #2                  // Field num:I
            14: ireturn
          LineNumberTable:
            line 11: 0
            line 12: 10
    

    可以看到,这就是我们自定义的add()方法;
    exception_table_length的值为0x0000,即异常表长度为0,所以其异常表也没有;
    attributes_count的值为0x0001,即code属性表里面还有一个其他的属性表;
    属性名索引值为0x000a,即这同样也是一个LineNumberTable属性,LineNumberTable属性看下一小节。

    4.8.3.2 LineNumberTable属性

    LineNumberTable属性是用来描述Java源码行号字节码行号之间的对应关系。

    4.8.3.2.1 LineNumberTable属性表结构
    类型 名称 数量 含义
    u2 attribute_name_index 1 属性名索引
    u4 attribute_length 1 属性长度
    u2 line_number_table_length 1 行号表长度
    line_number_info line_number_table line_number_table_length 行号表

    line_number_info(行号表),其长度为4个字节,前两个为start_pc,即字节码行号;后两个为line_number,即Java源代码行号

    4.8.3.2.2 LineNumberTable属性解读

    前面出现了两个LineNumberTable属性,先看第一个:
    18.字节码-LineNumberTable属性1.png
    attributes_count的值为0x0001,即code属性表里面还有一个其他的属性表;
    属性名索引值为0x000a,查看常量池中的第10项:

      #10 = Utf8               LineNumberTable
    

    即这是一个LineNumberTable属性。
    attribute_length的值为0x00 00 00 0a,即其长度为10,后面10个字节的都是LineNumberTable属性的内容;
    line_number_table_length的值为0x0002,即其行号表长度长度为2,即有两个行号表;
    第一个行号表其值为0x00 00 00 07,即字节码第0行对应Java源码第7行;
    第二个行号表其值为0x00 04 00 08,即字节码第4行对应Java源码第8行。

    同样,使用javap命令也能看到:

          LineNumberTable:
            line 7: 0
            line 8: 4
    

    第二个LineNumberTable属性为:
    19.字节码-LineNumberTable属性2.png
    这里就不逐一看了,同样使用javap命令可得:

          LineNumberTable:
            line 11: 0
            line 12: 10
    

    所以这些行号是有什么用呢?当程序抛出异常时,我们就可以看到报错的行号了,这利于我们debug;使用断点时,也是根据源码的行号来设置的。

    4.8.3.2 SourceFile属性

    前面将常量池、字段集合、方法集合等都解读完了。最终剩下的就是一些附加属性了。
    先来看看剩余还未解读的字节码:
    18.字节码-附加属性.png
    同样,前面2个字节表示附加属性计算器,其值为0x0001,即还有一个附加属性。

    最后这一个属性就是SourceFile属性,即源码文件属性。
    先来看看其结构:

    4.8.3.2.1 SourceFile属性结构
    类型 名称 数量 含义
    u2 attribute_name_index 1 属性名索引
    u4 attribute_length 1 属性长度
    u2 sourcefile_index 1 源码文件索引

    可以看到,其长度总是固定的8个字节。

    4.8.3.2.2 SourceFile属性解读

    属性名索引的值为0x000d,即常量池中的第13项,查询可得:

      #13 = Utf8               SourceFile
    

    属性长度的值为0x00 00 00 02,即长度为2;
    源码文件索引的值为0x000e,即常量池中的第14项,查询可得:

     #14 = Utf8               Demo.java
    

    所以,我们能够从这里知道,这个Class文件的源码文件名称为Demo.java。同样,当抛出异常时,可以通过这个属性定位到报错的文件。

    至此,上面的字节码就完全解读完毕了。

    4.8.4 其他属性

    Java虚拟机中预定义的属性有20多个,这里就不一一介绍了,通过上面几个属性的介绍,只要领会其精髓,其他属性的解读也是易如反掌。

    5.总结

    通过手动去解读字节码文件,终于大概了解到其构成和原理了。断断续续写了比较长的时间,终于写完了,撒花~

    实际上,我们可以使用各种工具来帮我们去解读字节码文件,而不用直接去看这些16进制,神烦啊,哈哈。溜了溜了。

    溜了.gif

    6. 附录

    6.1 Java虚拟机字节码指令表

    字节码 助记符 指令含义
    0x00 nop 什么都不做
    0x01 aconst_null 将null推送至栈顶
    0x02 iconst_m1 将int型-1推送至栈顶
    0x03 iconst_0 将int型0推送至栈顶
    0x04 iconst_1 将int型1推送至栈顶
    0x05 iconst_2 将int型2推送至栈顶
    0x06 iconst_3 将int型3推送至栈顶
    0x07 iconst_4 将int型4推送至栈顶
    0x08 iconst_5 将int型5推送至栈顶
    0x09 lconst_0 将long型0推送至栈顶
    0x0a lconst_1 将long型1推送至栈顶
    0x0b fconst_0 将float型0推送至栈顶
    0x0c fconst_1 将float型1推送至栈顶
    0x0d fconst_2 将float型2推送至栈顶
    0x0e dconst_0 将do le型0推送至栈顶
    0x0f dconst_1 将do le型1推送至栈顶
    0x10 bipush 将单字节的常量值(-128~127)推送至栈顶
    0x11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶
    0x12 ldc 将int, float或String型常量值从常量池中推送至栈顶
    0x13 ldc_w 将int, float或String型常量值从常量池中推送至栈顶(宽索引)
    0x14 ldc2_w 将long或do le型常量值从常量池中推送至栈顶(宽索引)
    0x15 iload 将指定的int型本地变量
    0x16 lload 将指定的long型本地变量
    0x17 fload 将指定的float型本地变量
    0x18 dload 将指定的do le型本地变量
    0x19 aload 将指定的引用类型本地变量
    0x1a iload_0 将第一个int型本地变量
    0x1b iload_1 将第二个int型本地变量
    0x1c iload_2 将第三个int型本地变量
    0x1d iload_3 将第四个int型本地变量
    0x1e lload_0 将第一个long型本地变量
    0x1f lload_1 将第二个long型本地变量
    0x20 lload_2 将第三个long型本地变量
    0x21 lload_3 将第四个long型本地变量
    0x22 fload_0 将第一个float型本地变量
    0x23 fload_1 将第二个float型本地变量
    0x24 fload_2 将第三个float型本地变量
    0x25 fload_3 将第四个float型本地变量
    0x26 dload_0 将第一个do le型本地变量
    0x27 dload_1 将第二个do le型本地变量
    0x28 dload_2 将第三个do le型本地变量
    0x29 dload_3 将第四个do le型本地变量
    0x2a aload_0 将第一个引用类型本地变量
    0x2b aload_1 将第二个引用类型本地变量
    0x2c aload_2 将第三个引用类型本地变量
    0x2d aload_3 将第四个引用类型本地变量
    0x2e iaload 将int型数组指定索引的值推送至栈顶
    0x2f laload 将long型数组指定索引的值推送至栈顶
    0x30 faload 将float型数组指定索引的值推送至栈顶
    0x31 daload 将do le型数组指定索引的值推送至栈顶
    0x32 aaload 将引用型数组指定索引的值推送至栈顶
    0x33 baload 将boolean或byte型数组指定索引的值推送至栈顶
    0x34 caload 将char型数组指定索引的值推送至栈顶
    0x35 saload 将short型数组指定索引的值推送至栈顶
    0x36 istore 将栈顶int型数值存入指定本地变量
    0x37 lstore 将栈顶long型数值存入指定本地变量
    0x38 fstore 将栈顶float型数值存入指定本地变量
    0x39 dstore 将栈顶do le型数值存入指定本地变量
    0x3a astore 将栈顶引用型数值存入指定本地变量
    0x3b istore_0 将栈顶int型数值存入第一个本地变量
    0x3c istore_1 将栈顶int型数值存入第二个本地变量
    0x3d istore_2 将栈顶int型数值存入第三个本地变量
    0x3e istore_3 将栈顶int型数值存入第四个本地变量
    0x3f lstore_0 将栈顶long型数值存入第一个本地变量
    0x40 lstore_1 将栈顶long型数值存入第二个本地变量
    0x41 lstore_2 将栈顶long型数值存入第三个本地变量
    0x42 lstore_3 将栈顶long型数值存入第四个本地变量
    0x43 fstore_0 将栈顶float型数值存入第一个本地变量
    0x44 fstore_1 将栈顶float型数值存入第二个本地变量
    0x45 fstore_2 将栈顶float型数值存入第三个本地变量
    0x46 fstore_3 将栈顶float型数值存入第四个本地变量
    0x47 dstore_0 将栈顶do le型数值存入第一个本地变量
    0x48 dstore_1 将栈顶do le型数值存入第二个本地变量
    0x49 dstore_2 将栈顶do le型数值存入第三个本地变量
    0x4a dstore_3 将栈顶do le型数值存入第四个本地变量
    0x4b astore_0 将栈顶引用型数值存入第一个本地变量
    0x4c astore_1 将栈顶引用型数值存入第二个本地变量
    0x4d astore_2 将栈顶引用型数值存入第三个本地变量
    0x4e astore_3 将栈顶引用型数值存入第四个本地变量
    0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置
    0x50 lastore 将栈顶long型数值存入指定数组的指定索引位置
    0x51 fastore 将栈顶float型数值存入指定数组的指定索引位置
    0x52 dastore 将栈顶do le型数值存入指定数组的指定索引位置
    0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
    0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
    0x55 castore 将栈顶char型数值存入指定数组的指定索引位置
    0x56 sastore 将栈顶short型数值存入指定数组的指定索引位置
    0x57 pop 将栈顶数值弹出 (数值不能是long或do le类型的)
    0x58 pop2 将栈顶的一个(long或do le类型的)或两个数值弹出(其它)
    0x59 dup 复制栈顶数值并将复制值压入栈顶
    0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
    0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
    0x5c dup2 复制栈顶一个(long或do le类型的)或两个(其它)数值并将复制值压入栈顶
    0x5d dup2_x1 dup_x1 指令的双倍版本
    0x5e dup2_x2 dup_x2 指令的双倍版本
    0x5f swap 将栈最顶端的两个数值互换(数值不能是long或do le类型的)
    0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶
    0x61 ladd 将栈顶两long型数值相加并将结果压入栈顶
    0x62 fadd 将栈顶两float型数值相加并将结果压入栈顶
    0x63 dadd 将栈顶两do le型数值相加并将结果压入栈顶
    0x64 is 将栈顶两int型数值相减并将结果压入栈顶
    0x65 ls 将栈顶两long型数值相减并将结果压入栈顶
    0x66 fs 将栈顶两float型数值相减并将结果压入栈顶
    0x67 ds 将栈顶两do le型数值相减并将结果压入栈顶
    0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶
    0x69 lmul 将栈顶两long型数值相乘并将结果压入栈顶
    0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶
    0x6b dmul 将栈顶两do le型数值相乘并将结果压入栈顶
    0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
    0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶
    0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶
    0x6f ddiv 将栈顶两do le型数值相除并将结果压入栈顶
    0x70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶
    0x71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶
    0x72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶
    0x73 drem 将栈顶两do le型数值作取模运算并将结果压入栈顶
    0x74 ineg 将栈顶int型数值取负并将结果压入栈顶
    0x75 lneg 将栈顶long型数值取负并将结果压入栈顶
    0x76 fneg 将栈顶float型数值取负并将结果压入栈顶
    0x77 dneg 将栈顶do le型数值取负并将结果压入栈顶
    0x78 ishl 将int型数值左移位指定位数并将结果压入栈顶
    0x79 lshl 将long型数值左移位指定位数并将结果压入栈顶
    0x7a ishr 将int型数值右(符号)移位指定位数并将结果压入栈顶
    0x7b lshr 将long型数值右(符号)移位指定位数并将结果压入栈顶
    0x7c iushr 将int型数值右(无符号)移位指定位数并将结果压入栈顶
    0x7d lushr 将long型数值右(无符号)移位指定位数并将结果压入栈顶
    0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶
    0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈顶
    0x80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶
    0x81 lor 将栈顶两long型数值作“按位或”并将结果压入栈顶
    0x82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶
    0x83 lxor 将栈顶两long型数值作“按位异或”并将结果压入栈顶
    0x84 iinc 将指定int型变量增加指定值(i++, i–, i+=2)
    0x85 i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶
    0x86 i2f 将栈顶int型数值强制转换成float型数值并将结果压入栈顶
    0x87 i2d 将栈顶int型数值强制转换成do le型数值并将结果压入栈顶
    0x88 l2i 将栈顶long型数值强制转换成int型数值并将结果压入栈顶
    0x89 l2f 将栈顶long型数值强制转换成float型数值并将结果压入栈顶
    0x8a l2d 将栈顶long型数值强制转换成do le型数值并将结果压入栈顶
    0x8b f2i 将栈顶float型数值强制转换成int型数值并将结果压入栈顶
    0x8c f2l 将栈顶float型数值强制转换成long型数值并将结果压入栈顶
    0x8d f2d 将栈顶float型数值强制转换成do le型数值并将结果压入栈顶
    0x8e d2i 将栈顶do le型数值强制转换成int型数值并将结果压入栈顶
    0x8f d2l 将栈顶do le型数值强制转换成long型数值并将结果压入栈顶
    0x90 d2f 将栈顶do le型数值强制转换成float型数值并将结果压入栈顶
    0x91 i2b 将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
    0x92 i2c 将栈顶int型数值强制转换成char型数值并将结果压入栈顶
    0x93 i2s 将栈顶int型数值强制转换成short型数值并将结果压入栈顶
    0x94 lcmp 比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
    0x95 fcmpl 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
    0x96 fcmpg 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
    0x97 dcmpl 比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
    0x98 dcmpg 比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
    0x99 ifeq 当栈顶int型数值等于0时跳转
    0x9a ifne 当栈顶int型数值不等于0时跳转
    0x9b iflt 当栈顶int型数值小于0时跳转
    0x9c ifge 当栈顶int型数值大于等于0时跳转
    0x9d ifgt 当栈顶int型数值大于0时跳转
    0x9e ifle 当栈顶int型数值小于等于0时跳转
    0x9f if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转
    0xa0 if_icmpne 比较栈顶两int型数值大小,当结果不等于0时跳转
    0xa1 if_icmplt 比较栈顶两int型数值大小,当结果小于0时跳转
    0xa2 if_icmpge 比较栈顶两int型数值大小,当结果大于等于0时跳转
    0xa3 if_icmpgt 比较栈顶两int型数值大小,当结果大于0时跳转
    0xa4 if_icmple 比较栈顶两int型数值大小,当结果小于等于0时跳转
    0xa5 if_acmpeq 比较栈顶两引用型数值,当结果相等时跳转
    0xa6 if_acmpne 比较栈顶两引用型数值,当结果不相等时跳转
    0xa7 goto 无条件跳转
    0xa8 jsr 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
    0xa9 ret 返回至本地变量
    0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令)
    0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令)
    0xac ireturn 从当前方法返回int
    0xad lreturn 从当前方法返回long
    0xae freturn 从当前方法返回float
    0xaf dreturn 从当前方法返回do le
    0xb0 areturn 从当前方法返回对象引用
    0xb1 return 从当前方法返回void
    0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶
    0xb3 putstatic 为指定的类的静态域赋值
    0xb4 getfield 获取指定类的实例域,并将其值压入栈顶
    0xb5 putfield 为指定的类的实例域赋值
    0xb6 invokevirtual 调用实例方法
    0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法
    0xb8 invokestatic 调用静态方法
    0xb9 invokeinterface 调用接口方法
    0xba 无此指令
    0xbb new 创建一个对象,并将其引用值压入栈顶
    0xbc newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
    0xbd anewarray 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
    0xbe arraylength 获得数组的长度值并压入栈顶
    0xbf athrow 将栈顶的异常抛出
    0xc0 checkcast 检验类型转换,检验未通过将抛出ClassCastException
    0xc1 instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
    0xc2 monitorenter 获得对象的锁,用于同步方法或同步块
    0xc3 monitorexit 释放对象的锁,用于同步方法或同步块
    0xc4 wide <待补充>
    0xc5 multianewarray 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
    0xc6 ifnull 为null时跳转
    0xc7 ifnonnull 不为null时跳转
    0xc8 goto_w 无条件跳转(宽索引)
    0xc9 jsr_w 跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶
    展开全文
  • YOLO配置文件理解

    万次阅读 多人点赞 2017-03-23 19:28:00
    YOLO配置文件理解

    YOLO配置文件理解

    [net]
    batch=64                           每batch个样本更新一次参数。
    subdivisions=8                     如果内存不够大,将batch分割为subdivisions个子batch,每个子batch的大小为batch/subdivisions。
                                       在darknet代码中,会将batch/subdivisions命名为batch。
    height=416                         input图像的高
    width=416                          Input图像的宽
    channels=3                         Input图像的通道数
    momentum=0.9                       动量
    decay=0.0005                       权重衰减正则项,防止过拟合
    angle=0                            通过旋转角度来生成更多训练样本
    saturation = 1.5                   通过调整饱和度来生成更多训练样本
    exposure = 1.5                     通过调整曝光量来生成更多训练样本
    hue=.1                             通过调整色调来生成更多训练样本
    
    learning_rate=0.0001               初始学习率
    max_batches = 45000                训练达到max_batches后停止学习
    policy=steps                       调整学习率的policy,有如下policy:CONSTANT, STEP, EXP, POLY, STEPS, SIG, RANDOM
    steps=100,25000,35000              根据batch_num调整学习率
    scales=10,.1,.1                    学习率变化的比例,累计相乘
    
    [convolutional]
    batch_normalize=1                  是否做BN
    filters=32                         输出多少个特征图
    size=3                             卷积核的尺寸
    stride=1                           做卷积运算的步长
    pad=1                              如果pad为0,padding由 padding参数指定。如果pad为1,padding大小为size/2
    activation=leaky                   激活函数:
                                       logistic,loggy,relu,elu,relie,plse,hardtan,lhtan,linear,ramp,leaky,tanh,stair
    
    [maxpool]
    size=2                             池化层尺寸
    stride=2                           池化步进
    
    [convolutional]
    batch_normalize=1
    filters=64
    size=3
    stride=1
    pad=1
    activation=leaky
    
    [maxpool]
    size=2
    stride=2
    
    ......
    ......
    
    
    #######
    
    [convolutional]
    batch_normalize=1
    size=3
    stride=1
    pad=1
    filters=1024
    activation=leaky
    
    [convolutional]
    batch_normalize=1
    size=3
    stride=1
    pad=1
    filters=1024
    activation=leaky
    
    [route]                            the route layer is to bring finer grained features in from earlier in the network
    layers=-9
    
    [reorg]                            the reorg layer is to make these features match the feature map size at the later layer. 
                                       The end feature map is 13x13, the feature map from earlier is 26x26x512. 
                                       The reorg layer maps the 26x26x512 feature map onto a 13x13x2048 feature map 
                                       so that it can be concatenated with the feature maps at 13x13 resolution.
    stride=2
    
    [route]
    layers=-1,-3
    
    [convolutional]
    batch_normalize=1
    size=3
    stride=1
    pad=1
    filters=1024
    activation=leaky
    
    [convolutional]
    size=1
    stride=1
    pad=1
    filters=125                        region前最后一个卷积层的filters数是特定的,计算公式为filter=num*(classes+5) 
                                       5的意义是5个坐标,论文中的tx,ty,tw,th,to
    activation=linear
    
    [region]
    anchors = 1.08,1.19,  3.42,4.41,  6.63,11.38,  9.42,5.11,  16.62,10.52          预选框,可以手工挑选,
                                                                                    也可以通过k means 从训练样本中学出
    bias_match=1
    classes=20                         网络需要识别的物体种类数
    coords=4                           每个box的4个坐标tx,ty,tw,th
    num=5                              每个grid cell预测几个box,和anchors的数量一致。当想要使用更多anchors时需要调大num,且如果调大num后训练时Obj趋近0的话可以尝试调大object_scale
    softmax=1                          使用softmax做激活函数
    jitter=.2                          通过抖动增加噪声来抑制过拟合
    rescore=1                          暂理解为一个开关,非0时通过重打分来调整l.delta(预测值与真实值的差)
    
    object_scale=5                     栅格中有物体时,bbox的confidence loss对总loss计算贡献的权重
    noobject_scale=1                   栅格中没有物体时,bbox的confidence loss对总loss计算贡献的权重
    class_scale=1                      类别loss对总loss计算贡献的权重                      
    coord_scale=1                      bbox坐标预测loss对总loss计算贡献的权重
    
    absolute=1
    thresh = .6
    random=0                           random为1时会启用Multi-Scale Training,随机使用不同尺寸的图片进行训练。
    

    darknet对应代码

    找到cfg文件解析的代码,选择detector demo 作为入口

    darknet.c文件 main 函数开始

        } else if (0 == strcmp(argv[1], "detector")){
        run_detector(argc, argv);
    

    Detector.c文件 run_detector函数

    char *prefix = find_char_arg(argc, argv, "-prefix", 0);
    float thresh = find_float_arg(argc, argv, "-thresh", .24);
    float hier_thresh = find_float_arg(argc, argv, "-hier", .5);
    int cam_index = find_int_arg(argc, argv, "-c", 0);
    int frame_skip = find_int_arg(argc, argv, "-s", 0);
    if(argc < 4){
        fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
        return;
    }
    char *gpu_list = find_char_arg(argc, argv, "-gpus", 0);
    char *outfile = find_char_arg(argc, argv, "-out", 0);
    
    ......
    ......
    
    else if(0==strcmp(argv[2], "demo")) {
        list *options = read_data_cfg(datacfg);
        int classes = option_find_int(options, "classes", 20);
        char *name_list = option_find_str(options, "names", "data/names.list");
        char **names = get_labels(name_list);
        demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, hier_thresh);
    }
    

    read_data_cfg函数解析配置文件,保存到options指针。

    class

    int classes = option_find_int(options, "classes", 20);
    

    classes为YOLO可识别的种类数

    batch、learning_rate、momentum、decay和 subdivisions

    demo.c文件demo函数

    net = parse_network_cfg(cfgfile);
    

    Parser.c文件 parse_network_cfg函数

    list *sections = read_cfg(filename);
    node *n = sections->front;
    if(!n) error("Config file has no sections");
    network net = make_network(sections->size - 1);
    net.gpu_index = gpu_index;
    size_params params;
    
    section *s = (section *)n->val;
    list *options = s->options;
    if(!is_network(s)) error("First section must be [net] or [network]");
    parse_net_options(options, &net);
    

    parse_net_options函数

    net->batch = option_find_int(options, "batch",1);
    net->learning_rate = option_find_float(options, "learning_rate", .001);
    net->momentum = option_find_float(options, "momentum", .9);
    net->decay = option_find_float(options, "decay", .0001);
    int subdivs = option_find_int(options, "subdivisions",1);
    net->time_steps = option_find_int_quiet(options, "time_steps",1);
    net->batch /= subdivs;
    net->batch *= net->time_steps;
    net->subdivisions = subdivs;
    

    learning_rate为初始学习率,训练时的真正学习率和学习率的策略及初始学习率有关。

    momentum为动量,在训练时加入动量可以帮助走出local minima 以及saddle point。

    decay是权重衰减正则项,用来防止过拟合。

    batch的值等于cfg文件中的batch/subdivisions 再乘以time_steps。
    time_steps在yolo默认的cfg中是没有配置的,所以是默认值1。
    因此batch可以认为就是cfg文件中的batch/subdivisions。

    前面有提到batch的意义是每batch个样本更新一次参数。

    而subdivisions的意义在于降低对GPU memory的要求。
    darknet将batch分割为subdivisions个子batch,每个子batch的大小为batch/subdivisions,并将子batch命名为batch。

    我们看下训练时和batch有关的代码

    Detector.c文件的train_detector函数

    #ifdef GPU
        if(ngpus == 1){
            loss = train_network(net, train);
        } else {
            loss = train_networks(nets, ngpus, train, 4);
        }
    #else
        loss = train_network(net, train);
    #endif
    

    Network.c文件的train_network函数

    int batch = net.batch;
    int n = d.X.rows / batch;
    float *X = calloc(batch*d.X.cols, sizeof(float));
    float *y = calloc(batch*d.y.cols, sizeof(float));
    
    int i;
    float sum = 0;
    for(i = 0; i < n; ++i){
        get_next_batch(d, batch, i*batch, X, y);
        float err = train_network_datum(net, X, y);
        sum += err;
    }
    

    train_network_datum函数

    *net.seen += net.batch;
    ......
    ......
    forward_network(net, state);
    backward_network(net, state);
    float error = get_network_cost(net);
    if(((*net.seen)/net.batch)%net.subdivisions == 0) update_network(net);
    

    我们看到,只有((*net.seen)/net.batch)%net.subdivisions == 0时才会更新网络参数。
    *net.seen是已经训练过的子batch数,((*net.seen)/net.batch)%net.subdivisions的意义正是已经训练过了多少个真正的batch。

    policy、steps和scales

    Parser.c文件 parse_network_cfg函数

    char *policy_s = option_find_str(options, "policy", "constant");
    net->policy = get_policy(policy_s);
    net->burn_in = option_find_int_quiet(options, "burn_in", 0);
    if(net->policy == STEP){
        net->step = option_find_int(options, "step", 1);
        net->scale = option_find_float(options, "scale", 1);
    } else if (net->policy == STEPS){
        char *l = option_find(options, "steps");   
        char *p = option_find(options, "scales");   
        if(!l || !p) error("STEPS policy must have steps and scales in cfg file");
    
        int len = strlen(l);
        int n = 1;
        int i;
        for(i = 0; i < len; ++i){
            if (l[i] == ',') ++n;
        }
        int *steps = calloc(n, sizeof(int));
        float *scales = calloc(n, sizeof(float));
        for(i = 0; i < n; ++i){
            int step    = atoi(l);
            float scale = atof(p);
            l = strchr(l, ',')+1;
            p = strchr(p, ',')+1;
            steps[i] = step;
            scales[i] = scale;
        }
        net->scales = scales;
        net->steps = steps;
        net->num_steps = n;
    } else if (net->policy == EXP){
        net->gamma = option_find_float(options, "gamma", 1);
    } else if (net->policy == SIG){
        net->gamma = option_find_float(options, "gamma", 1);
        net->step = option_find_int(options, "step", 1);
    } else if (net->policy == POLY || net->policy == RANDOM){
        net->power = option_find_float(options, "power", 1);
    }
    

    get_policy函数

    if (strcmp(s, "random")==0) return RANDOM;
    if (strcmp(s, "poly")==0) return POLY;
    if (strcmp(s, "constant")==0) return CONSTANT;
    if (strcmp(s, "step")==0) return STEP;
    if (strcmp(s, "exp")==0) return EXP;
    if (strcmp(s, "sigmoid")==0) return SIG;
    if (strcmp(s, "steps")==0) return STEPS;
    fprintf(stderr, "Couldn't find policy %s, going with constant\n", s);
    return CONSTANT;
    

    学习率动态调整的策略有多种,YOLO默认使用的是steps。

    yolo-voc.cfg文件:

    steps=100,25000,35000

    scales=10,.1,.1

    Network.c文件get_current_rate函数

    int batch_num = get_current_batch(net);
    int i;
    float rate;
    switch (net.policy) {
        case CONSTANT:
            return net.learning_rate;
        case STEP:
            return net.learning_rate * pow(net.scale, batch_num/net.step);
        case STEPS:
            rate = net.learning_rate;
            for(i = 0; i < net.num_steps; ++i){
                if(net.steps[i] > batch_num) return rate;
                rate *= net.scales[i];
                //if(net.steps[i] > batch_num - 1 && net.scales[i] > 1) reset_momentum(net);
            }
            return rate;
    

    get_current_batch获取的是(*net.seen)/(net.batch*net.subdivisions),即真正的batch。

    steps的每个阶段是根据batch_num划分的,根据配置文件,学习率会在batch_num达到100、25000、35000时发生改变。

    当前的学习率是初始学习率与当前阶段及之前所有阶段对应的scale的总乘积。

    convolutional超参数加载

    Parser.c文件parse_network_cfg函数

    LAYER_TYPE lt = string_to_layer_type(s->type);
            if(lt == CONVOLUTIONAL){
                l = parse_convolutional(options, params);
    

    parse_convolutional函数

    int n = option_find_int(options, "filters",1);
    int size = option_find_int(options, "size",1);
    int stride = option_find_int(options, "stride",1);
    int pad = option_find_int_quiet(options, "pad",0);
    int padding = option_find_int_quiet(options, "padding",0);
    if(pad) padding = size/2;
    
    char *activation_s = option_find_str(options, "activation", "logistic");
    ACTIVATION activation = get_activation(activation_s);
    
    int batch,h,w,c;
    h = params.h;
    w = params.w;
    c = params.c;
    batch=params.batch;
    if(!(h && w && c)) error("Layer before convolutional layer must output image.");
    int batch_normalize = option_find_int_quiet(options, "batch_normalize", 0);
    

    需要注意的是如果enable了pad,cfg文件中的padding不会生效,实际的padding值为size/2。

    random

    YOLOv2新增了一些训练技巧,Multi-Scale Training就是其中之一,如果random置为1,会启用Multi-Scale Training。
    启用Multi-Scale Training时每10个Batch,网络会随机地选择一个新的图片尺寸,由于使用的down samples是32,所以不同的尺寸大小也选择为32的倍数{320,352…..608},最小320*320,最大608*608,网络会自动改变尺寸,并继续训练的过程。
    这一策略让网络在不同的输入尺寸上都能达到一个很好的预测效果,同一网络能在不同分辨率上进行检测。当输入图片尺寸比较小的时候跑的比较快,输入图片尺寸比较大的时候精度高。

    route 和 reorg

    YOLOv2新增了Fine-Grained Features技巧,参考特征金字塔和ResNet,把高分辨率特征与低分辨率特征联系在一起,从而增加对小物体的识别精度。
    这里写图片描述
    借用一下ResNet的identity mappings示意图
    YOLOv2加上了一个Passthrough Layer来取得之前的某个26*26分辨率的层的特征。这个Passthrough layer把26 * 26的特征图与13 * 13的特征图联系在一起,把相邻的特征堆积在不同的Channel之中,类似与Resnet的Identity Mapping,从而把26*26*512变成13*13*2048。
    route层起连接作用,reorg层来match特征图尺寸。

    展开全文
  • 文章目录utils.py 文件理解:一些小脚本文件1. 包含的函数1.1. def to_cpu(tensor):1.2. def load_classes(path): 加载数据集的类别1.3. def weights_init_normal(m): 权重初始化这里还要写一个文档来理解 self....

    utils.py 文件的理解:一些小脚本文件

    1. 包含的函数

    1.1. def to_cpu(tensor):

    1. 代码如下:
    def to_cpu(tensor):  # 张量 转移到 cpu
        return tensor.detach().cpu()  # .detach() 创建成一个没有梯度的tensor, 如果想要保留梯度就使用tensor.clone()
    
    1. 理解如下:
      输入GPU上的 tensor ,
      返回GPU上的 tensor

    1.2. def load_classes(path): 加载数据集的类别

    1. 代码如下:
    def load_classes(path):  # 读取路径中文件成 列表 data/coco.names
        """
        Loads class labels at 'path'
        """
        fp = open(path, "r")
        names = fp.read().split("\n")[:-1]  # 读取coco数据集的名称,按行读取,每次只取每一行的最后一个
        return names  # 返回的是 coco name 类别名称的列表 list
    
    
    1. 理解如下:
      输入:包含数据集名称的文件路径,如:data/coco.names
      data/coco.names 的内容(打开像一个文本文档)如下:
    person
    bicycle
    car
    …… # 一共80行,coco 数据集有80类别
    

    输出:

    ['person', 'bicycle', 'car', 'motorbike', ……]
    

    1.3. def weights_init_normal(m): 权重初始化

    1. 代码如下:
    def weights_init_normal(m):  # 自定义初始化权重的函数
        # m 是网络中的(每)一个submodule(子模块),这weights_init_normal函数被调用来初始化模型中每一个子模块的参数。
        # model.apply(fn) 的作用是 将 fn function to be applied to each submodule
        classname = m.__class__.__name__
        if classname.find("Conv") != -1:  # 这里等于 -1 表示,
            torch.nn.init.normal_(m.weight.data, 0.0, 0.02)  # 将正太分布N(0, 0.2)生成的值赋值给 m.weight.data 中的每个元素
        elif classname.find("BatchNorm2d") != -1:
            torch.nn.init.normal_(m.weight.data, 1.0, 0.02)
            torch.nn.init.constant_(m.bias.data, 0.0)  # 将 0 赋值给 m.bias.data
    
    1. 理解如下:

    这里定义的这个 weights_init_normal 函数会作为 model.apply(fn)的输入fn。即 fn 是函数weights_init_normal。

    函数在 train.py 中被使用,使用如下

    model.apply(weights_init_normal)   # train.py 中模型初始化参数
    

    model 的 apply 函数是torch中 Module(object) 类中的一个标准函数。
    torch 中 model.apply 定义如下

    class Module(object):
    	def apply(self, fn):
    	    for module in self.children():
    	        module.apply(fn)  # 这是一个递归
    		fn(self)  # 这里的fn 就是 我们定义的 def weights_init_normal(m) 初始化权重函数
    		return self
    

    这里还要写一个文档来理解 self.children 的使用方法.

    1.4. def rescale_boxes(boxes, current_dim, original_shape): 将相对网络输入的box转化成原始图片的box

    1. 代码如下:
    def rescale_boxes(boxes, current_dim, original_shape):  # 将相对网络输入的box转化成原始图片的box。
        """ Rescales bounding boxes to the original shape
        现在的 boxes: 是输入神经网络中的图片的 boxes, 不是原始图片的 boxes ,所以需要将 boxes 转换成原始图片的 boxes。
        current_dim: 通常是 416
        original_shape: coco数据集中的原始图片的 shape 各不相同
        """
        orig_h, orig_w = original_shape
        # The amount of padding that was added
        pad_x = max(orig_h - orig_w, 0) * (current_dim / max(original_shape))  # 水平上 左右 pad 的和
        pad_y = max(orig_w - orig_h, 0) * (current_dim / max(original_shape))  # 垂直上 上下 pad 的和
        # Image height and width after padding is removed
        unpad_h = current_dim - pad_y
        unpad_w = current_dim - pad_x
        # Rescale bounding boxes to dimension of original image
        boxes[:, 0] = ((boxes[:, 0] - pad_x // 2) / unpad_w) * orig_w
        boxes[:, 1] = ((boxes[:, 1] - pad_y // 2) / unpad_h) * orig_h
        boxes[:, 2] = ((boxes[:, 2] - pad_x // 2) / unpad_w) * orig_w
        boxes[:, 3] = ((boxes[:, 3] - pad_y // 2) / unpad_h) * orig_h
        return boxes
    
    1. 理解如下:
      输入:
      boxes:是 yolo(相对于current_dim) 的检测结果,是一个列表,存放过的是box左上角和右下角两个点的四个坐标值: xyxy 。
      current_dim:当前图片的宽度或高度,416(下图中用黄色的小正方形表示)
      original_shape:h=1920, w=1080(下图中用蓝色额大矩形表示)
      返回:
      相对于输入图片的boxes 的坐标xyxy(一个box 用左上角和右下角的坐标来标志)
      在这里插入图片描述

    1.5. def xywh2xyxy(x):

    1. 代码如下:
    def xywh2xyxy(x):  # 将中心坐标和高宽,转成左上角右下角的坐标
        y = x.new(x.shape)  # 创建一个新的空间
        y[..., 0] = x[..., 0] - x[..., 2] / 2
        y[..., 1] = x[..., 1] - x[..., 3] / 2
        y[..., 2] = x[..., 0] + x[..., 2] / 2
        y[..., 3] = x[..., 1] + x[..., 3] / 2
        return y
    
    1. 理解如下:
      输入:列表,四个值分别是 中心点的坐标xy, box 的宽度和高度
      输入:列表,四个值分别是 左上角的坐标,右下角的坐标。

    1.6. def ap_per_class(tp, conf, pred_cls, target_cls): 不懂

    """ Compute the average precision, given the recall and precision curves.
        Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
        # Arguments
            tp:    True positives (list).
            conf:  Objectness value from 0-1 (list).
            pred_cls: Predicted object classes (list).
            target_cls: True object classes (list).
        # Returns
            The average precision as computed in py-faster-rcnn.
        """
    

    1.7. def compute_ap(recall, precision): 不懂

       """ Compute the average precision, given the recall and precision curves.
        Code originally from https://github.com/rbgirshick/py-faster-rcnn.
    
        # Arguments
            recall:    The recall curve (list).
            precision: The precision curve (list).
        # Returns
            The average precision as computed in py-faster-rcnn.
        """
    

    1.8. def get_batch_statistics(outputs, targets, iou_threshold): 评估一个 batch 中多张图片的检测结果

    1. 代码如下:
    2. 理解如下
      2.1. 输入:
      outputs: 一个 batch 的图片的检测结果
      output = outputs[i]: 所有预测图片中编号为 i 的检测结果。一个 batch 中第 i 张图片的检测结果,含多个box,每一个box 有坐标(四个数据),概率,类别编号
      annotations = targets[targets[:, 0] == sample_i][:, 1:]
      targets: GT
      iou_threshold: 判断为 true 的最低交并比要求
      2.2. 输出:
      2.3. 过程:
      2.3.1 按一个 batch 中图片的编号遍历检测结果,将第 i 张图片的检测结果存入 output(一张图片的box)
      2.3.2. 从 targets 中找到那个图片编号同样是 i 的GT,作为 annotations(一张图片的box)
      2.3.3. 遍历一张图片output 中的每一个 box, 将这个box 与 annotations 中的每一个框计算交并比

    1.9. def bbox_wh_iou(wh1, wh2): # 求 box 与 anchor 的交并比

    1. 代码如下:
    def bbox_wh_iou(wh1, wh2):  # 求两个 box 的交并比
        wh2 = wh2.t()  # 转换数据的格式,并没有交换数据的位置
        w1, h1 = wh1[0], wh1[1]
        w2, h2 = wh2[0], wh2[1]
        inter_area = torch.min(w1, w2) * torch.min(h1, h2)  # 宽高都取最下的那个
        union_area = (w1 * h1 + 1e-16) + w2 * h2 - inter_area
        return inter_area / union_area
    
    1. 理解如下:
      输入的 wh1 和 wh2 都是yolo格式的坐标,即比例坐标。这里的两个 box 是wh 不相同,但他们两的中心点的坐标是相同的,所以计算交并比的时候,只需要计算w h的数据,比需要中心点的坐标数据。

    1.10. def bbox_iou(box1, box2, x1y1x2y2=True): 计算两个点坐标的box的交并比

    
    def bbox_iou(box1, box2, x1y1x2y2=True):
        """
        Returns the IoU of two bounding boxes
        """
        if not x1y1x2y2: 将 xywh的表示方式转换成 xyxy的表示方式
            # Transform from center and width to exact coordinates
            b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2
            b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2
            b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2
            b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2
        else:
            # Get the coordinates of bounding boxes
            b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3]
            b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3]
    
        # get the corrdinates of the intersection rectangle
        inter_rect_x1 = torch.max(b1_x1, b2_x1)
        inter_rect_y1 = torch.max(b1_y1, b2_y1)
        inter_rect_x2 = torch.min(b1_x2, b2_x2)
        inter_rect_y2 = torch.min(b1_y2, b2_y2)
        # Intersection area  # torch.clamp 截断处理,将小于min的值取值为min
        inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1 + 1, min=0) * torch.clamp(
            inter_rect_y2 - inter_rect_y1 + 1, min=0
        )
        # Union Area
        b1_area = (b1_x2 - b1_x1 + 1) * (b1_y2 - b1_y1 + 1)  # + 1 是因为边界宽度有个 1 ,注意这个点。
        b2_area = (b2_x2 - b2_x1 + 1) * (b2_y2 - b2_y1 + 1)
    
        iou = inter_area / (b1_area + b2_area - inter_area + 1e-16)
    
        return iou
    
    1. 理解如下:
      求交并比的重点:
      无论两个矩形框的表示形式是怎样的,都要转化成左上角右下角 xyxy 的格式。
      求左边界和上边界用 max
      求右边界和下边界用 min
      相交的宽度为负时,赋值为 0
      box 的宽度等于 右坐标 - 左坐标 + 1

    1. 11. def non_max_suppression(prediction, conf_thres=0.5, nms_thres=0.4): # 非极大抑制

    1. 代码如下:

    def non_max_suppression(prediction, conf_thres=0.5, nms_thres=0.4):  # 非极大抑制
        """
        Removes detections with lower object confidence score than 'conf_thres' and performs
        Non-Maximum Suppression to further filter detections.
        Returns detections with shape:
            (x1, y1, x2, y2, object_conf, class_score, class_pred)
        """
    
        # From (center x, center y, width, height) to (x1, y1, x2, y2)
        prediction[..., :4] = xywh2xyxy(prediction[..., :4])  # 处理一个batch 每一张图片每一个对象的数据:坐标转换(只改变每一张图片的每一个预测框中的前四个坐标值,其他值不变)
    #    prediction =  tensor([[
    #         [1.2897e+01, 1.8118e+01, 1.7194e+02,  ..., 1.1081e-02,5.1385e-03, 3.2795e-03],
    #         [5.7479e+01, 2.5666e+01, 1.1065e+02,  ..., 6.1178e-03,3.3721e-03, 9.3959e-04],
    #         [7.7897e+01, 2.4972e+01, 1.4381e+02,  ..., 1.4635e-03,3.0854e-03, 8.7795e-04],
    #         ...,
    #         [3.9590e+02, 4.1341e+02, 7.7788e+01,  ..., 3.0758e-04, 1.1135e-04, 1.2358e-04],
    #         [4.0272e+02, 4.1227e+02, 4.9530e+01,  ..., 3.6771e-04, 3.1682e-04, 4.5721e-04],
    #         [4.1132e+02, 4.1317e+02, 7.5396e+01,  ..., 1.0301e-03, 9.6391e-04, 1.7760e-03]]])
    
        output = [None for _ in range(len(prediction))]  # 图片的长度
        for image_i, image_pred in enumerate(prediction):  # 遍历一张图片的每一个预测框(这里处理的对象是一张图片)
            # Filter out confidence scores below threshold
            image_pred = image_pred[image_pred[:, 4] >= conf_thres]  # 过滤掉 边框置信度小于阈值的框。0123是坐标,4是置信度,5:是种类
            # If none are remaining => process next image
            if not image_pred.size(0):  # 如果 没有满足阈值要求的框,就访问下一个框
                continue
            # Object confidence times class confidence
            score = image_pred[:, 4] * image_pred[:, 5:].max(1)[0]  # 求当前预测框类别中的极大值([0])。 在1方向(max(1)),找到每行(属于80个种类中的某一类的最大值)的最大值。
            #  框的位置执行度 * 框中对象的执行度(找的是每一个框的这个最大值,这里的每一行就是每一个框)
            # Sort by it
            image_pred = image_pred[(-score).argsort()]  # .argsort() 返回从大到小排序索引值 (.argsort 是从小到大排序,加了符号之后就是从大到小的排序。)
    # image_pred =  tensor([
    #        [6.9979e+01, 1.7314e+02, 1.7263e+02, 3.4342e+02, 9.9952e-01, 2.7360e-06,
    #         3.1124e-05, 7.5148e-08, 2.1126e-07, 7.7389e-09, 2.3194e-08, 5.6478e-10,
    #         3.2973e-08, 4.0248e-07, 9.3334e-09, 9.8726e-08, 1.9962e-08, 1.4138e-08,
    #         4.4086e-07, 1.7810e-05, 3.2029e-03, 9.9335e-01, 1.4822e-06, 1.3331e-05,
    #         2.8374e-06, 2.3131e-07, 3.8589e-06, 6.9625e-08, 1.2171e-06, 3.3805e-06,
    #         1.5626e-07, 9.1728e-08, 8.7323e-08, 1.3251e-07, 1.4588e-06, 2.2201e-06,
    #         7.4246e-08, 1.9406e-07, 1.9214e-07, 1.8509e-08, 9.3417e-08, 2.9386e-06,
    #         2.2876e-07, 2.0008e-07, 1.7397e-08, 1.9292e-09, 9.9117e-09, 1.0033e-09,
    #         1.1846e-08, 4.8930e-08, 1.7767e-09, 2.1066e-07, 1.0837e-07, 1.8093e-07,
    #         2.0299e-09, 4.5130e-07, 2.2242e-09, 1.1962e-07, 2.4569e-05, 2.8944e-07,
    #         3.8133e-08, 1.9768e-07, 5.1894e-07, 1.6013e-06, 5.0550e-06, 6.7384e-08,
    #         3.8338e-08, 1.3275e-07, 2.6257e-08, 3.8255e-08, 2.3908e-08, 1.3826e-07,
    #         2.6799e-09, 3.1228e-08, 6.6355e-08, 2.0321e-08, 4.7900e-07, 1.1243e-08,
    #         1.9466e-08, 2.2574e-07, 5.0917e-10, 2.6649e-08, 7.6042e-07, 7.5514e-08,
    #         1.9578e-08],
    #         [……],
    #         [……],
    #         ……])
             
    
            class_confs, class_preds = image_pred[:, 5:].max(1, keepdim=True)  # 返回每一行指定位置(5:)的最大值,和最大值的索引(类别取得最大值的索引就是类别编号)
    # class_confs, class_preds = 
    # tensor([[0.9933],
    #        [0.9998],
    #        [0.9996],
    #        [0.9880],
    #        [0.9423],
    #        [0.9894],
    #        [0.9210],
    #        [0.9850],
    #        [0.9857]]) 
    # tensor([[16],
    #        [ 1],
    #        [ 1],
    #        [16],
    #        [ 7],
    #        [16],
    #        [ 7],
    #        [16],
    #        [16]])
    #
            detections = torch.cat((image_pred[:, :5], class_confs.float(), class_preds.float()), 1)  # 提取出 80个类别中class_confs最大的那一个,及其下标 class_preds;其他大的79个类别就不保留了
            # .cat 是将数据按行拼接起来。01234(image_pred[:, :5])表示4个坐标和1个框置信度,5(class_confs)表示类别置信度,6(class_preds)表示类别编号
    # detections =  tensor([
    #        [ 69.9790, 173.1447, 172.6293, 343.4247,   0.9995,   0.9933,  16.0000],
    #        [ 92.8356, 111.2542, 305.3051, 294.4642,   0.9927,   0.9998,   1.0000],
    #        [ 63.1184, 116.6243, 311.2260, 294.3138,   0.9900,   0.9996,   1.0000],
    #        [ 68.8240, 157.3179, 172.9849, 340.8053,   0.9996,   0.9880,  16.0000],
    #        [254.1283,  98.5848, 373.9042, 144.4332,   0.9970,   0.9423,   7.0000],
    #        [ 70.7442, 184.7154, 168.4275, 335.5417,   0.9453,   0.9894,  16.0000],
    #        [253.6593,  95.9781, 372.7262, 144.6945,   0.9973,   0.9210,   7.0000],
    #        [ 70.6210, 173.2062, 169.2387, 327.5236,   0.9228,   0.9850,  16.0000],
    #        [ 72.2745, 174.2344, 167.7682, 325.6776,   0.8158,   0.9857,  16.0000]])
    
            # Perform non-maximum suppression
            keep_boxes = []
            while detections.size(0):
                # detections[0, :4].unsqueeze(0)是 tensor([[ 69.9790, 173.1447, 172.6293, 343.4247]])
                large_overlap = bbox_iou(detections[0, :4].unsqueeze(0), detections[:, :4]) > nms_thres  # 计算出每一个box 与box[0] 交并比
                # detections[0, :4]:第一个检测对象(框)的四个坐标;.unsqueeze(0) 是为了增加一个维度,方便后面的计算
                # detections[:, :4]:图片中的每一个检测对象的四个坐标
                label_match = detections[0, -1] == detections[:, -1]  # 判断每一个 box 的类别与 box[0]的类别
                # label_match 是 tensor([1, 0, 0, 1, 0, 1, 0, 1, 1], dtype=torch.uint8)
    
                # Indices of boxes with lower confidence scores, large IOUs and matching labels
                invalid = large_overlap & label_match  # 将的得到的两个列表求与,box中与box[0] 同时满足IOU和类别 box
                #  invalid =  tensor([1, 0, 0, 1, 0, 1, 0, 1, 1], dtype=torch.uint8)
    
    
                # invalid 是 tensor([1, 0, 0, 1, 0, 1, 0, 1, 1], dtype=torch.uint8)
                # 表示下标为 3 5 7 8 的box与下标为0 的box 具有相同的类别且满足交并比要求
                weights = detections[invalid, 4:5]  # 将box 的置信度当成box 的 weights(可靠性、正确的程度)使用
                # weights =  tensor([[0.9995], [0.9996], [0.9453], [0.9228], [0.8158]])
    
                # Merge overlapping bboxes by order of confidence
                detections[0, :4] = (weights * detections[invalid, :4]).sum(0) / weights.sum()  # 根据每一个box 的权重,进行加权求平均坐标
    # detections =  
    #tensor([[ 70.4133, 172.3040, 170.3421, 335.0493,   0.9995,   0.9933,  16.0000],
    #        [ 92.8356, 111.2542, 305.3051, 294.4642,   0.9927,   0.9998,   1.0000],
    #        [ 63.1184, 116.6243, 311.2260, 294.3138,   0.9900,   0.9996,   1.0000],
    #        [ 68.8240, 157.3179, 172.9849, 340.8053,   0.9996,   0.9880,  16.0000],
    #        [254.1283,  98.5848, 373.9042, 144.4332,   0.9970,   0.9423,   7.0000],
    #        [ 70.7442, 184.7154, 168.4275, 335.5417,   0.9453,   0.9894,  16.0000],
    #        [253.6593,  95.9781, 372.7262, 144.6945,   0.9973,   0.9210,   7.0000],
    #        [ 70.6210, 173.2062, 169.2387, 327.5236,   0.9228,   0.9850,  16.0000],
    #        [ 72.2745, 174.2344, 167.7682, 325.6776,   0.8158,   0.9857,  16.0000]])
    
                keep_boxes += [detections[0]]  # 访问刚才生成的box, 将其(这个目标 box)添加到 keep_boxes 中。最终这个keep_boxes 会存放所有需要的box框
                # keep_boxes =  [tensor([ 70.4133, 172.3040, 170.3421, 335.0493,   0.9995,   0.9933,  16.0000])]
                detections = detections[~invalid]  # invalid 是一个列表,表示与box[0]重叠的目标,形如[1, 0, 0, 1, 1,0],
                #  这里取反再赋值就是 将detectioins 的值更新为 刚才没有分析过的 box, 对没有分析过的box 继续查找重叠,加权合并重叠。
                # 直到 detections 中没有任何 box
    # detections =  tensor([[ 92.8356, 111.2542, 305.3051, 294.4642,   0.9927,   0.9998,   1.0000],
    #        [ 63.1184, 116.6243, 311.2260, 294.3138,   0.9900,   0.9996,   1.0000],
    #        [254.1283,  98.5848, 373.9042, 144.4332,   0.9970,   0.9423,   7.0000],
    #        [253.6593,  95.9781, 372.7262, 144.6945,   0.9973,   0.9210,   7.0000]])
            if keep_boxes:
                output[image_i] = torch.stack(keep_boxes)  # 将一张图片的检测结果 keep_boxes (多个box)打包作为一张图片的检测结果。
                # output[image_i] 一张图片的加测结果
    
        return output  # 多张图片的检测结果
    
    

    2. 理解如下:

    输入:

    prediction: 存放的是多个图片的检测结果,每张图片检测到多个目标框,每个目标框有多个值,依次是x y w h 边框置信度 80个类别的置信度。[[120 120 15 15 0.8 0.1 0.2 0.3……], [……], [……]],这里要理解成每一box边框只有一个边框置信度,当有80个类别置信度,最后的置信度这两个置信度的乘积。
    conf_thres: 置信度阈值,是边框置信度和类别置信度的乘积。
    nms_thres: 交并比阈值,合并相同类别的box时,同时需要满足的最低的交并比要求。

    输出:边框置信度 类别置信度 类别index

    函数算法:

    1. 遍历多张图片中的一张图片的检测结果
    2. 计算这一张图片中,每个box 的置信度(边框置信度 乘以 80个类别的置信度),只保留使得置信度最大的那个类别,将所有的box 按照置信度降序排。(每个box 只保留使置信度最大的类别,都不用考虑阈值参数)这样就可以得到 每一个box 的最大的那个置信度和 类别
    3. 每个box 只保留使得置信度最大的 边框执行度和类别执行度,以及类别index
    4. 对所有的目标box进行遍历,找到所有类别相同且满足IOU的这列框,对这些框进行加权 merge ,加权合并的框当做最后的框输出。这样就找到了该图片的所有输出框了。

    1.12. def build_targets(pred_boxes, pred_cls, target, anchors, ignore_thres): 看不懂

    end

    展开全文
  • 怎样理解Linux的文件系统

    千次阅读 2015-08-19 15:19:47
    怎样理解Linux的文件系统  Linux所有文件都从root开始,用'/'代表, 并且延伸到子目录。DOS/Windows有不同的分区同时目录都存于分区上。Linux则通过'加载'的方式把所有分区都放置在root下制定的目录里。...
  • 关于文件系统的一些理解

    千次阅读 2017-09-07 13:21:08
    文件系统无非是组织文件如何储存起来 ...现在什么1.5倍之类的概念不太适用了,要看具体使用,对于大型科学计算可以给大一点,因为对性能要求不高,对于数据库之类的,呵呵,没有内存就别玩,放到swap上,性能
  • 简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段  2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句,再将...
  • FILE 及 文件句柄的理解

    千次阅读 2014-09-28 09:02:03
    当你读或写一个文件时,必须先通知系统,告诉他你的举动,这便是一个打开文件的过程。在这里说写一个文件(w方式),如果文件不存在,便创建一个文件,失败那就不用说拉,如果成功拉呢?系统将怎样管理你的文件(你...
  • 深入理解 WIN32 PE 文件格式

    千次阅读 2016-11-28 15:57:26
    深入理解 WIN32 PE 文件格式
  • 深入理解HDFS:Hadoop分布式文件系统

    万次阅读 多人点赞 2016-07-15 22:33:44
    文本详细介绍了HDFS中的许多概念,对于理解Hadoop分布式文件系统很有帮助。1. 介绍在现代的企业环境中,单机容量往往无法存储大量数据,需要跨机器存储。统一管理分布在集群上的文件系统称为分布式文件系统。而一旦...
  • Java虚拟机中定义的Class文件格式。每一个Class文件都对应着唯一一个类或接口的定义信息,但是相对地,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。我们只是通俗地将任意一个有效...
  • 简直不要太硬了!一文带你彻底理解文件系统

    万次阅读 多人点赞 2020-03-25 13:19:06
    对于一些应用程序来说,存储空间的大小是充足的,但是对于其他一些应用程序,比如航空订票系统、银行系统、企业记账系统来说,这些容量又显得太小了。 第二个问题是,当进程终止时信息会丢失。对于一些应用程序...
  • YOLOV3实战4:Darknet中cfg文件说明和理解

    万次阅读 多人点赞 2018-10-12 09:36:17
    大家好,我是小p,从今天起,将逐渐从源码角度解析Darknet,欢迎加入对象检测群813221712讨论和交流,进群请看群公告! 今天将要说明的是Darknet中的cfg文件,废话少说,直接干!(以cfg/yolov3.cfg为例,其它类似)...
  • 在研究图片服务器问题时,了解到现在很多大公司基本上都是用分布式文件系统来存储海量小文件,比如Facebook有haystack,淘宝有TFS,京东有JFS。最近在研究TFS,结合之前学习的linux下的inode相关知识,了解到在ext...
  • 对IOC的相关理解总结

    万次阅读 多人点赞 2020-04-20 13:54:07
    (一)理解IoC,即“控制反转” (二)IoC具体做什么? (三)理解IoC和DI的关系 二、对IOC容器初始化的理解 四、对DI依赖注入的理解(主要是) 参考书籍、文献和资料 一、对IOC和DI的基本认识 (一)理解IoC...
  • pandas读取excle和csv文件常见用法 https://blog.csdn.net/geekleee/article/details/52903082
  • ldiskfs(有些时候被错误地称为Linux ext4文件系统)是对Linux ext3文件系统的打了很多补丁的一个版本,由Sun Microsystems公司开发和维护。ldiskfs是Linux ext3和ext4文件系统的超集。现在它只被Lustre文件系统用在...
  • 文章目录COCO数据集的标签文件 .json1. 读取文件,显示文件中的信息,代码如下:2. json 中文件的类型如下:3. 字典的长度是:4. 字典的 key 有:5. info 对应键值的内容为:6. images 对应键值的 部分内容为:7. ...
  • 2.2 常量池 紧跟着次主版本号的是常量池入口,常量池可以理解为class文件中的资源仓库。每个class的常量池大小都可能不一样,所以在常量池入口需要放置一项u2类型的数据,代表常量池容量计数器。注意,这个容量...
  • 理解ROS节点 1.图概念概述 Nodes:节点,一个节点即为一个可执行文件,它可以通过ROS与其它节点进行通信。Messages:消息,消息是一种ROS数据类型,用于订阅或发布到一个话题。 Topics:话题,节点可以发布消息到...
  • “Ceph是一个开源的、统一的、分布式的存储系统”,这是我们宣传Ceph时常说的一句话,其中“统一”是说Ceph可以一套...本文旨在让小白用户理解Ceph的块存储、文件系统存储和对象存储接口。 一. Ceph的块设备存...
  • 检测并删除被占用的文件

    千次阅读 热门讨论 2014-06-21 10:54:43
    在操作系统使用过程中,经常会遇到一些文件被某些程序占用而无法被删除的事情。这个时候,如果是手动进行的删除可能影响还小,因为有很多方式可以解除引用,比如借助于其它的某软件工具。但是在实际编程中,如果给一...
  • 本文旨在让小白用户理解Ceph的块存储、文件系统存储和对象存储接口。 一. Ceph的块设备存储接口 什么是块设备? 块设备是i/o设备中的一类,是将信息存储在固定大小的块中,每个块都有自己的地址,还可以在设
  • 1) 理解绕过Content-Type检测文件类型上传的原理 2) 学习绕过Content-Type检测文件类型上传的过程 [实验原理] 当浏览器在上传文件到服务器的时候,服务器对所上传文件的Content-Type类型进行检测,如果是白名单...
  • 对于编译原理的理解

    千次阅读 2018-03-15 20:42:31
    今天组长教育了一下整个程序的编译过程,感觉自己对于这块了解还是很少,有许多知识之前知道,现在忘记了,还有很多规则只是知道,但并不知道它为什么要这样写,所以再次记录一下,有什么问题或者错误希望大家在评论...
  • 我们先来详细分析“方法一”的操作: ...2.编译完程序后,在工程目录的output文件夹中找到编译后生产的.hex文件; 用 notepad++ 或者 UltraEdit 打开 程序 的.hex文件 hex文件格式: (1)
  • 理解Java死锁之死锁检测

    千次阅读 2018-06-19 09:15:41
    看此文章前请先了解之前一篇文章 “Java死锁之理解死锁” 中的死锁示例我们在开发中应该尽量避免死锁,但是如果真的有死锁产生那么我们怎么在一个复杂的项目中快速的找到死锁产生的原因呢?我大概总结了一下常用的几种...
  • 对于断言 ASSERT 的理解

    千次阅读 2014-03-05 18:03:54
    昨天看了《代码大全》的“防御式编程”章节,解惑了长期以来自己对于断言的理解。 书中给了使用断言的指导意见,如下 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况。避免把需要执行的...
  • :个人理解为切换后 hostM1 跟 hostM2 互换角色(切换记录为 dnindex.properties 文件的内容 host1=0 更改为 host1=1 ),根据 balance=1 的规则,备用主是可以做为读的,故 Master1 重新启动后 , 此时 hostM1 、 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 310,130
精华内容 124,052
关键字:

对于文件检验的理解