【Online Judge】4.输入输出

流的重定向

记得学C++的时候就接触到了重定向这个概念,比如说下面的cmd命令:

ipconfig > D:/out.txt

cmd命令窗口里执行ipconfig 命令后,本机IP的信息会显示在当前的命令窗口里。但是我们使用了流的重定向,即添加了 > 符号,于是把输出流定向到了out.txt 文件里,而控制台没有输出任何的信息。

再比如说C++里的输入和输出:

std::cin >> n;
std::cout << n;

这个>>符号可以说是非常的形象了,指向谁就流向谁。在cmd>>代表追加模式,具体可以参考文件的打开方式 fopen("a.txt","a")。我们再来一个例子:

java Main < test.in > test.out
# 即设定了输入流,也设置了输出流的方向

当然上面的例子只是在命令模式下的,不太适用于我们当前的需求,我们需要的是代码的形式。各种语言都有它设置流的方法:

比如说C语言的:

freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);

Java的:

static void System.setIn(InputStream in);
static void System.setOut(OutputStream out);

并发需求

单个Judger肯定是不能满足需求的,当多个用户同时提交请求的时候,如果我们是一个一个去执行那真的是太慢了。假设我是用户,提交代码后等待超过4秒后,如果还没有结果的话,我肯定会受不了(当然特殊情况例外)。这时候我们就需要到并发执行了,分别为多个Judger的并发,以及多个测试用例的并发。

但是问题又来了:并发的时候大家用的是同一个输入、输出流。这如果不给区分开来,那肯定会出问题!


ThreadLocal

既然每个线程都需要一个单独的输入、输出流,那每开一条线程就单独new一个流?那肯定是不行的,这样管理起来太麻烦了。

很明显,ThreadLocal 就很适合我们的需求:为每个线程单独管理他们的专有数据。

不懂 ThreadLocal ? 没关系,让我们看一下官方文档:

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get
set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。

ThreadLocal 实例通常是类中的
private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal
实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

ThreadLocal底层大概用的是一个 Map 来存储数据,其中的key就是一个线程对象。而且又使用了虚引用,线程被销毁后会自动帮我们回收垃圾,多好啊~

大概的流程如下图所示,我们主要在读取数据的时候下点手脚。好了,让我们编写自己的流:
在这里插入图片描述

/**
 * 一个用于多线程的输入流,每个线程都有自己的输入流,互不干扰<p>
 * 内部使用 ThreadLocal
 * @author yly
 */
public class InputStreamLocal extends InputStream{

	private volatile ThreadLocal<InputStream> stream = new ThreadLocal<InputStream>();
	
	/**
	 * 从输入流中读取一个byte的数据
	 */
	@Override
	public int read() throws IOException {
		return this.stream.get().read();
	}
	
	/**
	 * 设置当前线程的输入流
	 * @param in 输入流
	 */
	public void setStream(InputStream in) {
		this.stream.set(in);
	}

}
/**
 * 一个基于ThreadLocal的多线程输出流<p>
 * 每个线程都有一个属于自己的输出流,互不干扰
 * @author yly
 *
 */
public class OutputStreamLocal extends OutputStream{

	/**
	 * 一般来说输出流是不需要设置的,我们只关心输出的结果,所以要设置一个默认的流供我们存储数据
	 * OutputStream 只是个抽象类,不提供存储,也不能获取内容。
	 * 故使用ByteArrayOutputStream,它内部封装了一个 byte[] buf 用于存储数据
	 */
	private volatile ThreadLocal<ByteArrayOutputStream> stream = new ThreadLocal<ByteArrayOutputStream>() {
		/**
		 * 当调用ThreadLocal的get方法获取内容时,如果还没有对该值set过
		 * 那么它会调用initialValue方法来获取一个默认的值,该方法默认返回null
		 */
		@Override
		protected ByteArrayOutputStream initialValue() {
			return new ByteArrayOutputStream();
		};
	};
	
	/**
	 * 往当前线程的输出流中写入一个byte的数据
	 */
	@Override
	public void write(int b) throws IOException {
		this.stream.get().write(b);;
	}
	
	
	/**
	 * 获取当前输出流中的数据,并重置当前的输出流
	 * @return byte数组
	 */
	public byte[] getData() {
		ByteArrayOutputStream outputStream = this.stream.get();
		byte[] res = outputStream.toByteArray();
		outputStream.reset();
		return res;
	}

}

看了一下ByteArrayOutputStream的底层源码,真的是深有感触:里面内置了一个byte数组,执行写操作的时候就对该byte数组进行写入数据。那岂不是可以创建一个既可以读又可以写的InOrOutStream ?一看 InputStreamOutputStream 都是定义为抽象类,Java的单继承瞬间让我的想法破灭了。。。


杂项

核心的代码完成得差不多了,让我们捋一下接下来的需求

  • 给我一个字符串类型的测试用例,然后转成输入流
  • 计算时间的消耗,以及内存的消耗
  • 使用线程池来对线程的管理
  • 答案的对拍
  • Judger单独设为一个系统(中间件),只要通过Socket来通信

因为使用的是 JavaCompiler 来编译Java代码,这个局限性实在是太大了,我们当前只能测评Java的代码,而不能兼并C/C++, Python。如果使用 Docker 的虚拟环境的话就相对简单得多了。这个拓展我们后面再考虑吧。。。太难了,我都没用过Docker~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值