java 写文件注意事项

背景

今天用多线程使用不同的BufferWriter往同一文件写内容,发生数据错位。

就是一行数据不完整,被分割了。如

abc
efg

变成了

abefg
c

然后我想起了之前写过一个demo,也是这样写的,但没发生数据错位问题呀!怎么这次就这么邪乎?

之前的demo是这样的:两个线程同时往test.txt文件写入1000行

"I am a java coder, i am strong!\n"
public class SingleThreadWriteFileDemo {

    private static final Logger logger = LoggerFactory.getLogger(SingleThreadWriteFileDemo.class);

    private static final String content = "I am a java coder, i am strong!\n";

    public static void main(String[] args) throws InterruptedException {
        twoThreadFileWriter();
    }

    private static void twoThreadFileWriter() {
        Stream.of(1,2).forEach(i -> {
            new Thread(SingleThreadWriteFileDemo::fileWriter).start();
        });
    }

    public static void fileWriter() {
        try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test.txt", true)))) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000; i++) {
                try {
                    writer.write(content);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            printDuration(start);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最终的文件是正常的2000行 I am a java coder, i am strong!

我不太相信,又把之前的demo跑了好几遍,结果还是正常的2000行 I am a java coder, i am strong!

这时我就纳闷了,这java,是不是写点赞它的话,它就能给你跑正常?

于是乎我变本加厉,决定给它再加点彩虹屁,在内容加了个程度副词 very

"I am a java coder, i am very strong!\n"

这时候错位的情况再次出现了(果然人还是不能夸太多,你看他这不就飘了)

java 写文件注意事项_第1张图片
但我依旧很纳闷,为什么改了一下内容,数据就发生错位了。难道真的是它飘了?要不我改回去?(改回去结果当然还是正常的哈哈哈哈哈)

既然是多线程不安全,兄弟们,那就上锁!于是乎代码变成了这样

public class SingleThreadWriteFileDemo {

    private static final Logger logger = LoggerFactory.getLogger(SingleThreadWriteFileDemo.class);

    private static final String content = "I am a java coder, i am strong!\n";

    public static void main(String[] args) throws InterruptedException {
        twoThreadFileWriter();
    }

    private static void twoThreadFileWriter() {
        Stream.of(1,2).forEach(i -> {
            new Thread(SingleThreadWriteFileDemo::fileWriter).start();
        });
    }

    public static void fileWriter() {
        try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test.txt", true)))) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000; i++) {
                try {
                    writer(writer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            printDuration(start);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

	// 把写内容的方法单拎出来,加了个锁
	private static synchronized void write(BufferedWriter writer) throws IOException {
        writer.write(content);
    }
}

哼,加了锁,你还能给我错位?走你!

再次打开 test.txt ,同样的位置,熟悉的配方,还是222行错位了。

等下,多线程并发问题不一向以随机著称吗?为什么这次错位的位置还是222行?难道它真的这么2?

我不相信,于是又跑了几遍,错位的位置还是在222行

奇了怪了,到底是什么魔法,可以让它错得那么始终如一

忽然,不知道是天意还是开窍了,我将错位之前的字符全部选中,也就是从第一行到第222行 I am a java cod 的所有字符,这时候idea给出的字符数是8192!
在这里插入图片描述
8192?8192?8192?这个数字怎么这么熟悉?我肯定在哪里见过!

啊!这不就是BufferedWriter的默认缓冲区大小吗?

那这个大小又跟这错位位置有什么关系呢?

不妨我们去看看BufferedWriter的源码,看它到底在鬼鬼祟祟做什么?

public class BufferedWriter extends Writer {
	public void write(String s, int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();

            int b = off, t = off + len;
            while (b < t) {
                int d = min(nChars - nextChar, t - b);
                s.getChars(b, b + d, cb, nextChar);
                b += d;
                nextChar += d;
                if (nextChar >= nChars)
                    flushBuffer();
            }
        }
    }
}

哟吼,没想到一进来它自己就给上了把锁

然当缓冲区满了之后,就把缓冲区的字符写到文件中

我马上打开计算器计算一波

缓冲区是8192,而一行 ```I am a java coder, i am very strong!\n```是37个字符(包括空格和换行符)

8197/37=221余15

所以写了完整的221行

第222行就是余的这15个字符: I am a java cod

这还不是问题的关键,关键是缓冲区一刷,它就把锁给释放了

锁一释放,问题就大了

还有个老6(线程2)在虎视眈眈地盯着这把锁呢

注意这把锁不是 BufferedWriter的锁,是 FileOutputStream 的锁

它抢到了这把锁之后,看到自己的缓冲区也满了,二话不说就把自己的缓冲区刷了先

这下真的是书接上文了,只不过接得不大好看

问题整明白了,那咋解决啊?

我简单粗暴地加上了一句 writer.flush();

public class SingleThreadWriteFileDemo {

    private static final Logger logger = LoggerFactory.getLogger(SingleThreadWriteFileDemo.class);

    private static final String content = "I am a java coder, i am strong!\n";

    public static void main(String[] args) throws InterruptedException {
        twoThreadFileWriter();
    }

    private static void twoThreadFileWriter() {
        Stream.of(1,2).forEach(i -> {
            new Thread(SingleThreadWriteFileDemo::fileWriter).start();
        });
    }

    public static void fileWriter() {
        try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test.txt", true)))) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000; i++) {
                try {
                    write(writer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            printDuration(start);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

	// BufferedWriter里面有锁了,就把synchronized关键字给删了
	private static void write(BufferedWriter writer) throws IOException {
        writer.write(content);
        // 直接把
        writer.flush();
    }
}

原本以为这样做就可以让那句没有完整输出的语句给刷出来

但是细想一下,这么一操作,不是每次 writer.write(String)都把缓存给刷到磁盘了吗?

那我还要这 Buffer 有啥用?

那 BufferedWriter 有什么方法可以知道缓存满了吗?

当它要刷缓存的时候,顺带帮我把那句被截断话的一起给刷出去

但是很遗憾,确认过眼神之后,不是对的人,并没有这样的方法

你可能感兴趣的:(java,jvm,数据库)