Java 为什么首先读取Files.readAllBytes时bufsize为1?

Java 为什么首先读取Files.readAllBytes时bufsize为1?,java,linux,kernel,driver,Java,Linux,Kernel,Driver,我正在编写一个简单的Linux USB字符驱动程序,允许从它创建的设备节点读取短字符串 它工作得很好,但我注意到使用cat从设备节点读取数据与使用cat从Java程序读取数据之间存在差异 使用cat读取,在第一次调用文件\u操作时,传入大小为131072的缓冲区。读取函数并复制5字节字符串: kernel: [46863.186331] usbtherm: Device was opened kernel: [46863.186407] usbtherm: buffer: 131072, rea

我正在编写一个简单的Linux USB字符驱动程序,允许从它创建的设备节点读取短字符串

它工作得很好,但我注意到使用
cat从设备节点读取数据与使用cat从Java程序读取数据之间存在差异

使用
cat
读取,在第一次调用
文件\u操作时,传入大小为131072的缓冲区。读取
函数并复制5字节字符串:

kernel: [46863.186331] usbtherm: Device was opened
kernel: [46863.186407] usbtherm: buffer: 131072, read: 5, offset: 5
kernel: [46863.186444] usbtherm: done, returning 0
kernel: [46863.186481] usbtherm: Device was released
使用
文件读取。readAllBytes
,在第一次调用时传入大小为1的缓冲区,然后传入大小为8191的缓冲区,并复制剩余的4个字节:

kernel: [51442.728879] usbtherm: Device was opened
kernel: [51442.729032] usbtherm: buffer: 1, read: 1, offset: 1
kernel: [51442.729102] usbtherm: buffer: 8191, read: 4, offset: 5
kernel: [51442.729140] usbtherm: done, returning 0
kernel: [51442.729158] usbtherm: Device was released
file\u operations.read
功能(包括调试
printk
)是:


在这两种情况下读取的字符串都是相同的,所以我想这没关系,我只是想知道为什么会有不同的行为?

GNU
cat

从源头上说,

您可以看到,缓冲区的大小是由coreutils'
io_bliksize()
决定的,它在这方面具有相当大的优势

