面试小结之IO篇

最近面试一些公司,被问到的关于Java NIO编程的问题,以及自己总结的回答。

谈谈对Java IO的认识。

  • 对于I/O操作来说, 其根本的作用在于传输数据。输入和输出指的仅是数据的流向,实际传输是通过某些具体的媒介来完成的,其中最主要的是文件系统和网络连接;
  • 早期的java.io包把I/O操作抽象成数据的流动,进而有了流的概念;在Java NIO中,则把I/O操作抽象成端到端的一个数据连接,这就有了通道(channel)的概念;
  • Java中最基本的流是在字节这个层次上进行操作的;在read方法的调用是阻塞的,这可能会成为应用中的瓶颈(可以通过available方法获取在不阻塞的情况下可以获取到的字节数);流无法重新使用,BufferedInputStream通过mark和reset操作可以实现流中部分内容的重复读取;另外一种重用输入流的方式是把它转换成数据来使用;
  • 输出流是通过write方法把数据存放在缓冲区(缓冲区满了会自动执行写入),使用flush方法强制进行实际的写入操作;
  • 其他常用流:FileInput(Output)Stream、ByteArrayInput(Output)Stream、字符流(new BufferedReader(new InputStreamReader(inputStream)));

介绍一下Java NIO中的Buffer、Channel和Selector的概念和作用。

  • Java NIO的缓冲区:使用数组的方式不够灵活且性能差,Java NIO的缓冲区功能更加强大;容量(capacity)表示缓冲区的额定大小,需要在创建时指定(allocate静态方法);读写限制(limit)表示缓冲区在进行读写操作时的最大允许位置;读写位置(position)表示当前进行读写操作时的位置;缓冲区的很多操作(clear、flip、rewind)都是操作limit和position的值来实现重复读写;
  • Java NIO的通道:channel表示为一个已经建立好的到支持I/O操作的实体(如文件和网络)的连接,在此连接上进行数据的读写操作,使用的是缓冲区来实现读写;
    public void openAndWrite() throws IOException {
        FileChannel channel = FileChannel.open(Paths.get("my.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
        ByteBuffer buffer = ByteBuffer.allocate(64);
        buffer.putChar('A').flip();
        channel.write(buffer);
    }
  • Socket和ServerSocket类中提供的建立连接和数据传输相关的方法都是阻塞式的;对服务端通常使用线程池的方式来调用ServerSocket.accept方法来监听连接请求;Java NIO提供了非阻塞式和多路复用的套接字连接;
    public void startSimpleServer() throws IOException{
        ServerSocketChannel channel = ServerSocketChannel.open();
        channel.bind(new InetSocketAddress("localhost", 10800));
        while(true){
            try(SocketChannel sc = channel.accept()){
                sc.write(ByteBuffer.wrap("Hello".getBytes("UTF-8")));
            }
        }
    }
  • 套接字通道的多路复用的思想比较简单,通过一个专门的选择器(Selector)来同时对多个套接字通道进行监听;当其中的某些套接字通道上有它感兴趣的事件发生时,这些通道就会变为可用状态,可以在选择器的选择操作中被选中;可用通道的选择一般是通过操作系统提供的底层操作系统调用来实现的,性能也比较高;
public class LoadWebPageUseSelector {

    // 通过Selector同时下载多个网页的内容
    public void load(Set<URL> urls) throws IOException {
        Map<SocketAddress, String> mapping = urlToSocketAddress(urls);

        // 1. 创建Selector
        Selector selector = Selector.open();

        // 2. 将套接字Channel注册到Selector上
        for (SocketAddress address : mapping.keySet()) {
            register(selector, address);
        }

        int finished = 0;
        int total = mapping.size();

        ByteBuffer buffer = ByteBuffer.allocate(32 * 1024);
        int len = -1;
        while (finished < total) {
            // 3. 调用select方法进行通道选择,该方法会阻塞,直到至少有一个他们所感兴趣的事件发生,然后可以通过selectedKeys获取被选中的通道的对象集合
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isValid() && key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();

                    // 4. 如果连接成功,则发送HTTP请求;失败则取消该连接;
                    boolean success = channel.finishConnect();
                    if (!success) {
                        finished++;
                        key.cancel();
                    } else {
                        InetSocketAddress address = (InetSocketAddress) channel.getRemoteAddress();
                        String path = mapping.get(address);
                        String request = "GET" + path + "HTTP/1.0\r\n\r\nHost:" + address.getHostString() + "\r\n\r\n";
                        ByteBuffer header = ByteBuffer.wrap(request.getBytes("UTF-8"));
                        channel.write(header);
                    }
                } else if (key.isValid() && key.isReadable()) {
                    // 5. 当channel处于可读时则读取channel的数据并写入文件
                    SocketChannel channel = (SocketChannel) key.channel();
                    InetSocketAddress address = (InetSocketAddress) channel.getRemoteAddress();
                    String filename = address.getHostName() + ".txt";
                    FileChannel destChannel = FileChannel.open(Paths.get(filename), StandardOpenOption.APPEND, StandardOpenOption.CREATE);
                    buffer.clear();

                    // 6. 当返回0时表示本次没有数据可读不需要操作;如果为-1则表示所有数据亿级读取完毕,可以关闭;
                    while ((len = channel.read(buffer)) > 0 || buffer.position() != 0) {
                        buffer.flip();
                        destChannel.write(buffer);
                        buffer.compact();
                    }

                    if (len == -1) {
                        finished++;
                        key.cancel();
                    }
                }
            }
        }
    }

    private void register(Selector selector, SocketAddress address) throws IOException {
        SocketChannel channel = SocketChannel.open();

        // 设置为非阻塞模式
        channel.configureBlocking(false);
        channel.connect(address);

        // 注册时需要指定感兴趣的事件类型
        channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
    }

    private Map<SocketAddress, String> urlToSocketAddress(Set<URL> urls) {
        Map<SocketAddress, String> mapping = new HashMap<>();
        for (URL url : urls) {
            int port = url.getPort() != -1 ? url.getPort() : url.getDefaultPort();
            SocketAddress address = new InetSocketAddress(url.getHost(), port);
            String path = url.getPath();
            if (url.getQuery() != null) {
                path = path + "?" + url.getQuery();
            }

            mapping.put(address, path);
        }

        return mapping;
    }
}

