#java #performance #bufferedinputstream #bufferedoutputstream
#java #Производительность #bufferedinputstream #bufferedoutputstream
Вопрос:
Есть два вопроса.
- Что на самом деле делает программа, если закодировать bis.read() вместо bis.read(bys) ? (Это работает в любом случае, хотя и намного медленнее.)
- Почему bos.запись (bys) намного быстрее, чем bos.запись (bys, 0, len)? (Я ожидал, что скорость выполнения обоих одинакова.)
Спасибо!
public class CopyFileBfdBytes {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("d:/Test1/M1.MP3");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("d:/Test2/M2.mp3");
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] bys = new byte[8192];
int len;
while ((len = bis.read(bys)) != -1){
// while ((len = bis.read()) != -1){ // 1. Why does it still work when bys in bis.read() is missing?
bos.write(bys);
// bos.write(bys, 0, len); // 2. Why is this slower than bos.write(bys)?
bos.flush();
}
fis.close();
bis.close();
fos.close();
bos.close();
}
}
Комментарии:
1.
bis.read()
возвращает не количество прочитанных байтов, а ровно один байт, представленный как int в диапазоне0-255
или-1
, если EOF был достигнут.bos.write(bys, 0, len)
не медленнее, ноlen
на самом деле это не длина2. 1. Итак, bis.read() на самом деле является bis.read(байт), верно? 2. Если я правильно понимаю, когда byte[] bys = новый байт [1024];, bos. запись (bys, 0, len); быстрее, когда byte[] bys = новый байт [8192];, bos. запись (bys); быстрее, верно?
3.
InputStream#read()
иInputStream#read(byte[])
это совершенно разные подходы к чтению данных из InputStream! Первый читает по одному байту за раз (он может использовать буфер внутри, но это зависит от реализации), а второй пытается прочитать как можно больше байтов в указанный байтовый массив. Первый возвращает фактический прочитанный байт, второй возвращает количество байтов, которые были прочитаны в указанный массив. Я постараюсь дать более подробный ответ
Ответ №1:
Прежде всего, кажется, что вы просто хотите скопировать файл как есть. Для этого существуют гораздо более простые (и, возможно, даже более эффективные подходы).
Другие подходы к копированию данных
Копирование файлов
Если все, что вам нужно, это скопировать фактические файлы, как в вашем примере, вы можете просто использовать:
package example;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class SO66024231 {
public static void main(String[] args) throws IOException {
Files.copy(Paths.get("d:/Test1/M1.MP3"), Paths.get("d:/Test2/M2.mp3"));
}
}
Это, скорее всего, самое сложное (например, другие разработчики действительно видят, что вы хотите сделать) и может быть сделано очень эффективно с помощью базовой системы.
Копирование данных из любого источника в любое место назначения (InputStream в OutputStream)
Если вам нужно передать данные из любого InputStream в любой OutputStream, вы можете использовать метод InputStream#transferTo(OutputStream)
:
package example;
import java.io.*;
public class SO66024231 {
public static void main(String[] args) throws IOException {
try (InputStream fis = new FileInputStream("d:/Test1/M1.MP3")) {
try (OutputStream fos = new FileOutputStream("d:/Test2/M2.mp3")) {
fis.transferTo(fos);
}
}
}
}
Подробное описание вашего вопроса
Примечание: я буду говорить о InputStream
s и OutputStream
s в целом. Вы использовали BufferedInputStream
и BufferedOutputStream
. Это конкретные реализации, которые внутренне буферизуют данные. Эта внутренняя буферизация не имеет ничего общего с буферизацией, о которой я расскажу дальше!
Входной поток
Существует фундаментальное различие между InputStream#read()
и InputStream#read(byte[])
.
InputStream#read()
считывает один байт из входного потока и возвращает его. Возвращаемое значение находится int
в диапазоне 0-255
или -1
, если поток исчерпан (данных больше нет).
package example;
import java.io.*;
public class SO66024231 {
public static void main(String[] args) throws IOException {
final byte[] myBytes = new byte[]{-1, 0, 3, 4, 5, 6, 7, 8, 127};
printAllBytes(new ByteArrayInputStream(myBytes));
}
public static void printAllBytes(InputStream in) throws IOException {
int currByte;
while ((currByte = in.read()) != -1) {
System.out.println((byte) currByte);// note the cast to byte!
}
// prints: -1, 0, 3, 4, 5, 6, 7, 8, 127
}
}
InputStream#read(byte[])
однако это совершенно другое. Он принимает в byte[]
качестве параметра, который используется в качестве буфера. Затем он (внутренне) пытается заполнить данный буфер таким количеством байтов, которое он может получить в данный момент, и возвращает фактическое количество байтов, которые он заполнил, или -1
если поток исчерпан.
Пример:
package example;
import java.io.*;
public class SO66024231 {
public static void main(String[] args) throws IOException {
final byte[] myBytes = new byte[]{-1, 0, 3, 4, 5, 6, 7, 8, 127};
printAllBytes(new ByteArrayInputStream(myBytes));
}
public static void printAllBytes(InputStream in) throws IOException {
final byte[] buffer = new byte[2];// do not use this small buffer size. This is just for the example
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
// loop from 0 to bytesRead, !NOT! to buffer.length!!!
for (int i = 0; i < bytesRead; i ) {
System.out.println(buffer[i]);
}
}
// prints: -1, 0, 3, 4, 5, 6, 7, 8, 127
}
}
Плохой пример:
Теперь плохой пример. Следующий код содержит ошибки программирования, поэтому не используйте это!
Теперь мы выполняем цикл от 0
до buffer.length
, но наши входные данные содержат ровно 9
байты. Это означает, что на последней итерации наш буфер будет заполнен только одним байтом. Второй байт в нашем буфере не будет затронут.
package example;
import java.io.*;
public class SO66024231 {
/**
* ERROURNOUS EXAMPLE!!! DO NOT USE
*/
public static void main(String[] args) throws IOException {
final byte[] myBytes = new byte[]{-1, 0, 3, 4, 5, 6, 7, 8, 127};
printAllBytes(new ByteArrayInputStream(myBytes));
}
public static void printAllBytes(InputStream in) throws IOException {
final byte[] buffer = new byte[2];// do not use this small buffer size. This is just for the example
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
for (int i = 0; i < buffer.length; i ) {
System.out.println(buffer[i]);
}
}
// prints: -1, 0, 3, 4, 5, 6, 7, 8, 127, 8 <-- see; the 8 is printed because we ignored the bytesRead value in our for loop; the 8 is still in our buffer from the previous iteration
}
}
OutputStream
Теперь, когда я описал различия в чтении, я опишу вам различия в записи.
Во-первых, правильный пример (использование OutputStream.write(byte[], int, int)
):
package example;
import java.io.*;
import java.util.Arrays;
public class SO66024231 {
public static void main(String[] args) throws IOException {
final byte[] myBytes = new byte[]{-1, 0, 3, 4, 5, 6, 7, 8, 127};
final byte[] copied = copyAllBytes(new ByteArrayInputStream(myBytes));
System.out.println(Arrays.toString(copied));// prints: [-1, 0, 3, 4, 5, 6, 7, 8, 127]
}
public static byte[] copyAllBytes(InputStream in) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final byte[] buffer = new byte[2];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
return bos.toByteArray();
}
}
И плохой пример:
package example;
import java.io.*;
import java.util.Arrays;
public class SO66024231 {
/*
ERRORNOUS EXAMPLE!!!!
*/
public static void main(String[] args) throws IOException {
final byte[] myBytes = new byte[]{-1, 0, 3, 4, 5, 6, 7, 8, 127};
final byte[] copied = copyAllBytes(new ByteArrayInputStream(myBytes));
System.out.println(Arrays.toString(copied));// prints: [-1, 0, 3, 4, 5, 6, 7, 8, 127, 8] <-- see; the 8 is here again
}
public static byte[] copyAllBytes(InputStream in) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final byte[] buffer = new byte[2];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
bos.write(buffer);
}
return bos.toByteArray();
}
}
Это потому, что, как и в наших примерах с InputStream
, если мы проигнорируем bytesRead
, мы запишем в наше значение одно значение OutputStream
, которое нам не нужно: байт 8
из предыдущей итерации. Это связано с тем, что внутренне OutputStream#write(byte[])
это (в большинстве реализаций) просто ярлык для OutputStream.write(buffer, 0, buffer.length)
. Это означает, что он записывает весь буфер в OutputStream
.
Комментарии:
1. Спасибо разработчикам! Я подробнее рассмотрю это позже и вернусь, когда получу дополнительные вопросы, которые будут уточнены с вашей ценной помощью. Еще раз спасибо!
2. Вам не нужны вложенные
try
блоки. Одинtry
может обрабатывать несколько ресурсов, т.е.try(InputStream fis = new FileInputStream("d:/Test1/M1.MP3"); OutputStream fos = new FileOutputStream("d:/Test2/M2.mp3")) { fis.transferTo(fos); }