Реализация доступа к базе данных — КиберПедия 

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...

Реализация доступа к базе данных

2020-04-01 150
Реализация доступа к базе данных 0.00 из 5.00 0 оценок
Заказать работу

Общее описание

Поисковая программа (SearchEngein.class) написана на языке Java с использованием технологии Java servlets (17) на базе веб сервера Apache (16). Чтобы программа работала в системе, под управлением операционной системой Windows NT должны присутствовать следующие компанеты приведенные в списке:

1. Веб сервер Apache, по архитектуру Win32;

2. Apache Java server, так же под архитектуру Win32;

3. Виртуальная Java машина (JVM) также под архитектуру Win32;

4. Java Servlet Development Kit (JSDK) 2.0

Настройка и установка этих команентов была описана выше. Тонкости настройки для правильной работы программы буду описаны ниже.

Ограничении по применению программы практически не существует кроме аппаратного обеспечения, минимальная конфигурация системы должна быть такой:

Процессор с тактовой чистатой 266 или выше.

Память не меньше 64 мегабайт, чем больше, тем лучше.

Жесткий диск любой.

Остальное оборудование по усмотрению администратора сервера.

Выше было сказано, что ограничений нет, уточняю почему, так как программа откомпилирована в аппаратно-независимый код (байт-код)  оно с легкостью может быть перенесена на другую платформу MacOS или Unix-система. Нужно будет всего лишь переписать файлы классы на диск, естественно настроив систему соответствующим образом.

Описание алгоритма

Алгоритм основан на методе перебора каждой записи всего массива и сравнении введенной строки запроса с полями записи прочитанной из массива. После того как запись и запрос совпали запить выдается в нужном формате для отображения в браузере. Пока весь массив не будет прочитан последовательно, сессия с пользователем не будет закончена.

А теперь более детально остановимся на алгоритме, ниже приведен формат одной отдельно взятой записи из базы данных в формате RUSMARC (см. Приложение 1).

00878nam 22002537 45000010000000330050003300172450005002442600029400153000030900096500031800636500038

10045653004260024653004500019653004690016020004850010091004950008092005030017090005200008852005280022852005500011040005610014041005750008008005830041 BOOK00000876 BOOK00000001 ‑19981027165203.0 ‑00­aАктуальные вопросы преподавания хореографического искусства­nВып. 7­bМатериалы межвуз. науч.-метод. конф. "Современные технологии обучения в гуманитарном вузе"­cСанкт-Петербургский гуманитарный ун-т профсоюзов; Редкол. А.С.Запесоцкий и др. ‑0 ­aСПб.­c1994 ‑ ­a22с.‑ ­aВысшая школа­xМетодика преподавания­xМатериалы конференции‑ ­aХореографическое искусство­xПреподавание‑ ­aТехнологии обучения‑ ­aФормы обучения‑ ­aКонференция‑ ­c2.100‑ ­aЩ32‑ ­a14.35.09­a18‑ ­cЩ32‑ ­bч/з­t2­hЩ32­iА437‑ ­bаб.­t3‑ ­aВСГАКИ-10‑ ­arus‑950614s1990 rur      00000 rus d‑

А теперь этаже запись только уже с пояснениями:

00878nam 22002537 4500 – Маркер 24 символа

– словарь 12 символов 1) Метка поля – 3 символа полный список всех меток приведен в Приложении 1.

2) Начальная позиция относительна начала записи -5 символов

3) Размер поля – 4 символа

1 - 001 00000 0033

2 - 005 00033 0017

3 - 245 00050 0244

4 - 260 00294 0015

5 - 300 00309 0009

6 - 650 00318 0063

7 - 650 00381 0045

8 - 653 00426 0024

9 - 653 00450 0019

10- 653 00469 0016

11- 020 00485 0010

12- 091 00495 0008

13- 092 00503 0017

14- 090 00520 0008

15- 852 00528 0022

16- 852 00550 0011

17- 040 00561 0014

18- 041 00575 0008

