IO是java绕不过去的槛,在开发中io无处不在, 正如同 世界上本没有路,java io写多了,也就知道了大体是什么意思,在读完thinking in java 感觉就更清晰了,结合具体的业务场景,整理一下 ,什么是IO。为什么JAVA要这么设计IO。

先来一道开胃菜

我想要读取控制台输入的字符

         InputStreamReader isr = new InputStreamReader(System.in);
         BufferedReader br = new BufferedReader(isr);
         String s = null;
         try {
             System.out.println("开始输入 。。。");
             s = br.readLine();
             while (s  != null) {
                 if (s.equalsIgnoreCase("yes")) {
                     break;
                 }
                 System.out.println(s.toUpperCase());
                 System.out.println("是否停止输入(yes/no)");
                 s = br.readLine();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }finally{
             if(br !=null){
                 try {
                     br.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             if(isr != null){
                 try {
                     isr.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
     

  解释一下:我从控制台读取一行字符,然后打印一下。这就是一个简单的流了。

  整理一下: 就是我先 得到一个用于读取 控制台输入的流,然后 我·打印我得到的东西,这里有个细节就是 流一定得关闭,这是底线,关闭的顺序:先声明的后关闭

稍微深入一点。我用Inputstream 去读取字符串并转化为想要的编码格式

 public static String getStrFormInputStream(String str,
             String encoding) throws IOException {
         InputStream inputStream = new ByteArrayInputStream(str.getBytes(encoding));
         BufferedReader bf = null;
         InputStreamReader inputStreamReader = null;
         try {
             inputStreamReader = new InputStreamReader(inputStream);
             bf = new BufferedReader(inputStreamReader);
             StringBuffer sb = new StringBuffer();
             String line = "";
             while ((line = bf.readLine()) != null) {
                 sb.append(line);
             }
             return sb.toString();
         } finally {
             if (bf != null) {
                 bf.close();
             }
             if (inputStreamReader != null) {
                 inputStreamReader.close();
             }
             if (inputStream != null) {
                 inputStream.close();
             }
         }

     }

   这就偏实际一点,当你拿到一个字符串的时候,读取的时候,有一个细节:最好加上编码格式

   解释一下:实际上读取的地方 只有这一点 line = bf.readLine() ,那么之前的是做什么呢,  我其实是在组装我想要的铲子。这也是 开发中比较常用的“包装器模式”

我想把字符串转为贴合实际的ByteArrayInputStream, 再转化为更常用的Reader(InputStreamReader)  再包装上buffer(BufferedReader)。

当我选择输出的时候:

当我查看我以前的开发记录时,发现实际业务中 绝大多数输出都是输出到文件的。想找一个简单的输出示例,并不容易

 public void sendMessage(String str,
             String host,
             int port) throws UnsupportedEncodingException, IOException {
         String sendEncoding = "GBK";
         Writer writer = null;
         Socket client = null;
         try {
             // 与服务端建立连接
             client = new Socket(host, port);
             // 建立连接后就可以往服务端写数据了
             writer = new OutputStreamWriter(client.getOutputStream(), sendEncoding);
             System.out.println(str);
             writer.write(str);
             writer.flush();// 写完后要记得flush
         } finally {
             if (writer != null) {
                 try {
                     writer.close();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
             if (client != null) {
                 try {
                     client.close();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }

     }

  输出的地方其实很简单: writer.write(str); 其他的地方 建立服务器连接,将str写到短信中和此处关系不大,需要注意 :无论是输入输出,用完了一定关闭流,这是底线

我有一文件

  文件读写才是一个真正的业务场景中占比绝大多数的。

 文件的基本操作 

String filePath = "D:\\";
        File file = new File(filePath);
        long length = 0;
        if (file.exists()){
            length = file.length();
        }else{
           file.mkdirs();
        }

在java中 File 就是 File , 它既可以是具体的文件,也可以是一个文件目录

读写文件总体步骤

当你读写文件的时候,看到直接读取字节实际上应用更广一点,当然用读取字符也是占很大比重,所以,这个问题就丢给各位读者,到底是想用哪个。反正都行。

public byte[] getBytesByFileName(String filePath) {
        byte[] buffer = null;
        try {
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            bos.close();
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }
public static void moveFile(String sourcePath,
            String targetPath) throws Exception {
        File sourceRoot = new File(sourcePath);
        if (!sourceRoot.exists()) {
            throw new Exception("要移动的文件不存在");
        }
        if (sourceRoot.isFile()) {
            boolean success = true;
            File targetFile = new File(targetPath);
            if (!targetFile.getParentFile().exists()) {
                if (!targetFile.getParentFile().mkdirs()) {
                    success = false;
                }
            }
            if (!targetFile.exists()) {
                if (!targetFile.createNewFile()) {
                    success = false;
                }
            }
            if (!success) {
                throw new Exception("目标目录创建失败");
            }
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
            byte[] d = new byte[1024];
            int length = -1;
            try {
                bis = new BufferedInputStream(new FileInputStream(sourceRoot));
                bos = new BufferedOutputStream(new FileOutputStream(targetFile));
                while ((length = bis.read(d)) != -1) {
                    bos.write(d, 0, length);
                }
                bos.flush();
            } catch (IOException e) {
                e.printStackTrace();
                success = false;
            } finally {
                if (bos != null) {
                    bos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                bos = null;
                bis = null;
            }
            if (success) {
                sourceRoot.deleteOnExit();
            }
        } else {
            File[] files = sourceRoot.listFiles();
            for (File file : files) {
                moveFile(file.getAbsolutePath(), targetPath + File.separator + file.getName());
            }
            sourceRoot.deleteOnExit();
        }
    }

  移动文件:实际上就是从一个文件中读取文件,然后写到另一个文件中,这算是一个非常详细的例子。分析代码:我想判断源文件是否存在,我再去创建目标文件夹和目标文件,当然,你也可以不用mkdir()

直接用 mkdirs()也行。

当然我写文件时的数据(调用write()方法传入的数据)不一定是来自文件也有可能来自一个list,一个byte[]数组。

public void writeFile(String str,
            File file) throws IOException {
        OutputStream out = null;
        try {
            out = new FileOutputStream(file, true); // 是否追加
            byte[] b = str.getBytes();
            out.write(b);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
public void exportByModel(List<Map<String, Object>> data,
            byte[] exportModel,
            String fileNameLocation) throws Exception {
        InputStream in = null;
        Reader reader = null;
        OutputStream out = null;
        Writer des = null;
        CharArrayWriter writer = null;
        try {
            // 读取模板
            in = new ByteArrayInputStream(exportModel);
            reader = new InputStreamReader(in);
            // 设置输出位置
            out = new FileOutputStream(fileNameLocation);
            String encoding = "GBK";
            try {
                des = new OutputStreamWriter(out, encoding);// 不设置utf-8,中文不支持
            } catch (Exception e) {
                des = new OutputStreamWriter(out, "GBK");// 编码设置异常,直接按照GBK输出
            }
            // 执行
            writer = VelocityHelper.getInstance().evaluateToWriter(data, reader);
            writer.writeTo(des);
            des.flush();
        } catch (Exception e) {
            throw new  Exception("写入文件异常");
        } finally {
            if (writer != null)
                writer.close();
            if (des != null)
                des.close();
            if (out != null)
                out.close();
            if (reader != null)
                reader.close();
            if (in != null)
                in.close();
        }
    }

读写图片

  public void createImage(BufferedImage image,
             String fileLocation) throws IOException {
         FileOutputStream fos = null;
          BufferedOutputStream bos = null;
          try {
              fos = new FileOutputStream(fileLocation);
             bos = new BufferedOutputStream(fos);
            // JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
             // encoder.encode(image);
             ImageIO.write(image, "JPEG", bos);
          } catch (Exception e) {
              e.printStackTrace();
         } finally {
              if (fos != null) {
                  fos.close();
              }
              if (bos != null) {
                 bos.close();
              }
       }
      }
     boolean isJob = etlTrans.getFromKjb();
         byte[] xml = etlTrans.getXml();
         ByteArrayInputStream bais = new ByteArrayInputStream(xml);
         TransMeta transMeta = null;
         JobMeta jobMeta = null;
         int imageWidth = 1400;// 图片的宽度
         int imageHeight = 900;// 图片的高度
         int wordWidth = 6;
         BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
         Graphics graphics = image.getGraphics();
         // 字体不支持时,中文就显示口口口了
         graphics.setFont(new java.awt.Font("宋体", java.awt.Font.BOLD, 20));
         // Font oldFont = graphics.getFont();
         graphics.setColor(Color.WHITE);
         graphics.fillRect(0, 0, imageWidth, imageHeight);

读写xml文件

public static void objectXmlEncoder(Object obj,
            String fileName) throws FileNotFoundException, IOException, Exception {
        // 创建输出文件
        File fo = new File(fileName);
        // 文件不存在,就创建该文件
        if (!fo.exists()) {
            // 先创建文件的目录
            String path = fileName.substring(0, fileName.lastIndexOf('.'));
            File pFile = new File(path);
            pFile.mkdirs();
        }
        // 创建文件输出流
        FileOutputStream fos = new FileOutputStream(fo);
        // 创建XML文件对象输出类实例
        XMLEncoder encoder = new XMLEncoder(fos);
        // 对象序列化输出到XML文件
        encoder.writeObject(obj);
        encoder.flush();
        // 关闭序列化工具
        encoder.close();
        // 关闭输出流
        fos.close();
    }

xml文件读写,实际上也是一样的,只不过调用的是xml提供的读写工具类

分文件写数据

 private void writeLog(FileOutputStream writer,
             List<ExceptionLog> cellExceptionLogs) throws IOException {
         StringBuilder builder = new StringBuilder();
         int len = cellExceptionLogs.size();
         for (int i = 0; i < len; i++) {
             ExceptionLog exceptionLog = cellExceptionLogs.get(i);
             processSystemLogData(exceptionLog, builder);
             if ((i + 1) % 500 == 0) {
                 writer.write(builder.toString().getBytes("UTF-8"));
                 writer.flush();
                 builder = null;
                 builder = new StringBuilder();
             }
         }
         if (len % 500 != 0) {
             writer.write(builder.toString().getBytes("UTF-8"));
             writer.flush();
         }
     }
     private void processSystemLogData(ICellExceptionLog exception,
             StringBuilder builder) {
         builder.append(exception.getId() + Constant.REPORTUNITSEPARATOR);
         builder.append(exception.getCode() + Constant.REPORTUNITSEPARATOR);
         builder.append(exception.getName() + Constant.REPORTUNITSEPARATOR);
         builder.append(exception.getDescription()+ Constant.REPORTUNITSEPARATOR);
         builder.append("\r\n");
     }

综合应用:编写一个页面,提供给用户下载数据。用户选择下载位置

本质上是一样,只不过输出的流特殊一点(参考于java web文件下载功能实现

第一步:写一个提供给用户的按钮

<a href="/day06/ServletDownload?filename=cors.zip">压缩包</a>  

第二步:编写对应的servlet方法。一共分两步:把文件读入文件流,把文件流内的数据写入到response中(response.getOutputStream().write(bytes, 0, bytes.length);)

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         //获得请求文件名
        String filename = request.getParameter("filename");
        System.out.println(filename);
        //设置文件MIME类型
        response.setContentType(getServletContext().getMimeType(filename));
        //设置Content-Disposition
        response.setHeader("Content-Disposition", "attachment;filename="+filename);
        //读取目标文件,通过response将目标文件写到客户端
        //获取目标文件的绝对路径
        String fullFileName = getServletContext().getRealPath("/download/" + filename);
        //System.out.println(fullFileName);
        //读取文件
        InputStream in = new FileInputStream(fullFileName);
        OutputStream out = response.getOutputStream();
        //写文件
        int b;
        while((b=in.read())!= -1)
        {
            out.write(b);
        }
     · in.close();
        out.close();
    }  

断点续传

   所谓断点续传 就是文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web服务器的时候要多加一条信息--从哪里开始。

断点续传的简单实例:

这是浏览器想要断点续传的时候发给服务器的信息

//        GET /down.zip HTTP/1.0
//        User-Agent: NetFox
//        RANGE: bytes=2000070-
//        Accept: text/html, image/gif, image/jpeg, *; q=.2, *; q=.2
//        上边的信息,是浏览器发给web用户的断点续传的信息

服务器的操作: 这是一段简单的模拟从一定位置些文件的代码

//假设从 2000070 处开始保存文件,代码如下:
        RandomAccess oSavedFile = new  RandomAccessFile("down.zip","rw");
        long nPos = 2000070;
        // 定位文件指针到 nPos 位置
        oSavedFile.seek(nPos);
        byte[] b = new byte[1024];
        int nRead;
        // 从输入流中读入字节流,然后写到文件中
        while((nRead=input.read(b,0,1024)) > 0)
        {
        oSavedFile.write(b,0,nRead);   

实际上,sevlet断点续传肯定不是这么简单,但是总体思路一致,我犹豫好久,最终还是把真正用于web项目的断点续传的服务器操作贴出(虽然我已经去了大多数和断点续传无关的业务逻辑,但是代码还是稍显复杂)

         public static final int buf_size = 8192;
         ServletOutputStream out = response.getOutputStream();
         RandomAccessFile raf = null;
         File file = null;
         try {
             file = new File(filefullname);//这是我们写文件的数据来源
             raf = new RandomAccessFile(file, "rw");
             long pos = 0;
             long end = 0;//这写开始写的位置,结束的位置可以从请求报文得到
             long fLength = file.length();
             //如果有断点续传的信息
             if (queryStringMap.get("Range") != null) {
                 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//设置返回信息为206
                 String range = ((String) queryStringMap.get("Range")).replaceAll("bytes=", "");
                 pos = Long.valueOf(range.split("-")[0]);
                 end = Long.valueOf(range.split("-")[1]);
             } else {
                 end = fLength - 1;
             }
             //是我这次请求需要写多少长度的文件
             long length = end - pos + 1;
             //下面是返回报文信息
             response.setHeader("Content-Length", String.valueOf(length));
             response.setContentType("application/x-msdownload");
             response.addHeader("content-disposition", "attachment;filename=\"" + filename + "\"");
             //下边是调用RandomAccess方法进行断点续传
             byte[] buf = new byte[buf_size];
             int n = 0;
             int i = 0;//用来for循环中统计写的次数
             raf.seek(pos);// 定位文件指针到 pos 位置
             // buf_size 是定义的大文件写的快,如果你的文件还没一块大,直接写就好
             int p = (int) (length / buf_size);//我要写多少块(byte[])
             int b_size = (int) (length % buf_size);
             if (b_size > 0)
                 p++;
             while (i < p) {
                 i++;
                 if (i == p && b_size > 0) {
                     buf = new byte[b_size];
                     n = raf.read(buf, 0, b_size);
                 } else {
                     n = raf.read(buf, 0, buf_size);
                 }
                 out.write(buf);
                 pos += n;
                 raf.seek(pos);
             }
         }catch(Exception e){
             e.printStackTrace();
         }finally{
 //            ....
         }
             //...
     

服务器操作完成之后返回的相应信息: 可以清楚的看到比普通的返回信息多了一个content-range

//Content-Length=106786028
//Content-Range=bytes 2000070-106786027/106786028
//Date=Mon, 30 Apr 2001 12:55:20 GMT
//ETag=W/"02ca57e173c11:95b"
//Content-Type=application/octet-stream
//Server=Microsoft-IIS/5.0
//Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT  

当然实际项目都是多线程的,可以参考Java--使用多线程下载,断点续传技术原理(RandomAccessFile)

RandomAccessFile的常用方法介绍 可以参考java的RandomAccessFile类

 

给你一数据库

 protected Map<String, Object> getRecord(ResultSet rset,
             ResultSetMetaData metaData,
             int colnum) throws SQLException {
         Map<String, Object> resultMap = new HashMap<String, Object>();
         for (int columnCount = 1; columnCount <= colnum; columnCount++) {
             String aliasName = metaData.getColumnLabel(columnCount);
             Object columnValue = null;
             String columnName = aliasName != null ? aliasName : metaData.getColumnName(columnCount);
             int columnType = metaData.getColumnType(columnCount);
             //...
             if (columnType == Types.BLOB || columnType == Types.LONGVARCHAR
                     || columnType == Types.LONGVARBINARY) {
                 if (rset.getBlob(columnName) != null) {

                     InputStream res = rset.getBlob(columnName).getBinaryStream();
                     int BUFFER_SIZE = 4096;
                     ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                     try {
                         byte[] data = new byte[4096];
                         int count = -1;
                         while ((count = res.read(data, 0, BUFFER_SIZE)) != -1)
                             outStream.write(data, 0, count);

                         data = null;
                         columnValue = new String(outStream.toByteArray());

                     } catch (Exception e) {
                         throw new SQLException("GenericDaoImpl.jdbc.error");
                     } finally {
                         try {
                             if (outStream != null)
                                 outStream.close();
                             if (res != null)
                                 res.close();
                         } catch (IOException e) {
                             throw new SQLException("GenericDaoImpl.jdbc.error");
                         }
                     }
                 }
             } else if (columnType == Types.CLOB) {
                 Clob clob = rset.getClob(columnName);
                 if (clob != null) {
                     columnValue = clob.getSubString((long) 1, (int) clob.length());
                 }
             } else {
                 columnValue = rset.getObject(columnName);
             }
             resultMap.put(columnName.toUpperCase(), columnValue);
         }
         return resultMap;
     }

数据库中读取记录代码

一般情况下,web项目采用一些orm框架足以支撑,数据库字段的读写,但是,有时候为了效率或者是特有的业务要求。会自己编写dao层支持。

而在读取数据库记录,写入Map的时候,如何读取一些特殊字段,比如Blob, 上述代码就是描述如何读取blob字段

IO概括:

 文件的读写 ,在java中是非常常用。

 而设计者设计读写控制的时候,也整理的颇为详细,可能的结果就是,给调用者带来很大的困惑,这么多类,咋用。

 其实完全不用担心:java写文件只有三步,百变不离其宗

    1:我找到三样东西:   铲子(IO工具类); 沙子(我要读取的数据); 篓子(我要放的东西)

    2:用铲子把沙子放到篓子里

    3:我把铲子还给人家

至于我用 何种铲子,我要铲的是沙子 还是面粉,我要放到篓子还是框子里。先别管,也别上来就看那个javaIIO的类示意图   

按铲子区分:一般单一的铲子不适用,需要组合多种功能

  java中有读写字符的(reader/writer) 读写字节的(inputstream,outputstream)。自由选择

  java按照功能 还会有 filereader xmlreder imgio   buffereader 等

按照沙子来看:

  字符串,byte[] , List<T>,数据库查询的结果集

按照篓子来看

  可以放到Map String 图片 文件 xml

  

  

  

Java IO在实际项目开发中应用的更多相关文章

  1. Java 容器在实际项目开发中应用

    前言:在java开发中我们离不开集合数组等,在java中有个专有名词:"容器" ,下面会结合Thinking in Java的知识和实际开发中业务场景讲述一下容器在Web项目中的用 ...

  2. 《Maven在Java项目开发中的应用》论文笔记(十七)

    标题:Maven在Java项目开发中的应用 一.基本信息 时间:2019 来源:山西农业大学 关键词:Maven:Java Web:仓库:开发人员:极限编程; 二.研究内容 1.Maven 基本原理概 ...

  3. Java项目开发中实现分页的三种方式一篇包会

    前言   Java项目开发中经常要用到分页功能,现在普遍使用SpringBoot进行快速开发,而数据层主要整合SpringDataJPA和MyBatis两种框架,这两种框架都提供了相应的分页工具,使用 ...

  4. java.io.IOException: 您的主机中的软件中止了一个已建立的连接解决办法

    问题现象和http://hi.baidu.com/cara_cloud/item/193a3ee327546d395a2d64be描述的一样,就是在eclipse的console栏中一直显示java. ...

  5. 团队项目开发中,常见的版本控制有svn,git

    团队项目开发中,常见的版本控制有svn,git

  6. java.io.IOException: 你的主机中的软件中止了一个已建立的连接。

    1.异常表现:我在jsp文件中有一个<form>表单,里面有一个<button>保存事件按钮.<button  onclick="addOrUPdate()&q ...

  7. 如何解决tomcat中的应用报java.io.IOException: 您的主机中的软件中止了一个已建立的连接

    转载: 施勇: https://blog.csdn.net/shiyong1949/article/details/72845634 这两天突然看到日志文件中有“java.io.IOException ...

  8. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  9. tomcat报错:java.io.IOException: 您的主机中的软件中止了一个已建立的连接。

    tomcat报错: org.apache.catalina.connector.ClientAbortException: java.io.IOException: 您的主机中的软件中止了一个已建立的 ...

随机推荐

  1. Plupload上传插件自定义图片的修改

    若自定义的一个上传图片效果,代码(可能不全),当用户再次点击所有或任意一个上传图片的input时,uploader.files已经多了客户再次上传的图片,但是你就想要最后的两张图片,这就可以使用到up ...

  2. Deep Learning in R

    Introduction Deep learning is a recent trend in machine learning that models highly non-linear repre ...

  3. R语言快速深度学习进行回归预测(转)

    深度学习在过去几年,由于卷积神经网络的特征提取能力让这个算法又火了一下,其实在很多年以前早就有所出现,但是由于深度学习的计算复杂度问题,一直没有被广泛应用. 一般的,卷积层的计算形式为: 其中.x分别 ...

  4. js实现谷歌坐标转百度坐标

    js实现谷歌坐标转百度坐标 谷歌坐标转百度坐标 实现算法如下(以js为例,其他语言调整就行): //$lat 维度:$lng 经度  function GCJTobaidu($lat, $lng){ ...

  5. Promise (2) 基本方法

    "I'm Captain Jack Sparrow" 加勒比海盗5上映,为了表示对杰克船长的喜爱,昨天闪现了几次模仿船长的走路姿势(哈哈哈,简直妖娆). 为了周天能去看电影,要赶紧 ...

  6. 一天搞定CSS:表单(form)--20

    1.表单标签 2.input标签属性与取值 代码演示 <!DOCTYPE html> <html> <head> <meta charset="UT ...

  7. Floyd判圈算法

    Floyd判圈算法 leetcode 上 编号为202 的happy number 问题,有点意思.happy number 的定义为: A happy number is a number defi ...

  8. Oracle查询数据出来乱码问题?

    为什么Oracle 查询出来的数据会产生乱码?   安装的数据库和客户端编码编码不一致就会产生乱码,要想解决此问题改一下客户端的编码即可 1. select * from table; 如果是这种问题 ...

  9. python爬虫之re正则表达式库

    python爬虫之re正则表达式库 正则表达式是用来简洁表达一组字符串的表达式. 编译:将符合正则表达式语法的字符串转换成正则表达式特征 操作符 说明 实例 . 表示任何单个字符 [ ] 字符集,对单 ...

  10. [codeforces113D]Museum

    D. Museum time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input ...