本地 WordPress Docker 环境搭建

前段时间我换了一台电脑,打算重新弄一个方便一点的开发环境,于是用 docker compose 写了一套。

什么是 Docker?

我不太清楚现在站长们是不是都了解 Docker,为了避免有些人不了解,还是简单介绍一下

Docker 是一种开源的容器化平台,允许开发者将应用程序及其所有依赖项(库、环境变量、配置文件等)打包到一个标准化的单元中,称为"容器"。容器与虚拟机不同,它们共享主机操作系统内核,因此更加轻量级、启动更快且资源开销更小。Docker 提供了隔离的运行环境,确保了应用程序在不同环境中能够一致运行。

如果不太了解 Docker,可以简单认为这就是一个在你的电脑上运行的虚拟机。

什么是 Docker Compose?

由于运行 WordPress 需要搭建 php + mysql + nginx,因此需要多个容器环境。

Docker Compose 就是干这个用的,它可以定义和运行多容器,只需要用一个单独的 YAML 配置文件就可以定义整个应用栈的各个服务、网络配置、数据卷等,然后使用一条命令即可启动所有服务。

如果你也在用 Docker,那么可以用我提供的环境用于本地调试你的主题或插件。如果还没有用,强烈建议你了解一下 Docker。

用于本地的 WordPress 开发环境

将下面的文件保存为 docker-comcompose.yaml,放在一个你看的顺眼的文件夹中。


services:
  # Nginx 服务
  nginx:
    image: nginx:alpine
    container_name: lnmp_nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./www:/var/www/html
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/logs:/var/log/nginx
    depends_on:
      - php
    networks:
      - lnmp_network
  # PHP-FPM 服务 (包含常用扩展)
  php:
    build:
      context: .
      dockerfile: php.Dockerfile
    container_name: lnmp_php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/conf.d/custom.ini
    networks:
      - lnmp_network
  # MySQL 服务
  mysql:
    image: mysql:8.0
    container_name: lnmp_mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: app_db
      MYSQL_USER: app_user
      MYSQL_PASSWORD: userpassword
    volumes:
      - ./mysql/data:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - lnmp_network
  # phpMyAdmin (可选)
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: lnmp_phpmyadmin
    depends_on:
      - mysql
    environment:
      PMA_HOST: mysql
      PMA_PORT: 3306
    ports:
      - "8080:80"
    networks:
      - lnmp_network
networks:
  lnmp_network:
    driver: bridge

将下面的文件保存为 php.Dockerfile,放在 docker-compose.yaml 同级。


FROM php:8.2-fpm-alpine
# 安装常用扩展
RUN apk add --no-cache \
        freetype \
        libpng \
        libjpeg-turbo \
        freetype-dev \
        libpng-dev \
        libjpeg-turbo-dev \
    && docker-php-ext-configure gd \
        --with-freetype \
        --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd pdo pdo_mysql mysqli \
    && apk del --no-cache \
        freetype-dev \
        libpng-dev \
        libjpeg-turbo-dev
# 安装其他常用扩展
RUN docker-php-ext-install opcache && docker-php-ext-enable opcache
WORKDIR /var/www/html

如何启动

1. 下载 wordpress 源码到 www 目录

2. 将下面的配置保存为 app.conf,放在 nginx/conf.d 文件夹下。


server {
    listen 80;
    server_name localhost;
    root /var/www/html/wordpress;
    index index.php index.html;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    location ~ /\.ht {
        deny all;
    }
}

最后执行:

docker compose up -d

如何停止

docker compose down

喜欢折腾的可以拿去折腾,如果有什么不明白的,可以直接在评论区留言或者直接给我发信息。

AWS 免费的 EC2 实例停止原因

前段时间我被 aws 的广告吸引了,它提供免费一年的 EC2 实例。

我兴冲冲过去注册,然后选择了它提供的免费的 t2.micro 实例。

结果问题来了,当我放一个简单的网站上去,第二天我发现网站无法访问了!

我尝试 ssh 登陆主机,发现主机竟然也无法登陆了,看起来像是挂了。
于是进入 aws 后台重启了一下服务器,好了。

我以为这是偶然情况,结果没过两天,网站又无法访问了。

这让我觉得,大名鼎鼎的 aws,就这?试用体验极其糟糕!我转身就买了别的主机了(不过原因还是 aws 确实太贵了,个人站长真用不起)。