19- 008 00583 0041

Поля с данными

1 - ‑ BOOK00000876 BOOK00000001 

2 - ‑19981027165203.0

3 - ‑00­aАктуальные вопросы преподавания хореографического искусства­nВып. 7­bМатериалы межвуз. науч.-метод. конф. "Современные технологии обучения в гуманитарном вузе"­cСанкт-Петербургский гуманитарный ун-т профсоюзов; Редкол. А.С.Запесоцкий и др.

4 - ‑0 ­aСПб.­c1994

5 - ‑ ­a22с.

6 - ‑ ­aВысшая школа­xМетодика преподавания­xМатериалы конференции

7 - ‑ ­aХореографическое искусство­xПреподавание

8 - ‑ ­aТехнологии обучения

9 - ‑ ­aФормы обучения

10- ‑ ­aКонференция

11- ‑ ­c2.100

12- ‑ ­aЩ32

13- ‑ ­a14.35.09­a18

14- ‑ ­cЩ32

15- ‑ ­bч/з­t2­hЩ32­iА437

16- ‑ ­bаб.­t3

17- ‑ ­aВСГАКИ-10

18- ‑ ­arus

19- ‑950614s1990 rur      00000 rus d

Программа начинает работать после того когда от клиента приходит запрос на страницу по определенному URL (например: http://www.real.ulan-.ude.ru/serv/SearchEngein), для выполнения запроса пользователя веб-сервер запускает JServ, который в свою очередь обрабатывает запрос и определяет какой именно сервлет требуется запустить и в какой зоне он находится. Информацию о зонах размещения всех сервлетов Jserv считывает из файла настройки. Чтобы сервлет начал выполняться JServ предварительно запускает виртуальную Java машину и только после этого начинает работать сервлет это значит что запрос пользователя будет обработан и пользователь получит запрошенную страничку. Что же происходит на стороне сервера в этот момент когда пользователь ждет пока загрузится страница. А происходит вот что, управление по отображению всей информации в окне браучера переходит сервлету, программе написанной на Java. Рассмотрим это более детально. Сервлет инициализируется и начинает передачу данных в формате HTML пользователю. Первое что увидит пользователь это будет поисковая форма  (см. рис. 6).

Рис. 6

Поисковая форма

Программный код поисковой формы выглядит так:

out.println("<form method=\"get\" action=\"/serv/SearchEngein\">"+

"<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">"+

"<tr bgcolor=\"#3399FF\"> "+

"<td class=\"text\">&nbsp;&nbsp;&nbsp;Запрос</td>"+

"<td class=\"text\">&nbsp;&nbsp;&nbsp;Каталог</td> "+

"<td width=\"207\">&nbsp;</td>"+

"</tr>"+

"<tr>"+

"<td "+

"<input type=\"text\" name=\"Query\" maxlength=\"100\" size=\"38\" value=\"\">"+

"</td>"+

"<td "+

"<select name=\"select\" size=\"1\">"+

"<option value=\"MARCFILE.Book\" selected>"+ConvertISO(getINIVar("KATALOG.Book"))+"</option>"+

"<option value=\"MARCFILE.Stat\">"+ConvertISO(getINIVar("KATALOG.Stat"))+"</option>"+

"<option value=\"MARCFILE.Periud\">"+ConvertISO(getINIVar("KATALOG.Periud"))+"</option>"+

"<option value=\"MARCFILE.Podpis\">"+ConvertISO(getINIVar("KATALOG.Podpis"))+"</option>"+

"<option value=\"MARCFILE.Ucheb\">"+ConvertISO(getINIVar("KATALOG.Ucheb"))+"</option>"+

"</select>"+

"</td>"+

"<td "+

"<input type=\"submit\" name=\"Start\" value=\"Поиск\">"+

"</td>"+

"</tr>"+

"</table>"+

"</form>");

Рассмотрим код более пристально.

В тэге <form> присутствуют параметры metod и action.

1) метод (metod) говорит браузеру о том что данные(запрос) будет отправлены серверу;

