我有几个我需要解析的CSV文件。并在以后用于MYSQL中的Insert。我已经写了一个解析器,但我想尽可能避免编码重复
我已经记住,我应该使用抽象类,或者可能是工厂,但我无法真正指出设计它的最佳方法。
所以这是我的解析器:
public class LocusAliasCsvParser {
private static final String[] FILE_HEADER_MAPPING = {"id", "locusID", "organismid", "variable", "alias"};
private static final String ID = "id";
private static final String LOCUS_ID = "locusID";
private static final String ORGANISM_ID = "organismid";
private static final String VARIABLE = "variable";
private static final String ALIAS = "alias";
public static List<AliasLocus> readCsvFile(String fileName) {
FileReader fileReader = null;
CSVParser csvFileParser = null;
CSVFormat csvFileFormat = CSVFormat.DEFAULT.withHeader(FILE_HEADER_MAPPING);
List<AliasLocus> aliases = new ArrayList();
try {
fileReader = new FileReader(fileName);
csvFileParser = new CSVParser(fileReader, csvFileFormat);
//Get a list of CSV file records
List csvRecords = csvFileParser.getRecords();
//Read the CSV file. Header is ignored (i == 1)
for (int i = 1; i < csvRecords.size(); i++) {
CSVRecord record = (CSVRecord) csvRecords.get(i);
AliasLocus aliasLocus = new AliasLocus(Integer.parseInt(record.get(ID)),
record.get(LOCUS_ID),
record.get(ORGANISM_ID),
record.get(VARIABLE),
record.get(ALIAS));
aliases.add(aliasLocus);
}
} catch (Exception e) {
System.out.println("Error in CsvFileReader !!!");
e.printStackTrace();
} finally {
try {
fileReader.close();
csvFileParser.close();
} catch (IOException e) {
System.out.println("Error while closing fileReader/csvFileParser !!!");
e.printStackTrace();
}
}
return aliases;
}
每次改变的事情是:
public class LocusAliasCsvParser {
private static final String[] FILE_HEADER_MAPPING = {"id", "locusID", "organismid", "variable", "alias"};
private static final String ID = "id";
private static final String LOCUS_ID = "locusID";
private static final String ORGANISM_ID = "organismid";
private static final String VARIABLE = "variable";
private static final String ALIAS = "alias";
和:
public static List<AliasLocus> readCsvFile(String fileName) {
AliasLocus aliasLocus = new AliasLocus(Integer.parseInt(record.get(ID)),
record.get(LOCUS_ID),
record.get(ORGANISM_ID),
record.get(VARIABLE),
record.get(ALIAS));
是否有人可以建议使用最佳设计模式或结构来进行最少的代码重复?
感谢
答案 0 :(得分:1)
您应该使用接口分离不同的问题,并实现用于读取csv文件的模板方法。
让我们分6个步骤设置一个简单的框架。
您需要一个知道如何获取csv结构的类。
public interface CsvMetadataSource {
public CsvMetadata getCsvMetadata();
}
您需要一个可以解析csv行的类。来自1的CsvMetadata
是放置逻辑的好地方。
public class CsvMetadata {
private List<String> columns;
public CsvMetadata(List<String> columns) {
this.columns = columns;
}
public Map<String, String> parseLine(String line) {
// simple implementation
String[] values = line.split(",");
Map<String, String> record = new HashMap<>();
for (int i = 0; i < columns.size(); i++) {
String column = columns.get(i);
String value = null;
if (i < values.length) {
value = values[i];
}
record.put(column, value);
}
return record;
}
}
您需要一个可以将已解析的行映射到对象的类。知道行号也可能有用。
public interface CsvRecordMapper<T> {
public T map(Map<String, String> csvRecord, int lineNumber);
}
您需要一个知道如何处理映射对象的类。
public interface CsvObjectCallback<T> {
public void process(T object);
}
您需要一个实现模板方法的类来读取csv数据,并且可以通过上述接口的实现进行扩展。使用便捷方法将所有对象作为列表读取也可能是好事。
public class CsvReader {
private CsvMetadataSource csvMetadataSource = null;
public CsvReader() {
}
public CsvReader(CsvMetadataSource csvMetadataSource) {
this.csvMetadataSource = csvMetadataSource;
}
public <T> List<T> readAll(Reader csvInputReader, CsvRecordMapper<T> csvLineMapper) throws IOException {
CollectCsvObjectCallback<T> collectCsvObjectCallback = new CollectCsvObjectCallback<>();
read(csvInputReader, csvLineMapper, collectCsvObjectCallback);
return collectCsvObjectCallback.getObjects();
}
public <T> void read(Reader csvInputReader, CsvRecordMapper<T> csvLineMapper,
CsvObjectCallback<T> csvObjectCallback) throws IOException {
try (BufferedReader lineReader = new BufferedReader(csvInputReader);) {
CsvMetadataSource effectiveCsvMetadataSource = getCsvMetadataSource(lineReader);
read(csvLineMapper, csvObjectCallback, lineReader, effectiveCsvMetadataSource);
}
}
private CsvMetadataSource getCsvMetadataSource(BufferedReader lineReader) throws IOException {
CsvMetadataSource effectiveCsvMetadataSource = csvMetadataSource;
if (effectiveCsvMetadataSource == null) {
String headerLine = lineReader.readLine();
effectiveCsvMetadataSource = new RowBasedCsvMetadataSource(headerLine);
}
return effectiveCsvMetadataSource;
}
private <T> void read(CsvRecordMapper<T> csvLineMapper, CsvObjectCallback<T> csvObjectCallback,
BufferedReader lineReader, CsvMetadataSource effectiveCsvMetadataSource) throws IOException {
CsvMetadata effectiveCsvMetadata = effectiveCsvMetadataSource.getCsvMetadata();
if (effectiveCsvMetadata != null) {
String line;
int csvRecordNumber = 0;
while ((line = lineReader.readLine()) != null) {
Map<String, String> csvRecordValues = effectiveCsvMetadata.parseLine(line);
T object = csvLineMapper.map(csvRecordValues, csvRecordNumber++);
csvObjectCallback.process(object);
}
}
}
}
class RowBasedCsvMetadataSource implements CsvMetadataSource {
private String row;
public RowBasedCsvMetadataSource(String row) {
this.row = row;
}
@Override
public CsvMetadata getCsvMetadata() {
String[] columns = row.split(",");
return new CsvMetadata(Arrays.asList(columns));
}
}
class CollectCsvObjectCallback<T> implements CsvObjectCallback<T> {
private List<T> objects = new ArrayList<>();
@Override
public void process(T object) {
objects.add(object);
}
public List<T> getObjects() {
return objects;
}
}
最后,您只需实施CsvRecordMapper
即可轻松调整新的csv文件格式。 E.g。
public class UserCsvRecordMapper implements CsvRecordMapper<User> {
public User map(Map<String, String> csvRecord, int lineNumber) {
String firstname = csvRecord.get("FIRST NAME");
String lastname = csvRecord.get("LAST NAME");
String username = csvRecord.get("USERNAME");
String email = csvRecord.get("EMAIL ADDRESS");
return new User(firstname, lastname, username, email);
}
}
public class User {
private String firstname;
private String lastname;
private String username;
private String email;
public User(String firstname, String lastname, String username, String email) {
this.firstname = firstname;
this.lastname = lastname;
this.username = username;
this.email = email;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
@Override
public String toString() {
return "User [firstname=" + firstname + ", lastname=" + lastname + ", username=" + username + ", email=" + email
+ "]";
}
}
从客户的角度来看,它很容易使用。
CSV
FIRST NAME,LAST NAME,USERNAME,PASSWORD,EMAIL ADDRESS,PHONE NUMBER,PASSPORT,GROUPS,USERCODE,TITLE,ADDRESS 1 ,ADDRESS 2,CITY,STATE,ZIP
Frank,Riley,friley,changeme,friley@kanab.org,123-456-7890,3,"1,3",1040,Teacher,328 Innovation,Suite # 200 ,state college,PA,16803
Steve,Brannigan,sbrannigan,changeme,sbrannigan@kanab.org,123-456-7890,3,1,1041,Teacher,328 Innovation,Suite # 200 ,state college,PA,16803
Marie,Ambrose,mambrose,changeme,mambrose@kanab.org,123-456-7890,3,1,1042,Teacher,328 Innovation,Suite # 200 ,state college,PA,16803
一个简单的主类
public class Main {
public static void main(String[] args) throws IOException {
InputStream csvInputStream = Main.class.getResourceAsStream("example.csv");
InputStreamReader inputStreamReader = new InputStreamReader(csvInputStream);
CsvReader csvReader = new CsvReader();
List<User> users = csvReader.readAll(inputStreamReader, new UserCsvRecordMapper());
for (User user : users) {
System.out.println(user);
}
}
}
结果是
User [firstname=Frank, lastname=Riley, username=friley, email=friley@kanab.org]
User [firstname=Steve, lastname=Brannigan, username=sbrannigan, email=sbrannigan@kanab.org]
User [firstname=Marie, lastname=Ambrose, username=mambrose, email=mambrose@kanab.org]
答案 1 :(得分:0)
这是我非常直截了当的解决方案。
将CSVRecord中的转换器声明为每个所需的实体:
class FirstModel(models.Model):
first_model_item = models.ForeignKey(Item)
quantity = models.IntegerField()
class SecondModel(models.Model):
second_model_item = models.ForeignKey(FirstModel)
quantity = models.IntegerField()
然后使Parser更具普遍性
class CreateSecondModelView(generics.CreateAPIView):
permission_classes = ()
queryset = SecondModel.objects.all()
serializer_class = SecondModeSerializer
def perform_create(self, serializer):
instance = serializer.save()
first_model_item = instance.item
first_model_item.quantity -= instance.quantity
first_model_item.save()
class UpdateSecondModelView(generics.RetrieveUpdateAPIView):
permission_classes = ()
queryset = SecondModel.objects.all()
serializer_class = SecondModeSerializer
def perform_update(self, serializer):
instance = serializer.save()
#here i want old value of quantity before updating.
然后按以下方式使用
class AliasLocusMapper {
public static final String[] FILE_HEADER_MAPPING = {"id", "locusID", "organismid", "variable", "alias"};
private static final String ID = "id";
private static final String LOCUS_ID = "locusID";
private static final String ORGANISM_ID = "organismid";
private static final String VARIABLE = "variable";
private static final String ALIAS = "alias";
public static AliasLocus mapRecord(CSVRecord record) {
return new AliasLocus(Integer.parseInt(record.get(ID)),
record.get(LOCUS_ID),
record.get(ORGANISM_ID),
record.get(VARIABLE),
record.get(ALIAS));
}
}
class ProductMapper { // Product is an example class
public static final String[] FILE_HEADER_MAPPING = {"id", "title", "price"};
private static final String ID = "id";
private static final String TITLE = "title";
private static final String PRICE = "price";
public static Product mapRecord(CSVRecord record) {
return new Product(Integer.parseInt(record.get(ID)),
record.get(TITLE),
record.get(PRICE));
}
}