一段时间后,我又研究了一下 aws 的实例,发现我误会了它。aws 的稳定性应该是没得说的,然而它的 t 开头实例确实很坑!

t 开头的实例叫做“可突发性实例”,它的 CPU 是有额度的,当额度耗尽之后,就没办法处理任何事情了,机器就和挂了一样。

所以当你的网站有爬虫访问的时候,特别是这个免费的实例类型 CPU 非常小,额度很容易耗尽,然后网站就挂了。

因此试用的 t2.micro 类型大概就只能试用了,不能用来做网站,不然隔段时间挂一下这网站直接废了。

C++ new 分配的内存一定在堆上吗?

最近我突然想到 placement new 可以直接在指定位置直接构造对象而不分配内存,于是有了这篇文章。

1. 一般情况下,new 在堆上分配内存

在大多数情况下,new 确实会在 堆(heap) 上分配内存:

int* p = new int(42); // 在堆上分配内存
  • 堆内存 由 C++ 运行时库(如 malloc/free)管理。
  • 必须手动释放,否则会导致内存泄漏:
    delete p; // 释放堆内存

2. new 还可以在栈上分配内存

C++ 提供了 placement new(定位 new),允许在指定的内存地址上构造对象,而 不分配新内存。因此,我们可以让 new 在栈上分配内存:

#include <new>

void demo() {
    alignas(int) char buffer[sizeof(int)]; // 栈上的内存
    int* p = new (buffer) int(42); // 在栈上构造 int

    std::cout << *p << std::endl; // 输出 42

    // 不需要 delete,但要手动调用析构函数(如果是类对象)
    p->~int(); // 对于 int 这种 trivial 类型,可以省略
}

关键点

  • buffer 是栈上的数组,new (buffer) int 只是在该地址构造对象。
  • 不能使用 delete p,因为内存是栈上的,delete 会尝试释放堆内存,导致未定义行为(UB)。
  • 必须确保内存对齐(如 alignas(int)),否则某些 CPU 架构(如 ARM)会崩溃。

3. new 还可以在其他存储区域分配内存

除了堆和栈,new 还可以在 自定义内存池共享内存硬件寄存器映射内存 上分配内存,只要提供正确的地址即可。

示例:在静态存储区分配

#include <new>

char static_buffer[sizeof(int)]; // 静态存储区(全局变量)

int main() {
    int* p = new (static_buffer) int(100); // 在静态区构造对象
    std::cout << *p << std::endl;
    p->~int();
    return 0;
}
  • static_buffer 位于程序的 数据段(data segment),而不是堆或栈。

示例:在内存池中分配

#include <new>
#include <vector>

class MemoryPool {
public:
    void* allocate(size_t size) {
        return pool.data(); // 返回内存池地址
    }
private:
    std::vector<char> pool{1024}; // 模拟内存池
};

int main() {
    MemoryPool pool;
    int* p = new (pool.allocate(sizeof(int))) int(200); // 在内存池构造
    std::cout << *p << std::endl;
    p->~int();
    return 0;
}
  • 这里 new 在自定义的内存池上分配,而不是堆或栈。

4. 为什么需要 placement new

(1)避免堆分配的开销

  • 高频小对象(如游戏中的粒子系统)可以使用栈或内存池,减少 new/delete 的代价。

    (2)控制对象的内存位置

  • 嵌入式系统可能需要将对象放在特定地址(如硬件寄存器)。
  • 共享内存(IPC)需要在固定地址构造对象。

    (3)实现自定义内存管理

  • 内存池、对象池等技术依赖 placement new 在预分配的内存上构造对象。

因此,new 分配的内存不一定在堆上,它的行为取决于具体的使用方式。理解这一点有助于优化性能并避免未定义行为。

How to Properly Merge Defaults with User Settings in JavaScript

When working with JavaScript, we often need to set default configurations and allow users to override certain values. A common but flawed approach is using the logical OR (||) operator for defaults, which can lead to unexpected behavior. This article explains why || is problematic for object merging and how to correctly override defaults using the ES6 spread operator (...).

1. The Problem: Why || Falls Short

Consider this typical pattern:

const enabledEngines = this.settings?.enabledEngines || {
    baidu: true,
    google: true,
    bing: true,
    so: true,
    sogou: true
};

