День добрый.
1С я занимаюсь давно, но этим летом подписался я на курсы Java. Java как Java, ведет спец, который занимался Enterprise разработкой, все понятно, все знакомо. Сначала все казалось велосипедостроением, пока не дошли до StreamAPI, которые позволяют работать с данными очень офигенно, мощно, разнопланово. Эдакая смесь функционала ТаблицЗначений и запросов (Свернуть, НайтиСтроки, ВыбратьПервые, Сортировать), причем даже многопоточно, всего одной командой.
Но это лирика. Делая задачи, внезапно заметил, что выполняются они как - то слишком уж шустро. Сделал пример, запустил его в Java, а потом в 1С и крайне озадачился, сначало не понял, а потом как понял.
Итак, возьмем простой пример. Пусть у нас есть 12 текстовых файлов с данными о оборотах по магазинам за год.
На самом деле нам вообще пофиг, что там за данные, просто их должно быть много. У нас их будет 3 360 000 записей, раскиданных по 12 файлам. Надо вывести данные о оборотах по месяцам по одному из магазинов. Ок. Что мы делаем в 1С:
- Находим все файлы *.txt в каталоге, записываем их в массив
- Замеряем время начала в миллисекундах
- Создаем ТаблицуЗначений
- Проходим по каждому файлу, по каждой строке через ЧтениеТекста
- Каждую прочитанную строку разбиваем на массив по разделителю.
- Вставляем в таблицу значений, преобразуя строки к числам, строки к датам
- Замеряем время окончания и выводим его
- Получаем время чтения данных =26 506 мсек. (26 секунд, с отключенным отладчиком).
Вот этот код:
МассивФайлов=НайтиФайлы("C:\Java_Projects\RawData","*.txt");
ВремяНачала=ТекущаяУниверсальнаяДатаВМиллисекундах();
ТаблицаДанных=Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("ИмяМагазина");
ТаблицаДанных.Колонки.Добавить("Дебит");
ТаблицаДанных.Колонки.Добавить("Кредит");
ТаблицаДанных.Колонки.Добавить("ДатаОперации");
ТаблицаДанных.Колонки.Добавить("МесяцОперации");
Для Каждого Файл Из МассивФайлов Цикл
Чтение=Новый ЧтениеТекста(Файл.ПолноеИмя,КодировкаТекста.UTF8);
ЭтоПерваяСтрока=Истина;
Пока Истина Цикл
ТекущиеДанныеТекстом=Чтение.ПрочитатьСтроку();
Если ТекущиеДанныеТекстом=Неопределено Тогда
Прервать;
КонецЕсли;
Если ЭтоПерваяСтрока=Истина Тогда
ЭтоПерваяСтрока=Ложь;
Продолжить;
КонецЕсли;
ТекущиеДанныеМассивом=СтрРазделить(ТекущиеДанныеТекстом,";");
НоваяСтрока=ТаблицаДанных.Добавить();
НоваяСтрока.ИмяМагазина=ТекущиеДанныеМассивом[0];
НоваяСтрока.Дебит=Число(СтрЗаменить(ТекущиеДанныеМассивом[1],".",","));
НоваяСтрока.Кредит=Число(СтрЗаменить(ТекущиеДанныеМассивом[2],".",","));
ДатаСтрокой=ТекущиеДанныеМассивом[3];
ДатаСтрокой=СтрЗаменить(ДатаСтрокой,"/",".");
ДатаСтрокой=ДатаСтрокой+" 00:00:00";
НоваяСтрока.ДатаОперации=Дата(ДатаСтрокой);
НоваяСтрока.МесяцОперации=НачалоМесяца(НоваяСтрока.ДатаОперации);
КонецЦикла;
КонецЦикла;
ВремяОкончания=ТекущаяУниверсальнаяДатаВМиллисекундах();
Сообщить(СтрШаблон("Время чтения данных %1 мсек.",Строка(ВремяОкончания-ВремяНачала)));
Да, можно и схитрить, считать весь файл в строку, разбить строку в массив, НО.
Абсолютно тот же алгоритм мы сделаем в Java и получим 4973 мсек (5 секунд) вот так:
static void LoadFromFiles(URI resPath) {
File dir = new File(resPath);
FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
Boolean result = false;
if (name.toUpperCase().endsWith(".TXT")) {
result = true;
}
return result;
}
};
File[] txtFiles = dir.listFiles(filter);
for (File txtFile : txtFiles) {
String currentFileName = txtFile.getAbsolutePath();
try {
FileReader reader = new FileReader(currentFileName);
BufferedReader bufReader = new BufferedReader(reader);
Boolean firstLine = true;
while (bufReader.ready()) {
String currentLine = bufReader.readLine();
if (firstLine) {
firstLine = false;
continue;
}
String[] arrayData = currentLine.split(";");
TrowData rowData = new TrowData();
rowData.name = arrayData[0];
rowData.debet = Double.parseDouble(arrayData[1]);
rowData.credit = Double.parseDouble(arrayData[2]);
SimpleDateFormat dateFormater = new SimpleDateFormat("dd/M/yyyy");
rowData.operationDate = dateFormater.parse(arrayData[3]);
Calendar calendar = new GregorianCalendar();
calendar.setTime(rowData.operationDate);
calendar.set(Calendar.DAY_OF_MONTH, 1);
rowData.operationMounth = calendar.getTime();
dataTable.add(rowData);
}
} catch (IOException fileExсpt) {
System.out.println(String.format("Не удалось прочитать файл %s по причине %s", currentFileName, fileExсpt.getMessage()));
return;
} catch (ParseException dateExсpt) {
System.out.println(String.format("Не удалось преобразовать данные о дате операции в формат даты по причине %s", dateExсpt.getMessage()));
return;
}
}
}
Ладно. Ладно, интерпретируемый код, виртуальная машина, такие дела.
Но пойдем дальше по задаче - найти и вывести обороты по одному из магазинов по месяцам, например по Пятерочке. В 1С мы это сделаем так:
ВремяНачала=ТекущаяУниверсальнаяДатаВМиллисекундах();
СтруктураПоиска=Новый Структура;
СтруктураПоиска.Вставить("ИмяМагазина","pyterochka");
ТаблицаДанных.Индексы.Добавить("ИмяМагазина");
МассивСтрок=ТаблицаДанных.НайтиСтроки(СтруктураПоиска);
ФильтрованнаяТаблицаДанных=ТаблицаДанных.Скопировать(МассивСтрок);
ФильтрованнаяТаблицаДанных.Свернуть("МесяцОперации","Дебит,Кредит");
ВремяОкончания=ТекущаяУниверсальнаяДатаВМиллисекундах();
Сообщить(СтрШаблон("Время обработки данных %1 мсек.",Строка(ВремяОкончания-ВремяНачала)));
Сообщить(СтрШаблон("Обработано %1 записей.",Строка(ТаблицаДанных.Количество())));
Максимальное использование встроенных методов, ведь так? Ну, сделайте это быстрее. И мы получим время обработки данных 6 547 мсек.
Сделаем это на Java:
static void getTotalsPerMonthByShop(String shopName) {
Map<Date, Double> tmpResult = dataTable.stream().
filter(rowData -> rowData.name.equals(shopName)).
sorted(Comparator.comparing(rowData -> rowData.operationMounth)).
collect(Collectors.groupingBy(rowData -> rowData.operationMounth, Collectors.summingDouble(rowData -> rowData.debet - rowData.credit)));
LinkedHashMap<Date, Double> sortedResult=tmpResult.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).
collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e1, LinkedHashMap::new));
System.out.println(String.format("Отчет о итоговой прибыли за каждый месяц по магазину %s",shopName));
Calendar calendar=new GregorianCalendar();
for (Map.Entry<Date, Double> rowData : sortedResult.entrySet()) {
calendar.setTime((Date)rowData.getKey());
String userFriendlyDate=String.format("%02d", calendar.get(Calendar.MONTH)+1)+"."+Integer.toString(calendar.get(Calendar.YEAR));
DecimalFormat dF = new DecimalFormat( "#.00" );
String userFriendlyValue=dF.format(rowData.getValue());
System.out.println(userFriendlyDate + " : " + userFriendlyValue);
}
}
И получим время обработки данных 147 мсек.
Прошу, ткните меня туда, где я не прав.
Код Java доступен тут
https://github.com/Hadgehogs/FS3/blob/main/src/Lesson5/Expert/TfinReport.java
Тестовые данные и тестовая конфа будут прикреплены к публикации.
Версия 1С 8.3.21.1508, файловая, однако я пробовал и на 8.3.17 и на 8.3.20.
Компьютер I5-12600k, 64 ОЗУ 2300, SSD 980 Samsung, план питания - высокий, антивирус отключен, Windows 10.
Тестовые файлы следует извлечь в папку C:\Java_Projects\RawData, ну, либо поменять пути в примерах.
upd.
Убрал ненужное индексирование таблицы значений, время сократилось до 1616 мсек, но все равно разница с Java составляет 10 раз. Обновил cf-ник в примере