2) действие (action) – в этом параметре находится путь к программе на старое сервера которая примет отправленный запрос для обработки.

Следующий интересующий нас неотъемлемый компонент это тэг <input>, который тоже имеет несколько параметров type, name, value. Этот тэг является строкой ввода, рассмотрим его параметры.

1) тип (type)  равный “text” говорит о том что это строка ввода;

2) имя (name) название запроса т.е. имя которое присваивается тексту введенному в строку ввода, в моей программе это Query;

3) значение (value) значение строки по умолчанию при начальной загрузке.

Еще один значимый тэг формы <select> - это список для выбора базы в которой будет производится поиск. Данный тэг имеет свое имя которое указано в параметре name. Сам же список последовательно указан в тэге <option> относящимся к тэгу <select>. Каждая из строк начинающихся тэгом <option> является элементом списка для выбора. У тэга <option> есть параметр value в котором указан псевдоним выбранного пункта из списка – это нужно для определения какой пункт из списка выбран.

И наконец последний значимый тэг <input>, в отличии от первого тэга <input> у этого тэга параметр тип(type) равен “submit” - это значит что это кнопка отправки запроса обработчику который указан в тэге <form>. У кнопки тоже есть имя (name) и значение (value) кнопка называется Start. Последним тэгом, всегда является закрывающий тэг </form>.

После того как пользователь ввел запрос и нажал на кнопку “Поиск” в адресная строка браузера приобретет примерно вид:

http://localhost/serv/SearchEngein?Query=%E1%A8%E1%E2%A5%AC%A0&select=MARCFILE.Stat&Start=%8F%AE%A8%E1%AA

К ссылке на сервлет прибавилось три параметра отделенные от адреса сервлета вопросительным знаком первый параметр это Query (запрос), второй select говорящий сервлету в какой базе производить поиск.

Первое что делает программа - это считывает файл настройки db.ini – который находится в папке c:\www\db. В данном файле находятся данные о место нахождении интересующей базы данных или говоря проще локальный путь к базе данных. Определив интересующую базу данных и установив ее место нахождения, программа начинает процесс поиска всех удовлетворяющих запросу данных (библиографических описаний).

Программа считывает всю запись в массив, после чего начинается определение места нахождения полей и их длинны. Разберем код процедуры.

