当前位置: 首页 > news >正文

Android学习19 -- NDK4--共享内存(TODO)

在安卓的NDK(Native Development Kit)中,C++共享内存通常用于不同进程间的通信,或者在同一进程中多线程之间共享数据。这种方法相较于其他形式的IPC(进程间通信)来说,具有更高的性能和低延迟。共享内存提供了一块可以被多个进程访问的内存区域,但它的使用需要小心,以避免并发问题和内存访问冲突。

以下是如何在安卓NDK下使用C++进行共享内存的一些基本概念和实现方法:

1. 使用 ashmem(Android共享内存)

Android提供了ashmem(Android Shared Memory)用于跨进程共享内存。它是安卓平台上特定于共享内存的一种机制,可以通过文件描述符来实现共享内存区域。

基本步骤:
  1. 创建共享内存: 使用ashmem_create_region()函数创建共享内存区域。

    #include <sys/mman.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <ashmem.h>int create_shared_memory(size_t size) {int fd = ashmem_create_region("my_shared_memory", size);if (fd < 0) {perror("Failed to create shared memory");return -1;}return fd;
    }
    
  2. 映射共享内存: 使用mmap函数将共享内存映射到进程的虚拟地址空间。

    void* map_shared_memory(int fd, size_t size) {void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("Failed to map shared memory");return nullptr;}return addr;
    }
    
  3. 写入/读取共享内存: 映射后,可以直接通过指针访问共享内存。

    void write_to_shared_memory(void* addr, const char* data) {memcpy(addr, data, strlen(data) + 1);
    }void read_from_shared_memory(void* addr) {printf("Data from shared memory: %s\n", (char*)addr);
    }
    
  4. 关闭共享内存: 使用munmapclose来卸载和关闭共享内存。

    void unmap_shared_memory(void* addr, size_t size) {munmap(addr, size);
    }void close_shared_memory(int fd) {close(fd);
    }
    

2. 使用 mmap 和普通文件

如果需要跨进程共享内存,可以使用mmap映射一个物理文件作为共享内存。文件在磁盘上存在,但多个进程可以通过映射来共享这个文件的内存区域。

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int create_shared_memory_file(const char* path, size_t size) {int fd = open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (fd == -1) {perror("Failed to open file");return -1;}if (ftruncate(fd, size) == -1) {perror("Failed to set file size");close(fd);return -1;}return fd;
}void* map_shared_memory_file(int fd, size_t size) {void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("Failed to map shared memory");return nullptr;}return addr;
}void unmap_shared_memory(void* addr, size_t size) {munmap(addr, size);
}

3. 线程间共享内存

在同一进程中的多个线程也可以通过共享内存来交换数据。共享内存在多线程场景下的使用是非常常见的,因为它允许线程直接访问共享数据,而无需进行复杂的序列化。

#include <pthread.h>
#include <atomic>std::atomic<int> shared_counter(0);void* thread_func(void* arg) {shared_counter.fetch_add(1, std::memory_order_relaxed);return nullptr;
}int main() {pthread_t threads[10];for (int i = 0; i < 10; ++i) {pthread_create(&threads[i], nullptr, thread_func, nullptr);}for (int i = 0; i < 10; ++i) {pthread_join(threads[i], nullptr);}printf("Shared counter: %d\n", shared_counter.load());return 0;
}

4. 注意事项

  • 同步问题:共享内存的访问需要确保线程或进程间的同步。可以使用mutexsemaphore或其他同步机制来防止竞态条件。

  • 资源管理:共享内存资源需要适当管理,避免内存泄漏。确保共享内存被正确地映射、使用和释放。

  • 权限控制:对于跨进程的共享内存,必须确保各个进程具有访问该内存区域的权限,否则可能会导致访问错误。

总结

安卓NDK中使用C++进行共享内存操作主要通过ashmemmmap来实现。ashmem是专为Android设计的共享内存机制,适用于跨进程通信;而mmap可以用于映射普通文件作为共享内存。共享内存在多进程和多线程之间的数据交换中非常有用,但需要注意同步和资源管理的问题。