Key Issues with ||:

  1. || Only Checks for Falsy Values, Failing to Override false

    • If this.settings?.enabledEngines is { google: false }, the || operator won’t apply the fallback (since an object is truthy), incorrectly keeping google: true.
    • The fallback only triggers if the left side is undefined/null.
  2. No Partial Overrides

    • If a user only wants to disable one engine (e.g., { bing: false }), || forces a full replacement, discarding other defaults.

2. The Solution: Object Spread Operator (...)

The correct approach is to let user settings override defaults without discarding the entire object. The ES6 spread operator (...) solves this elegantly:

const enabledEngines = {
    baidu: true,
    google: true,
    bing: true,
    so: true,
    sogou: true,
    ...this.settings?.enabledEngines  // User settings override defaults
};

Why This Works:

  1. Correctly Handles false Overrides
    • If the user passes { google: false }, google is properly disabled while other defaults remain.
  2. Supports Partial Updates
    • Users only need to specify changes; unspecified keys keep their defaults.
  3. Safer Than ||
    • Even an empty object {} won’t trigger an unwanted fallback.

3. Real-World Examples

Case 1: User Disables Google

const userSettings = { google: false };

//  Wrong (||)
const badResult = userSettings || { google: true, bing: true };
// => { google: false } ( bing is lost!)

//  Right (...)
const goodResult = { google: true, bing: true, ...userSettings };
// => { google: false, bing: true } (Correct!)

Case 2: No User Settings Provided

const userSettings = undefined;

//  Wrong (||)
const badResult = userSettings || { google: true };
// => { google: true } (Works but inflexible)

//  Right (...)
const goodResult = { google: true, ...userSettings };
// => { google: true } (Same outcome, but consistent)

4. When Is || Acceptable?

While || isn’t ideal for objects, it’s still useful for:

  • Primitive defaults (e.g., string, number, boolean)
    const limit = userLimit || 10;  //  Fails if userLimit is 0!
  • Full replacements (not merging)
    const config = userConfig || defaultConfig;  // Replaces entirely

For object merging, always prefer ....

服务器时间不对,如何处理?

当服务器时间不正确时,可以按照以下步骤排查和修复:


1. 检查当前时间与时区

# 查看当前系统时间
date

# 查看时区(Linux)
timedatectl     # 或使用 `ls -l /etc/localtime`

# 查看时区(Windows)
systeminfo | find "时区"
  • 若时区错误:修正时区(见步骤2)。
  • 若时间错误但时区正确:可能是时间未同步(见步骤3)。

2. 修正时区

Linux

# 列出所有时区(如 Asia/Shanghai)
timedatectl list-timezones

# 设置时区
sudo timedatectl set-timezone Asia/Shanghai

# 手动更新硬件时钟(可选)
sudo hwclock --systohc

Windows

  • 通过图形界面:
    控制面板 > 时钟和区域 > 设置时间和日期 > 更改时区
  • 或使用命令行:
    tzutil /s "China Standard Time"

3. 同步网络时间

Linux(使用 NTP 服务)

# 安装/启动 NTP 服务(根据发行版选择)
sudo apt install ntpdate   # Debian/Ubuntu
sudo yum install ntp       # CentOS/RHEL

# 手动同步时间(以阿里云 NTP 为例)
sudo ntpdate ntp.aliyun.com

# 启用自动同步(使用 systemd-timesyncd 或 chrony)
sudo timedatectl set-ntp true

# 验证同步状态
timedatectl status

Windows

  • 图形界面:
    设置 > 时间和语言 > 日期和时间 > 自动设置时间
  • 或命令行强制同步:
    w32tm /resync

4. 检查硬件时钟(BIOS 时间)

  • Linux

    # 查看硬件时钟时间
    sudo hwclock --show
    
    # 将系统时间写入硬件时钟(解决重启后时间错误)
    sudo hwclock --systohc
  • Windows
    重启进入 BIOS 检查时间是否正确,或使用命令:

    # 同步硬件时钟
    w32tm /resync

5. 其他可能问题

  • 虚拟机时间漂移:安装虚拟机增强工具(如 VMware Tools/VirtualBox Guest Additions)。
  • NTP 服务冲突:确保只启用一个时间同步服务(如 ntpdchrony)。
  • 防火墙限制:确保 UDP 123 端口(NTP 端口)未被拦截。

6. 验证修复

# 再次检查时间
date
timedatectl status   # Linux

如果问题持续,尝试重启服务器或联系运维人员检查硬件时钟电池(CMOS 电池可能耗尽)。


