#java #design-patterns #data-structures #queue #heap-memory
#java #шаблоны проектирования #структуры данных #очередь #куча-память
Вопрос:
У меня есть следующая проблема с дизайном, для решения которой я надеюсь получить вашу помощь. Ниже приведен упрощенный взгляд на то, как выглядит код
class DataProcessor{
public List<Record> processData(DataFile file){
List<Record> recordsList = new ArrayList<Record>();
for(Line line : file.getLines()){
String processedData = processData(line);
recordsList.add(new Record(processedData));
}
}
private String processData(String rawLine){
//code to process line
}
}
class DatabaseManager{
saveRecords(List<Record> recordsList){
//code to insert records objects in database
}
}
class Manager{
public static void main(String[] args){
DatabaseManager dbManager = new DatabaseManager("e:\databasefile.db");
DataFile dataFile = new DataFile("e:\hugeRawFile.csv");
DataProcessor dataProcessor = new DataProcessor();
dbManager.saveRecords(dataProcessor.processData(dataFile));
}
}
Как вы можете видеть, метод «ProcessData» класса «DataProcessor» принимает объект DataFile, обрабатывает весь файл, создает объект записи для каждой строки, а затем возвращает список объектов «Записи».
Моя проблема с методом «ProcessData»: когда необработанный файл действительно огромен, объекты «Список записей» занимают много памяти, и иногда программа выходит из строя. Мне нужно изменить текущий дизайн, чтобы минимизировать использование памяти. «DataProcessor» не должен иметь прямого доступа к «DatabaseManager». Я думал о передаче очереди в метод «ProcessData», где один поток запускает метод «ProcessData», чтобы вставить объект «Record» в очередь, в то время как другой поток удаляет объект «Record» из очереди и вставляет его в базу данных. Но я не уверен в проблемах с производительностью при этом.
Ответ №1:
Возложите ответственность за запуск процесса на наиболее ограниченный ресурс (в вашем случае DataProcessor
) — это обеспечит наилучшее соблюдение ограничений, а не доведет их до предела.
Примечание: даже не думайте о многопоточности, это не принесет вам никакой пользы для обработки файлов. Потоки будут решением, если ваши данные поступают по проводам, когда вы не знаете, когда поступит ваш следующий блок данных. Возможно, у вас есть дела поважнее с вашим процессорным временем, чем ждать, «пока коровы придут домой на ночлег» (ухмылка). Но с файлами? Вы знаете, что у задания есть начало и конец, поэтому выполняйте его как можно быстрее.
class DataProcessor{
public List<Record> processData(DataFile file){
List<Record> recordsList = new ArrayList<Record>();
for(Line line : file.getLines()){
String processedData = processData(line);
recordsList.add(new Record(processedData));
}
}
private String processData(String rawLine){
//code to process line
}
public void processAndSaveData(DataFile dataFile, DatabaseManager db) {
int maxBuffSize=1024;
ArrayList<Record> buff=new ArrayList<Record>(maxBuffSize);
for(Line line : file.getLines()){
String processedData = processData(line);
buff.add(new Record(processedData));
if(buff.size()==maxBuffSize) {
db.saveRecords(buff);
buff.clear();
}
}
// some may be still unsaved here, less that maxBuffSize
if(buff.size()>0) {
db.saveRecords(buff);
// help the CG, let it recycle the records
// without needing to look "is buff still reacheable"?
buff.clear();
}
}
}
class Manager{
public static void main(String[] args){
DatabaseManager dbManager = new DatabaseManager("e:\databasefile.db");
DataFile dataFile = new DataFile("e:\hugeRawFile.csv");
DataProcessor dataProcessor = new DataProcessor();
// So... do we need another stupid manager to tell us what to do?
// dbManager.saveRecords(dataProcessor.processData(dataFile));
// Hell, no, the most constrained resource knows better
// how to deal with the job!
dataProcessor.processAndSaveData(dataFile, dbManager);
}
}
[править] Обращаясь к «но мы договорились о том, что и как, и теперь вы приходите, чтобы сказать нам, что нам нужно написать дополнительный код?»
Создайте AbstractProcessor
класс и попросите своих товарищей просто вывести из него.
class AbstractProcessor {
// sorry, need to be protected to be able to call it
abstract protected Record processData(String rawLine);
abstract protected Class<? extends Record> getRecordClass();
public void processAndSaveData(DataFile dataFile, DatabaseManager db) {
Class<? extends Record> recordType=this.getRecordClass();
if(recordType.equals(MyRecord1.class) {
// buffered read and save MyRecord1 types specifically
}
else if(recordType.equals(YourRecord.class)) {
// buffered read and save YourRecord types specifically
}
// etc...
}
}
Теперь все, что им нужно сделать, это «закодировать» extends AbstractProcessor
и сделать их processData(String)
защищенными и написать тривиальный метод, объявляющий его тип записи (также может быть перечислением). Это не значит, что вы требуете от них огромных усилий и делаете то, что было бы дорогостоящим (или даже невозможным для входного файла TB), операцией «как можно быстрее».
Комментарии:
1. спасибо за это. на самом деле у меня было что-то подобное. Но в моей реальной программе у меня есть много классов, расширяющих «DataProcessor» для разных типов данных. Эти классы разрабатываются разными разработчиками, и требованием было, чтобы классам «Dataprocessor» не требовалось ничего, кроме файла данных, и они ничего не знали о классе «DatabaseManager». Итак, я переработал код, чтобы он был похож на мой упомянутый код, но затем у меня возникла проблема с памятью. и, кстати, я все еще начинающий программист.
2. @AnthonyJ. Что ж, смотрите Мои дополнительные комментарии в конце моего ответа. Если ваши коллеги не согласны, вы обречены в отношении этой задачи — нет способа обойти ее.
3. хорошо, еще раз спасибо, и да, это не потребует большой работы с их стороны. но мне интересно, почему, по-вашему, очередь не будет работать? если «Менеджер» передает очередь в «DataProcessor», то «Менеджер» извлекает «Записи» из этой очереди и передает их менеджеру «Базы данных».
4. @AnthonyJ. поскольку идентификатор вашего узкого места ресурса теперь не база данных, это память, вызванная огромными размерами пакета. Как только вы уменьшите размер пакета, может случиться , что следующим узким местом будет база данных, но это может быть и не . Такова природа узкого места: это «максимально ограниченный ресурс». Если вы устраните одно узкое место, вы обнаружите другое, менее ограниченное, которое вы не могли видеть раньше, потому что оно было «более узким» спереди. Пока вы не устраните узкое место перед вами, преждевременно обращаться к следующему, потому что вы можете не знать, какой следующий.
5. @AnthonyJ. «но разве очередь с максимальным размером 1024 … не решает проблему с горловиной бутылки памяти?» с примечанием 1024 это не магическое число, которое решает все. На практике, немного поэкспериментировав, вы можете обнаружить, что другие значения могут быть более подходящими для максимальной производительности. Черт возьми, может оказаться, что это зависит от размера базы данных — после некоторой перекачки данных некоторые индексы могут стать фрагментированными или неоптимально сбалансированными — поэтому большие пакеты, которые были хороши в начале, становятся слишком большими к концу.
Ответ №2:
Вы должны иметь возможность использовать потоковую передачу для выполнения этого в одном потоке, по одной записи за раз в памяти. Реализация зависит от технологии, используемой вашим DatabaseManager.
Комментарии:
1. не могли бы вы уточнить? DatabaseManager использует интерфейс JDBC для SQLite и просто подготовленную инструкцию, в которой я получаю значения из объекта «Record» и помещаю их в инструкцию «insert», затем выполняю эту вставку statment.
2. вот пример: tododev.wordpress.com/2014/08/14 / …