在安卓NDK下,多个进程共享一个共享内存的例子通常可以通过ashmem(Android共享内存)来实现。ashmem允许不同的进程通过文件描述符访问同一块内存区域,从而实现跨进程的共享内存通信。

下面是一个简单的例子,展示了如何使用ashmem在两个进程间共享内存。进程A写入共享内存,进程B读取共享内存。

步骤概述:

  1. 进程A:创建共享内存并将数据写入内存。
  2. 进程B:访问同一共享内存并读取数据。

1. 进程A:创建共享内存并写入数据

进程A代码 (process_a.cpp)
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ashmem.h>int create_shared_memory(size_t size) {int fd = ashmem_create_region("my_shared_memory", size);if (fd < 0) {perror("Failed to create shared memory");return -1;}return fd;
}void* map_shared_memory(int fd, size_t size) {void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("Failed to map shared memory");return nullptr;}return addr;
}int main() {const size_t size = 1024;  // 定义共享内存大小const char* message = "Hello from Process A!";// 创建共享内存int fd = create_shared_memory(size);if (fd < 0) {return 1;}// 映射共享内存void* addr = map_shared_memory(fd, size);if (addr == nullptr) {close(fd);return 1;}// 将数据写入共享内存strncpy((char*)addr, message, size);printf("Process A: Written to shared memory: %s\n", message);// 关闭共享内存munmap(addr, size);close(fd);return 0;
}

2. 进程B:访问共享内存并读取数据

进程B代码 (process_b.cpp)
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ashmem.h>void* map_shared_memory(int fd, size_t size) {void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("Failed to map shared memory");return nullptr;}return addr;
}int main() {const size_t size = 1024;  // 定义共享内存大小// 打开已有的共享内存文件描述符int fd = open("/dev/ashmem/my_shared_memory", O_RDONLY);if (fd < 0) {perror("Failed to open shared memory");return 1;}// 映射共享内存void* addr = map_shared_memory(fd, size);if (addr == nullptr) {close(fd);return 1;}// 读取共享内存中的数据printf("Process B: Read from shared memory: %s\n", (char*)addr);// 关闭共享内存munmap(addr, size);close(fd);return 0;
}

3. 编译和运行

  1. 编译代码: 你需要将process_a.cppprocess_b.cpp分别编译成独立的可执行文件。

    在终端中使用NDK编译工具(如ndk-buildCMake)编译代码。确保设置正确的NDK环境。

    例如,使用CMake时,创建一个CMakeLists.txt文件来设置编译参数:

    cmake_minimum_required(VERSION 3.4.1)add_executable(process_a process_a.cpp)
    add_executable(process_b process_b.cpp)
    

    使用CMake进行构建:

    mkdir build
    cd build
    cmake ..
    make
    
  2. 运行进程A: 启动进程A,它会创建共享内存并写入数据。

    ./process_a
    
  3. 运行进程B: 启动进程B,它会读取进程A写入共享内存中的数据。

    ./process_b
    

4. 说明

  • 创建共享内存:进程A通过调用ashmem_create_region()函数来创建共享内存。这个函数返回一个文件描述符,可以通过该文件描述符在其他进程中访问共享内存。

  • 映射共享内存:进程A和进程B通过mmap()函数将共享内存映射到各自的地址空间,读取或写入共享内存的数据。

  • 数据传输:进程A将数据写入共享内存,进程B从共享内存读取数据。这两者通过共享内存实现了数据传输。

  • 进程间共享内存访问:共享内存的访问方式是基于文件描述符的,通过文件描述符,可以在不同的进程之间共享这块内存。ashmem是一种轻量级的共享内存方式,适用于安卓中的跨进程数据共享。

5. 注意事项

  • 由于共享内存没有内建的同步机制,多个进程同时访问时需要通过锁(例如mutex、semaphore)来保证数据一致性。
  • 在实际开发中,可能还需要设置共享内存的权限(如只读或读写),以保证数据的安全性。

通过这个例子,你可以看到如何在安卓NDK中使用C++进行共享内存的创建和访问,实现多个进程间的数据共享。