通过以上步骤,绝大多数时间问题可以解决。根据服务器类型(物理机/虚拟机/云服务器)和操作系统选择对应方法。

bash array[@] 语法详解

最近我写的 bash 脚本中需要遍历数组,用到了 bash 中的 array[@] 这种语法。

在 Bash shell 脚本中,array[@] 是一种特殊的数组引用语法,它有以下几个重要特点:

基本含义

  • array[@] 表示引用数组 array 的所有元素
  • @ 是一个特殊的下标,表示"数组中的所有元素"

array[*] 的区别

  • array[@]array[*] 都表示数组的所有元素
  • 关键区别在于它们在引号中的展开方式:
    • "${array[@]}":每个元素保持独立,即使包含空格也会被正确处理
    • "${array[*]}":所有元素合并为一个字符串,用第一个字符的 IFS(内部字段分隔符)连接

使用示例

fruits=("apple" "banana" "cherry")

# 打印所有元素
echo "${fruits[@]}"  # 输出:apple banana cherry

# 在引号中使用
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done
# 输出:
# apple
# banana
# cherry

带空格的元素处理

当数组元素包含空格时,"${array[@]}" 能正确处理:

files=("file 1.txt" "file 2.txt")

# 正确方式 - 每个元素保持独立
for file in "${files[@]}"; do
    echo "$file"
done
# 输出:
# file 1.txt
# file 2.txt

# 错误方式 - 空格会导致元素被拆分
for file in ${files[@]}; do
    echo "$file"
done
# 输出:
# file
# 1.txt
# file
# 2.txt

for ... in 介绍

你可能注意到了,这里面用到了 for ... in 这种循环结构。

介绍数组遍历怎么少的了 for ... in 呢

Bash 中的 for...in 循环是一种常用的循环结构,用于遍历一组值或列表中的元素。

for ... in 基本语法

for variable in list
do
    commands
done

或者写在一行:

for variable in list; do commands; done

for ... in 使用示例

  1. 遍历固定列表

    for i in 1 2 3 4 5
    do
       echo "Number: $i"
    done
  2. 遍历字符串列表

    for color in red green blue
    do
       echo "Color: $color"
    done
  3. 使用通配符遍历文件

    for file in *.txt
    do
       echo "Text file: $file"
    done
  4. 使用命令输出作为列表

    for user in $(cat /etc/passwd | cut -d: -f1)
    do
       echo "User: $user"
    done
  5. 使用范围表达式

    for i in {1..5}
    do
       echo "Number: $i"
    done
  6. 带步长的范围表达式

    for i in {1..10..2}
    do
       echo "Odd number: $i"
    done

for ... in 特殊用法

  • 遍历位置参数

    for arg in "$@"
    do
      echo "Argument: $arg"
    done
  • 遍历数组

    arr=("apple" "banana" "cherry")
    for fruit in "${arr[@]}"
    do
      echo "Fruit: $fruit"
    done

注意事项

  1. list 中的元素默认以空格分隔
  2. 如果元素包含空格,应该用引号括起来
  3. dodone 是必须的关键字
  4. 循环变量不需要提前声明

最近发现一个今天吃什么的在线小工具

人生三大难题:早餐吃什么?午餐吃什么?晚餐吃什么?

你是不是也在为今天吃什么而苦恼?

我有个朋友给我推荐了个网站 https://zaixianjisuanqi.com/jin-tian-chi-shen-me

一看,做的还挺好!

试了一下,可以根据简单的需求来随机生成今天的菜单,感觉还蛮有用的。

你要是也经常做饭,然而不知道该吃什么,那么可以尝试一下这个工具。

php opcache 优化

opcache 优化建议

[Zend Opcache]
zend_extension=/www/server/php/84/lib/php/extensions/no-debug-non-zts-20240924/opcache.so
opcache.enable=1
opcache.memory_consumption=256          ; 增加到256MB,如果应用较大
opcache.interned_strings_buffer=32      ; 保持32MB,对于大型应用足够
opcache.max_accelerated_files=100000    ; 增加到100000,如果文件很多
opcache.revalidate_freq=60              ; 增加到60秒,减少检查频率
opcache.fast_shutdown=1
opcache.enable_cli=1                    ; 如果CLI脚本需要加速则保留
opcache.save_comments=0                 ; 禁用可节省内存
opcache.validate_timestamps=0           ; 生产环境建议禁用,更新后手动重置
opcache.jit_buffer_size=256m            ; 如果使用JIT且内存充足可增加
opcache.jit=1255                         ; 更激进的JIT模式(1255=函数+循环优化)
opcache.huge_code_pages=1               ; 启用大内存页支持(需系统支持)

