万字长文理解无界队列和有界队列和适用场景
>大家好,我是 V 哥,无界队列(Unbounded Queue)和有界队列(Bounded Queue)是两种常见的数据结构,用于存储和管理数据项。在计算机科学和并发编程中,它们有不同的特性和应用场景。下面详细解释这两者的概念、特点和适用场景。点赞收藏加关注,高效学习不迷路
。
一、无界队列(Unbounded Queue)
1. 定义
无界队列是指在逻辑上没有限制队列中可以容纳的元素数量的队列。也就是说,无论向队列中添加多少元素,队列都能够处理,而不会因为超出某个限制而抛出异常或阻塞操作。
2. 特点
- 动态扩展:无界队列可以根据需要动态扩展内存,以容纳更多的元素。
- 适合高并发:在高并发的环境下,无界队列可以有效地处理大量的请求,而不会因为容量限制导致阻塞。
- 内存占用:由于没有容量限制,长期使用可能导致内存消耗过大,甚至引发内存溢出。
3. 适用场景
- 任务调度:在异步任务调度中,使用无界队列可以将任务放入队列中,消费者可以随时处理。
- 事件处理:在事件驱动的系统中,使用无界队列可以接收大量事件并异步处理。
4. 任务调度案例
下面是一个使用Java实现异步任务调度的示例,使用无界队列(BlockingQueue
)来存放任务,消费者可以随时从队列中取出任务进行处理。
一、应用场景
在异步任务调度中,生产者不断生成任务并将其放入队列,而消费者则从队列中取出任务并处理。无界队列允许生产者在任何时候放入任务,而不会因为队列已满而阻塞,适合于处理流量波动的场景。
二、Java 实现
我们使用LinkedBlockingQueue
来实现无界队列,并创建生产者和消费者线程。
1. Maven依赖(如果使用Maven)
我们使用Maven构建项目,在pom.xml
中添加以下依赖:
<dependencies> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.7.32</version> </dependency> </dependencies>
2. 代码实现
import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; class Task { private final String name; public Task(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "Task{" + "name='" + name + '\'' + '}'; } } // 生产者类 class TaskProducer implements Runnable { private final BlockingQueue<task> taskQueue; public TaskProducer(BlockingQueue<task> taskQueue) { this.taskQueue = taskQueue; } @Override public void run() { int taskCount = 0; while (true) { try { // 模拟任务生成 Task task = new Task("Task-" + taskCount++); System.out.println("Producing " + task); taskQueue.put(task); // 将任务放入队列 Thread.sleep(100); // 模拟生产间隔 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } // 消费者类 class TaskConsumer implements Runnable { private final BlockingQueue<task> taskQueue; public TaskConsumer(BlockingQueue<task> taskQueue) { this.taskQueue = taskQueue; } @Override public void run() { while (true) { try { Task task = taskQueue.take(); // 从队列中取出任务 System.out.println("Consuming " + task); Thread.sleep(200); // 模拟处理时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } public class AsyncTaskScheduler { public static void main(String[] args) { BlockingQueue<task> taskQueue = new LinkedBlockingQueue<>(); // 创建无界队列 // 启动生产者线程 Thread producerThread = new Thread(new TaskProducer(taskQueue)); producerThread.start(); // 启动消费者线程 Thread consumerThread = new Thread(new TaskConsumer(taskQueue)); consumerThread.start(); // 让线程运行一段时间后停止 try { Thread.sleep(5000); // 运行5秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { producerThread.interrupt(); // 中断生产者线程 consumerThread.interrupt(); // 中断消费者线程 } } }
三、来解释一下代码
-
Task类:表示一个任务对象,包含任务的名称和字符串表示。
-
TaskProducer类:
- 实现
Runnable
接口,负责生成任务并将其放入队列。 - 使用
BlockingQueue
来存放任务,通过taskQueue.put(task)
将任务放入队列。 - 通过
Thread.sleep(100)
模拟任务生成的时间间隔。
- 实现
-
TaskConsumer类:
- 也实现
Runnable
接口,负责从队列中取出任务并处理。 - 使用
taskQueue.take()
从队列中取出任务,如果队列为空,它将阻塞直到有任务可用。 - 通过
Thread.sleep(200)
模拟处理任务的时间。
- 也实现
-
AsyncTaskScheduler类:
- 主类,创建一个
LinkedBlockingQueue
实例作为无界队列。 - 启动生产者和消费者线程。
- 运行一段时间后中断线程,以停止程序。
- 主类,创建一个
四、运行效果
运行这个程序时,控制台会显示生产者生成的任务和消费者处理的任务。由于使用的是无界队列,生产者可以不断生成任务而不会被阻塞,消费者则可以从队列中取出任务并处理。
五、注意事项
- 资源管理:在实际应用中,应注意线程的管理,确保在不需要时可以优雅地关闭线程。
- 异常处理:示例中的异常处理较简单,实际应用中可以根据需求更细致地处理不同异常情况。
- 性能考虑:在高并发的环境中,需要关注性能和资源消耗,合理调整任务生成和消费的速度。
5. 事件处理案例
在事件驱动的系统中,无界队列可以用来接收和处理大量的事件。这种设计使得事件的生产者可以快速将事件放入队列,而消费者则可以异步地处理这些事件。以下是一个使用Java实现此应用场景的示例。
一、应用场景
在事件驱动的系统中,事件生产者会不断产生事件(如用户操作、系统通知等),并将其放入无界队列中。事件消费者则从队列中异步读取这些事件并进行处理,例如发送通知、更新数据库或触发其他操作。无界队列确保生产者不会因为事件处理速度慢而被阻塞。
二、Java 实现
我们使用BlockingQueue
来实现无界队列,并创建生产者和消费者线程来处理事件。
1. Maven依赖(如果使用Maven)
用Maven构建项目,添加slf4j依赖:
<dependencies> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.7.32</version> </dependency> </dependencies>
2. 代码实现
import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; // 事件类 class Event { private final String message; public Event(String message) { this.message = message; } public String getMessage() { return message; } @Override public String toString() { return "Event{" + "message='" + message + '\'' + '}'; } } // 事件生产者类 class EventProducer implements Runnable { private final BlockingQueue<event> eventQueue; public EventProducer(BlockingQueue<event> eventQueue) { this.eventQueue = eventQueue; } @Override public void run() { int eventCount = 0; while (true) { try { // 模拟事件生成 Event event = new Event("Event-" + eventCount++); System.out.println("Producing " + event); eventQueue.put(event); // 将事件放入队列 Thread.sleep(50); // 模拟生产间隔 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } // 事件消费者类 class EventConsumer implements Runnable { private final BlockingQueue<event> eventQueue; public EventConsumer(BlockingQueue<event> eventQueue) { this.eventQueue = eventQueue; } @Override public void run() { while (true) { try { Event event = eventQueue.take(); // 从队列中取出事件 System.out.println("Consuming " + event); Thread.sleep(100); // 模拟处理时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } public class EventDrivenSystem { public static void main(String[] args) { BlockingQueue<event> eventQueue = new LinkedBlockingQueue<>(); // 创建无界队列 // 启动事件生产者线程 Thread producerThread = new Thread(new EventProducer(eventQueue)); producerThread.start(); // 启动事件消费者线程 Thread consumerThread = new Thread(new EventConsumer(eventQueue)); consumerThread.start(); // 让线程运行一段时间后停止 try { Thread.sleep(5000); // 运行5秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { producerThread.interrupt(); // 中断生产者线程 consumerThread.interrupt(); // 中断消费者线程 } } }
三、来解释一下代码
-
Event类:表示事件对象,包含事件的消息内容和字符串表示。
-
EventProducer类:
- 实现
Runnable
接口,负责生成事件并将其放入事件队列。 - 使用
BlockingQueue<event>
来存放事件,通过eventQueue.put(event)
将事件放入队列。 - 使用
Thread.sleep(50)
模拟事件生成的时间间隔。
- 实现
-
EventConsumer类:
- 同样实现
Runnable
接口,负责从队列中取出事件并进行处理。 - 使用
eventQueue.take()
从队列中取出事件,如果队列为空,则阻塞等待事件。 - 使用
Thread.sleep(100)
模拟处理事件的时间。
- 同样实现
-
EventDrivenSystem类:
- 主类,创建一个
LinkedBlockingQueue
实例作为无界队列。 - 启动事件生产者和消费者线程。
- 运行一段时间后中断线程,以停止程序。
- 主类,创建一个
四、运行效果
运行此程序时,控制台会显示生产者生成的事件和消费者处理的事件。由于使用的是无界队列,生产者可以快速生成事件而不会被阻塞,而消费者则会异步地从队列中取出事件进行处理。
五、注意事项
- 资源管理:在实际应用中,应注意线程的管理,确保在不需要时可以优雅地关闭线程。
- 异常处理:示例中的异常处理较简单,实际应用中可以根据需求更细致地处理不同异常情况。
- 性能考虑:在高并发的环境中,需要关注性能和资源消耗,合理调整事件生成和消费的速度。
六、小结
通过这个示例,咱们可以看到如何在Java中使用无界队列实现事件驱动系统。生产者不断生成事件并放入队列,消费者则异步处理这些事件,提升了系统的响应速度和处理能力。这种设计模式适用于高并发、高流量的系统,能够有效管理资源并提升系统的整体性能。
二、有界队列(Bounded Queue)
1. 定义
有界队列是指在逻辑上限制了队列中可以容纳的元素数量的队列。队列在初始化时设置一个最大容量,当达到该容量时,再尝试添加新元素将会失败或阻塞。
2. 特点
- 固定容量:有界队列在创建时指定最大容量,超过该容量后,新的入队操作将被拒绝或阻塞。
- 流量控制:有界队列可以防止系统过载,通过限制请求数量来实现流量控制。
- 内存管理:通过限制队列大小,可以有效管理内存使用,避免长时间运行导致的内存泄漏。
3. 适用场景
- 生产者-消费者模型:在生产者-消费者问题中,有界队列可以确保生产者不会生产过多的任务,从而导致内存耗尽。
- 限流控制:在高流量的API中,可以使用有界队列来控制请求的处理速度,防止系统过载。
4. 生产者-消费者模型案例
在生产者-消费者问题中,有界队列用于限制生产者可以生成的任务数量,从而避免内存耗尽的情况。在这种模式中,生产者线程负责生成任务并将其放入队列,而消费者线程则从队列中取出任务进行处理。有界队列通过设置一个最大容量来限制队列中的任务数量。当队列已满时,生产者会被阻塞,直到消费者取走一些任务,从而释放出空间。
以下是一个使用Java实现生产者-消费者问题的示例,使用有界队列(ArrayBlockingQueue
)来限制队列的容量。
一、应用场景
在这个应用场景中,生产者线程生成任务并放入有界队列,而消费者线程从队列中取出任务并处理。通过这种方式,可以有效管理内存,防止生产者生成过多任务而导致系统资源耗尽。
二、Java 实现
我们使用ArrayBlockingQueue
来实现有界队列,并创建生产者和消费者线程。
1. Maven依赖(如果使用Maven)
用Maven构建项目,在pom.xml
中添加以下依赖:
<dependencies> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.7.32</version> </dependency> </dependencies>
2. 代码实现
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; // 任务类 class Task { private final String name; public Task(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "Task{" + "name='" + name + '\'' + '}'; } } // 生产者类 class TaskProducer implements Runnable { private final BlockingQueue<task> taskQueue; public TaskProducer(BlockingQueue<task> taskQueue) { this.taskQueue = taskQueue; } @Override public void run() { int taskCount = 0; while (true) { try { // 模拟任务生成 Task task = new Task("Task-" + taskCount++); System.out.println("Producing " + task); taskQueue.put(task); // 将任务放入队列 Thread.sleep(100); // 模拟生产间隔 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } // 消费者类 class TaskConsumer implements Runnable { private final BlockingQueue<task> taskQueue; public TaskConsumer(BlockingQueue<task> taskQueue) { this.taskQueue = taskQueue; } @Override public void run() { while (true) { try { Task task = taskQueue.take(); // 从队列中取出任务 System.out.println("Consuming " + task); Thread.sleep(200); // 模拟处理时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } public class ProducerConsumerProblem { public static void main(String[] args) { BlockingQueue<task> taskQueue = new ArrayBlockingQueue<>(5); // 创建有界队列,最大容量为5 // 启动生产者线程 Thread producerThread = new Thread(new TaskProducer(taskQueue)); producerThread.start(); // 启动消费者线程 Thread consumerThread = new Thread(new TaskConsumer(taskQueue)); consumerThread.start(); // 让线程运行一段时间后停止 try { Thread.sleep(10000); // 运行10秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { producerThread.interrupt(); // 中断生产者线程 consumerThread.interrupt(); // 中断消费者线程 } } }
三、来解释一下代码
-
Task类:表示一个任务对象,包含任务的名称和字符串表示。
-
TaskProducer类:
- 实现
Runnable
接口,负责生成任务并将其放入任务队列。 - 使用
BlockingQueue<task>
来存放任务,通过taskQueue.put(task)
将任务放入队列。如果队列已满,生产者会被阻塞,直到有空间可用。 - 通过
Thread.sleep(100)
模拟任务生成的时间间隔。
- 实现
-
TaskConsumer类:
- 同样实现
Runnable
接口,负责从队列中取出任务并进行处理。 - 使用
taskQueue.take()
从队列中取出任务,如果队列为空,消费者会被阻塞,直到有任务可用。 - 通过
Thread.sleep(200)
模拟处理任务的时间。
- 同样实现
-
ProducerConsumerProblem类:
- 主类,创建一个
ArrayBlockingQueue
实例作为有界队列,最大容量为5。 - 启动生产者和消费者线程。
- 运行10秒后中断线程,以停止程序。
- 主类,创建一个
四、运行效果
运行这个程序时,控制台会显示生产者生成的任务和消费者处理的任务。由于使用的是有界队列,当队列达到最大容量时,生产者会被阻塞,直到消费者取走任务,释放出空间。这种机制有效地防止了生产者过量生产任务导致内存耗尽的情况。
五、注意事项
- 资源管理:在实际应用中,应注意线程的管理,确保在不需要时可以优雅地关闭线程。
- 异常处理:示例中的异常处理较简单,实际应用中可以根据需求更细致地处理不同异常情况。
- 性能考虑:在高并发的环境中,需要关注性能和资源消耗,合理调整任务生成和消费的速度。
六、小结
通过这个示例,咱们可以看到如何在Java中使用有界队列实现生产者-消费者问题。生产者在队列达到最大容量时被阻塞,确保了不会因为生成过多任务而导致内存耗尽。这种设计模式有效地管理了资源,提高了系统的稳定性和可预测性。
5. 限流控制案例
在高流量的API中,使用有界队列可以有效地控制请求的处理速度,从而防止系统过载。通过限制请求的数量,有界队列能够保证系统在高并发情况下仍然能够稳定运行。以下是一个使用Java实现限流控制的示例,使用有界队列(ArrayBlockingQueue
)来处理请求。
一、应用场景
在这个应用场景中,API接收客户端的请求,将请求放入有界队列中,消费者线程从队列中取出请求进行处理。通过设定队列的容量,可以有效控制请求的处理速度,确保系统不会因为请求过多而崩溃。
二、Java 实现
我们使用ArrayBlockingQueue
来实现有界队列,并创建请求生产者和消费者线程。
1. Maven依赖(如果使用Maven)
使用Maven构建项目,在pom.xml
中添加以下依赖:
<dependencies> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.7.32</version> </dependency> </dependencies>
2. 代码实现
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; // 请求类 class Request { private final String clientId; public Request(String clientId) { this.clientId = clientId; } public String getClientId() { return clientId; } @Override public String toString() { return "Request{" + "clientId='" + clientId + '\'' + '}'; } } // 请求生产者类 class RequestProducer implements Runnable { private final BlockingQueue<request> requestQueue; public RequestProducer(BlockingQueue<request> requestQueue) { this.requestQueue = requestQueue; } @Override public void run() { int requestCount = 0; while (true) { try { // 模拟请求生成 Request request = new Request("Client-" + requestCount++); System.out.println("Producing " + request); requestQueue.put(request); // 将请求放入队列 Thread.sleep(50); // 模拟生产请求的间隔 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } // 请求消费者类 class RequestConsumer implements Runnable { private final BlockingQueue<request> requestQueue; public RequestConsumer(BlockingQueue<request> requestQueue) { this.requestQueue = requestQueue; } @Override public void run() { while (true) { try { Request request = requestQueue.take(); // 从队列中取出请求 System.out.println("Consuming " + request); Thread.sleep(100); // 模拟处理请求的时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } } } } public class RateLimitingControl { public static void main(String[] args) { BlockingQueue<request> requestQueue = new ArrayBlockingQueue<>(5); // 创建有界队列,最大容量为5 // 启动请求生产者线程 Thread producerThread = new Thread(new RequestProducer(requestQueue)); producerThread.start(); // 启动请求消费者线程 Thread consumerThread = new Thread(new RequestConsumer(requestQueue)); consumerThread.start(); // 让线程运行一段时间后停止 try { Thread.sleep(10000); // 运行10秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { producerThread.interrupt(); // 中断生产者线程 consumerThread.interrupt(); // 中断消费者线程 } } }
三、来解释一下代码
-
Request类:表示请求对象,包含客户端ID和字符串表示。
-
RequestProducer类:
- 实现
Runnable
接口,负责生成请求并将其放入请求队列。 - 使用
BlockingQueue<request>
来存放请求,通过requestQueue.put(request)
将请求放入队列。如果队列已满,生产者会被阻塞,直到有空间可用。 - 使用
Thread.sleep(50)
模拟请求生成的时间间隔。
- 实现
-
RequestConsumer类:
- 同样实现
Runnable
接口,负责从队列中取出请求并进行处理。 - 使用
requestQueue.take()
从队列中取出请求,如果队列为空,消费者会被阻塞,直到有请求可用。 - 使用
Thread.sleep(100)
模拟处理请求的时间。
- 同样实现
-
RateLimitingControl类:
- 主类,创建一个
ArrayBlockingQueue
实例作为有界队列,最大容量为5。 - 启动请求生产者和消费者线程。
- 运行10秒后中断线程,以停止程序。
- 主类,创建一个
四、运行效果
运行这个程序时,控制台会显示生产者生成的请求和消费者处理的请求。由于使用的是有界队列,当队列达到最大容量时,生产者会被阻塞,直到消费者取走请求,释放出空间。这种机制有效地控制了请求的处理速度,确保了系统不会因为请求过多而过载。
五、注意事项
- 资源管理:在实际应用中,应注意线程的管理,确保在不需要时可以优雅地关闭线程。
- 异常处理:示例中的异常处理较简单,实际应用中可以根据需求更细致地处理不同异常情况。
- 性能考虑:在高并发的环境中,需要关注性能和资源消耗,合理调整请求生成和消费的速度。
六、总结
通过这个示例,咱们可以看到如何在Java中使用有界队列实现API的限流控制。生产者在队列达到最大容量时被阻塞,确保了不会因为生成过多请求而导致系统过载。这种设计模式有效地管理了资源,提高了系统的稳定性和可预测性。在高流量的API场景中,限流控制是确保服务质量的重要手段。
三、总结
- 无界队列适用于需要处理大量数据且不关心内存占用的场景,能够动态适应任务量,但需要关注内存管理。
- 有界队列则在资源有限或需要严格控制流量的场景中更加合适,可以有效防止系统过载和内存溢出。
理解这两种队列的特性和应用场景,能够帮助我们在不同的业务需求中选择合适的数据结构,以提高系统的性能和稳定性。原创不易,关注威哥爱编程,一起学习 Java 的点点滴滴。</request></request></request></request></request></request></task></task></task></task></task></task></event></event></event></event></event></event></task></task></task></task></task>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
什么是 JWT?它是如何工作的?
松哥最近辅导了几个小伙伴秋招,有小伙伴在面小红书时遇到这个问题,这个问题想回答全面还是有些挑战,松哥结合之前的一篇旧文和大伙一起来聊聊。 一 无状态登录 1.1 什么是有状态 有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下: 服务端保存大量数据,增加服务端压力 服务端保存用户状态,不支持集群化部署 1.2 什么是无状态 微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即: 服务端不保存任何客户端请求者信息 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份 那么这种无状态性有哪些好处呢? ...
- 下一篇
🔥SmartAdmin v3.7 | 重磅支持 Java17+SpringBoot3
SmartAdmin「高质量代码、简洁、高效、安全」的快速开发平台 v3.7.0 重磅支持 Java17 和 SpringBoot3.X,更新如下: 【新增】支持Java17 和 Java8 【新增】支持SpringBoot3 和 SpringBoot2 【优化】优化AES和SM4加密 【优化】优化三级等保文档 :一篇文章搞懂三级等保 SmartAdmin 由 中国·洛阳 1024创新实验室 基于SpringBoot+Sa-Token+Mybatis-Plus 和 Vue3+Vite5+Ant Design Vue ,坚持以 「高质量代码」为核心,「简洁、高效、安全」的中后台解决方案! 国内首个满足《网络安全-三级等保》、《数据安全》, 支持登录限制、支持接口国产加解密、支持数据加解密等一系列安全措施的开源项目。 前端同时支持JavaScript和TypeScript双版本,,后端同时支持Java8+SpringBoot2.X和Java17+SpringBoot3.X 双版本。 我们开源一套漂亮的代码和一套整洁的代码规范,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境