在安卓NDK下,使用共享内存实现生产者消费者模型,涉及多个进程通过共享内存进行数据交换。一个进程作为生产者写入数据,多个消费者进程从共享内存中读取数据。

关键概念:

  1. 共享内存:生产者和消费者进程使用共享内存区域进行数据交换。ashmem是一个适用于安卓的共享内存机制。
  2. 同步机制:由于多个进程访问共享内存,必须确保数据一致性和避免竞争条件。因此,使用同步机制(如信号量)来控制数据的读写。

示例描述:

  • 生产者进程:产生数据并写入共享内存。
  • 消费者进程:从共享内存中读取数据并消费。

为了实现这一模型,我们将使用ashmem来创建共享内存区域,并使用简单的信号量(例如通过sem_t)来同步进程之间的读写。

1. 数据结构和共享内存设计

首先定义一个简单的共享内存结构,其中包括一个缓冲区、读写指针和信号量,用于同步。

// shared_memory.h
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H#include <semaphore.h>#define BUFFER_SIZE 10  // 缓冲区大小// 共享内存结构
struct shared_memory {int buffer[BUFFER_SIZE];   // 数据缓冲区int read_index;            // 读取位置int write_index;           // 写入位置sem_t full;                // 缓冲区中的数据数量sem_t empty;               // 缓冲区中的空位数量sem_t mutex;               // 互斥锁,保护缓冲区的读写
};#endif

2. 生产者进程(写入共享内存)

生产者进程不断生成数据,并将数据写入共享内存。在每次写入后,生产者会通知消费者进程。

生产者代码 (producer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <string.h>
#include "shared_memory.h"
#include <ashmem.h>int main() {// 打开共享内存int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);if (fd == -1) {perror("Failed to open shared memory");return -1;}// 映射共享内存struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (shm == MAP_FAILED) {perror("Failed to map shared memory");close(fd);return -1;}// 写入数据的生产者循环for (int i = 0; i < 100; ++i) {// 等待空槽位sem_wait(&shm->empty);sem_wait(&shm->mutex);// 生成数据并写入缓冲区shm->buffer[shm->write_index] = i;printf("Producer: produced %d\n", i);// 更新写入位置shm->write_index = (shm->write_index + 1) % BUFFER_SIZE;// 释放互斥锁,通知消费者sem_post(&shm->mutex);sem_post(&shm->full);// 模拟生产者的工作时间sleep(1);}// 解除共享内存映射munmap(shm, sizeof(struct shared_memory));close(fd);return 0;
}

3. 消费者进程(读取共享内存)

消费者进程从共享内存中读取数据,并进行消费。在每次读取后,消费者会通知生产者进程。

消费者代码 (consumer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <string.h>
#include "shared_memory.h"
#include <ashmem.h>int main() {// 打开共享内存int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);if (fd == -1) {perror("Failed to open shared memory");return -1;}// 映射共享内存struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (shm == MAP_FAILED) {perror("Failed to map shared memory");close(fd);return -1;}// 消费者循环for (int i = 0; i < 100; ++i) {// 等待缓冲区中有数据sem_wait(&shm->full);sem_wait(&shm->mutex);// 从缓冲区读取数据int item = shm->buffer[shm->read_index];printf("Consumer: consumed %d\n", item);// 更新读取位置shm->read_index = (shm->read_index + 1) % BUFFER_SIZE;// 释放互斥锁,通知生产者sem_post(&shm->mutex);sem_post(&shm->empty);// 模拟消费者的工作时间sleep(1);}// 解除共享内存映射munmap(shm, sizeof(struct shared_memory));close(fd);return 0;
}

4. 创建和初始化共享内存

共享内存需要在生产者和消费者进程启动前创建,并初始化必要的信号量。这里我们需要一个初始化共享内存的代码。