public void dbFileRead(String dbNamePath, PrintStream out, String query) {

Сперва производится инициализация всех переменных используемых при работе процедуры.

Первый блок. Переменные для занесения значений полей

 String mAvtor = null; // 100

 String msAvtor = null; // 700

 String mName = null; // 245

 String mPrinter = null; // 260

 String mSize = null; // 300

 String mKey = null; // 653

 String mSeria = null; // 490

 String mRubrika = null; // 650

 String mBBK = null; // 91

 String mKaIndex = null; // 90

Второй блок. Файловые переменные для перемещения по файлу

 long fPosMarker = 0, // Позиция относительно начала

fPosData = 0; // Начальная позиция данных

 

 boolean done = false;

Третий блок. Перемнный для работы с данными

int mC =0,       // Счетчик прочитанных записей 

mE =0;      // Счетчик найденых соответствий

 byte Jumper[] = new byte[5]; // Размер запяси - символьный

 int JIndex = 0, // Размер запяси - числовой

JTemp = 0, // Размер данных + словарь

MIndex = 0, // Счетчик для массива

MTemp = 0; // Счетчик полей

Начало выполнени поика. Сперва проверяется имеет ли запрос query занчение неравное пусто, если условие выполняется и запрос имеет не нуливое занчение устанавливается связь с файлом данных. Начальная позиция чтения равна нулю.

if (query!= null){

try { RandomAccessFile dbfile = new RandomAccessFile(dbNamePath,"r");

// Цикл чтения файла по маркерам

while (fPosMarker!= dbfile.length()) {

try { mC++;

       dbfile.seek(fPosMarker);

       dbfile.read(Jumper);

       String jBuf = new String(Jumper);

       JIndex = Integer.parseInt(jBuf,10);

         int b = 0;

Прочитав начальный блок из 5 символов говорящий о длине записи он преобразуется из символьного значенья в числовое. Затем определяется длинная словаря которая равна 12*n, где n – равно количеству заполненных полей в одной записи.

// Поиск конца словаря

          while (b!= MD){

           dbfile.seek(fPosMarker+24+MIndex);

            b = dbfile.read();

           MTemp++;

           MIndex = MTemp;

           }

           MTemp= MTemp - 1;

Определив конечную позицию словаря производится считывание в массив блока состоящего из данных - метка поля; начальная позиция поля, относительно конца словаря; длинная поля и символах.

// чтение Словаря из файла в отдельный массив

          byte Dic[] = new byte[MTemp];

           dbfile.seek(fPosMarker+24);

            dbfile.read(Dic);

// чтение полей данных из файла в массив

       fPosData = fPosMarker+24+MTemp;

         String sDic = new String(Dic);

          int DI2 = 0,

              DI3 = 0,

              DI4 = 0,

              DI5 = 0,

          PNum = 0, // Номер поля числовой

          PLength = 0, // Длинна поля числовая

          PStart = 0; // Начальная позиция поля чиловая

Получив данные в результате преобразований, это строка, начинается последовательное вычитание метки поля, начальной позиции, размера поля.

// сканирование номеров полей

          while (DI2!= MTemp){

           DI3=DI2+3;

           String DStr = sDic.substring(DI2,DI3);// Номер поля

           DI4=DI3+5;

           String DStr2 = sDic.substring(DI3,DI4);// Начальная позиция

           DI5=DI4+4;

           String DStr3 = sDic.substring(DI4,DI5);// Длинна поля

           DI2=DI2+12;

            PLength = Integer.parseInt(DStr3,10);// Узнаем длинну поля

            PStart = Integer.parseInt(DStr2,10);// Узнаем начало поля

            PNum = Integer.parseInt(DStr,10);// Код

            byte Pole[] = new byte[PLength];

Как только первая запись о первом поле разобрана на составляющие проверяется его метка, которая говорить относится или нет, поле к тому списку полей который нас интересует. Если да то производится считывание его из файла. После того как поле считано над значением поля производится ряд преобразований, таких как, вычитание из поля служебной информации относящейся к формату MARC.

// Чтение поля из файла

            switch (PNum) {

              case 100: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mAvtor = TagRemove(Pol.substring(5));break;}

             case 700: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 msAvtor = TagRemove(Pol.substring(5));break;}

             case 245: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mName = TagRemove(Pol.substring(5));break;}

             case 490: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mSeria = TagRemove(Pol.substring(5));break;}

             case 91: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mBBK = TagRemove(Pol.substring(5));break;}

             case 90: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mKaIndex  = TagRemove(Pol.substring(5));break;}

             case 260: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mPrinter = TagRemove(Pol.substring(5));break;}

             case 300: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mSize = TagRemove(Pol.substring(5));break;}

             case 653: {

              dbfile.seek(fPosData+PStart);

              dbfile.read(Pole);

              String Pol = new String(Pole);

               if (Pol == null) Pol=" ";

 mKey = TagRemove(Pol.substring(5));break;}

             default: {}

            }// switch

          }// конец проверки полей

Получив все данные которые нас интересовали, создается запись состоящая из нескольких полей.

              if (mAvtor == null) mAvtor=" ";

           if (msAvtor == null) msAvtor=" ";

           if (mName == null) mName=" ";

           if (mPrinter == null) mPrinter=" ";

           if (mSize == null) mSize=" ";

           if (mKey == null) mKey=" ";

           if (mKaIndex == null) mKaIndex=" ";

           if (mBBK == null) mBBK=" ";

           if (mSeria == null) mSeria=" ";

