1. Потоци от данни

Рядко една програма съществува като остров сама по себе си. Програмите обикновено по няHowъв начин взаимодействат с "външния свят". Това може да стане чрез четене на данни от клавиатурата, изпращане на съобщения, изтегляне на страници от интернет or, обратно, качване на файлове на отдалечен сървър.

Можем да посочим всички тези поведения с една дума: обмен на данни между програмата и външния свят. Чакай, това не е само една дума.

Разбира се, самият обмен на данни може да бъде разделен на две части: получаване на данни и изпращане на данни. Например, четете данни от клавиатурата с помощта на Scannerобект — това е получаване на данни. И вие показвате данни на екрана с помощта на System.out.println()команда - това е изпращане на данни.

В програмирането терминът "поток" се използва за описание на обмен на данни. Откъде идва този термин?

В реалния живот можете да имате поток от вода or поток от съзнание. В програмирането имаме потоци от данни .

Потоците са универсален инструмент. Те позволяват на програмата да получава данни отвсякъде (входни потоци) и да изпраща данни навсякъде (изходни потоци). По този начин има два вида:

  • Входящият поток е за получаване на данни
  • Изходен поток е за изпращане на данни

За да направят потоците „осезаеми“, създателите на Java написаха два класа: InputStreamи OutputStream.

Класът InputStreamима read()метод, който ви позволява да четете данни от него. И OutputStreamкласът има write()метод, който ви позволява да записвате данни в него. Те имат и други методи, но повече за това по-късно.

Байтови потоци

За Howви данни говорим? Какъв формат приема? С други думи, Howви типове данни поддържат тези класове?

Това са общи класове, така че поддържат най-често срещания тип данни - byte. Може OutputStreamда записва byteове (и масиви от byteове), а InputStreamобект може да чете byteове (or масиви от byteове). Това е всичко — те не поддържат ниHowви други типове данни.

В резултат на това тези потоци се наричат ​​още потоци от byteове .

Една от характеристиките на потоците е, че техните данни могат да се четат (or записват) само последователно. Не можете да прочетете данни от средата на поток, без да прочетете всички данни, които идват преди него.

Ето How работи четенето на данни от клавиатурата през Scannerкласа: четете данни от клавиатурата последователно, ред по ред. Четем ред, след това следващия ред, след това следващия ред и т.н. Уместно методът за четене на редове се нарича nextLine().

Записването на данни в OutputStreamсъщо се извършва последователно. Добър пример за това е конзолният изход. Извеждате ред, последван от още един и още един. Това е последователен изход. Не можете да изведете първия ред, след това десетия и след това втория. Всички данни се записват в изходен поток само последователно.

Потоци от символи

Наскоро научихте, че низовете са вторият най-популярен тип данни и наистина е така. Много информация се предава под формата на знаци и цели низове. Компютърът е отличен в изпращането и получаването на всичко като byteове, но хората не са толкова перфектни.

Отчитайки този факт, Java програмистите написаха още два класа: Readerи Writer. Класът Readerе аналогичен на InputStreamкласа, но неговият read()метод чете не byteове, а символи ( char). Класът Writerотговаря на OutputStreamкласа. И точно като Readerкласа, той работи със знаци ( char), а не с byteове.

Ако сравним тези четири класа, получаваме следната картина:

Байтове (byte) Знаци (char)
Четене на данни
InputStream
Reader
Записване на данни
OutputStream
Writer

Практическо приложение

Самите класове InputStream, и не се използват директно от никого, тъй като не са свързани с конкретни обекти, от които могат да се четат данни (or в които могат да се записват данни) OutputStream. Но тези четири класа имат много потомствени класове, които могат да направят много.ReaderWriter


2. InputStreamклас

Класът InputStreamе интересен, защото е родителски клас за стотици класове потомци. Той няма ниHowви собствени данни, но има методи, които всички негови производни класове наследяват.

Като цяло, рядко се случва обекти на поток да съхраняват данни вътрешно. Потокът е инструмент за четене/запис на данни, но не и за съхранение. Въпреки това има изключения.

Методи на InputStreamкласа и всички негови потомствени класове:

Методи Описание
int read()
Чете един byte от потока
int read(byte[] buffer)
Чете масив от byteове от потока
byte[] readAllBytes()
Чете всички byteове от потока
long skip(long n)
Пропуска nbyteове в потока (чете и ги изхвърля)
int available()
Проверява колко byteа остават в потока
void close()
Затваря потока