初始化共享内存 (init_shared_memory.cpp)
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <ashmem.h>
#include "shared_memory.h"int main() {// 创建共享内存int fd = open("/dev/ashmem/my_shared_memory", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (fd == -1) {perror("Failed to create shared memory");return -1;}// 设置共享内存大小if (ftruncate(fd, sizeof(struct shared_memory)) == -1) {perror("Failed to set memory size");close(fd);return -1;}// 映射共享内存struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (shm == MAP_FAILED) {perror("Failed to map shared memory");close(fd);return -1;}// 初始化缓冲区shm->read_index = 0;shm->write_index = 0;// 初始化信号量sem_init(&shm->full, 1, 0);       // full: initially 0sem_init(&shm->empty, 1, BUFFER_SIZE);  // empty: initially BUFFER_SIZEsem_init(&shm->mutex, 1, 1);      // mutex: initially unlocked (1)// 解除共享内存映射munmap(shm, sizeof(struct shared_memory));close(fd);printf("Shared memory initialized.\n");return 0;
}

5. 编译和运行

首先编译这三个程序:init_shared_memory.cppproducer.cppconsumer.cpp

g++ init_shared_memory.cpp -o init_shared_memory -lstdc++ -lpthread
g++ producer.cpp -o producer -lstdc++ -lpthread
g++ consumer.cpp -o consumer -lstdc++ -lpthread

然后,按照以下顺序执行:

  1. 初始化共享内存: 在任何生产者或消费者进程运行之前,先运行初始化共享内存的程序。

    ./init_shared_memory
    
  2. 启动生产者进程: 启动生产者进程,它会开始生成数据并写入共享内存。

    ./producer
    
  3. 启动消费者进程: 启动多个消费者进程,读取共享内存中的数据。

    ./consumer
    

    可以启动多个消费者进程,如:

    ./consumer &
    ./consumer &
    

总结

在这个例子中,我们通过共享内存和信号量实现了生产者-消费者模式。生产者通过共享内存写入数据,多个消费者读取并消费数据。信号量确保了在多进程环境中对共享内存的同步访问。

在安卓 NDK 中使用 C++ 和共享内存实现生产者消费者模型,且采用 条件变量 进行同步,需要确保生产者和消费者在共享内存的读写操作中正确同步。

关键步骤:

  1. 共享内存:使用 ashmem 共享内存来传递数据。
  2. 互斥锁和条件变量:使用 pthread_mutex_tpthread_cond_t 来实现生产者和消费者的同步。

示例设计:

  • 生产者进程:生成数据并写入共享内存。
  • 消费者进程:从共享内存中读取数据并进行消费。
  • 通过条件变量来保证数据写入和读取的同步:
    • 生产者在缓冲区满时等待。
    • 消费者在缓冲区空时等待。

1. 数据结构和共享内存设计

// shared_memory.h
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H#include <pthread.h>#define BUFFER_SIZE 10  // 缓冲区大小// 共享内存结构
struct shared_memory {int buffer[BUFFER_SIZE];   // 数据缓冲区int read_index;            // 读取位置int write_index;           // 写入位置pthread_mutex_t mutex;     // 互斥锁,保护缓冲区的读写pthread_cond_t full_cond;  // 条件变量,缓冲区满时等待pthread_cond_t empty_cond; // 条件变量,缓冲区空时等待
};#endif

2. 初始化共享内存

初始化共享内存区域,包括互斥锁和条件变量。

初始化共享内存 (init_shared_memory.cpp)
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <ashmem.h>
#include "shared_memory.h"int main() {// 创建共享内存int fd = open("/dev/ashmem/my_shared_memory", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (fd == -1) {perror("Failed to create shared memory");return -1;}// 设置共享内存大小if (ftruncate(fd, sizeof(struct shared_memory)) == -1) {perror("Failed to set memory size");close(fd);return -1;}// 映射共享内存struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (shm == MAP_FAILED) {perror("Failed to map shared memory");close(fd);return -1;}// 初始化缓冲区shm->read_index = 0;shm->write_index = 0;// 初始化互斥锁和条件变量pthread_mutex_init(&shm->mutex, NULL);pthread_cond_init(&shm->full_cond, NULL);pthread_cond_init(&shm->empty_cond, NULL);// 解除共享内存映射munmap(shm, sizeof(struct shared_memory));close(fd);printf("Shared memory initialized.\n");return 0;
}