; 文件缓存相关(可选)
;opcache.file_cache=/tmp/opcache        ; 启用文件缓存可减少内存使用
;opcache.file_cache_only=0              ; 同时使用内存和文件缓存
;opcache.file_cache_consistency_checks=1 ; 文件缓存一致性检查

extension=/www/server/php/84/lib/php/extensions/no-debug-non-zts-20240924/igbinary.so

关键调整说明:

  1. 生产环境建议

    • 设置 opcache.validate_timestamps=0 并在部署时手动清除缓存
    • 禁用 opcache.save_comments 可节省约5-10%内存
  2. JIT优化

    • 模式从1205改为1255可启用更全面的优化
    • 缓冲区增加到256MB(如果服务器内存充足)
  3. 性能提升

    • opcache.huge_code_pages=1 可提升性能(需系统配置大内存页)
    • 考虑启用文件缓存减少内存压力
  4. 监控建议

    • 使用 opcache_get_status() 监控内存使用情况
    • 确保 opcache.memory_consumption 不会频繁达到上限

opcache.revalidate_freq 含义

opcache.revalidate_freq=60 这个配置项控制的是 OPcache 检查PHP脚本是否被修改的时间间隔(单位为秒)。以下是详细解释:

作用原理

  1. 检查机制

    • 当这个值设为60时,OPcache会每隔60秒检查一次被缓存的PHP脚本文件是否在磁盘上被修改过
    • 如果发现文件被修改,会自动重新缓存新版本的文件
  2. validate_timestamps 的关系

    • 这个配置只有opcache.validate_timestamps=1(默认值)时才生效
    • 如果设置 validate_timestamps=0,则完全禁用检查,revalidate_freq 会失效

生产环境建议

场景 推荐配置 原因
开发环境 revalidate_freq=2 + validate_timestamps=1 需要频繁看到代码改动效果
生产环境 validate_timestamps=0 + 部署时手动重置 完全避免检查开销,最高性能
折中方案 revalidate_freq=60 + validate_timestamps=1 平衡性能与实时性

性能影响

  1. 设置较高值(如60)的优点

    • 减少磁盘I/O操作(每次检查都需要stat()系统调用)
    • 提升约5-10%的请求处理速度(测试数据)
  2. 潜在风险

    • 代码更新后最长需要等待60秒才能生效
    • 可通过opcache_reset()强制刷新缓存

最佳实践

  1. 生产环境建议禁用时间戳验证(validate_timestamps=0),通过部署脚本在更新后执行:
    sudo service php-fpm reload
  2. 如果必须启用检查,建议值:
    • 高流量站点:≥300秒
    • 普通站点:60-120秒
    • 开发环境:1-5秒

这个配置的调整需要权衡实时性性能,根据您的部署流程选择最适合的方案。

C++打印任意proto消息方法

在打印proto时,总需要将pb文件加入源代码,然后编译,然后再解析,实在太麻烦。

能不能直接根据proto文件直接输出明文消息呢?实际上是可以的!

void GetMessageTypeFromProtoFile(
        const std::string& proto_filename,
        google::protobuf::FileDescriptorProto* file_desc_proto) {
    using namespace google::protobuf;
    using namespace google::protobuf::io;
    using namespace google::protobuf::compiler;

    FILE* proto_file = fopen(proto_filename.c_str(), "r");
    {
        if (proto_file == NULL) {
            LOG(FATAL) << "Cannot open .proto file: " << proto_filename;
        }

        FileInputStream proto_input_stream(fileno(proto_file));
        Tokenizer tokenizer(&proto_input_stream, NULL);
        Parser parser;
        if (!parser.Parse(&tokenizer, file_desc_proto)) {
            LOG(FATAL) << "Cannot parse .proto file:" << proto_filename;
        }
    }
    fclose(proto_file);

    // Here we walk around a bug in protocol buffers that
    // |Parser::Parse| does not set name (.proto filename) in
    // file_desc_proto.
    if (!file_desc_proto->has_name()) {
        file_desc_proto->set_name(proto_filename);
    }
}