Нека прегледаме накратко тези методи:

read()метод

Методът read()чете един byte от потока и го връща. Може да сте объркани от intвида на връщането. Този тип е избран, защото intе стандартният целочислен тип. Първите три byteа на intще бъдат нула.

read(byte[] buffer)метод

Това е вторият вариант на read()метода. Позволява ви да четете byteов масив от InputStreamвсичко наведнъж. Масивът, който ще съхранява byteовете, трябва да бъде предаден като аргумент. Методът връща число — броя на действително прочетените byteове.

Да приемем, че имате буфер от 10 килоbyteа и четете данни от файл, използвайки FileInputStreamкласа. Ако файлът съдържа само 2 килоbyteа, всички данни ще бъдат заредени в буферния масив и методът ще върне числото 2048 (2 килоbyteа).

readAllBytes()метод

Много добър метод. Той просто чете всички данни от InputStreamдокато не се изчерпи и ги връща като едноbyteов масив. Това е много удобно за четене на малки файлове. Големите файлове може физически да не се побират в паметта и методът ще хвърли изключение.

skip(long n)метод

Този метод ви позволява да пропуснете първите n byteа от InputStreamобекта. Тъй като данните се четат строго последователно, този метод просто чете първите n byteа от потока и ги изхвърля.

Връща броя byteове, които действително са бor пропуснати (в случай че потокът е приключил преди nbyteовете да бъдат пропуснати).

int available()метод

Методът връща броя byteове, които все още остават в потока

void close()метод

Методът close()затваря потока от данни и освобождава външните ресурси, свързани с него. След като потокът бъде затворен, повече данни не могат да се четат от него.

Нека напишем примерна програма, която копира много голям файл. Не можем да използваме readAllBytes()метода за четене на целия файл в паметта. Пример:

Код Забележка
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStreamза четене от file
OutputStreamза запис във файл

Буфер, в който ще четем данните
Докато има данни в потока

Прочетете данни в буфера
Запишете данните от буфера във втория поток

В този пример използвахме два класа: FileInputStreamе наследник на InputStreamза четене на данни от файл и FileOutputStreamе потомък на OutputStreamза запис на данни във файл. За втория клас ще говорим малко по-късно.

Друг интересен момент тук е променливата real. Когато последният блок данни бъде прочетен от файл, той лесно може да има по-малко от 64 KB данни. Съответно трябва да изведем не целия буфер, а само част от него - първите realbyteове. Точно това се случва в write()метода.



3. Readerклас

Класът Readerе пълен аналог на InputStreamкласа. Единствената разлика е, че работи със знаци ( char), а не с byteове. Точно като InputStreamкласа, Readerкласът не се използва никъде самостоятелно: той е родителският клас за стотици класове наследници и дефинира общи методи за всички тях.

Методи на Readerкласа (и всички негови потомствени класове):

Методи Описание
int read()
Чете един charот потока
int read(char[] buffer)
Чете charмасив от потока
long skip(long n)
Пропуска n charsв потока (чете и ги отхвърля)
boolean ready()
Проверява дали все още има нещо останало в потока
void close()
Затваря потока

Методите са много подобни на тези на InputStreamкласа, въпреки че има малки разлики.

int read()метод

Този метод чете един charот потока и го връща. Типът charсе разширява до int, но първите два byteа от резултата винаги са нула.

int read(char[] buffer)метод

Това е вторият вариант на read()метода. Позволява ви да четете масив от char Readerнаведнъж. Масивът, който ще съхранява знаците, трябва да бъде предаден като аргумент. Методът връща число — броят действително прочетени знаци.

skip(long n)метод

Този метод ви позволява да пропуснете първите n знака от Readerобекта. Работи точно по същия начин като аналогичния метод на InputStreamкласа. Връща броя на действително пропуснатите знаци.

boolean ready()метод

Връща, trueако има непрочетени byteове в потока.

void close()метод

Методът close()затваря потока от данни и освобождава външните ресурси, свързани с него. След като потокът бъде затворен, повече данни не могат да се четат от него.

За сравнение, нека напишем програма, която копира текстов файл:

Код Забележка
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Readerза четене от файл
Writerза запис във файл

Буфер, в който ще четем данните
Докато има данни в потока

Четене на данни в буфер
Записване на данните от буфера във втория поток