3. 生产者进程(写入共享内存)

生产者进程会不断生成数据并将其写入共享内存。当缓冲区满时,生产者会等待,直到消费者消费一些数据。

生产者代码 (producer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <string.h>
#include <ashmem.h>
#include "shared_memory.h"int main() {// 打开共享内存int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);if (fd == -1) {perror("Failed to open shared memory");return -1;}// 映射共享内存struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (shm == MAP_FAILED) {perror("Failed to map shared memory");close(fd);return -1;}// 写入数据的生产者循环for (int i = 0; i < 100; ++i) {pthread_mutex_lock(&shm->mutex);// 如果缓冲区满,则等待while ((shm->write_index + 1) % BUFFER_SIZE == shm->read_index) {pthread_cond_wait(&shm->full_cond, &shm->mutex);}// 生成数据并写入缓冲区shm->buffer[shm->write_index] = i;printf("Producer: produced %d\n", i);// 更新写入位置shm->write_index = (shm->write_index + 1) % BUFFER_SIZE;// 通知消费者数据已准备好pthread_cond_signal(&shm->empty_cond);pthread_mutex_unlock(&shm->mutex);// 模拟生产者的工作时间sleep(1);}// 解除共享内存映射munmap(shm, sizeof(struct shared_memory));close(fd);return 0;
}

4. 消费者进程(读取共享内存)

消费者进程会从共享内存中读取数据并进行消费。当缓冲区为空时,消费者会等待,直到生产者写入数据。

消费者代码 (consumer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <string.h>
#include <ashmem.h>
#include "shared_memory.h"int main() {// 打开共享内存int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);if (fd == -1) {perror("Failed to open shared memory");return -1;}// 映射共享内存struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (shm == MAP_FAILED) {perror("Failed to map shared memory");close(fd);return -1;}// 消费者循环for (int i = 0; i < 100; ++i) {pthread_mutex_lock(&shm->mutex);// 如果缓冲区空,则等待while (shm->read_index == shm->write_index) {pthread_cond_wait(&shm->empty_cond, &shm->mutex);}// 从缓冲区读取数据int item = shm->buffer[shm->read_index];printf("Consumer: consumed %d\n", item);// 更新读取位置shm->read_index = (shm->read_index + 1) % BUFFER_SIZE;// 通知生产者可以继续写入pthread_cond_signal(&shm->full_cond);pthread_mutex_unlock(&shm->mutex);// 模拟消费者的工作时间sleep(1);}// 解除共享内存映射munmap(shm, sizeof(struct shared_memory));close(fd);return 0;
}

5. 编译和运行

首先编译这三个程序:init_shared_memory.cppproducer.cppconsumer.cpp

g++ init_shared_memory.cpp -o init_shared_memory -lstdc++ -lpthread
g++ producer.cpp -o producer -lstdc++ -lpthread
g++ consumer.cpp -o consumer -lstdc++ -lpthread

然后,按照以下顺序执行:

  1. 初始化共享内存: 在任何生产者或消费者进程运行之前,先运行初始化共享内存的程序。

    ./init_shared_memory
    
  2. 启动生产者进程: 启动生产者进程,它会开始生成数据并写入共享内存。

    ./producer
    
  3. 启动消费者进程: 启动多个消费者进程,读取共享内存中的数据。

    ./consumer
    

    可以启动多个消费者进程,如:

    ./consumer &
    ./consumer &
    

总结

在这个例子中,我们使用了 条件变量 来实现生产者消费者模型。在每次写入和读取数据时,生产者和消费者会根据缓冲区的状态进行等待和通知,从而保证了数据的一致性。信号量可以用来控制资源的访问,而条件变量则提供了更灵活的同步机制,尤其是在等待和通知特定条件时。

相关文章:

Android学习19 -- NDK4--共享内存(TODO)

