C++20 中的同步输出流:`std::basic_osyncstream` 深入解析与应用实践

文章目录
- 一、`std::basic_osyncstream` 的背景与动机
- 二、`std::basic_osyncstream` 的基本原理
- 三、`std::basic_osyncstream` 的使用方法
- (一)基本用法
- (二)多线程环境下的使用
- (三)与文件流的结合
- 四、`std::basic_osyncstream` 的高级特性
- (一)缓冲区管理
- (二)与其他 C++20 特性的结合
- 1\. 与 `std::format` 的结合
- 2\. 与协程的结合
- 五、`std::basic_osyncstream` 的性能分析
- (一)同步机制的开销
- (二)缓冲区管理的开销
- (三)性能优化建议
- 六、`std::basic_osyncstream` 的应用场景
- (一)日志系统
- (二)多线程数据处理
- (三)文件写入
- 七、`std::basic_osyncstream` 的实现原理
- (一)`std::basic_syncbuf` 的角色
- (二)线程安全的实现机制
- (三)缓冲区刷新策略
- 八、`std::basic_osyncstream` 的优势与局限性
- (一)优势
- (二)局限性
- 九、`std::basic_osyncstream` 的最佳实践
- (一)合理设置缓冲区大小
- (二)减少不必要的同步操作
- (三)使用线程池
- (四)避免过度使用 `std::basic_osyncstream`
- 十、`std::basic_osyncstream` 的未来展望
- (一)性能优化
- (二)功能扩展
- (三)与其他特性的集成
- 十一、总结
在多线程编程中,输出流的同步问题一直是困扰开发者的一大难题。传统的
std::ostream(如
std::cout)在多线程环境下无法保证输出的顺序性和完整性,容易导致输出内容交织、顺序混乱等问题。为了解决这一问题,C++20 引入了
std::basic_osyncstream,它为多线程环境下的输出流同步提供了一种高效、简洁的解决方案。
一、std::basic_osyncstream 的背景与动机
在多线程程序中,多个线程可能同时尝试向同一个输出流(如控制台或文件)写入数据。由于 std::ostream 本身并不提供线程安全机制,这种并发写入会导致数据竞争(race condition),使得输出结果不可预测。例如,以下代码展示了在多线程环境下使用 std::cout 输出时可能出现的问题:
#include <iostream>
#include <thread>
#include <vector>void print_thread_id(int id) {std::cout << "Thread " << id << " is running\n";
}int main() {constexpr int num_threads = 5;std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.emplace_back(print_thread_id, i);}for (auto& t : threads) {t.join();}return 0;
}
在上述代码中,多个线程同时向 std::cout 输出,可能会导致输出内容交错,例如:
Thread 0 is runningThread 1 is running
Thread 2 is running
Thread 3 is running
Thread 4 is running
为了解决这种问题,C++20 引入了 std::basic_osyncstream。它通过为每个线程提供独立的缓冲区,并在适当的时候将缓冲区的内容原子式地写入目标流,从而保证了输出的顺序性和完整性。
二、std::basic_osyncstream 的基本原理
std::basic_osyncstream 是 std::basic_syncbuf 的便捷包装器。其核心思想是利用 RAII(Resource Acquisition Is Initialization)机制,为每个线程创建一个独立的同步缓冲区(sync buffer)。当线程向 std::basic_osyncstream 写入数据时,数据首先被写入到线程的独立缓冲区中,而不是直接写入目标流。只有在以下两种情况下,缓冲区的内容才会被原子式地写入目标流:
- 对象析构:当
std::basic_osyncstream对象析构时,其内部的缓冲区内容会被自动写入目标流。 - 显式刷新:调用
std::basic_osyncstream的emit方法或插入换行符(如std::endl)时,缓冲区的内容会被刷新到目标流。
这种设计使得 std::basic_osyncstream 能够在不牺牲性能的前提下,提供线程安全的输出流操作。
三、std::basic_osyncstream 的使用方法
(一)基本用法
std::basic_osyncstream 是一个模板类,它依赖于底层流类型(如 std::ostream 或 std::wostream)。要使用 std::basic_osyncstream,首先需要包含头文件 <syncstream>,然后创建一个 std::basic_osyncstream 对象,并将其绑定到一个底层流对象上。以下是一个简单的示例:
#include <iostream>
#include <syncstream>int main() {std::osyncstream sync_out(std::cout); // 创建同步输出流对象sync_out << "Hello, ";sync_out << "World!\n";return 0;
}
在上述代码中,std::osyncstream 对象 sync_out 将输出绑定到 std::cout。由于 std::osyncstream 的存在,即使在多线程环境下,输出内容也不会交错。
(二)多线程环境下的使用
std::basic_osyncstream 的主要优势在于它能够解决多线程环境下的输出同步问题。以下是一个多线程输出的示例:
#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>void print_thread_info(std::basic_osyncstream<std::ostream>& sync_out, int id) {sync_out << "Thread " << id << " is running\n";
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(print_thread_info, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}
在上述代码中,多个线程通过 std::basic_osyncstream 对象 sync_out 向 std::cout 输出信息。由于 std::basic_osyncstream 的同步机制,每个线程的输出都能够按顺序输出,而不会出现内容交错的情况。
(三)与文件流的结合
std::basic_osyncstream 不仅可以与 std::cout 结合使用,还可以与文件流(如 std::ofstream)一起使用。以下是一个将输出写入文件的示例:
#include <fstream>
#include <syncstream>
#include <thread>
#include <vector>void write_to_file(std::basic_osyncstream<std::ofstream>& sync_out, int id) {sync_out << "Thread " << id << " is writing to file\n";
}int main() {std::ofstream file("output.txt");std::basic_osyncstream<std::ofstream> sync_out(file);std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(write_to_file, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}
在上述代码中,多个线程通过 std::basic_osyncstream 对象 sync_out 将数据写入文件 output.txt。由于 std::basic_osyncstream 的同步机制,文件中的输出内容是按顺序排列的。
四、std::basic_osyncstream 的高级特性
(一)缓冲区管理
std::basic_osyncstream 的底层依赖于 std::basic_syncbuf,它负责管理缓冲区。std::basic_syncbuf 提供了灵活的缓冲区管理机制,允许开发者自定义缓冲区的大小和行为。例如,可以通过以下方式设置缓冲区的大小:
#include <syncstream>
#include <iostream>int main() {std::osyncstream sync_out(std::cout);sync_out.rdbuf()->pubsetbuf(nullptr, 0); // 禁用缓冲区sync_out << "Hello, World!\n";return 0;
}
在上述代码中,通过调用 pubsetbuf 方法,可以禁用缓冲区或设置缓冲区的大小。
(二)与其他 C++20 特性的结合
C++20 引入了许多新特性,如 std::format 和协程(Coroutines)。std::basic_osyncstream 可以与这些特性结合使用,进一步提升代码的可读性和性能。
1. 与 std::format 的结合
std::format 提供了一种安全、灵活的字符串格式化机制。将 std::basic_osyncstream 与 std::format 结合使用,可以简化多线程环境下的日志输出。以下是一个示例:
#include <iostream>
#include <format>
#include <syncstream>
#include <thread>void log_message(std::basic_osyncstream<std::ostream>& sync_out, int thread_id, int value) {sync_out << std::format("Thread [{}] reports value: {}\n", thread_id, value);
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::thread t1(log_message, std::ref(sync_out), 1, 42);std::thread t2(log_message, std::ref(sync_out), 2, 100);t1.join();t2.join();return 0;
}
在上述代码中,std::format 负责格式化字符串,而 std::basic_osyncstream 负责同步输出。这种组合使得日志输出既安全又高效。
2. 与协程的结合
协程是 C++20 中引入的一种新的并发编程机制。std::basic_osyncstream 可以与协程结合使用,实现更复杂的并发输出逻辑。以下是一个简单的示例:
#include <iostream>
#include <syncstream>
#include <coroutine>
#include <thread>struct AsyncLog {std::basic_osyncstream<std::ostream>& sync_out;int thread_id;struct promise_type {AsyncLog get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};void await_resume() {}void await_suspend(std::coroutine_handle<> h) {sync_out << "Thread " << thread_id << " is logging\n";h.resume();}
};void log_thread(std::basic_osyncstream<std::ostream>& sync_out, int id) {AsyncLog{sync_out, id}.await_suspend(std::noop_coroutine());
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::thread t1(log_thread, std::ref(sync_out), 1);std::thread t2(log_thread, std::ref(sync_out), 2);t1.join();t2.join();return 0;
}
在上述代码中,AsyncLog 是一个协程,它通过 std::basic_osyncstream 同步输出日志信息。这种结合使得协程能够与同步输出流无缝协作。
五、std::basic_osyncstream 的性能分析
虽然 std::basic_osyncstream 提供了线程安全的输出流操作,但它可能会引入一定的性能开销。主要的性能开销来自于同步机制和缓冲区管理。以下是一些性能分析的关键点:
(一)同步机制的开销
std::basic_osyncstream 的同步机制基于互斥锁(mutex)。每次线程向 std::basic_osyncstream 写入数据时,都会尝试获取互斥锁。如果多个线程同时尝试写入,可能会导致线程阻塞,从而影响性能。然而,这种开销通常是可以接受的,因为它能够保证输出的顺序性和完整性。
(二)缓冲区管理的开销
std::basic_osyncstream 使用缓冲区来减少对底层流的写入操作。虽然缓冲区可以提高性能,但缓冲区的大小和刷新策略也会影响性能。如果缓冲区过大,可能会导致内存占用增加;如果缓冲区过小,可能会导致频繁的刷新操作。因此,合理设置缓冲区大小是优化性能的关键。
(三)性能优化建议
为了优化 std::basic_osyncstream 的性能,可以采取以下措施:
- 合理设置缓冲区大小:根据实际需求调整缓冲区大小,避免缓冲区过大或过小。
- 减少不必要的同步操作:如果输出内容较短,可以考虑使用
std::endl或std::flush显式刷新缓冲区,而不是依赖析构函数自动刷新。 - 使用线程池:在多线程环境下,使用线程池可以减少线程创建和销毁的开销,从而提高性能。
六、std::basic_osyncstream 的应用场景
std::basic_osyncstream 在多线程编程中具有广泛的应用场景,以下是一些典型的例子:
(一)日志系统
在多线程应用程序中,日志系统是必不可少的。std::basic_osyncstream 可以用于实现线程安全的日志输出,确保日志信息的顺序性和完整性。以下是一个简单的日志系统实现:
#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>class Logger {
public:static void log(const std::string& message) {std::basic_osyncstream<std::ostream> sync_out(std::cout);sync_out << message << std::endl;}
};void worker_thread(int id) {Logger::log("Thread " + std::to_string(id) + " is running");
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(worker_thread, i);}for (auto& t : threads) {t.join();}return 0;
}
在上述代码中,Logger 类使用 std::basic_osyncstream 实现线程安全的日志输出。多个线程通过 Logger::log 方法输出日志信息,而不会出现内容交错的情况。
(二)多线程数据处理
在多线程数据处理中,std::basic_osyncstream 可以用于输出处理结果。以下是一个简单的数据处理示例:
#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>void process_data(std::basic_osyncstream<std::ostream>& sync_out, int data) {// 模拟数据处理int result = data * 2;sync_out << "Thread " << std::this_thread::get_id() << " processed data: " << data << ", result: " << result << std::endl;
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(process_data, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}
在上述代码中,多个线程通过 std::basic_osyncstream 输出数据处理结果。由于 std::basic_osyncstream 的同步机制,输出内容是按顺序排列的。
(三)文件写入
在多线程环境下,向文件写入数据时也需要保证线程安全。std::basic_osyncstream 可以与文件流结合使用,实现线程安全的文件写入。以下是一个示例:
#include <fstream>
#include <syncstream>
#include <thread>
#include <vector>void write_to_file(std::basic_osyncstream<std::ofstream>& sync_out, int data) {sync_out << "Thread " << std::this_thread::this_thread::get_id() << " wrote data: " << data << std::endl;
}int main() {std::ofstream file("output.txt");std::basic_osyncstream<std::ofstream> sync_out(file);std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(write_to_file, std::ref(sync_out), i);}for (auto& t : threads) {t.join();}return 0;
}
在上述代码中,多个线程通过 std::basic_osyncstream 向文件 output.txt 写入数据。由于 std::basic_osyncstream 的同步机制,文件中的输出内容是按顺序排列的,不会出现数据交错的情况。
七、std::basic_osyncstream 的实现原理
为了更好地理解 std::basic_osyncstream 的工作原理,我们需要深入探讨其底层实现机制。std::basic_osyncstream 是基于 std::basic_syncbuf 的一个封装,而 std::basic_syncbuf 是 C++20 中引入的一个同步缓冲区类模板。
(一)std::basic_syncbuf 的角色
std::basic_syncbuf 是 std::basic_osyncstream 的底层缓冲区管理器。它继承自 std::basic_streambuf,并重写了关键的虚函数,如 overflow 和 sync,以实现同步写入。std::basic_syncbuf 的主要职责是:
- 缓冲区管理:为每个线程分配独立的缓冲区,减少对底层流的直接写入操作,从而提高性能。
- 同步写入:在缓冲区满或显式刷新时,将缓冲区的内容原子式地写入底层流,确保线程安全。
(二)线程安全的实现机制
std::basic_syncbuf 使用互斥锁(mutex)来实现线程安全的写入操作。当一个线程尝试写入数据时,它会首先获取互斥锁,然后将数据写入缓冲区。如果缓冲区满了或者调用了 emit 方法,缓冲区的内容会被刷新到底层流。在刷新过程中,互斥锁会确保只有一个线程能够访问底层流,从而避免数据竞争。
(三)缓冲区刷新策略
std::basic_syncbuf 的缓冲区刷新策略是影响性能的关键因素之一。缓冲区的刷新可以通过以下几种方式触发:
- 显式刷新:调用
std::basic_osyncstream的emit方法或插入换行符(如std::endl)时,缓冲区的内容会被刷新到底层流。 - 缓冲区满:当缓冲区达到其最大容量时,缓冲区的内容会被自动刷新到底层流。
- 对象析构:当
std::basic_osyncstream对象析构时,其内部的缓冲区内容会被自动写入底层流。
八、std::basic_osyncstream 的优势与局限性
(一)优势
- 线程安全:
std::basic_osyncstream提供了线程安全的输出流操作,解决了多线程环境下的输出混乱问题。 - 性能优化:通过缓冲区管理,减少了对底层流的直接写入操作,从而提高了性能。
- 易用性:
std::basic_osyncstream的使用方法与传统的std::ostream类似,易于上手。 - 灵活性:可以与多种底层流(如
std::cout、std::ofstream)结合使用,满足不同的输出需求。
(二)局限性
- 性能开销:虽然
std::basic_osyncstream通过缓冲区管理减少了对底层流的写入操作,但同步机制本身仍会引入一定的性能开销。特别是在高并发场景下,互斥锁的争用可能会导致线程阻塞,从而影响性能。 - 缓冲区管理的复杂性:合理设置缓冲区大小是优化性能的关键,但缓冲区大小的设置需要根据具体应用场景进行调整。如果缓冲区过大,可能会导致内存占用增加;如果缓冲区过小,可能会导致频繁的刷新操作。
- 对底层流的依赖:
std::basic_osyncstream的性能和行为在很大程度上依赖于底层流的实现。例如,如果底层流的写入操作本身就很慢,std::basic_osyncstream的性能也会受到影响。
九、std::basic_osyncstream 的最佳实践
为了充分发挥 std::basic_osyncstream 的优势,同时避免其局限性带来的影响,以下是一些最佳实践建议:
(一)合理设置缓冲区大小
缓冲区大小的设置需要根据具体应用场景进行调整。一般来说,缓冲区大小应该根据以下因素进行权衡:
- 内存占用:较大的缓冲区会占用更多的内存,但可以减少对底层流的写入操作,从而提高性能。
- 刷新频率:较小的缓冲区会导致更频繁的刷新操作,从而增加性能开销。
- 输出延迟:较大的缓冲区可能会导致输出延迟增加,因为数据需要在缓冲区中积累到一定程度才会被刷新。
在实际应用中,可以通过实验和性能测试来确定最优的缓冲区大小。例如,可以通过以下代码设置缓冲区大小:
#include <iostream>
#include <syncstream>int main() {std::osyncstream sync_out(std::cout);sync_out.rdbuf()->pubsetbuf(nullptr, 1024); // 设置缓冲区大小为 1024 字节sync_out << "Hello, World!\n";return 0;
}
(二)减少不必要的同步操作
虽然 std::basic_osyncstream 提供了线程安全的输出流操作,但过多的同步操作可能会引入不必要的性能开销。为了减少同步操作,可以采取以下措施:
- 显式刷新缓冲区:如果输出内容较短,可以考虑使用
std::endl或std::flush显式刷新缓冲区,而不是依赖析构函数自动刷新。显式刷新可以减少缓冲区的占用时间,从而降低同步操作的开销。 - 批量写入:尽量将多个输出操作合并为一个批量操作,减少对
std::basic_osyncstream的调用次数。例如,可以通过以下代码实现批量写入:
#include <iostream>
#include <syncstream>
#include <sstream>int main() {std::osyncstream sync_out(std::cout);std::ostringstream oss;oss << "Hello, " << "World!\n";sync_out << oss.str();return 0;
}
在上述代码中,通过 std::ostringstream 将多个输出操作合并为一个字符串,然后一次性写入 std::basic_osyncstream,从而减少了同步操作的次数。
(三)使用线程池
在多线程环境下,线程的创建和销毁是一个相对耗时的操作。使用线程池可以减少线程的创建和销毁次数,从而提高性能。线程池预先创建了一组线程,并在需要时将任务分配给这些线程。这样可以避免频繁创建和销毁线程带来的开销。
以下是一个简单的线程池实现示例:
#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>class ThreadPool {
public:ThreadPool(size_t num_threads) {for (size_t i = 0; i < num_threads; ++i) {threads.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queue_mutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if (stop && tasks.empty()) {return;}task = std::move(tasks.front());tasks.pop();}task();}});}}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (auto& t : threads) {t.join();}}template <typename F, typename... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}tasks.emplace([task]() { (*task)(); });}condition.notify_one();return res;}private:std::vector<std::thread> threads;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop = false;
};void print_message(std::basic_osyncstream<std::ostream>& sync_out, const std::string& message) {sync_out << message << std::endl;
}int main() {std::basic_osyncstream<std::ostream> sync_out(std::cout);ThreadPool pool(4);pool.enqueue(print_message, std::ref(sync_out), "Message from thread 1");pool.enqueue(print_message, std::ref(sync_out), "Message from thread 2");pool.enqueue(print_message, std::ref(sync_out), "Message from thread 3");pool.enqueue(print_message, std::ref(sync_out), "Message from thread 4");return 0;
}
在上述代码中,ThreadPool 类管理了一个线程池,enqueue 方法用于将任务提交到线程池中。通过使用线程池,可以减少线程的创建和销毁次数,从而提高性能。
(四)避免过度使用 std::basic_osyncstream
虽然 std::basic_osyncstream 提供了线程安全的输出流操作,但在某些情况下,过度使用可能会导致不必要的性能开销。例如,如果输出操作本身不需要线程安全,或者可以通过其他方式实现线程安全,那么可以考虑不使用 std::basic_osyncstream。
以下是一些可以避免使用 std::basic_osyncstream 的情况:
- 单线程环境:如果程序运行在单线程环境中,那么可以使用传统的
std::ostream,而无需使用std::basic_osyncstream。 - 独立输出流:如果每个线程都有自己的独立输出流,那么可以避免使用
std::basic_osyncstream。例如,可以为每个线程创建一个独立的文件流,从而避免线程间的竞争。 - 日志系统:在某些情况下,可以使用专门的日志库(如
spdlog或log4cpp)来实现线程安全的日志输出,而无需使用std::basic_osyncstream。这些日志库通常提供了更高效的线程安全机制和更丰富的功能。
十、std::basic_osyncstream 的未来展望
std::basic_osyncstream 是 C++20 中引入的一个重要特性,它为多线程环境下的输出流同步提供了一种高效、简洁的解决方案。随着 C++ 标准的不断发展,std::basic_osyncstream 也可能会得到进一步的改进和优化。
以下是一些可能的发展方向:
(一)性能优化
随着硬件技术的不断发展,多核处理器的性能越来越高。为了充分发挥多核处理器的性能,std::basic_osyncstream 可能会引入更多的性能优化措施。例如,可以使用无锁编程技术(lock-free programming)来减少互斥锁的开销,从而提高性能。
(二)功能扩展
std::basic_osyncstream 目前主要支持输出流的同步操作,但未来可能会扩展其功能,支持更多的同步操作类型。例如,可以引入同步输入流(std::basic_isyncstream),从而实现线程安全的输入操作。
(三)与其他特性的集成
C++ 标准中引入了许多新特性,如协程(Coroutines)、模块(Modules)和概念(Concepts)。未来,std::basic_osyncstream 可能会与这些特性进一步集成,从而提供更强大的功能。例如,可以将协程与 std::basic_osyncstream 结合使用,实现更复杂的并发输出逻辑。
十一、总结
std::basic_osyncstream 是 C++20 中引入的一个重要特性,它为多线程环境下的输出流同步提供了一种高效、简洁的解决方案。通过使用 std::basic_osyncstream,可以避免多线程环境下的输出混乱问题,提高程序的可读性和可维护性。
在使用 std::basic_osyncstream 时,需要注意其性能开销和局限性。通过合理设置缓冲区大小、减少不必要的同步操作和使用线程池等措施,可以充分发挥 std::basic_osyncstream 的优势,同时避免其局限性带来的影响。
随着 C++ 标准的不断发展,std::basic_osyncstream 也可能会得到进一步的改进和优化。未来,我们可以期待 std::basic_osyncstream 在性能、功能和与其他特性的集成方面取得更大的进步。
总之,std::basic_osyncstream 是一个多线程编程中不可或缺的工具,它为开发者提供了一种简单而强大的方式来解决多线程环境下的输出流同步问题。通过深入理解其原理和使用方法,我们可以更好地利用这一特性,提升程序的质量和性能。
相关文章:
C++20 中的同步输出流:`std::basic_osyncstream` 深入解析与应用实践
文章目录 一、std::basic_osyncstream 的背景与动机二、std::basic_osyncstream 的基本原理三、std::basic_osyncstream 的使用方法(一)基本用法(二)多线程环境下的使用(三)与文件流的结合 四、std::basic_…...
Android 关于compose的一些坑和理解
** 1.如何在 WindowManager.addView 中使用 Jetpack Compose** 一、引出问题 Android 开发中,很常见的一个场景,通过 WindowManager.addView() 添加一个 View 到屏幕上。Android 最新的视图框架 Jetpack Compose,如何应用进来。这个被添加的…...
LeetCode 30 —— 30.串联所有单词的子串
题目: 给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。 注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。 示例 1ÿ…...
go语言中的strings库
strings库 func EqualFold func EqualFold(s, t string) bool判断两个utf-8编码字符串(将unicode大写、小写、标题三种格式字符视为相同)是否相同。 func main() {fmt.Println(strings.EqualFold("hello", "hello")) //truefmt.…...
【嵌入式硬件】三款DCDC调试笔记
关于开关电源芯片,重点关注输入电源范围、输出电流、最低压降。 1.MP9943: 以MP9943为例,输入电压范围4-36V,输出最大电流3A,最低压降为0.3V 调整FB使正常输出为5.06V 给定6V空载、5V空载、5V带2A负载的情况: 6V带2A…...
Cannot find module @rollup/rollup-win32-x64-msvc
方法1 在package.json中添加postinstall: "scripts": {"postinstall": "node -e \"const { platform } process; if (platform win32) { require(child_process).execSync(npm install rollup/rollup-win32-x64-msvc, { stdio: inherit });…...
Linux中修改文件的权限用什么命令?
一、核心语法 chmod [选项] [权限模式] 文件名二、权限模式详解 1. 数字模式(推荐使用) 通过rwx权限对应的数值组合: r(读)→ 4w(写)→ 2x(执行)→ 1无权限 → 0 组合规…...
【FPGA开发】FPGA点亮LED灯(增加按键暂停恢复/复位操作)
目录 一、VScode下载安装 1.1 官网下载 1.2 安装插件 二、LED流水灯点亮 2.1 任务说明 2.2 新建项目 2.3 创建Verilog文件添加至顶层实体 2.4 引脚分配 2.5 选择烧录器 2.6 添加烧录文件&下载 2.7 烧录结果 三、增加按键操作 3.1 按键暂停和恢复功能&…...
companion object和object 从kotlin转java分析
说明 companion object 中 companion类中的方法是普通的方法 在外部类中生成静态变量,静态companion 对象 object MyClass2 中 类中方法是普通方法 在MyClass2中生成静态变量,静态MyClass2对象, 一 companion object 使用 kotlin转java pa…...
Spring MVC 执行流程:一个请求在 Spring MVC 中是如何执行的?
当用户发送一个 HTTP 向 Spring MVC 应用,该请求在 Spring MVC 的执行流程如下: 当用户向 Spring MVC 发起一个 HTTP 请求,该请求会被 Dispatcher Servlet(前端控制器)拦截;DispatcherServlet 调用 Handler…...
三主热备架构
1.要求 角色主机名软件IP地址用户client192.168.72.90keepalivedvip192.168.72.100masterserverAkeepalived, nginx192.168.72.30backupserverBkeepalived, nginx192.168.72.31backupserverCkeepalived, nginx192.168.72.32webtomcat1tomcat192.168.72.41webtomcat2tomcat192.1…...
HTML 表单处理进阶:验证与提交机制的学习心得与进度(二)
步步为营:表单提交机制全面解析 提交方式详解 GET 与 POST 对比 在 HTML 表单提交中,GET 和 POST 是最为常用的两种提交方式,它们在诸多方面存在显著差异。 安全性:GET 方式将表单数据附加在 URL 的查询字符串中,数…...
JavaScript | 爬虫逆向 | 语法基础| 01
一、摘要 实践是最好的导师 二、环境配置 在开始之前,需要确保你的计算机上已经安装了 Node.js。Node.js 是一个开源的、跨平台的 JavaScript 运行时环境,它允许你在服务器端运行 JavaScript 代码。 1. 下载 安装地址:https://nodejs.org…...
python解决多个矢量点图层合并为一个点图层
1、解决矢量点图层的合并 2、解决多个点图层分别合并为不同图层(一个文件夹下所有点图层合并为一个图层,以下代码为两个文件夹,分别合并为两个总的图层) import geopandas as gpd import os import pandas as pddef merge_shapef…...
VL开源模型实现文本生成图片
一、 基础知识 根据描述生成图片的视觉-语言模型(Vision-Language Models, VL 模型)是近年来多模态生成领域的热点研究方向。这些模型能够根据自然语言描述生成高质量的图像,广泛应用于艺术创作、设计辅助、虚拟场景构建等领域。 1 根据描述…...
字节跳动实习生主导开发强化学习算法,助力大语言模型性能突破
目录 禹棋赢的背景与成就 主要成就 DAPO算法的技术细节 算法优势 禹棋赢的研究历程 关键时间节点 字节跳动的“Top Seed人才计划” 计划特点 小编总结 在大模型时代,经验不再是唯一的衡量标准,好奇心、执行力和对新技术的敏锐洞察力成为推动技术…...
九、JavaScript作用域、预解析
一、JavaScript作用域 1.JavaScript作用域 ①代码名字(变量)在某个范围内起作用和效果 目的是为了提高程序的可靠性更重要的是减少命名冲突 ②js的作用域(es6)之前:全局作用域 局部作用域 ③全局作用域:整…...
前后端+数据库的项目实战:hbu迎新网-较复杂(下)javaweb
目录 十一、实现对内容的富文本编辑(换行、图片颜色等等样式) (1)下载富文本编辑器,引入资源 (2)将原项目的内容部分替换为富文本编辑器 1、替换添加页面 2、替换修改页面(和添…...
Java-模块二-2
整数类型 byte:在 Java 中占用8位(1字节),因此它的取值范围是从 -128 到 127。这是最小的整数类型,适合用于节省空间的情况。 short:这种类型的大小是16位(2字节),允许的…...
Redis、Memcached应用场景对比
环境 Redis官方网站: Redis - The Real-time Data Platform Redis社区版本下载地址:Install Redis | Docs Memcached官方网站:memcached - a distributed memory object caching system Memcached下载地址:memcached - a dis…...
【单片机通信技术应用——学习笔记三】液晶屏显示技术,取模软件的应用
一、液晶显示技术简介 1.RGB信号线 RGB是一种色彩模式,是工业界的一种颜色标准,是通过红(R)、绿(G)、蓝(B)三个颜色通道的变化,以及它们相互之间的叠加来得到各式各样的…...
MySQL颠覆版系列————MySQL新特性(开启数据库的新纪元)上篇
文章目录 前言一、窗口函数(Window Functions)1.1 窗口函数概念1.2 常见的窗口函数 二、公用表表达式(Common Table Expressions, CTEs)2.1 公用表表达式的概念2.2 常见的公用表表达式 三、JSON增强3.1 JSON增强的概念3.2 常见的J…...
MySQL 调优:查询慢除了索引还能因为什么?
文章目录 情况一:连接数过小情况二:Buffer Pool 太小 MySQL 查询慢除了索引还能因为什么?MySQL 查询慢,我们一般也会想到是因为索引,但除了索引还有哪些原因会导致数据库查询变慢呢? 以下以 MySQL 中一条 S…...
Java实习生面试题(2025.3.23 be)
一、v-if与v-show的区别 v-show 和 v-if 都是 Vue 中的条件渲染指令,它们的主要区别在于渲染策略:v-if 会根据条件决定是否编译元素,而 v-show 则始终编译元素,只是通过改变 CSS 的 display 属性来控制显示与隐藏。 二、mybatis-…...
如何在百度搜索上删除与自己名字相关的资料
个人信息的网络足迹如同一张无形的网,将我们与世界的每一个角落紧密相连。然而,当某些与自己名字相关的资料不再希望被公众轻易检索到时,如何在百度搜索中有效“隐身”,成为了一个亟待解决的问题。面对复杂多变的网络环境…...
【C语言】C语言使用随机数srand,rand
C语言使用随机数srand,rand 可直接编译使用: #include <stdio.h> #include <time.h> #include <stdlib.h> #include <unistd.h>/* c语言提供的跟随机数有关的函数:int rand(void);返回值:产生的随机数void srand(unsiqned int seed);参数…...
为容器指定固定IP地址
文章目录 为容器指定固定IP地址可以通过以下步骤实现,适用于Docker环境:**方法一:使用Docker自定义桥接网络****方法二:Docker Compose配置****关键注意事项** 为容器指定固定IP地址可以通过以下步骤实现,适用于Docker…...
kube-score K8S Yaml静态代码分析工具详解
kube-score 是一款专注于 Kubernetes 配置文件的静态代码分析工具,旨在通过自动化检查帮助用户识别资源配置中的潜在问题,并遵循最佳实践以提升集群的安全性、稳定性和性能。以下是其核心功能、使用方法和应用场景的详细解析: 一、核心功能与…...
Spring Boot 整合 Elasticsearch 实践:从入门到上手
引言 Elasticsearch 是一个开源的分布式搜索引擎,广泛用于日志分析、搜索引擎、数据分析等场景。本文将带你通过一步步的教程,在 Spring Boot 项目中整合 Elasticsearch,轻松实现数据存储与查询。 1. 创建 Spring Boot 项目 首先ÿ…...
使用外部事件检测接入 CDH 大数据管理平台告警
CDH 大数据管理平台 CDH(Cloudera Distribution Hadoop)是一个企业级的大数据平台,由 Cloudera 公司提供,它包含了 Apache Hadoop 生态系统中的多种开源组件,并对其进行了优化和集成,以支持大规模数据存储…...