Это собственно самая запись

           MarcRecord Rec = new MarcRecord(mAvtor,

                                            msAvtor,

                                            mName,

                                            mPrinter,

                                            mSize,

                                            mKey,

                                            mSeria,

                                            mBBK,

                                            mKaIndex);

Данные которые занесены в запись теперь осуществляется сравнивание их с запросом. Сравнение осуществляется после преобразование к одному регистру, это нужно для того чтобы найти полный список всех имеющихся записей относящихся к веденному запросу.

            String q = toLow(query);

            String p01 = toLow(Rec.rAvtor);

            String p02 = toLow(Rec.rsAvtor);

            String p03 = toLow(Rec.rName);

            String p04 = toLow(Rec.rKey);

Затем распознанные данные сравниваются с запросом, сравнивание производится только с несколькими полями. Список полей приведен ниже:

100 – Автор

700 – Второй автор

245 – Название произведения

653 – Ключевые слова

 

            if (p01.indexOf(q)!= -1 ||

                 p02.indexOf(q)!= -1 ||

                 p03.indexOf(q)!= -1 ||

                 p04.indexOf(q)!= -1)

                { mE++;

При совпадении запись сразу же отправляется браузеру для отображения в читабельной для пользователя форме.

out.println("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">"+

"<tr bgcolor=\"#3399FF\">"+

"<td colspan=\"3\" class=\"text\">&nbsp;&nbsp;&nbsp;Автор:&nbsp;"+

"<font color=\"#000000\">"+

Rec.rAvtor+" "+

Rec.rsAvtor+

"</font></td></tr><tr>"+

"<td colspan=\"3\" class=\"bodytext\">"+mE+". "+mC+

"&nbsp;<b>Название:</b>&nbsp;"+

Rec.rName+"<br>"+

Rec.rPrinter+" "+

Rec.rSize+"<br>"+

Rec.rBBK+" "+

Rec.rKaIndex+" "+

Rec.rSeria+

"</td></tr></table>");

}

В конце обработки одной записи независимо соответствовала она запросу или нет производится переход к следующей записи.

       fPosMarker = fPosMarker+JIndex;

       MTemp = 0;

       MIndex = 0;

      }

В случае ошибки (исключительной ситуации) цыкал обработки записи, прерывается и выдается сообщение об ошибки.

  catch (IOException e) {

                 out.println("Ошибка!!!"+"<br>");

                 done=true; }

}

 }

Если же файл отсутствует то программа выдаст сообщение о том что файл базы данных отсутствует на сервере.

 catch (IOException e) { out.println("Ошибка доступа к "+dbNamePath); }

}

 if (mE == 0) {

out.println("Запос: "+query+" не найден");

} // end If

}

После того как проведено сравнение запроса и данных имеющихся в поле, при совпадении запись преобразуются в формат HTML. Преобразуются только несколько полей, список полей которые выдаются по запросу в случае совпадений приведен ниже:

100 – Автор

700 – Второй автор

245 – Название произведения

490 – Серия

91 – Индекс ББК

90 – Каталожный индекс

260 - Издательство

300 – Объем, размер

653 – Ключевые слова

Код вывода в HTML формате выглядит так:

out.println("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">"+

"<tr bgcolor=\"#3399FF\">"+

"<td colspan=\"3\" class=\"text\">&nbsp;&nbsp;&nbsp;Автор:&nbsp;"+

"<font color=\"#000000\">"+

Rec.rAvtor+" "+

Rec.rsAvtor+

"</font></td></tr><tr>"+

"<td colspan=\"3\" class=\"bodytext\">"+mE+". "+mC+

"&nbsp;<b>Название:</b>&nbsp;"+

Rec.rName+"<br>"+

Rec.rPrinter+" "+

Rec.rSize+"<br>"+

Rec.rBBK+" "+

Rec.rKaIndex+" "+

Rec.rSeria+

"</td></tr></table>");

После чего программа производит считывание и обработку следующей записи. Более детально алгоритм расписан в листинге программы (см. прил.3), а результаты теста программы (см. прил. 4)


Заключение