这个函数的输入是一个 .proto 文件的文件名。输出是一个 FileDescriptorProto 对象。这个对象里存储着对 .proto 文件解析之后的结果。我们接下来用这些结果动态生成某个 protocol message 的 instance(或者用C++术语叫做object)。然后可以调用这个 instance 自己的 ParseFromArray/String 成员函数,来解析数据文件中的每一条记录的内容。请看如下代码:

//-----------------------------------------------------------------------------
// Print contents of a record file with following format:
//
//   { <int record_size> <KeyValuePair> }
//
// where KeyValuePair is a proto message defined in mpimr.proto, and
// consists of two string fields: key and value, where key will be
// printed as a text string, and value will be parsed into a proto
// message given as |message_descriptor|.
//-----------------------------------------------------------------------------

void PrintDataFile(
        const std::string& data_filename,
        const google::protobuf::FileDescriptorProto& file_desc_proto,
        const std::string& message_name) {
    const int kMaxRecieveBufferSize = 32 * 1024 * 1024;  // 32MB
    static char buffer&#91;kMaxRecieveBufferSize];

    std::ifstream input_stream(data_filename.c_str());
    if (!input_stream.is_open()) {
        LOG(FATAL) << "Cannot open data file: " << data_filename;
    }

    google::protobuf::DescriptorPool pool;
    const google::protobuf::FileDescriptor* file_desc = pool.BuildFile(file_desc_proto);
    if (file_desc == NULL) {
        LOG(FATAL) << "Cannot get file descriptor from file descriptor"
                   << file_desc_proto.DebugString();
    }

    const google::protobuf::Descriptor* message_desc =
            file_desc->FindMessageTypeByName(message_name);
    if (message_desc == NULL) {
        LOG(FATAL) << "Cannot get message descriptor of message: " << message_name;
    }

    google::protobuf::DynamicMessageFactory factory;
    const google::protobuf::Message* prototype_msg = factory.GetPrototype(message_desc);
    if (prototype_msg == NULL) {
        LOG(FATAL) << "Cannot create prototype message from message descriptor";
    }
    google::protobuf::Message* mutable_msg = prototype_msg->New();
    if (mutable_msg == NULL) {
        LOG(FATAL) << "Failed in prototype_msg->New(); to create mutable message";
    }

    uint32_t proto_msg_size;  // uint32 is the type used in reocrd files.
    for (;;) {
        input_stream.read((char*)&proto_msg_size, sizeof(proto_msg_size));

        if (proto_msg_size > kMaxRecieveBufferSize) {
            LOG(FATAL) << "Failed to read a proto message with size = " << proto_msg_size
                       << ", which is larger than kMaxRecieveBufferSize (" << kMaxRecieveBufferSize
                       << ")."
                       << "You can modify kMaxRecieveBufferSize defined in " << __FILE__;
        }

        input_stream.read(buffer, proto_msg_size);
        if (!input_stream)
            break;

        if (!mutable_msg->ParseFromArray(buffer, proto_msg_size)) {
            LOG(FATAL) << "Failed to parse value in KeyValuePair:" << pair.value();
        }

        std::cout << mutable_msg->DebugString();
    }

    delete mutable_msg;
}

这个函数需要三个输入:
1)数据文件的文件名
2)之前GetMessageTypeFromProtoFile函数返回的FileDescriptorProto对象
3)数据文件中每条记录的对应的protocol message 的名字(注意,一个 .proto 文件里可以定义多个 protocol messages,所以我们需要知道数据记录对应的具体是哪一个 message)。

以上代码中利用了 DescriptorPool 从 FileDescriptorProto 解析出 FileDescriptor(描述 .proto 文件中所有的 messages)。然后用 DynamicMessageFactory 从 FileDescriptor 里找到我们关注的那个 message 的 MessageDescriptor。接下来,我们利用 DynamicMessageFactory 根据 MessageDescriptor 得到一个 prototype message instance。注意,这个 instance 是不能往里面写内容的(immutable)。我们需要调用其 New 成员函数,来生成一个 mutable 的 instance。

有了一个对应数据记录的 message instance,接下来就好办了。我们读取数据文件中的每条记录。注意:此处我们假设数据文件中以此存放了一条记录的长度,然后是记录内容,接下来是第二条记录的长度和内容,以此类推。所以在上述函数中,我们循环的读取记录长度,然后解析记录内容。值得注意的是,解析内容利用的是 mutable message instance 的 ParseFromArrary 函数;它需要知道记录的长度。因此我们必须在数据文件中存储每条记录的长度。

