Код класса расширяющего или реализующего что-нибудь выше перечисленное будет выполняться в отдельном потоке.
Мы уже затрагивали создание потоков когда рассматривали PipedOutputStream. И мы помним, что там было два потока и код одного потока выполнялся параллельно коду другого.
Поэтому суть потоков думаю понятна. Параллельное выполнение блоков кода друг другу.
Расширение Thread
Ниже в примере класс MyThread расширяет Thread.
Это значит, что в нем должен быть переопределен метод run.
В этом методе пишется код, который можно запускать в отдельном потоке.
То есть он будет выполняться параллельно другому коду, например тому, в котором выполняется код метода мейн.
Пример программы:
class MyThread extends Thread {
@Override
public void run() {
System.out.println(“run() method “);
}
}
public class ExtendsThread {
public static void main(String[] args) {
//В отличие от реализации Runnable (разберем
//в следующем уроке) для каждого потока класса
//расширяющего Thread необходимо создавать новый
//объект. Создаются три новых объекта MyThread
//как можно увидеть
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
//Теперь с помощью метода start в одном потоке
//запускается выполнение метода run у объекта t1,
//в другом потоке запускается выполнение метода
//run у объекта t2 и в третьем потоке запускается
//выполнение метода run у объекта t3.
//И ясное дело все эти потоки выполняются
//параллельно друг другу.
t1.start();
t2.start();
t3.start();
}
}
Вывод:
Каждый поток вывел по строке как видим.
Преимущества расширения Thread
Основное преимущество расширения Thread в том, что есть возможность переопределять не только run(), но и другие методы, такие как start().
Пример программы:
class MyThread extends Thread {
@Override
public void start() {
System.out.println(“Overriding a start() method”);
super.start();
}
@Override
public void run() {
System.out.println(“run() method “);
}
}
public class ExtendsThread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
Вывод:
Как видим, теперь и при вызове start тоже выводиться строка.
Причем порядок выведения строк на консоль может быть разный. В данном случае, на консоли мы видим, что один поток уже успел завершить свое выполнение перед тем как запустился третий поток.
FilterInputStream, FilterOutputStream – абстрактные классы для создания собственных потоков или добавления функционала к существующим.
В примере ниже создается класс расширяющий FilterInputStream, в котором мы реализуем свой поток байтового ввода. Этот созданный нами поток изменяет консольный байтовый ввод System.in так, чтобы он возвращал номер в таблице ASCII введенного пользователем в консоль символа, но сдвинутого на 13 символов в этой таблице.
Пример программы:
import java.io.*;
import java.util.*;
class Rot13InputStream extends FilterInputStream {
// Переданный в конструктор поток i
public Rot13InputStream(InputStream i) {
// Передаем в конструктор класса родителя через super.
super(i);
// Там этот поток сохраняется.
// И будет доступен здесь в наследнике через in,
// которым мы пользуемся ниже.
}
// Переопределим read, чтобы он возвращал сдвинутое значение
public int read() throws IOException {
// С помощью read считываем символ потоком in,
// переданный сюда через конструктор,
// и передаем его в созданный нами метод rot13.
return rot13(in.read());
}
// Сдвиг на 13 символов
public int rot13(int c) {
// Сильно не разбирайтесь в коде ниже. Просто знайте,
// что он сдвигает номер символа в ASCII на 13 символов.
if ((c >= ‘A’) && (c <= 'Z'))
c = (((c - 'A') + 13) % 26) + 'A';
if ((c >= ‘a’) && (c <= 'z'))
c = (((c - 'a') + 13) % 26) + 'a';
return c; // Этот сдвинутый номер возвращается
}
}
class FilterStreamLesson {
public static void main(String[] args) {
try {
Rot13InputStream b13is = new Rot13InputStream(System.in);
// Воспользуемся переопределенным нами read()
// в Rot13InputStream, который будет сдвигать номер
// введенного в поток символа на 13 значений
// в таблице ASCII и возвращать сдвинутый номер.
System.out.println(b13is.read());
} catch (Exception e) {
// Обработаем возможную ошибку
e.printStackTrace();
}
}
}
Вывод:
Номер символа а в таблице ASCII – 97, но поскольку мы сдвинули его на 13 символов выводиться 110.
ObjectInputStream, ObjectOutputStream – для сериалиации/десериализации. Для сохранения копии объекта в поток (в файл, например) для восстановления его потом (десереализации).
Пример программы:
import java.io.*;
import java.util.*;
class ObjectStreamLesson{
public static void main(String[] args) {
// сериализация
try(ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(“person.dat”)))
{
Person p = new Person(“Mike”, 25, 178, false);
// сериализуем объект p в файл person.dat
oos.writeObject(p);
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
// десериализация
try(ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(“person.dat”)))
{
// десериализуем объект p из файла person.dat
Person p = (Person) ois.readObject();
// выведем его данные на консоль с помощью printf
System.out.printf(“Name: %s \t Age: %d \n”,
p.getName(), p.getAge());
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
}
}
// чтобы сериализовать, класс должен быть Serializable
class Person implements Serializable{
private String name;
private int age;
private double height;
private boolean married;
Person(String n, int a, double h, boolean m){
name = n; age = a; height = h; married = m;
}
String getName() { return name; }
int getAge() { return age; }
double getHeight() { return height; }
boolean getMarried() { return married; }
}
Вывод:
То есть мы сохраняем в файл копию какого-либо объекта и когда она нам будет нужна в нашей программе достаем ее.
OutputStreamWriter – мост между символьными и байтовыми потоками.
Пример программы:
import java.io.*;
import java.util.*;
class OutputStreamWriterLesson {
public static void main(String[] args) {
try {
FileOutputStream outputStream =
new FileOutputStream(“person.txt”);
OutputStreamWriter outputStreamWriter =
new OutputStreamWriter(outputStream);
// OutputStreamWriter конвертирует записываемые в него
// символы в байты и эти байты записываются в файл байтовым
// потоком, который был обернут в OutputStreamWriter.
// В нашем случае FileOutputStream
String str = “My Some Text!”;
// Как видим записываем мы символы. То есть String,
// OutputStreamWriter конвертирует String в байты
// и передает байтовому потоку FileOutputStream
// для вывода в файл.
outputStreamWriter.write(str);
// Также с помощью flush достаем FileOutputStream
// из обертки OutputStreamWriter чтобы записать в него байты.
outputStreamWriter.flush();
} catch (Exception e) {}
}
}
PushbackInputStream – прочитав несколько байтов входного потока с помощью inpsrream.read()бывает необходимо вернуться и прочитать эти же уже прочитанные ранее байты еще раз.
Делается с помощью inpsrteam.unread().
Пример программы:
import java.io.*;
import java.util.*;
class PushbackStream {
public static void main(String[] args) {
try {
String str = “Hello world!!!”;
byte b[] = str.getBytes();
ByteArrayInputStream bin = new ByteArrayInputStream(b);
PushbackInputStream push = new PushbackInputStream(bin);
// читаем первый символ
System.out.print((char)push.read());
// Возвращаем его обратно.
// То есть сейчас мы извлекли первый элемент ‘H’ из str
// и если мы вызовем System.out.print((char)push.read());
// еще раз, то извлечется уже второй ‘e’. А мы хотим
// опять прочитать первый. Для этого мы можем
// вставить его перед ‘e’ и прочитать его еще
// раз вместо ‘e’. На самом деле нам не обязательно
// вставлять именно ‘H’ можно вставить любой символ.
// Важная ремарка, что сама строка str
// при всех этих манипуляциях не изменяется.
push.unread(‘H’);
// читаем снова первый возвращенный символ
System.out.print((char)push.read());
// дальше будет читать со второго символа
System.out.print((char)push.read());
System.out.print((char)push.read());
System.out.print((char)push.read());
} catch (Exception e) {}
}
}
Вывод:
Как видим, мы прочитали первый символ два раза. То есть мы прочитали его, потом вернули и прочитали его еще раз.
PipedInputStream, PipedOutputStream – применяется в многопоточной среде. Потоки в слове многопоточной, это не те потоки, которые мы сейчас изучаем (сейчас мы изучаем потоки ввода вывода). Эти же потоки понимайте как куски кода, которые выполняются параллельно. То есть в одном потоке один кусок кода, в другом другой и т.д, но главное все они выполняются параллельно друг другу.
PipedInputStream, PipedOutputStream связывают два потока I/O для для того чтобы они могли обмениваться информацией друг с другом.
Для передачи каких-либо данных между разными потоками лучше всего использовать этот класс.
Хотя потоки мы пока не разбирали, всё равно приведем пример.
Пример программы:
import java.io.*;
import java.util.*;
class PipedStreamLesson {
// Например, есть 2 независимых потока: ThreadA и ThreadB.
// Их классы создаются снизу после класса PipedStreamLesson.
// Это не те потоки, которые мы сейчас изучаем
// (сейчас мы изучаем потоки ввода-вывода).
// Эти же потоки понимайте как два куска кода,
// которые выполняются параллельно.
// То есть содержимое метода run() в ThreadA выполняется
// параллельно содержимому run() в ThreadB.
public static void main(String[] args) {
// Создаем концы канала: выходной и входной.
PipedInputStream pis;
PipedOutputStream pos;
try {
// Связываем в единый канал
// для передачи данных между ними.
pis = new PipedInputStream();
pos = new PipedOutputStream(pis);
// Запускаем потоки.
// После этого содержимое методов run
// в этих классах начнет выполняться параллельно.
new ThreadB(pis).start();
new ThreadA(pos).start();
// Также в эти потоки передаем связанные
// каналы pis и pos, которые будут
// использоваться для пересылки и считывания данных.
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
class ThreadA extends Thread {
PipedOutputStream pos;
ThreadA(PipedOutputStream pos) {
this.pos = pos;
}
@Override
public void run() {
try {
byte[] bytes = new byte[] {‘a’, ‘g’, ‘b’, ‘c’, ‘6’};
for (byte b : bytes) {
// Используем переданный в конструктор pos
// для передачи массива bytes в ThreadB.
pos.write(b);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
PipedInputStream pis;
ThreadB(PipedInputStream pis) {
this.pis = pis;
}
@Override
public void run() {
try {
int b = 0;
// pis считывает данные, передаваемые pos
// из ThreadA.
while ((b = pis.read()) != -1) {
System.out.println((char) b);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Вывод:
Как видим данные успешно пересланы в другой поток и выведены им на консоль.
SequenceInputStream – сливает потоки в один поток.
Он считывает от первого byte до последнего byte первого InputStream, затем делает то же самое со следующим InputStream и т.д. до последнего InputStream и объединяет их всех в один InputStream.
Допустим, есть два файла person1 и person2 по пути C:/HTML/somedata/:
Теперь давайте объединим два файловых потока, которые читают из этих файлов в один, и, таким образом, сможем читать данные из обоих файлов через один SequenceInputStream.
import java.io.*;
import java.util.*;
class SequenceStream {
public static void main(String[] args) {
try {
FileInputStream is1 =
new FileInputStream(“C:/HTML/somedata/person1.txt”);
FileInputStream is2 =
new FileInputStream(“C:/HTML/somedata/person2.txt”);
// объединяем
SequenceInputStream is = new SequenceInputStream(is2, is1);
// выведем содержимое обоих файлов через буферизованные потоки
BufferedInputStream in = new BufferedInputStream(is);
BufferedOutputStream out = new BufferedOutputStream(System.out);
int ch;
while ((ch = in.read()) != -1) {
out.write((char) ch);
out.flush();
}
} catch (Exception e) {}
}
}
StringReader, StringWriter – потоки, которые хранят строку в себес которой мы работаем через эти потоки как с любыми другими источниками дынных (фалами например).
Как раньше например мы записывали или выводили из консоли теперь будем записывать в строку или выводить из строки.
ByteArrayInputStream, ByteArrayOutputStream – аналог только работаем с массивом байтов вместо строки.
Пример программы:
import java.io.*;
import java.util.*;
class StringWRLesson {
public static void main(String[] args) {
try {
String s = “data”;
// Строчка ниже – ключевая: мы «превратили» строку в Reader.
// И теперь можем работать со строкой как с объектом Reader.
Reader reader = new StringReader(s);
// можем сделать строку (поток) буферезированной.
BufferedReader br = new BufferedReader(reader);
String line = br.readLine(); // считать строку
System.out.println(line);
Writer writer = new StringWriter();
// Пишем какую-то строку в Writer
// для дальнейшей записи ее в String переменную.
writer.write(line + “some text”);
// получаем текст, который был записан во Writer
String result = writer.toString();
System.out.println(result);
} catch (Exception e) {}
}
}
BufferedInputStream, BufferedOutputStream – чтение байтового потока и запись в байтовый поток с добавлением буфера для улучшения производительности.
Это класс обертка для InputStream с буферизацией.
Благодаря буферизации потоки, которые переданы в конструктор BufferedInputStream будут читать данные из буфера маленькими порциями, а буфер, чтобы сэкономить время и силы, читает их из потока источника большими порциями.
Пример программы:
import java.io.*;
import java.util.*;
class BufferedStreamLesson{
public static void main(String[] args) {
String text = “Hello world!”;
byte[] buffer = text.getBytes();
//ByteArrayInputStream, ByteArrayInputStream очевидно
//для работы с массивами байтов
//(читать из массива байтов/писать в массив байтов).
ByteArrayInputStream in = new ByteArrayInputStream(buffer);
//теперь in и System.out буферизованные что улучшает
//производительность.
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos=new BufferedOutputStream(System.out);
try{
int ch;
while((ch=bis.read()) != -1)
{
bos.write((char)ch);
//только что мы записали байт в
//буферизованный вывод BufferedOutputStream
//а для записи байта в поток, который
//обернут в BufferedOutputStream
//необходимо сделать flush
bos.flush();
}
}
catch(Exception e){
System.out.println(“gewgwg”);
}
}
}
DataInputStreamDataOutputStream – чтение байтового потока и запись в байтовый потокв формате примитивных типов данных.
Пример программы:
import java.io.*;
import java.util.*;
class DataStreamLesson{
public static void main(String[] args) {
try {
FileOutputStream fileOutputStream =
new FileOutputStream(“double.txt”);
FileInputStream fileInputStream =
new FileInputStream(“double.txt”);
// В данном примере файловый ввод/вывод.
// Запись в формате примитивных типов
// через файловый ввод.
DataInputStream dis =
new DataInputStream(fileInputStream);
// Чтение в формате примитивных типов
// через файловый вывод.
DataOutputStream dos =
new DataOutputStream(fileOutputStream);
// Запись double числа в файл.
dos.writeDouble(56.45);
// Ниже можно увидеть, что мы считали только
// что записанное в файл double число из файла
// в переменную double благодаря методу readDouble().
// То есть это тот поток, который нужен, когда
// необходимо считать откуда-то какой-то тип данных
// и, например, записать его в переменную этого типа.
// Есть методы и для других типов данных,
// например readChar, writeChar и тому подобное.
// Чтение в double переменную из файла только
// что записанного double числа.
double inpStrDouble = dis.readDouble();
System.out.println(inpStrDouble);
} catch (Exception e) {}
}
}
После dos.writeDouble(56.45); в файл double.txt записывается число 56.45 (в формате double, поэтому оно так выглядит в файле).
С помощью double inpStrDouble = dis.readDouble(); считываем из файла число, которое в формате double, в переменную double.