Глобальная информатизация общества приводит к тому, что потребность в информации, растет с каждым новым пользователем сети. При этом задачей специалистов в области информационных технологий обеспечить пользователей полной и достоверной информацией путем простого и удобного для пользователей доступа к накопленным массивам данных.

Главной задачей данной дипломной работы было создание программного интерфейса к существующей библиографической базе данных. Применение передовых технологии программирования позволили разработать программу, позволяющую производить поиск интересующей информации в базе данных не только по отдельно взятым ключевым словам, но и полному названию документа. Тестирование программы на массиве из 8366 записей показало, что поиск документа в конце массива занимает 2 минуты 16 секунд. Естественно, что при увеличении количества записей время обработки также будет увеличиваться. Массив данных, на котором проводилось тестирование, является реальной базой данных библиотеки ВСГАКиИ. Для того чтобы программа могла работать стабильно и с минимальными затратами времени на обработку запроса, нужно использовать ее на машине, обладающей большим быстродействием. Тестирование производилось на компьютере с такой конфигурацией: AMD K6-233, ОЗУ 64 Mb, Жесткий диск 2 Gb, под управлением операционной системы Windows NT 4.0 с установленным SP6a. Развитие направления связанного с поиском информации в массивах данных библиотек очень эффективно, так как потребность в этой информации через сеть Интернет возрастает с каждым новым пользователем.

Практическая реализация поставленной задачи показала  правильность выбранного подхода. Тем не менее, работа требует дальнейше доработаки для организации постоянного доступа читателей к библиографическим ресурсам библиотекам города через Интернет.


Литература

 

1. Глушаков С.В., Ломотьков Д.В. Базы данных: Учебный курс. – К.: Абрис, 2000. -504с.

2. Джейсон Мейнджер. Java: основы программирования:Пер. с англ. - К.: Издательская группа BHV,1997.-320с.

3. Пригорьев Ю.А. Проблемы выбора доступа к данным при проектировании информационных систем на основе СУБД//Информационные технологии. - 1999 - №5. С. 4-10.

4. Симкин Стив, Бартлет Нейл, Лесли Алекс. Программирование на Java. Путеводитель:Пер. с англ. – К. НИПФ «ДиаСофт Лтд», 1996. 736 с.

5. Кристиансен Т., Торкингтон Н. Perl: Библиотека программиста:Пер. с англ.- СПб.: Издательство «Питер», 2000. – 736с.: ил.

6. Холзнер Стивен. Perl: специальный справочник:Пер. с анг. – СПб.: Питер, 2000. – 496с.: ил.

7. Хейл, Бернард Ван. JDBC: Java и базы данных:Пер. с англ. М.,1999.-320с.

8. Эферган М. Java: справочник. – СПб.: Питер, 1998. -448с.: ил.

9. http://www.java.sun.com

10. http://www.sun.ru/java/start/intro/history.html

11. http://www.logos.com/marc/

12. http://www.rba.ru:8101/rusmarc/

13. http://httpd.apache.org/dist/httpd/binaries/win32/old/apache_1_3_14_win32_r2.exe

14. http://java.apache.org/jserv/dist/ApacheJServ-1.1.2-2.exe

15. http://www.netcraft.com/Survey

16. http://www.apache.org

17. http://java.apache.org

18. http://www.ruslibnet.ru/

 


Приложение 1

 Спецификация формата RUSMARC


Поделиться с друзьями:

История развития хранилищ для нефти: Первые склады нефти появились в XVII веке. Они представляли собой землянные ямы-амбара глубиной 4…5 м...

Адаптации растений и животных к жизни в горах: Большое значение для жизни организмов в горах имеют степень расчленения, крутизна и экспозиционные различия склонов...

Двойное оплодотворение у цветковых растений: Оплодотворение - это процесс слияния мужской и женской половых клеток с образованием зиготы...

Автоматическое растормаживание колес: Тормозные устройства колес предназначены для уменьше­ния длины пробега и улучшения маневрирования ВС при...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.165 с.