接下来这段程序演示如何调用 GetMessageTypeFromProtoFile 和 PrintDataFile:

int main(int argc, char** argv) {
    std::string proto_filename, message_name;
    std::vector<string> data_filenames;
    ::google::protobuf::FileDescriptorProto file_desc_proto;
    ParseCmdLine(argc, argv, &proto_filename, &message_name, &data_filenames);
    GetMessageTypeFromProtoFile(proto_filename, &file_desc_proto);
    for (int i = 0; i < data_filenames.size(); ++i) {
        PrintDataFile(data_filenames[i], file_desc_proto, message_name);
    }
    return 0;
}

leetcode接雨水

接雨水

这个题目比较火,原因是字节总拿这个题面试。因此做一下这个题。

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

 

示例 1:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

解答思路

  1. 求解所有的雨水数量,可以转换为求每一个柱子上方雨水的量之和。
  2. 每一个柱子上方雨水的量取决于这个柱子的左侧柱子右侧柱子的高度。如果都比当前柱子高,那么这个柱子雨水的量就是 min(左侧高度,右侧高度) - 当前高度。
  3. 最终问题就变成了求左侧柱子的高度和右侧柱子的高度了。

自然就能想到,可以用两个数组,一个记录当前柱子的左侧高度,一个记录当前柱子的右侧高度。最后遍历一下,直接就可以计算出雨水的量。

代码

class Solution {
public:
    int trap(vector<int>& height) {
        int total = height.size();
        std::vector<int> left_max(total, 0);
        std::vector<int> right_max(total, 0);
        for (int i = 1; i < total; i++) {
            left_max[i] = std::max(left_max[i-1], height[i-1]);
        }
        for (int i = total - 2; i > 0; i--) {
            right_max[i] = std::max(right_max[i+1], height[i+1]);
        }
        int res = 0;
        for (int i = 0 ; i < total; i++) {
            int min = std::min(left_max[i], right_max[i]);
            if (min > height[i]) {
                res += min - height[i];
            }
        }
        return res;
    }
};

你可能还喜欢下面这些文章

mysq常用函数大全

很少用到,但是有时候又必须用到,这里收集一下mysql的常用函数一、数学函数ABS(x)   返回x的绝对值BIN(x)   返回x的二进制(OCT返回八进制,HEX返回十六进制)CEILING(x)   返回大于x的最小整数值EXP(x)   返回值e(自然对数的底)的x次方FLOOR(x)   返回小于x的最大整数值GREATEST(x1,x2,...,xn)返回集合中最大的值LEAST(x1,x2,...,xn)      返回集合中最小的值LN(x)                    返回x的自然对数LOG(x,y)返回x的以y为底的对数MOD(x,y)              

memcacheq的安装与使用

1、安装libevent官网:http://www.libevent.org/2、安装 BerkeleyDB官网:http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html(下载需要登录)安装:安装完成之后:或者:添加:并执行:3、安装 MemcacheQ官网:http://memcachedb.org/memcacheq/测试是否安装成功:4、启动服务建立相关目录:启动服务:参数说明:-d : 以后台服务方式运行-l : 设置监听地址及端口(默认是22201)-A : 数据页大小-H : 数据保存目录-

欧拉计划:找出1000以下3与5的倍数之和

题目如果我们列出10以下的3和5的倍数,我们可以得到3,5,6,9。它们的和为23。请求出1000以下所有的3和5的倍数之和。原文If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.Find the sum of all the multiples of 3 or 5 below 1000.原文链接https://projecteuler.net/problem=1解答我会用php,pyth

utf8编码原理

在我的程序中,基本都使用utf8来编码(除非历史原因,实在是无法转换)。但我用的php在处理中文语言的时候,总显得有些生硬,总感觉没有处理英文那么流畅。比如为什么统计字符的数目要远大于汉字的个数?为什么截断中文乱码?为什么一串英文所组成的字符串可以使用数组的方式访问但是中文字符串为什么就是乱码?等等等等之类的问题。这一切的一切,都是因为对utf8编码不了解所导致的!虽然我们有mb_string这个扩展的对中文有很友好的支持,但对于编码原理,还是需要好好的了解一下。但对于初学者,我想你未必有耐心看完这篇文章,可以跳过直接看程序实例,这篇文章可以作为实例程序的参考作用。