2024年11月

在网上购物时候,不止可以通过名称搜索商品,也可以拍照上传图片搜索商品。比如某宝上拍个图片就能搜索到对应的商品。

腾讯、阿里都提供了类似的图像搜索服务,这类服务原理都差不多:

  • 在一个具体的图库上,新增或者删除图片。
  • 通过图片搜索相似的图片。

本文对接的是
腾讯云的图像搜索

添加配置

添加 maven 依赖:

<dependency>
    <groupId>com.tencentcloudapi</groupId>
    <artifactId>tencentcloud-sdk-java</artifactId>
    <version>3.1.1129</version>
</dependency>

引入配置:

tencentcloud:
  tiia:
    secretId: ${SECRET_ID}
    secretKey: ${SECRET_KEY}
    endpoint: tiia.tencentcloudapi.com
    region: ap-guangzhou
    groupId: test1

secretId 和 secretKey 都是在
API秘钥
地址:
https://console.cloud.tencent.com/cam/capi
,groupId 是图库 id。

配置 bean

@Data
@Configuration
@ConfigurationProperties("tencentcloud.tiia")
public class TencentCloudTiiaProperties {

    private String secretId;

    private String secretKey;

    private String endpoint = "tiia.tencentcloudapi.com";

    private String region = "ap-guangzhou";

    private String groupId;

}
@Configuration
@ConditionalOnClass(TencentCloudTiiaProperties.class)
public class TencentCloudTiiaConfig {

    @Bean
    public TiiaClient tiiaClient(TencentCloudTiiaProperties properties) {
        Credential cred = new Credential(properties.getSecretId(), properties.getSecretKey());
        HttpProfile httpProfile = new HttpProfile();
        httpProfile.setEndpoint(properties.getEndpoint());
        ClientProfile clientProfile = new ClientProfile();
        clientProfile.setHttpProfile(httpProfile);
        TiiaClient client = new TiiaClient(cred, properties.getRegion(), clientProfile);
        return client;
    }
}

tiiaClient 是搜图的核心,在后面新增、删除、搜索图片都会使用到。

图库更新

新建图库之后,需要将图片批量的导入到图库中。一般开始会批量将上架的图片批量导入到图片库,一般只需要操作一次。

商品有修改、新增、下架操作时,图片也需要有对应的更新操作。但是每次都更新都同步更新操作,可能会导致数据库频繁更新,服务器压力增加,需要改成,每次更新图片后,同步到缓存中,然后定时处理缓存的数据:

腾讯图像搜索没有图像更新接口,只有图像删除和新增的接口,那就
先调用删除,再调用新增的接口

删除图片

图片删除调用
tiiaClient.DeleteImages
方法,主要注意请求频率限制。

默认接口请求频率限制:10次/秒

这里就简单处理,使用线程延迟处理
Thread.sleep(100)
,删除图片只要指定 EntityId:

@Data
public class DeleteImageDTO {

    private String entityId;

    private List<String> picName;
}

如果指定 PicName 就删除 EntityId 下面的具体的图片,如果不指定 PicName 就删除整个 EntityId。

删除图片代码如下:

public void deleteImage(List<DeleteImageDTO> list) {
    if (CollectionUtils.isEmpty(list)) {
        return;
    }
    list.stream().forEach(deleteImageDTO -> {
        List<String> picNameList = deleteImageDTO.getPicName();
        if (CollectionUtils.isEmpty(picNameList)) {
            DeleteImagesRequest request = new DeleteImagesRequest();
            request.setGroupId(tiiaProperties.getGroupId());
            request.setEntityId(deleteImageDTO.getEntityId());
            try {
                // 腾讯限制qps
                Thread.sleep(100);
                tiiaClient.DeleteImages(request);
            } catch (TencentCloudSDKException | InterruptedException e) {
                log.error("删除图片失败, entityId {} 错误信息 {}", deleteImageDTO.getEntityId(), e.getMessage());
            }
        } else {
            picNameList.stream().forEach(picName -> {
                DeleteImagesRequest request = new DeleteImagesRequest();
                request.setGroupId(tiiaProperties.getGroupId());
                request.setEntityId(deleteImageDTO.getEntityId());
                request.setPicName(picName);
                try {
                    Thread.sleep(100);
                    tiiaClient.DeleteImages(request);
                } catch (TencentCloudSDKException | InterruptedException e) {
                    log.error("删除图片失败, entityId {}, 错误信息 {}", deleteImageDTO.getEntityId(), picName, e.getMessage());
                }
            });
        }
    });

}