/*截至2014年5月,128KiB被确定为最小blksize 以最大限度地减少系统调用开销

这就解释了cat的结果,因为128KiB是131072字节,GNUrus认为这是最小化系统调用开销的最佳方法

文件。readAllBytes

至少对于我这样一个简单的灵魂来说,这更难理解。

显示它只是调用
read(InputStream,initialSize)
,其中初始大小由字节通道的大小决定。
size()
方法也有一个有趣的注释

不是isRegularFile()文件的文件的大小取决于实现 具体的,因此不明确

最后,调用
InputStream.read(byteArray,offset,length)
进行读取(源代码中的注释来自原始源代码,由于
capacity-nread=0
,因此第一次到达while循环时,它不会读取到EOF):

InputStream.read(byteArray,offset,length)的文档/源文件
包含相关注释

如果长度为零,则不读取字节并返回0

由于
size()
为您的设备节点返回0字节,以下是
read(InputStream source,int initialSize)中发生的情况

在(;)
循环的
第一轮中:

  • capacity=0
    nread=0
    。因此,
    source.read
    中的
    while((n=
    source.read(buf,nread,capacity-nread))>0)
    将0字节读入
    buf
    并返回0:
    循环的条件为false,而
    循环的条件为false,它所做的只是
    n=0
    作为条件的副作用

  • 由于
    n=0
    中的
    source.read()
    如果(n<0 | |(n=source.read())<0)中断
    读取1个字节,表达式的计算结果为
    false
    :我们的
    for
    循环不退出。这将导致“缓冲区:1,读取:1,偏移量:1

  • 缓冲区的
    容量
    设置为
    缓冲区大小
    ,读取的单个字节放入
    buf[0]
    ,并且
    nread
    增加

(;)
循环的第二轮

  • 因此具有
    capacity=8192
    nread=1
    ,这使得
    而((n=source.read(buf,nread,capacity-nread))>0)nread+=n
    从偏移量1读取8191字节,直到
    源。read
    返回-1:EOF!在读取剩余的4个字节后发生。这将导致“缓冲区:8191,读取:4,偏移量:5

  • 由于现在
    n=-1
    ,if(n<0 | | |(n=source.read())<0)中的表达式中断
    n<0
    上短路,这使得我们的
    for
    循环在不读取更多字节的情况下退出


最后,该方法返回数组。copyOf(buf,nread)
:缓冲区中放置读取字节的那部分的副本。

Hmm,您提供的
read()
方法实际上是逐字节读取,没有
它尝试读取其余字节。
。第一个字节的特殊处理用于不同的情况,无论在错误/异常之前是否读取了任何字节。它影响返回值/异常,这是方法规范所要求的。@Tsyvarev同意,这是处理第一个字节的更好解释。我还对
size()
从何处获取它的“未指定”值感到困惑,这可能是它的根。
size()
为设备节点返回0,而它返回常规文件的实际大小,如
ls-l
@torstenerömer
size()
的0使
读取(inputStream,initialSize)
调用
读取(byte b[],int off,int len)
方法三次,一次读取0字节,一次读取1字节,一次读取
缓冲区大小-1
字节。所以我们开始讨论:)我将删除答案的最后一部分,并首先将
read(inputStream,initialSize)
的完整源代码编辑到其中。@TorstenRömer是的。简而言之,它先读取0个字节,然后读取1个字节以查看是否有任何内容需要读取,然后
BUFFER_SIZE-1
字节直到EOF。我要看看我是如何编辑答案的,以一种可读的方式解释这一点:你能看到设备节点的
size()
返回什么吗?
static ssize_t device_read(struct file *filp, char *buffer, size_t length,
        loff_t *offset)
{
    int err = 0;
    size_t msg_len = 0;
    size_t len_read = 0;

    msg_len = strlen(message);

    if (*offset >= msg_len)
    {
        printk(KERN_INFO "usbtherm: done, returning 0\n");
        return 0;
    }

    len_read = msg_len - *offset;
    if (len_read > length)
    {
        len_read = length;
    }

    err = copy_to_user(buffer, message + *offset, len_read);
    if (err)
    {
        err = -EFAULT;
        goto error;
    }

    *offset += len_read;

    printk(KERN_INFO "usbtherm: buffer: %ld, read: %ld, offset: %lld\n", 
            length, len_read, *offset);

    return len_read;

error:
    return err;
}
      insize = io_blksize (stat_buf);
public static byte[] readAllBytes(Path path) throws IOException {
    try (SeekableByteChannel sbc = Files.newByteChannel(path);
         InputStream in = Channels.newInputStream(sbc)) {
        long size = sbc.size();
        if (size > (long)MAX_BUFFER_SIZE)
            throw new OutOfMemoryError("Required array size too large");

        return read(in, (int)size);
    }
}
private static byte[] read(InputStream source, int initialSize)
        throws IOException {
    int capacity = initialSize;
    byte[] buf = new byte[capacity];
    int nread = 0;
    int n;
    for (;;) {
        // read to EOF which may read more or less than initialSize (eg: file
        // is truncated while we are reading)
        while ((n = source.read(buf, nread, capacity - nread)) > 0)
            nread += n;

        // if last call to source.read() returned -1, we are done
        // otherwise, try to read one more byte; if that failed we're done too
        if (n < 0 || (n = source.read()) < 0)
            break;

        // one more byte was read; need to allocate a larger buffer
        if (capacity <= MAX_BUFFER_SIZE - capacity) {
            capacity = Math.max(capacity << 1, BUFFER_SIZE);
        } else {
            if (capacity == MAX_BUFFER_SIZE)
                throw new OutOfMemoryError("Required array size too large");
            capacity = MAX_BUFFER_SIZE;
        }
        buf = Arrays.copyOf(buf, capacity);
        buf[nread++] = (byte)n;
    }
    return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}
    // buffer size used for reading and writing
    private static final int BUFFER_SIZE = 8192;