Java 7的版本对Java NIO有哪些增强?

  • Java 7中的NIO.2进一步增强,主要包括文件系统访问和异步I/O通道;
  • 引入Path接口作为文件系统中路径的一种抽象,来代替之前字符串处理的方式,更加语义化;引入DirectoryStream来支持目录下子目录和文件的遍历,它的优势在于它渐进式地遍历,每次只读取一定数量的内容,从而可以降低遍历时的开销(DirectoryStream<Path> stream = Files.newDirectoryStream(path, "*.java"));如果要递归地遍历子目录下的子目录,对整个目录树进行遍历,可以使用FileVisitor;通过引入文件视图FileAttributeView来获取和设置文件的各种属性;另外还提供了新的目录监视服务,当指定目录下的子目录或文件被创建、更新或删除时可以得到事件通知;Files工具类提供了一系列静态方法可以满足常见的需求;
    public void calculate() throws IOException, InterruptedException {
        WatchService service = FileSystems.getDefault().newWatchService();
        Path path = Paths.get("").toAbsolutePath();
        path.register(service, StandardWatchEventKinds.ENTRY_CREATE);
        while (true) {
            WatchKey watchKey = service.take();
            for (WatchEvent<?> event : watchKey.pollEvents()) {
                Path createdPath = (Path) event.context();
                createdPath = path.resolve(createdPath);
                long size = Files.size(createdPath);
                System.out.println(createdPath + "=>" + size);
            }

            watchKey.reset();
        }
    }
    
    public void manipulateFiles() throws IOException {
        Path newFile = Files.createFile(Paths.get("new.txt").toAbsolutePath());
        List<String> content = Arrays.asList("Hello", "World");
        Files.write(newFile, content, Charset.forName("UTF-8"));
        Files.size(newFile);

        byte[] bytes = Files.readAllBytes(newFile);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Files.copy(newFile, outputStream);
        Files.delete(newFile);
    }
  • 异步I/O通道一般提供两种使用方式:一会走是通过Future类的对象来表示异步操作的结果,另外一种是在执行操作时传入一个CompletionHandler接口的实现对象作为操作完成时的回调方法;异步文件通道由AsynchronousFileChannel类表示,它没有当前读写位置的概念。
    public void asyncWrite() throws IOException, ExecutionException, InterruptedException {
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("large.bin"),
                StandardOpenOption.CREATE, StandardOpenOption.WRITE);
        ByteBuffer buffer = ByteBuffer.allocate(32 * 1024 * 1024);
        Future<Integer> result = channel.write(buffer, 0);
        Integer len = result.get();
    }
    
    public void startAsyncSimpleServer() throws IOException {
        AsynchronousChannelGroup group = AsynchronousChannelGroup.withFixedThreadPool(10, Executors.defaultThreadFactory());
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(10080));
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel result, Void attachment) {
                serverChannel.accept(null, this);
                // 使用clientChannel
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                // 错误处理
            }
        });
    }

其他面试小结

扫一扫 关注我的微信公众号
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,184评论 11 349
  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,277评论 0 22
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,536评论 1 143
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 最开始看小王子这本书的时候,第一遍我没有看明白,又连续看了两遍。然后心里不知道为什么莫名的难过。很多人可能喜欢小王...
    你知道我不知道的阅读 511评论 0 1