新增图片

新增图片调用
tiiaClient.CreateImage
方法,这里也需要注意调用频率的限制。除此之外还有两个限制:

  • 限制图片大小不可超过 5M
  • 限制图片分辨率不能超过分辨率不超过 4096*4096

既然压缩图片需要耗时,那就每次上传图片先压缩一遍,这样就能解决调用频率限制的问题。压缩图片引入 thumbnailator:

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.20</version>
</dependency>

压缩工具类:

/**
 * 图片压缩
 * @param url             图片url
 * @param scale           压缩比例
 * @param targetSizeByte  压缩后大小 KB
 * @return
 */
public static byte[] compress(String url, double scale, long targetSizeByte) {
    if (StringUtils.isBlank(url)) {
        return null;
    }
    long targetSizeKB = targetSizeByte * 1024;
    try {
        URL u = new URL(url);
        double quality = 0.8;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
        do {
            Thumbnails.of(u).scale(scale) // 压缩比例
                    .outputQuality(quality) // 图片质量
                    .toOutputStream(outputStream);
            long fileSize = outputStream.size();
            if (fileSize <= targetSizeKB) {
                return outputStream.toByteArray();
            }
            outputStream.reset();
            if (quality > 0.1) {
                quality -= 0.1;
            } else {
                scale -= 0.1;
            }
        } while (quality > 0 || scale > 0);
    } catch (IOException e) {
        log.error(e.getMessage());
    }
    return null;
}

通过缩小图片尺寸和降低图片质量将图片压缩到固定的大小,这里都会先压缩一遍。解决调用频率限制的问题。

限制图片的分辨率也是使用到 thumbnailator 里面的 size 方法。

thumbnailator 压缩图片和限制大小,不能一起使用,只能分来调用。

设置尺寸方法:

/**
 * 图片压缩
 * @param imageData
 * @param width           宽度
 * @param height          高度
 * @return
 */
