Java中的File.length()返回不正确的值

7
在文件对象上调用.length()方法时,超过2GB的文件会返回一个错误的值。这是在运行于Tomcat容器中的Web应用程序中发生的。
例如,Windows报告为2,083,344,714字节的文件从Java返回1887961088。以下是环境详细信息:
- jdk 1.6.0_24(64位) - Java JotSpot(TM) 64-Bit Server VM(build 17.0-b17,混合模式) - Tomcat 6.0.29 - Windows Server 2008 r2
所以我立刻怀疑与32位VM有关的问题,但似乎一切都在64位下运行。我完全被卡住了,任何帮助将不胜感激,即使只是帮我开发一些测试来查看问题是否与以某种方式以32位模式运行有关。
编辑:
回答一些评论: 当我使用相同的JVM运行非常简单的测试用例(只输出文件长度)时,我得到了正确的值:2,083,344,714。
我非常自信这应该可以工作,但我感觉我的Tomcat配置/ Java配置/ 应用程序某些方面让我很难受。它仅影响某个大小的文件,这使得它似乎是32位与64位之间的问题,但据我所知,一切都是64位的。
编辑2:开始怀疑这几乎是不可能的,我可能有一个线程在文件完全复制之前取文件长度的问题。如果有人关心的话,我弄清楚后会在这里发布。

6
文件的主数据流实际包含多少字节?也就是说,您是否已经100%确认这是一个Java问题? - David Schwartz
1
你能在网页应用程序之外重现这个问题吗?例如,你能够隔离使用相同的JVM而不涉及Tomcat来看到这个问题吗?如果你能够这样做,我很乐意尝试复制。 - jheddings
David,谢谢您的回复。我该如何验证文件主流的大小? - eric
jheddings,我正在设置一个测试工具,感谢你的帮助。 - eric
1
@eric 在资源管理器中右键单击文件,查看其属性。 - user207421
显示剩余2条评论
1个回答

5

对我来说看起来很正常...

在Windows 7 x64上,DOS报告为8446545920字节的pagefile.sys

Java 7.0_02 x32 8446545920 / 7.87 GB

Java 1.6_30 x32 8446545920 / 7.87 GB

import java.io.File;
import java.text.NumberFormat;

public class TestFileSize {

    public static void main(String[] args) {
        File test1 = new File("C:/pagefile.sys");
        File test2 = new File("C:/hiberfil.sys");

        System.out.println(test1.length() + " / " + ByteFormatter.format(test1.length()));
        System.out.println(test2.length() + " / " + ByteFormatter.format(test2.length()));

    }

    public static class ByteFormatter {

        public static final long KILO_BYTES = 1024;
        public static final long MEGA_BYTES = 1024 * KILO_BYTES;
        public static final long GIGA_BYTES = 1024 * MEGA_BYTES;
        public static final long TERA_BYTES = 1024 * GIGA_BYTES;
        public enum Format {

            TeraBytes(TERA_BYTES),
            GigaBytes(GIGA_BYTES),
            MegaBytes(MEGA_BYTES),
            KiloBytes(KILO_BYTES);
            private long multiplier;

            private Format(long multiplier) {
                this.multiplier = multiplier;
            }

            public long getMultiplier() {
                return multiplier;
            }
        }

        public static String format(long bytes) {
            NumberFormat nf = NumberFormat.getNumberInstance();
            nf.setMaximumFractionDigits(2);
            nf.setMinimumFractionDigits(0);
            String format = bytes + " bytes";
            if (bytes / TERA_BYTES > 0) {
                format = nf.format((double) bytes / TERA_BYTES) + " tb";
            } else if (bytes / GIGA_BYTES > 0) {
                format = nf.format((double) bytes / GIGA_BYTES) + " gb";
            } else if (bytes / MEGA_BYTES > 0) {
                format = nf.format((double) bytes / MEGA_BYTES) + " mb";
            } else if (bytes / KILO_BYTES > 0) {
                format = nf.format((double) bytes / KILO_BYTES) + " kb";
            } else {
                format = nf.format(bytes) + " bytes";
            }

            return format;
        }

        public static String formatMegaBytes(long lMegaBytes) {

            return format((long) ((double) lMegaBytes * MEGA_BYTES));

        }

        public static String formatKiloBytes(long kbytes) {

            return format(kbytes * KILO_BYTES);

        }

        public static String formatGigaBytes(long gbytes) {

            return format(gbytes * GIGA_BYTES);

        }

        public static String formatTeraBytes(long gbytes) {

            return format(gbytes * TERA_BYTES);

        }

        public static long toBytes(String value, Format format) {

            long multipler = format.getMultiplier();

            long bytes = (long) (Double.parseDouble(value.trim()) * multipler);

            return bytes;

        }

        public static long toBytes(String sValue) {

            long lBytes = 0;

            if (sValue.indexOf(" ") > -1) {

                String sElements[] = sValue.split(" ");

                lBytes = Long.parseLong(sElements[0]);

                if (sElements[1].toLowerCase().startsWith("gb")) {

                    lBytes *= GIGA_BYTES;

                } else if (sElements[1].toLowerCase().startsWith("mb")) {

                    lBytes *= MEGA_BYTES;

                } else if (sElements[1].toLowerCase().startsWith("kb")) {

                    lBytes *= KILO_BYTES;

                }

            } else {

                sValue = sValue.trim();
                long lMultiplier = 1;
                String sBytes = null;

                if (sValue.toLowerCase().endsWith("gb")) {

                    sBytes = sValue.substring(0, sValue.toLowerCase().indexOf("gb"));
                    lMultiplier = GIGA_BYTES;

                } else if (sValue.toLowerCase().endsWith("mb")) {

                    sBytes = sValue.substring(0, sValue.toLowerCase().indexOf("mb"));
                    lMultiplier = MEGA_BYTES;

                } else if (sValue.toLowerCase().endsWith("kb")) {

                    sBytes = sValue.substring(0, sValue.toLowerCase().indexOf("kb"));
                    lMultiplier = KILO_BYTES;

                }

                lBytes = Long.parseLong(sBytes);

                lBytes *= lMultiplier;

            }

            return lBytes;

        }
    }
}

不错的例子... 有点过头了,但还是不错的 ;) - jheddings
@MadProgrammer,您知道在toBytes(String value, Format format)中这个丑陋的case可以避免吗? - Jagger
@Jagger 那段代码大约有10年的历史,来自Java 1.3。我已经很久没有看过它了(从我的库代码中取出)。但是没错,它很丑陋,但它能用。 - MadProgrammer
@MadProgrammer 不能是Java 1.3,因为它首次引入了enum在1.5中。我会提出一个“更整洁”的解决方案。 - Jagger
@Jagger 最简单的解决方案是将乘数嵌入枚举中,留待另一天处理 ;) - MadProgrammer
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接