[英]Multithreaded read & write of a file , cannot stop operation, writes bad file
我的任務是傳輸大型文件,這些文件的大小約為5 GB,因此需要進行讀寫操作。
我編寫了一次讀取5k的單線程版本,然后立即將5k寫入另一個位置,signle線程版本可以正常工作,我使用412 MB的zip文件夾進行測試,大約需要5秒鍾。
我的目標是實際編寫多線程版本,想到的自然設計模式是生產者(讀取器)消費者(寫入器)模式。
下面首先是我基於多線程版本的單線程版本:
import java.net.*;
import java.io.*;
import java.nio.*;
import java.util.Arrays;
public class ReadWrite {
URI readFrom = null;
URI writeTo = null;
//streams
FileInputStream fis = null;
FileOutputStream fos = null;
// good buffer size in Java is generally between 2k to 8k
byte[] byteBuffer = new byte[5 * 1024];
//just for testing
private int readSoFar = 0;
//const
ReadWrite(URI readFrom, URI writeTo) {
this.readFrom = readFrom;
this.writeTo = writeTo;
}
public URI getReadFrom() {
return readFrom;
}
public void setReadFrom(URI readFrom) {
this.readFrom = readFrom;
}
public URI getWriteTo() {
return writeTo;
}
public void setWriteTo(URI writeTo) {
this.writeTo = writeTo;
}
public void process() throws FileNotFoundException {
// by chunks therefore buffer
File fileToRead = new File(readFrom);
File fileToWrite = new File(writeTo);
try {
// if read & write destinations exist
if (fileToRead.exists()) {
fis = new FileInputStream(fileToRead);
// instantiate OutputStream
fos = new FileOutputStream(fileToWrite);
// read a chunk, then write , update read position, until there
// is no more to read
try {
int writeCounter = 0;
// read
while ((fis.read(byteBuffer, 0, byteBuffer.length)) != -1) {
try {
//just for testing & seeing output/progress
readSoFar= readSoFar + byteBuffer.length;
System.out.println("readSoFar:" + readSoFar);
// write
fos.write(byteBuffer);
// clear previous data
Arrays.fill(byteBuffer, (byte) 0);
System.out.println("writeCounter: " + writeCounter);
writeCounter++;
} catch (IOException exc) {
exc.printStackTrace();
}
}
} catch (IOException exc) {
exc.printStackTrace();
}
} else {
throw new FileNotFoundException();
}
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}// end class ReadWrite
}
主類FileCopy(單線程):
public class FileCopy {
public static void main(String[] args) {
try {
try {
//wls1033_dev.zip
new ReadWrite( new URI("file:/C:/Users/anaim/Pictures/wls1033_dev.zip"),new URI("file:/C:/Users/anaim/Pictures/result/wls1033_dev.zip")).process();
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}//end main class
輸出應如下所示:
。 。 。 readSoFar:423198720 writeCounter:82655 readSoFar:423203840 writeCounter:82656 readSoFar:423208960 writeCounter:82657
顯然,目標文件不應被刪減且與原始文件相同。
下面是基於單線程代碼的多線程版本,除了線程邏輯和通過同步的鎖,等待,通知實現。 多線程實際上正在工作,沒有死鎖或活躍性問題,但是讀/寫操作不會終止。
問題似乎出在變量“ readState”及其getReadState()方法似乎在主類中被調用時無法正確運行,而當readState == -1時, 讀/寫應該終止。
多線程讀取和寫入(類ReadProducerWriteConsumer)提供了一個readProcess和writeProcess,它們根據“布爾空”變量/標志的值與同步節等待
import java.net.*;
import java.io.*;
import java.nio.*;
import java.util.Arrays;
public class ReadProducerWriteConsumer {
URI readFrom = null;
URI writeTo = null;
//
FileInputStream fis = null;
FileOutputStream fos = null;
// good buffer size in Java is generally between 2k to 8k
byte[] byteBuffer = new byte[1024];
//
File fileToRead = null;
File fileToWrite = null;
//
private int readSoFar = 0;
int writeCounter = 0;
//
volatile private int readState = 0;
// Consumer & Producer state , hence has anything been read in order to be
// written
boolean empty = true;
ReadProducerWriteConsumer(URI readFrom, URI writeTo) {
this.readFrom = readFrom;
this.writeTo = writeTo;
//
fileToRead = new File(readFrom);
fileToWrite = new File(writeTo);
}
public long getReadState() {
return this.readState;
}
public void setReadState(int readState) {
this.readState = readState;
}
public URI getReadFrom() {
return readFrom;
}
public void setReadFrom(URI readFrom) {
this.readFrom = readFrom;
}
public URI getWriteTo() {
return writeTo;
}
public void setWriteTo(URI writeTo) {
this.writeTo = writeTo;
}
public synchronized void readProcess() throws FileNotFoundException {
// while false, while data is being written , wait
while (empty == false) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// by chunks therefore buffer
File fileToRead = new File(readFrom);
try {
// if read & write destinations exist
if (fileToRead.exists()) {
fis = new FileInputStream(fileToRead);
// read a chunk
try {
// while readSoFar!=-1
while (((this.readState = fis.read(byteBuffer, 0,
byteBuffer.length)) != -1) && empty != false) {
// just for testing & seeing output/progress
readSoFar = readSoFar + byteBuffer.length;
System.out.println("readSoFar:" + readSoFar);
// read a chunck , now that buffer is full set emoty to
// false
empty = false;
}
} catch (IOException exc) {
exc.printStackTrace();
}
} else {
throw new FileNotFoundException();
}
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// new data has been read , notify all threads waiting to consume
// data
notifyAll();
}
}
public synchronized void writeProcess() throws FileNotFoundException {
// while true, therefore there is nothing to write, wait
while (empty == true) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// by chunks therefore buffer
File fileToWrite = new File(writeTo);
try {
// instantiate OutputStream
fos = new FileOutputStream(fileToWrite);
// then write , update read position
// write
try {
fos.write(byteBuffer);
// clear previous data
Arrays.fill(byteBuffer, (byte) 0);
System.out.println("writeCounter: " + writeCounter);
writeCounter++;
} catch (IOException exc) {
exc.printStackTrace();
}
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// new data has been written , notify all threads waiting to
// read/produce more data
empty = true;
notifyAll();
}
}
}//end class ReadProducerWriteConsumer
readRunnable類實現一個runnable並調用ReadProducerWriteConsumer.readProcess()
import java.io.FileNotFoundException;
public class readRunnable implements Runnable {
ReadProducerWriteConsumer ReaderProducerWriterConsumer = null;
public readRunnable(ReadProducerWriteConsumer readerProducerWriterConsumer) {
super();
ReaderProducerWriterConsumer = readerProducerWriterConsumer;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
ReaderProducerWriterConsumer.readProcess();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// call ReaderProducerWriterConsumer read method
}//end readRunnable class
writeRunnable類實現一個runnable並調用ReadProducerWriteConsumer.writeProcess()
import java.io.FileNotFoundException;
public class writeRunnable implements Runnable
{
ReadProducerWriteConsumer ReaderProducerWriterConsumer = null;
public writeRunnable (ReadProducerWriteConsumer readerProducerWriterConsumer) {
super();
ReaderProducerWriterConsumer = readerProducerWriterConsumer;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
ReaderProducerWriterConsumer.writeProcess();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//ReaderProducerWriterConsumer write method
}//end writeRunnable
實例化線程並創建讀/寫線程的主類,直到沒有更多要讀取為止,使用ReadProducerWriteConsumer.getReadState()檢查此條件
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.util.Arrays;
public class FileCopy {
public static void main(String[] args) {
ReadProducerWriteConsumer ReaderProducerWriterConsumer = null;
try {
ReaderProducerWriterConsumer = new ReadProducerWriteConsumer(
new URI("file:/C:/Users/anaim/Pictures/pic.png"), new URI(
"file:/C:/Users/anaim/Pictures/result/pic.png"));
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread readThread = null;
Thread writeThread = null;
while (ReaderProducerWriterConsumer.getReadState() != -1) {
readThread = new Thread(new readRunnable(
ReaderProducerWriterConsumer));
writeThread = new Thread(new writeRunnable(
ReaderProducerWriterConsumer));
readThread.start();
writeThread.start();
try {
readThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
writeThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}// end main class
因此,多線程版本的問題在於讀/寫操作不會停止,因此寫操作會產生損壞的文件。
請提供智能解決方案。 謝謝。
錯誤發生在您的寫入過程中,該過程不會循環:它只會寫入一次,然后退出,然后讀取過程將重新填充緩沖區,並永遠等待它再次變空。
除此之外,您的設計很有趣,但是有點不尋常。
有一些問題:
FileInputStream.read函數不一定完全返回您請求的字節數,並且返回值告訴您已讀取了多少字節。 您應該在寫入過程中使用該值。
您正在使用循環將empty
布爾值作為鎖進行檢查。 使用信號量會更容易(請參見java.util.concurrent ),
或者,您可以使用BlockingQueue來同步您的讀取和寫入。
還有一個稱為循環緩沖區的數據結構,它可以讓您以“連續方式”將讀取的數據存儲在緩沖區中,但是我不知道Java中是否存在該數據結構。 基本上,您在緩沖區中保留了2個指針,一個指針指向緩沖區中剩余可用空間的開始,而另一個指針指向已使用空間的開始。 當您寫入緩沖區時,您將開始在可用空間的位置存儲數據。 如果到達緩沖區的末尾,則應重新開始(因此稱為“圓形”構想)。 您必須注意不要傳遞已使用的空間指針,但是如果您正確地進行了讀取(即,您所要求的空間不超過剩余的可用空間),則應該能夠避免該問題。 從緩沖區讀取時,將從已使用的空間指針開始,並讀取緩沖區中可用字節的數量。 也許您應該在以后的階段嘗試這種方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.