public static byte[] compressSize(byte[] imageData,String outputFormat,int width,int height) {
    if (imageData == null) {
        return null;
    }
    ByteArrayInputStream inputStream = new ByteArrayInputStream(imageData);
    try {
        BufferedImage bufferedImage = ImageIO.read(inputStream);
        int imageWidth = bufferedImage.getWidth();
        int imageHeight = bufferedImage.getHeight();
        if (imageWidth <= width && imageHeight <= height) {
            return imageData;
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
        Thumbnails.of(bufferedImage)
                .size(width,height)
                .outputFormat(outputFormat)
                .toOutputStream(outputStream);
        return outputStream.toByteArray();
    } catch (IOException e) {
        log.error(e.getMessage());
    }
    return null;
}

这里的 width 和 height 并不是直接设置图片的长度和长度,而是不会超过这个长度和宽度。如果有一个超过限制大小,压缩尺寸,长宽比保持不变。

新增图片需要指定 EntityId、url 以及 picName。

@Data
public class AddImageDTO {

    private String entityId;

    private String imgUrl;

    private String picName;

}

解决了图片压缩问题,上传图片就比较简单了:

public void uploadImage(List<AddImageDTO> list) {
    if (CollectionUtils.isEmpty(list)) {
        return;
    }
    list.stream().forEach(imageDTO -> {
        String imgUrlStr = imageDTO.getImgUrl();
        if (StringUtils.isBlank(imgUrlStr)) {
            // 跳过当前元素
            return;
        }
        CreateImageRequest request = new CreateImageRequest();
        request.setGroupId(tiiaProperties.getGroupId());
        request.setEntityId(imageDTO.getEntityId());
        String imageUrl = imageDTO.getImgUrl();
        // 限制大小
        byte[] bytes = ImageUtils.compress(imageUrl,0.6,1024 * 5);
        String imageFormat = imageUrl.substring(imageUrl.lastIndexOf(".") + 1);
        // 限制分辨率
        bytes = ImageUtils.compressSize(bytes,imageFormat,4096,4096);
        request.setImageBase64(new String(Base64.encodeBase64(bytes), StandardCharsets.UTF_8));
        //JSONObject tagJson = new JSONObject();
        //tagJson.put("code","搜索字段");
        //request.setTags(JSONObject.toJSONString(tagJson));
        request.setPicName(imageDTO.getPicName());
        try {
            tiiaClient.CreateImage(request);
        } catch (TencentCloudSDKException e) {
            log.error("图像上传失败 error:{}", e.getMessage());
        }
    });
}

Tags 图片自定义标签,设置图片的参数,搜索的时候就可以根据参数搜索到不同的图片。

更新图片

一般商品更新,将数据存入缓存中:

  String value = "demo key";
  SetOperations<String, Object> opsForSet = redisTemplate.opsForSet();
  opsForSet.add(RedisKeyConstant.PRODUCT_IMAGE_SYNC_CACHE_KEY, value);

再定时执行任务:

public void syncImage() {
    while (true) {
        SetOperations<String, Object> operations = redisTemplate.opsForSet();
        Object obj = operations.pop(RedisKeyConstant.PRODUCT_IMAGE_SYNC_CACHE_KEY);
        if (obj == null) {
            log.info("暂未发现任务数据");
            return;
        }
        String pop = obj.toString();
        if (StringUtils.isBlank(pop)) {
            continue;
        }
        DeleteImageDTO deleteImageDTO = new DeleteImageDTO();
        deleteImageDTO.setEntityId(pop);
        try {
            this.deleteImage(Collections.singletonList(deleteImageDTO));
        } catch (Exception e) {
            log.error("删除图片失败,entityId {}",pop);
        }
        // todo 获取数据具体的数据
        String imageUrl="";
        // todo picName 需要全局唯一
        String picName="";

        AddImageDTO addImageDTO = new AddImageDTO();
        addImageDTO.setEntityId(pop);
        addImageDTO.setImgUrl(imageUrl);
        addImageDTO.setPicName(picName);
        try {
            this.uploadImage(Collections.singletonList(addImageDTO));
        } catch (Exception e) {
            log.error("上传图片失败,entityId {}",pop);
        }


    }
}

operations.pop
从集合随机取出一个数据并移除数据,先删除图片,再从数据库中查询是否存在数据,如果存在就新增图片。

搜索图片

图像搜索调用
tiiaClient.SearchImage
方法,需要传图片字节流,压缩图片需要文件后缀。

@Data
public class SearchRequest {

  private byte[] bytes;

  private String suffix;

}


public ImageInfo [] analysis(SearchRequest searchRequest) throws IOException, TencentCloudSDKException {
    SearchImageRequest request = new SearchImageRequest();
    request.setGroupId(tiiaProperties.getGroupId());
    // 筛选,对应上传接口 Tags
    //request.setFilter("channelCode=\"" + searchRequest.getChannelCode() + "\"");、
    byte[] bytes = searchRequest.getBytes();
    bytes = ImageUtils.compressSize(bytes,searchRequest.getSuffix(),4096,4096);
    String base64 = Base64.encodeBase64String(bytes);
    request.setImageBase64(base64);
    SearchImageResponse searchImageResponse = tiiaClient.SearchImage(request);
    return searchImageResponse.getImageInfos();
}

根据返回的 ImageInfos 数组获取到 EntityId,就能获取对应的商品信息了。

总结

对接图像搜索,主要是做图像的更新和同步操作。相对于每次更新就同步接口,这种方式对于服务器的压力也比较大,
先将数据同步到缓存中,然后在定时的处理数据
,而搜索图片对于数据一致性相对比较宽松,分散库写入的压力。

新增图片使用 thumbnailator 压缩图片和缩小图片,对于调用请求频率限制,新增图片每次都会压缩一次图片,每次压缩时间大概都大于 100ms,解决了请求频率限制的问题。而删除图片,就简单使用线程休眠的方式休眠 100ms。

做好图片更新的操作之后,搜索图库使用
tiiaClient.SearchImage
方法就能获取到对应的结果信息了。

Github示例

https://github.com/jeremylai7/springboot-learning/blob/master/springboot-test/src/main/java/com/test/controller/ImageSearchController.java

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: CNN Mixture-of-Depths

创新点


  • 提出新的卷积轻量化结构
    MoD
    ,在卷积块(
    Conv-Blocks
    )内通过动态选择特征图中的关键通道进行集中处理,提高效率。
  • CNN MoD
    保留了静态计算图,这提高了训练和推理的时间效率,而且不需要定制的
    CUDA
    内核、额外的损失函数或微调。
  • 通过将
    MoD
    与标准卷积交替使用,能够实现相等性能下的推理加速或相等推理速度下的性能提高。

CNN Mixture-of-Depths


MoD
由三个主要组件组成:

  1. 通道选择器:根据输入特征图与当前预测的相关性选择前
    \(k\)
    个最重要的通道。
  2. 卷积快:从现有架构(如
    ResNets

    ConvNext
    )中进行改编,旨在增强选定通道的特征。
  3. 融合算子:将处理后的通道加到特征图的前
    \(k\)
    个通道上。

通道选择器

通道选择器主要分为两个阶段:

  1. 自适应通道重要性计算:通过自适应平均池化压缩输入特征图,随后通过一个具有瓶颈设计的两层全连接网络进行处理,设定
    \(r = 16\)
    ,最后通过
    sigmoid
    激活函数生成一个分数向量
    \(\mathbf{s} \in \mathbb{R}^C\)
    ,量化了相应通道的重要性。
  2. Top-k
    通道选择与路由:利用重要性分数
    \(\mathbf{s}\)
    选择前
    \(k\)
    个通道输入卷积块处理,原始特征图
    \(X\)
    则直接传递融合算子。

这个选择过程使得通道选择器能够高效地管理计算资源,同时保持固定的计算图,从而实现动态选择要处理的通道。

动态通道处理

每个卷积块中处理的通道数量
\(k\)
由公式
\(k = \lfloor \frac{C}{c} \rfloor\)
决定,其中
\(C\)
表示该块的总输入通道数,
\(c\)
是一个超参数,用于确定通道减少的程度。例如在一个标准的
ResNet
瓶颈块中,通常处理
1024
个通道,设置
\(c = 64\)
会将处理减少到仅
16
个通道(
\(k = 16\)
)。

通过实验发现,超参数
\(c\)
应设置为第一卷积块中输入通道的最大数量,并在整个
CNN
中的每个
MoD
块中保持相同。例如,
ResNet

\(c = 64\)
MobileNetV2

\(c = 16\)

卷积块的最后一步涉及将处理后的通道与从自适应通道重要性计算中获得的重要性评分相乘,确保在训练过程中梯度能够有效地传递回通道选择器,这是优化选择机制所必需的。

融合机制

将处理后的特征添加到
\(X\)
的前
\(k\)
个通道中,保留其余未处理的通道。融合后的特征图
\(\bar{X}\)
具有与原始输入
\(X\)
相同的通道数
\(C\)
,从而保留了后续层所需的维度。

论文在实验中测试了多种将处理后的通道重新集成到特征图
\(X\)
中的策略,包括将处理后的通道添加回其原始位置,但结果并未显示任何改进。实验表明,始终在特征图中使用相同位置来处理信息似乎是有益的,将处理后的通道添加到后
\(k\)
个通道中得到了与添加到前
\(k\)
个通道时相当的结果。

集成到
CNN
结构

MoD
可以集成到各种
CNN
架构中,例如
ResNets

ConvNext

VGG

MobileNetV2,
这些架构被组织成包含多个相同类型(即输出通道数相同)的卷积块(
Conv-Blocks
)的模块。

实验表明,交替使用
MoD
块和标准卷积块在每个模块中是一种最有效的集成方法。需要注意的是,
MoD
块替换每第二个卷积块,从而保持原始架构的深度(例如,
ResNet50
中的
50
层)。每个模块以一个标准块开始,例如
BasicBlock
,然后是一个
MoD
块。

这种交替模式表明,网络能够处理显著的容量减少,只要定期进行全容量卷积。此外,该方法确保
MoD
块不会干扰通常发生在每个模块的第一个块中的空间维度缩减卷积。

主要实验




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.

给网站免费升级HTTPS协议,可以通过申请并部署免费的SSL证书来实现。以下是一个详细的步骤指南:

一、申请免费SSL证书
选择证书颁发机构:
可以选择像JoySSL这样的公益项目,它提供免费、自动化的SSL/TLS证书颁发服务,适用于各种规模的网站。

免费SSL证书申请入口
提交申请:
登录所选证书颁发机构的官方网站,并创建一个账号,注:在注册的过程中需要填写注册码
230922
来获取免费证书申请权限。
根据需求选择合适的SSL证书类型,如单域名证书、多域名证书或通配符证书。
提交申请,并验证域名的所有权。这通常涉及DNS记录验证、文件验证或邮箱验证等方式。
下载证书:
验证通过后,证书颁发机构会签发证书。
下载收到的SSL证书文件,并解压备用。

二、部署SSL证书
登录服务器:
登录到托管网站的服务器。
上传证书:
将下载的SSL证书文件上传到服务器。
配置Web服务器:
根据所使用的Web服务器(如Apache、Nginx或IIS),修改相应的配置文件。
设置SSL模块和证书路径。
启用HTTPS监听端口(默认为443)。
重定向HTTP请求:
在Web服务器配置中设置规则,将所有HTTP请求自动重定向到对应的HTTPS URL。

三、更新网站链接
检查并更新内部链接:
确保网站上的所有内部链接(包括页面间的链接、CSS、JavaScript、图片等)都使用HTTPS协议。
如果存在混合内容(即页面通过HTTPS加载,但包含HTTP资源引用),浏览器可能会显示警告,影响用户体验和安全性。
使用开发者工具检查:
可以使用浏览器的开发者工具来检查并修正这些问题。

四、其他注意事项
备份网站数据:
在进行任何更改之前,备份网站数据以防万一。
定期更新证书:
免费SSL证书通常有一定的有效期。在证书到期之前,需要重新申请并部署新的证书。
监控和维护:
定期监控SSL证书的有效期和安全性。
使用在线SSL测试工具检查配置是否正确。

通过以上步骤,即可将网站从HTTP免费升级为HTTPS,从而享受加密通信带来的数据安全和用户信任提升。

最近微信出了linux版,用vmware装linux不过瘾,把一台闲置的笔记本装上了Manjaro KDE Plasma,经过一段时间的发展,Linux桌面可用性大大提高。
Kindle->Kindle Mate->Anki这条路在linux下
我用
Kindle ->
KindleVocab
->Anki这么代替了之后,
其他软件都能凑合用,加之用了电信的天翼云电脑后觉得又补全了几乎所有的缺憾

感谢信创,感谢国家,国内大公司出的软件都有信创包了

但是

天翼云电脑只有deb包(
https://desk.ctyun.cn/html/download/)

aur上的也安装不成功

我对yay命令不熟,只有用笨办法解决
首先是下载deb包

装了dpkg
安装失败

看失败信息,把这些包查了下chatgpt后都补上

然后用dpkg的强制安装。虽然报错,但是功能基本正常

前言

最近阅读
Aravis
源码,其中大量运用了GObject,于是打算学习一下。

此系列笔记仅主要面向初学者,不会很深入探讨源码的细节,专注于介绍GObject的基本用法。

此系列笔记参考
GObject Tutorial for beginners

本文可在
个人博客
中阅读,体验更加

套个盾:文中定义的名词只是为了更好地理解GObject,不具备权威性。

类和实例

在GObject中,每个可实例化类类型都与两个结构体相关联:一个是类结构体,一个是实例结构体。

  • 类结构体会被注册到类型系统中(具体注册方式在
    下一节
    讨论),在
    g_object_new
    首次调用时,类型系统会检查相应的类结构体是否已经被初始化为一个类变量,没有则创建并初始化。此后所有该类的实例变量都将共享这个已初始化的类变量。每个类变量只会被创建一次。
  • 每次调用
    g_object_new
    时都会创建实例变量。

在GObject系统中,类结构体和实例结构体都会被实例化,在内存中占有特定的空间。为了便于描述,我们将分配给类结构体的实例称为“类变量”,而分配给实例结构体的实例称为“实例变量”。

GObject实例的结构体定义如下

//file: gobject.h
typedef struct _GObject  GObject;
struct  _GObject
{
  GTypeInstance  g_type_instance;
  
  /*< private >*/
  guint          ref_count;  /* (atomic) */
  GData         *qdata;
};

GObject类的结构体定义如下(我们可以先不用了解结构的细节):

//file: gobject.h
typedef struct _GObjectClass             GObjectClass;
struct  _GObjectClass
{
  GTypeClass   g_type_class;

  /*< private >*/
  GSList      *construct_properties;

  /*< public >*/
  /* seldom overridden */
  GObject*   (*constructor)     (GType                  type,
                                 guint                  n_construct_properties,
                                 GObjectConstructParam *construct_properties);
  /* overridable methods */
  void       (*set_property)		(GObject        *object,
                                         guint           property_id,
                                         const GValue   *value,
                                         GParamSpec     *pspec);
  void       (*get_property)		(GObject        *object,
                                         guint           property_id,
                                         GValue         *value,
                                         GParamSpec     *pspec);
  void       (*dispose)			(GObject        *object);
  void       (*finalize)		(GObject        *object);
  /* seldom overridden */
  void       (*dispatch_properties_changed) (GObject      *object,
					     guint	   n_pspecs,
					     GParamSpec  **pspecs);
  /* signals */
  void	     (*notify)			(GObject	*object,
					 GParamSpec	*pspec);

  /* called when done constructing */
  void	     (*constructed)		(GObject	*object);

  /*< private >*/
  gsize		flags;

  gsize         n_construct_properties;

  gpointer pspecs;
  gsize n_pspecs;

  /* padding */
  gpointer	pdummy[3];
};

下面使用一个简单示例,来演示GObject的类和实例的使用

//file: example01.c
#include <glib-object.h>

int main (int argc, char **argv) 
{

    GObject* instance1,* instance2;     //指向实例的指针
    GObjectClass* class1,* class2;      //指向类的指针
   
    instance1 = g_object_new (G_TYPE_OBJECT, NULL);
    instance2 = g_object_new (G_TYPE_OBJECT, NULL);
    g_print ("The address of instance1 is %p\n", instance1);
    g_print ("The address of instance2 is %p\n", instance2);
 
    class1 = G_OBJECT_GET_CLASS (instance1);
    class2 = G_OBJECT_GET_CLASS (instance2);
    g_print ("The address of the class of instance1 is %p\n", class1);
    g_print ("The address of the class of instance2 is %p\n", class2);
 
    g_object_unref (instance1);
    g_object_unref (instance2);

    return 0;
}

其中:

  • g_object_new
    函数创建实例变量并返回指向它的指针。在实例变量第一次被创建之前,它对应的类变量也会被创建并初始化。
  • 参数
    G_TYPE_OBJECT
    是GObject基类的类型标识符,这是GObject类型系统的核心,所有其他GObject类型都从这个基类型派生。

  • G_OBJECT_GET_CLASS
    返回指向参数所属类变量的指针
  • g_object_unref
    会销毁实例变量并释放内存。

输出:

The address of instance1 is 0x55d3ddc05600
The address of instance2 is 0x55d3ddc05620
The address of the class of instance1 is 0x55d3ddc05370
The address of the class of instance2 is 0x55d3ddc05370

可以发现,两个实例变量的地址不同,但两个实例变量对应的类变量的地址相同,因为两个实例变量共享一个类变量

引用计数

引用计数机制的概念在此不做介绍

在GObject中,GObject实例具有引用计数机制:

//file: example02.c
#include <glib-object.h>
 
static void show_ref_count (GObject* instance) 
{
    if (G_IS_OBJECT (instance))
        /* Users should not use ref_count member in their program. */
        /* This is only for demonstration. */
        g_print ("Reference count is %d.\n", instance->ref_count);
    else
        g_print ("Instance is not GObject.\n");
}
 
int main (int argc, char **argv) 
{
    GObject* instance;

    instance = g_object_new (G_TYPE_OBJECT, NULL);
    g_print ("Call g_object_new.\n");
    show_ref_count (instance);
    g_object_ref (instance);
    g_print ("Call g_object_ref.\n");
    show_ref_count (instance);
    g_object_unref (instance);
    g_print ("Call g_object_unref.\n");
    show_ref_count (instance);
    g_object_unref (instance);
    g_print ("Call g_object_unref.\n");
    g_print ("Now the reference count is zero and the instance is destroyed.\n");
    g_print ("The instance memories are possibly returned to the system.\n");
    g_print ("Therefore, the access to the same address may cause a segmentation error.\n");

    return 0;
}

其中:

  • g_object_new
    创建一个实例变量,然后将变量的引用计数置为1
  • g_object_ref
    将其引用计数加1
  • g_object_unref
    将引用计数减1,如果此时引用计数为0,则析构变量。

输出:

Call g_object_new.
Reference count is 1.
Call g_object_ref.
Reference count is 2.
Call g_object_unref.
Reference count is 1.
Call g_object_unref.
Now the reference count is zero and the instance is destroyed.
The instance memories are possibly returned to the system.
Therefore, the access to the same address may cause a segmentation error.

初始化和析构过程

GObject初始化和销毁的实际过程比较复杂。以下是简单的描述,不做详细说明.

初始化

1.用类型系统注册GObject类型。这是在调用main函数之前的GLib的初始化过程中完成的。(如果编译器是gcc,则
__attribute__ ((constructor))
用于限定初始化函数。)
2.为GObjectClass和GObject结构分配内存
3.初始化GObjectClass结构内存。这个内存将是GObject的类变量。
4.初始化GObject结构内存。这个内存将是GObject的实例变量。

上述初始化过程在第一次调用
g_object_new
函数时执行。在第二次及后续调用
g_object_new
时,它只执行两个过程:①为GObject结构分配内存②初始化内存。

析构

1.销毁GObject实例。释放实例的内存

GObject变量类型是静态类型。静态类型永远不会破坏它的类。因此,即使被销毁的实例变量是最后一个,类变量仍然存在,直到程序终止。

参考文章

1.
GObject Tutorial for beginners

推荐

下一篇:
GObject学习笔记(二)类型创建与注册