ВУЗ: Московский технический университет связи и информатики
Категория: Методичка
Дисциплина: Программирование
Добавлен: 23.10.2018
Просмотров: 2558
Скачиваний: 31
На этой неделе мы изменим программу так, чтобы она использовала один или несколько фоновых ядер для вычисления фрактала. В частности, ядро обработки dispatch thread не будет использоваться для вычисления фрактала. Теперь, если вычисление будет выполняться несколькими ядрами, нам нужно будет разбить его на несколько независимых частей. При рисовании фракталов это очень легко сделать - мы можем просто дать каждому ядру одну строку вычисления фрактала. Более сложная часть заключается в том, что необходимо следить за важным ограничением Swing, т.е. мы только взаимодействуем с компонентами Swing в ядре обработке событий (event dispatch thread), но, к счастью, Swing снова предоставляет инструменты, чтобы сделать это задание простым.
На самом деле это очень распространенная ситуация в программировании пользовательского интерфейса: интерфейс должен запускать длительную операцию, но операция должна выполняться в фоновом режиме, чтобы пользовательский интерфейс оставался рабочим. Web-браузеры, вероятно, являются наиболее ярким примером этого. В то время как страница загружается и визуализируется, пользователь должен иметь возможность отменить операцию или щелкнуть ссылку, или выполнить любые другие операции. Чтобы облегчить такое взаимодействие, Swing предоставляет класс javax.swing.SwingWorker, который упрощает выполнение задачи в фоновом потоке. SwingWorker - абстрактный класс; Swing ожидает, что вы его расширите и предоставите функциональность для выполнения фоновой задачи. Наиболее важными методами для реализации являются:
DoInBackground () - этот метод фактически выполняет фоновую операцию. Swing вызывает этот метод в фоновом потоке, а не в ядре обработки событий (event dispatch thread);
Done () - этот метод вызывается, когда фоновая задача выполнена. Он вызывается в ядре обработки событий (event dispatch thread), поэтому этому методу разрешено взаимодействовать с пользовательским интерфейсом.
Класс SwingWorker имеет запутанную спецификацию, на самом деле это SwingWorker <T, V>. Тип T - это тип значения, возвращаемого функцией doInBackground (), когда вся задача завершена. Тип V используется, когда фоновая задача возвращает промежуточные значения в процессе работы. Эти промежуточные значения будут отображаться методами publish () и process (). Не всегда необходимо использовать один или оба этих типа; В этих случаях мы можем просто указать Object для неиспользуемого типа (типов).
Вы будете работать преимущественно с классом FractalExplorer. Некоторые из кодов будут новыми, но некоторые из них будут фактически переведены на код, который вы уже написали.
-
Вы должны создать подкласс SwingWorker под названием FractalWorker, который является внутренним классом FractalExplorer. Это самый простой способ написать этот код, так как ему потребуется доступ к нескольким внутренним членам FractalExplorer. Помните, что класс SwingWorker является общим/родовым generic, поэтому вам нужно будет указать параметры - вы можете просто указать Object для них обоих, потому что на самом деле мы не будем их использовать. Поэтому в итоге вы получите следующую строку кода:
private class FractalWorker extends SwingWorker<Object, Object>.
-
Класс FractalWorker будет отвечать за вычисление значений цвета для одного строки/ряда row фрактала, поэтому ему понадобятся два поля: целочисленная y-координата строки/ряда row, которая будет вычислена, и массив значений int для хранения вычисленных значений RGB Для каждого пикселя в этой строке. Конструктор должен взять y-координату в качестве аргумента и сохранить ее. Конструктору больше ничего не нужно будет делать. (В частности, вам не следует выделять массив ints, поскольку он не понадобится, пока строка/ряд не будет фактически вычислена).
-
Помните, что метод doInBackground () вызывается в фоновом потоке и отвечает за выполнение долгосрочной задачи. Поэтому в вашей имплементации/реализации вам нужно будет взять часть кода из вашей ранней функции «draw fractal» и поместить ее в этот метод. Конечно, вместо рисования на изображение-экран, петле loop нужно будет сохранить каждое значение RGB в соответствующий элемент целочисленного массива. На самом деле вы не сможете изменять изображение-дисплей из этого ядра, потому что вы нарушите ограничения потоковой обработки Swing!
-
Вместо этого выделите массив целых чисел в начале этого метода (он должен быть достаточно большим, чтобы хранить целую строку значений цвета), а затем сохраните цвет каждого пикселя в этот массив. Должно быть очень легко адаптировать код, который вы писали ранее, единственные различия в том, что вам нужно будет вычислить фрактал для указанной строки и что вы еще не обновляете экран/изображение.
Ваш метод doInBackground () должен возвратить что-то типа Object, так как это и есть объявление declaration SwingWorker <T, V>. Просто верните null!
-
Метод done () вызывается, когда фоновая задача завершена, и этот метод вызывается из ядра обработки событий Swing. Это означает, что вы можете модифицировать компоненты Swing в соответствии с содержанием вашего ядра heart. Поэтому в этом методе вы можете просто перебрать массив строк данных, рисуя в пикселях, которые были вычислены в doInBackground (). Проще не бывает.
Как и раньше, когда строка кода закончит рисование, вам нужно будет сообщить Swing, перерисовать часть дисплея/изображения, который был изменен. Поскольку вы изменили только одну строчку, было бы слишком сложно говорить о Swing, чтобы перерисовать весь дисплей! Поэтому вы можете использовать версию JComponent.repaint (), которая позволяет вам указать область для перерисовки. Обратите внимание, что метод немного странный - на переднем плане у него есть неиспользованный long параметр, и вы можете просто указать 0 для этого аргумента. Для остальных просто укажите строку, которая была нарисована - начиная с (0, y) и с размером (displaySize, 1).
После того как вы закончили свой класс фоновой задачи, следующим шагом будет его привязка к процессу рисования фракталов. Вы уже переместили часть своего кода из функции «draw fractal» в рабочий класс, поэтому теперь вы можете изменить свою функцию «draw fractal». Для этого в каждой строке на дисплее создайте отдельный рабочий объект, а затем вызовите execute () для объекта. Это запустит фоновый поток и запустит задачу в фоновом режиме!
Это все изменения, которые вам нужно сделать! Помните, что рабочий класс отвечает за генерацию данных строки и затем рисует строку, поэтому ваша функция «рисовать фрактал» должна быть очень простой.
После завершения и отладки этой функции у вас должен появиться гораздо более быстрый и отзывчивый пользовательский интерфейс. Если у вас несколько ядер, вы обязательно увидите существенное улучшение.
Вы заметите одну проблему с вашим пользовательским интерфейсом - если вы нажмете на экран или на кнопку во время перерисовки, программа обработает его, хотя клик должен быть проигнорирован до завершения операции. К счастью, это довольно просто исправить.
Игнорирование событий во время перерисовки
Самый простой способ решить проблему игнорирования событий во время перерисовки - отслеживать количество оставшихся строк, которые должны быть завершены, и игнорировать или отключать взаимодействия пользователя до тех пор, пока не будут нарисованы все строки. Это нужно делать очень осторожно, иначе у нас будут очень неприятные ошибки. Вот что мы можем сделать - добавить поле «оставшиеся строки» в наш класс Fractal Explorer и использовать его, чтобы узнать, когда будет завершена перерисовка. Мы будем читать и записывать это значение только из ядра обработки событий, чтобы мы никогда не вводили какие-либо параллельные обращения. Если мы будем взаимодействовать только с ресурсом из одного потока, у нас не будет никаких ошибок параллелизма. Вот что вам нужно сделать.
-
Создайте функцию void enableUI (boolean val), которая будет включать или отключать кнопки и выпадающий список вашего интерфейса на основе указанного значения. Для включения или отключения этих компонентов можно использовать метод Swing setEnabled (boolean). Убедитесь, что ваш метод обновляет включенное состояние кнопки сохранения, кнопки сброса и выпадающего списка.
Ваша функция «draw fractal» должна уметь делать еще две вещи. Во-первых, прежде всего, она должна вызывать enableUI (false), чтобы отключить все элементы пользовательского интерфейса во время рисования. Во-вторых, он должен установить значение «rows rows» в общее число строк, которые должны быть нарисованы. Сделайте это, прежде чем приступать к выполнению каких-либо рабочих задач, иначе это не будет обновлено должным образом. -
В вашем рабочем методе done () уменьшите значение «rows rows» на 1 в качестве последнего шага этой операции. Затем, если после уменьшения оставшихся строк не осталось, вызовите enableUI (true).
-
Наконец, измените имплементацию вашего приемника-мышки (mouse-listener), чтобы немедленно возвращаться, если значение «rows rows» не равно нулю. Другими словами, вы будете реагировать на щелчки мышью, только если больше нет строк, которые должны быть нарисованы. Обратите внимание, что нам не нужно делать аналогичные изменения в обработчике события действия, потому что мы отключаем все эти компоненты с помощью метода enableUI ().)
После того как вы выполните эти шаги, у вас должна получиться красивая программа отрисовки фракталов, которая может рисовать фракталы с несколькими потоками и это не позволит пользователям ничего делать, пока процесс рендеринга происходит в фоновом режиме.
Все сделано!
Задача № 7. Серфинг в Интернете
В этой задаче, вы будите писать код рудиментарного поискового робота. Ваш робот будет автоматически загружать веб-страницы из Интернета, искать новые ссылки на этих страницах и повторять. Сканер будет примерно таким простым, каким только можно себе представить: он будет просто искать новые URL-адреса (местоположения веб-страниц) на каждой странице, собирать их и распечатывать в конце. Более сложные веб-сканеры используются для того, чтобы делать такие вещи, как индексирование содержимого Интернета или очистка адресов электронной почты от спама; Если вы когда-либо использовали поисковую систему, вы запрашивали данные, генерируемые поисковым роботом.
Термины
-
URL: унифицированный указатель ресурса. Это адрес веб-страницы. В нашем случае он состоит из строки, за которым следует местоположение веб-сервера, а затем путь к веб-странице на сервере. Если последнее имя в пути не заканчивается на ".html", то фактически веб-страница предоставляется сервером. Это может быть "index.html", "index.shtml", "index.php", "default.htm", или что-либо другое, которое веб-сервер считает документом «по умолчанию» для конкретного каталога.
Примечание: существуют и другие допустимые URL-адреса, начиная с (например) "mailto://" или "ftp://". Мы не будем беспокоиться об этом для этого задания.
-
HTTP: Hyper Text Transfer Protocol (Протокол передачи гипертекста). Это стандартный текстовый протокол, используемый для передачи данных веб-страницы через Интернет. Последней спецификацией HTTP является версия 1.1, которую мы и будем использовать.
Обратите внимание, что HTTP-запрос ДОЛЖЕН оканчиваться пустой строкой, иначе запрос будет проигнорирован. Также обратите внимание на заглавные буквы. Если вы попытаетесь отправить «Get» или «host», то веб-сервер будет вами недоволен.
Некоторые URL-адреса не указывают документ или ресурс для извлечения. В этих случаях вы должны указать «/» в качестве ресурса для извлечения. Другими словами, запрашиваемый ресурс всегда будет начинаться с символа «/».
-
Socket(Сокет): Сокет (разъем) - это ресурс, предоставляемый операционной системой, который позволяет вам обмениваться данными с другими компьютерами по сети. Вы можете использовать сокет для установки соединения с веб-сервером, но вы должны использовать сокет TCP и «говорить» HTTP-протокол для того, чтобы сервер мог ответить.
-
Port(Порт): несколько разных программ на одном сервере могут прослушивать соединения, прослушивая разные порты. Каждый порт обозначается номером в диапазоне 1..65535. 1 - 1024 зарезервированы для операционной системы. У большинства видов серверов есть порт по умолчанию. Для HTTP-соединений мы обычно используем порт 80.
Программа для записи
Вот описание программы, которую вы должны написать.
-
Программа должна принимать в командной строке два параметра:
-
строку, представляющую URL, с которого можно начать просмотр;
-
положительное целое число, представляющее максимальную глубину поиска (см. ниже).
Если правильные аргументы не указаны, программа должна немедленно остановить и распечатать сообщение об использовании, например:
usage: java Crawler <URL> <depth>
2. Программа должна хранить URL в виде строки вместе со своей глубиной (которая равна 0 для начала). Вы должны создать специальный класс для представления пар [URL, depth].
3. Программа должна подключиться к данному сайту в URL-адресе на порт 80 с помощью сокета (см. ниже) и запросить указанную веб-страницу.
4. Программа должна анализировать возвращаемый текст, если он есть, построчно для любых подстрок, имеющих формат:
<a href="[любой URL начинающийся с http://]">
Найденные URL-адреса должны быть сохранены вместе с новым значением глубины в пары LinkedList (URL, depth ) (подробнее о LinkedLists см. ниже). Новое значение глубины должно быть больше, чем значение глубины URL-адреса, соответствующего анализируемой странице.
5. Далее программа должна закрыть соединение сокета с хостом.
6. Затем программа должна повторять шаги с третьего по шестой для каждого нового URL-адреса, если глубина, соответствующая URL-адресу, меньше максимальной. Обратите внимание, что при получении и поиске определенного URL глубина поиска увеличивается на 1. Если глубина URL-адреса достигает максимальной глубины (или больше), не извлекайте и не просматривайте эту веб-страницу.
7. Наконец, программа должна распечатать все URL, посещенные вместе с их глубиной поиска.
Допущения
Это довольно сложно разобрать, а тем более подключить, ко всем правильно и неправильно сформированным гиперссылкам в Интернете. Предположим, что каждая ссылка/ссылки правильно сформированы, с полностью квалифицированным именем хоста, ресурсным путем и всеми вышеперечисленными параметрами. Кроме того, на удивление, есть несколько больших сайтов, которые имеют много URL-адресов этой формы; вы можете попробовать http://slashdot.org/ или http://www.nytimes.com. (Заключительный слэш на slashdot.org важен)
Обратите внимание, что один из наиболее распространенных видов URL-адресов, который неприемлем, - это тот, который начинается с чего-то, кроме «http: //». Общие примеры включают «mailto: //» и «ftp: //». Если вы найдете их, вы должны их игнорировать.
Предположим, когда ваш BufferedReader возвращает значение null, сервер завершил отправку веб-страницы. На самом деле это может быть неверным для очень медленных веб-серверов, но для наших целей это должно быть вполне приемлемым.
Полезные классы и методы
Классы и методы должны помочь вам начать работу. Обратите внимание, что большинство этих методов выбрасывают различные виды исключений, с которыми вам придется работать. Опять же, посмотрите Java API, чтобы узнать, что это такое.