在安卓的NDK&#xff08;Native Development Kit&#xff09;中&#xff0c;C共享内存通常用于不同进程间的通信&#xff0c;或者在同一进程中多线程之间共享数据。这种方法相较于其他形式的IPC&#xff08;进程间通信&#xff09;来说&#xff0c;具有更高的性能和低延迟。共享…...

《Cocos Creator游戏实战》非固定摇杆实现原理

为什么要使用非固定摇杆 许多同学在开发摇杆功能时&#xff0c;会将摇杆固定在屏幕左下某一位置&#xff0c;不会让其随着大拇指触摸点改变&#xff0c;而且玩家只有按在了摇杆上才能移动人物&#xff08;触摸监听事件在摇杆精灵上)。然而&#xff0c;不同玩家的大拇指长度不同…...

RabbitMQ工作模式(详解 工作模式:简单队列、工作队列、公平分发以及消息应答和消息持久化)

文章目录 十.RabbitMQ10.1 简单队列实现10.2 Work 模式&#xff08;工作队列&#xff09;10.3 公平分发10.4 RabbitMQ 消息应答与消息持久化消息应答概念配置 消息持久化概念配置 十.RabbitMQ 10.1 简单队列实现 简单队列通常指的是一个基本的消息队列&#xff0c;它可以用于…...

【VScode】第三方GPT编程工具-CodeMoss安装教程

一、CodeMoss是什么&#xff1f; CodeMoss是一款集编程、学习和办公于一体的高效工具。它兼容多种主流平台&#xff0c;包括VSCode、IDER、Chrome插件、Web和APP等&#xff0c;支持插件安装&#xff0c;尤其在VSCode和IDER上的表现尤为出色。无论你是编程新手还是资深开发者&a…...

在JavaScript中,let 和 const有什么不同

在JavaScript中&#xff0c;let 和 const 是用于声明变量的关键字&#xff0c;但它们有一些重要的区别 1.重新赋值&#xff1a; let 声明的变量可以重新赋值。const 声明的变量必须在声明时初始化&#xff0c;并且之后不能重新赋值 let a 10; a 20; // 有效&#xff0c;a 的…...

Mysq学习-Mysql查询(4)

5.子查询 子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL4.1开始引入.在SELECT子句中先计算子查询,子查询结果作为外层另一个查询的过滤条件,查询可以基于一个表或者多个表. 子查询中常用的操作符有ANY(SOME),ALL,IN,EXISTS.子查询可以添加到SELECT,UPD…...

安装torch-geometric库

目录 1.查看 torch 和 CUDA 版本 2.依次下载和 torch 和 CUDA 对应版本的四个依赖库pyg-lib、torch-scatter、torch-sparse、torch-cluster以及torch-spline-conv 3.下载并安装torch-geometric库 1.查看 torch 和 CUDA 版本 查看CUDA版本 nvcc -V 查看pytorch版本 pip s…...

Java数组深入解析:定义、操作、常见问题与高频练习

一、数组的定义 1. 什么是数组 数组是一个容器&#xff0c;用来存储多个相同类型的数据。它属于引用数据类型&#xff0c;可以存储基本数据类型&#xff08;如int、char&#xff09;或者引用数据类型&#xff08;如String、对象&#xff09;。 2. 数组的定义方式 a. 动态初…...

Docker-构建自己的Web-Linux系统-镜像webtop:ubuntu-kde

介绍 安装自己的linux-server,可以作为学习使用&#xff0c;web方式访问&#xff0c;基于ubuntu构建开源项目 https://github.com/linuxserver/docker-webtop安装 docker run -d -p 1336:3000 -e PASSWORD123456 --name webtop lscr.io/linuxserver/webtop:ubuntu-kde登录 …...

【C语言练习(17)—输出杨辉三角形】

C语言练习&#xff08;17&#xff09; 文章目录 C语言练习&#xff08;17&#xff09;前言题目题目解析整体代码 前言 杨辉三角形的输出可以分三步&#xff0c;第一步构建一个三角形、第二步根据规律将三角形内容填写、第三步将三角形以等腰的形式输出 题目 请输出一个十行的…...

SpringMVC学习(二)——RESTful API、拦截器、异常处理、数据类型转换

