Linux系统读取目录内文件顺序

2022-06-02 15:03:25 浏览数 (1)

在上一篇应用依赖不同的Netty版本引发的错误文章中, 在WEB-INF/lib目录下存在多个版本的Netty, 应用加载jar包的顺序颠倒, 导致应用启动报错. 而重点就在于加载jar包顺序.

本篇文章, 我们简单验证下, 在Linux系统中, 读取目录下的文件, 它的顺序是怎样的.

我们还是拿上篇文章使用的实验环境

当然我这里增加了2个文件, read_dir.c 和 read_dir.py . C程序和Python程序, 接下来会使用这2个程序分别验证下在Linux系统中, 读取目录下文件的顺序是怎样的.

代码还是上一篇文章中的Example.java代码, 提前先类加载SingleThreadEventExecutor类, 然后在启动一个Netty服务端.

编译&运行Example.java代码

代码语言:javascript复制
// Example.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class Example {

    public static void main(String[] args) throws Exception {
        Class.forName("io.netty.util.concurrent.SingleThreadEventExecutor");

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);
        EventLoopGroup businessGroup = new NioEventLoopGroup(8);

        ServerBootstrap serverBootstrap = new ServerBootstrap();

        try {

            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)                    
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
                            ChannelPipeline channelPipeline = ch.pipeline();

                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

如下图, 程序正常运行

既然能正常运行, 表明程序是先加载的netty-all-4.1.43.Final.jar.

使用ll命令查看目录下的文件时, 如下图

netty-all-4.1.43.Final.jar的确排在netty-common-4.1.29.Final.jar前面, 那是因为它默认是按照字母排序的, 这个依据在man手册中可以查找到, 如下

man ls

描述中已经说明, ls默认按照字母次序排序文件

如果使用ll -r 查看目录内容, 又会看到另一种排序结果, 如下图, netty-common-4.1.29.Final.jar排在netty-all-4.1.43.Final.jar前面了

那么我们平时写的Java程序, 在加载某个目录下的Jar文件时, 比如Tomcat读取WEB-INF/lib目录下的jar文件时, 先读取哪个后读取哪个总该有个顺序吧, 它的底层不会像ls命令排序那样的, 那么它的底层是依据什么呢? 往下看

这里写了一个C程序(read_dir.c), 它的功能就是读取当前目录下的文件

代码语言:javascript复制
// read_dir.c

#define _GNU_SOURCE
#include <dirent.h>     /* Defines DT_* constants */
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define handle_error(msg) 
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct linux_dirent {
   unsigned long  d_ino;
   off_t          d_off;
   unsigned short d_reclen;
   char           d_name[];
};

#define BUF_SIZE 1024

int
main(int argc, char *argv[])
{
   int fd;
   long nread;
   char buf[BUF_SIZE];
   struct linux_dirent *d;
   char d_type;

   fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
   if (fd == -1)
       handle_error("open");

   for (;;) {
       // 调用系统函数getdents()
       nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
       if (nread == -1)
           handle_error("getdents");

       if (nread == 0)
           break;

       printf("--------------- nread=%ld ---------------n", nread);
       printf("inode#              file type    d_reclen    d_off   d_namen");
       for (long bpos = 0; bpos < nread;) {
           d = (struct linux_dirent *) (buf   bpos);
           printf("ld  ", d->d_ino);
           d_type = *(buf   bpos   d->d_reclen - 1);
           printf("%-10s ", (d_type == DT_REG) ?  "regular" :
                            (d_type == DT_DIR) ?  "directory" :
                            (d_type == DT_FIFO) ? "FIFO" :
                            (d_type == DT_SOCK) ? "socket" :
                            (d_type == DT_LNK) ?  "symlink" :
                            (d_type == DT_BLK) ?  "block dev" :
                            (d_type == DT_CHR) ?  "char dev" : "???");
           printf("M jd       %sn", d->d_reclen,
                   (intmax_t) d->d_off, d->d_name);
           bpos  = d->d_reclen;
       }
   }

   exit(EXIT_SUCCESS);
}

编译这个C程序

gcc -o read_dir read_dir.c

执行生成的read_dir, 输出结果如下

【第一列inode】在Linux文件系统中, 标识一个文件并不是根据它的名称, 而是根据这个inode值. 不同文件的inode值不同.

比如在tmp目录下有三个文件,分别是-not,1.txt,2.txt

如果要删除1.txt , 可以使用rm 1.txt把文件删除掉. 但是当使用rm -not删除-not文件时, 它就会提示错误

rm 命令会把中划线-后面当成命令参数, 而rm没有-n的命令参数,因此报错了. 这个时候我们就可以使用inode值删除文件.查看文件的inode值

-not文件的inode值是5018049, 于是使用rm `find . -inum 5018049`;命令就可以删除-not文件.

【第二列file type】表示文件类型

【第三列d_reclen】表示文件长度

【第四列d_off】可以理解成这个文件在目录中的偏移, 具体含义在它的结构体中有说明, 上面输出的每行记录都使用下面的结构体表示

【第五列d_name】表示文件名

而我们读取目录下的文件就是根据d_off值排序的.

我们再次使用Python语言程序验证下

代码语言:javascript复制
#! /usr/bin/env python

import os

r = os.listdir(".")
print(r)

输出的结果与C程序一致, 毕竟Python语言底层也是调用相同的C库函数.

我们在另一台Linux机器上验证下

程序启动报错了, 表明程序先加载的netty-common-4.1.29.Final.jar, 我们使用C程序验证下.

如上图, 根据d_off排序, netty-common-4.1.29.Final.jar的确排在netty-all-4.1.43.Final.jar前面, 所以应用启动报错了.

我们简单写个Java程序读取当前目录, 看一下Java程序读取的目录中的文件列表是否与上面一致.

代码语言:javascript复制
import java.io.File;

public class ReadDir {
    public static void main(String[] args) throws Exception {
        File file = new File("/root/2022-3-16");
        String[] files = file.list();
        for (String fileName : files) {
            System.out.println(fileName);
        }
    }
}

编译&运行

输出的结果与C程序一样, 毕竟Java程序底层也需要依赖C库函数.

对应的底层系统调用API是getdents

可以参考

https://man7.org/linux/man-pages/man2/getdents.2.html

或man getdents 查看下相关的介绍

附录: 本篇文章的实验代码地址

https://github.com/infuq/infuq-others/tree/master/Lab/2022-3-16

0 人点赞