一、RESTful (一)RESTful概述 RESTful是一种软件架构风格&#xff0c;用于设计网络应用程序。REST是“Representational State Transfer”的缩写&#xff0c;中文意思是“表现层状态转移”。它基于客户端-服务器模型和无状态操作&#xff0c;以及使用HTTP请求来处理数据。RES…...

React 第二十节 useRef 用途使用技巧注意事项详解

简述 useRef 用于操作不需要在视图上渲染的属性数据&#xff0c;用于访问真实的DOM节点&#xff0c;或者React组件的实例对象&#xff0c;允许直接操作DOM元素或者是组件&#xff1b; 写法 const inpRef useRef(params)参数&#xff1a; useRef(params)&#xff0c;接收的 …...

VIVO Java开发面试题及参考答案

TCP 能不能两次握手? TCP 不能两次握手。 在 TCP 连接建立过程中,三次握手是必不可少的。第一次握手是客户端向服务器发送一个带有 SYN(同步序列号)标志的 TCP 报文段,这个报文段包含了客户端初始的序列号。这一步的主要目的是告诉服务器,客户端想要建立连接,并且让服务…...

C# Winfrom chart图 实例练习

代码太多了我就不展示了&#xff0c;贴一些比较有代表性的 成品效果展示&#xff1a; Excel转Chart示例 简单说一下我的思路 \ 先把Excel数据展示在dataGridView控件上 XLIST 为 X轴的数据 XLIST 为 Y轴的数据 ZLIST 为 展示的数据进行数据处理点击展示即可 // 将Excel数…...

iOS从Matter的设备认证证书中获取VID和PID

设备认证证书也叫 DAC, 相当于每个已经认证的设备的标识。包含了 VID 和 PID. VID: Vendor ID &#xff0c;标识厂商 PID: Product ID&#xff0c; 标识设备的 根据 Matter 对于设备证书的规定&#xff0c;DAC证书subject应该包含VID 和 PID. 可通过解析 X509 证书读取subject…...

带着国标充电器出国怎么办? 适配器模式(Adapter Pattern)

适配器模式&#xff08;Adapter Pattern&#xff09; 适配器模式适配器模式&#xff08;Adapter Pattern&#xff09;概述talk is cheap&#xff0c; show you my code总结 适配器模式 适配器模式&#xff08;Adapter Pattern&#xff09;是面向对象软件设计中的一种结构型设计…...

破解海外业务困局:新加坡服务器托管与跨境组网策略

在当今全球化商业蓬勃发展的浪潮之下&#xff0c;众多企业将目光投向海外市场&#xff0c;力求拓展业务版图、抢占发展先机。而新加坡&#xff0c;凭借其卓越的地理位置、强劲的经济发展态势以及高度国际化的营商环境&#xff0c;已然成为企业海外布局的热门之选。此时&#xf…...

Mybatis-Plus快速入门

参考&#xff1a;黑马MyBatisPlus教程全套视频教程&#xff0c;快速精通mybatisplus框架 1.Mapper-plus配置 1.MapperScan("Mapper目录的位置") 2.Mapper层文件需要继承BaseMapper extends BaseMapper<实体类> 3.开启日志 4.配置类 Configuration public cl…...

Chrome被360导航篡改了怎么改回来?

一、Chrome被360导航篡改了怎么改回来&#xff1f; 查看是否被360主页锁定&#xff0c;地址栏输入chrome://version&#xff0c;看命令行end后面&#xff08;蓝色部分&#xff09;&#xff0c;是否有https://hao.360.com/?srclm&lsn31c42a959f 修改步骤 第一步&#xff1a…...

Coding(Jenkinsfile)+ Docker 自动化部署 Springboot —— 图文细节和一些注意事项说明

前言&#xff1a;本章讲述一下我使用Coding&#xff08;Jenkinsfile&#xff09; Docker部署Springboot项目过程&#xff0c;记录图文细节和一些需要注意的问题。 说明&#xff1a;为什么要使用Coding去集成Docker&#xff1f; 节约了服务器内存&#xff0c;不需要单独部署 Jen…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...