Reading Structured File in Java - java

I have written code for reading a structured file in Java, and extracting the line contents to create objects (of type Person).
The file has the following structure: [personNumber], [personName], [personAge], for example
0, Sherlock Holmes, 44
1, Harry Potter, 17
2, Jack Sparrow, 50
...
The code for reading the file and extracting the information is:
RandomAccessFile rf = new RandomAccessFile(fileName, "rw");
String line;
File myFile = new File(fileName);
scan = new Scanner(myFile);
String[] split;
//Read file
while((line = rf.readLine()) != null) {
split = scan.nextLine().split(", ");
pnumber = Integer.parseInt(split[0]);
name = split[1];
age = Integer.parseInt(split[2]);
thePerson = new Person(name, age, pnumber);
personList.addLast(thePerson);
}
This works well, and the Person-objects are correctly added to a singly linked list that I have programmed. No problems here.
However, the program is also supposed to be able to read a text file of the following format:
#People
0, Sherlock Holmes, 44
1, Harry Potter, 17
2, Jack Sparrow, 50
#Dogs
0, Scooby, 10
1, Milou, 7
2, Scar, 15
Is there a way to check if the line being read contains a hash (#) symbol, in which case the program understands that it is not to split this line, but simply that a new category is starting? So that you can decide which type of object you want to create from following lines, for instance person og dog objects. For simplicity, the order of the types (Person, Dog) will always be the same, but the number of lines following each category will vary. Hence, I need help with figuring out if a line contains a #-symbol, indicating the start of a new category, and only create objects from the following lines.

or if your # is always at the beginning of the string:
EDIT:
while((line = rf.readLine()) != null) {
if(line.charAt(0)=='#'){
scan.nextLine(); //use it to suit your need..
//alert the program to prepare for new type of object
//do something..
}else {
split = scan.nextLine().split(", ");
pnumber = Integer.parseInt(split[0]);
name = split[1];
age = Integer.parseInt(split[2]);
thePerson = new Person(name, age, pnumber);
personList.addLast(thePerson);
}
}

As this looks like homework, I will only answer your first question and let you figure out how to use it in your program. The way to check if the line you read contains a hash symbol is this:
line = scan.nextLine();
if(line.contains('#')){
// the line had a #
}
else{
//it did not.
}

You can do this:
String line = null;
String currentType = null;
while((line = rf.readLine()) != null) {
if (line.startsWith("#")) {
currentType = line.substring(1);
continue;
}
split = line.split(",");
if (currentType.equals("People")) {
pnumber = Integer.parseInt(split[0]);
name = split[1];
age = Integer.parseInt(split[2]);
thePerson = new Person(name, age, pnumber);
personList.addLast(thePerson);
} else if (currentType.equals("Dogs")) {
...
Dog dog = new Dog(name,age);
}
}

First assign readed line to a variable:
String line = scan.nextLine();
Then apply logic to its value:
if (line.startsWith("#")) {
// check which type of object is declared
} else {
// parse line according to object type
// add object to list
}
Also I recommend using separate parser for separate type of objects. I.e. DogLineParser, PersonLineParser, etc. all of which would implement common interface. Example interface could look like this
public interface LineParser<T> {
T parseLine(String line);
}
And example implementation could look like this:
public class PersonLineParser extends LineParser<Person> {
#Override
public Person parseLine(String line) {
String[] split = line.split(", ");
pnumber = Integer.parseInt(split[0]);
name = split[1];
age = Integer.parseInt(split[2]);
return new Person(name, age, pnumber);
}
}

while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if(line.startsWith('#')) {
// don't split the
}
else {
// you can split
}
}
scanner.close();

Related

How to read data from txt and save into array every 2 lines and remove the header

I want to read the data from text, then I will remove the header of the text and save the data into and array every 2 line, cause it still continues data.
visitor.txt
1 DAILY REPORT VISITOR
DATE : 02-02-22
0+------------------------------------------------------------------+
NO. DATE NAME ADDRESS
PHONE BIRTHDAY NEED
+------------------------------------------------------------------+
1 02-02-22 ELIZABETH ZEE WASHINGTON DC
+32 62 18-10-1985 BORROW BOOK
2 02-02-22 VICTORIA GEA BRUSEELS
+32 64 24-05-1986 VISITOR
3 02-02-22 GEORGE PHILIPS BRUSEELS
+32 76 02-05-1990 VISITOR
I want the data that save into an array like this.
1 02-02-22 ELIZABETH ZEE WASHINGTON DC +32 62 18-10-1985 BORROW BOOK
2 02-02-22 VICTORIA GEA BRUSEELS +32 64 24-05-1986 VISITOR
3 02-02-22 GEORGE PHILIPS BRUSEELS +32 76 02-05-1990 VISITOR
This is the code
BufferedReader bR = new BufferedReader(new FileReader(myfile));
int i =0;
String line;
try {
while (line = bufferedReader.readLine()) != null) {
i++;
String data = line.split("\\s", "")
if(data.matches("[0-9]{1,3}\\s.+")) {
String[] dataArray = data.split("\\s", -1);
String[] result = new String[30];
System.arraycopy(fileArray, 0, result, 0, fileArray.length);
String data1 = line.get(i).split("\\s", "")
String[] fileArray1 = data.split("\\s", -1);
String[] result1 = new String[30];
System.arraycopy(fileArray1, 0, result1,0,fileArray1.length);
}
}
The problem here is, I think this code is not effective cause it will be read the second line twice from data and data1. I want every 2 lines will save into one row in the database like the result of text. Do you have any solution?
It seems unlikely for me that one line would be read multiple times. Try to debug your code to see if that actually happens.
Otherwise, you could really skip the first line before starting processing:
BufferedReader bR = new BufferedReader(new FileReader(myfile));
int i =0;
String line;
try {
// alternative one
String firstline = bufferedReader.readLine();
String secondline = bufferedReader.readLine();
String mergedline = firstline + secondline; // the linefeed should have been removed but the data is retained
// alternative two
StringBuilder sb = new StringBuilder();
sb.append(bufferedReader.readLine()); // first line
sb.append(bufferedReader.readLine()); // second line
... = sb.toString(); // now do something with the merged lines
// the other stuff
while (line = bufferedReader.readLine()) != null) {
// process your data lines here
}
}
The result has actually a dynamic number of records. Then a fixed size array
no longer is suitable. Use List<String\[\]> instead: list.add(stringArray), list.get(i), list.size(), list.isEmpty().
The header seems to consist of 2 lines, but I may err.
I saw fields with a space, hence one cannot split on \s+ (one or more whitespace characters). I did split on \s\s+. Maybe you should better use the fixed length field boundaries with line1.substring(i1, i2).
FileReader uses the encoding on your current computer (=unportable file). I have made it explicit. If it always an US-ASCII file, without special characters, you could use StandardCharsets.US_ASCII. Then you can run the software on a Linux server, that normally uses UTF-8.
So without check of data format (which however makes sense):
private void stackOverflow() throws IOException {
List<String[]> data = loadData("mydata.txt");
System.out.println(data.size() + " records read");
for (String[] fields: data) {
System.out.println(Arrays.toString(fields));
}
}
private List<String[]> loadData(String myFile) throws IOException {
List<String[]> data = new ArrayList<>();
Path path = Paths.get(myFile);
try (BufferedReader bufferedReader =
Files.newBufferedReader(path, Charset.defaultCharset())) {
if (bufferedReader.readLine() != null
&& bufferedReader.readLine() != null) { // Skip both header lines.
String line1, line2;
while ((line1 = bufferedReader.readLine()) != null
&& (line2 = bufferedReader.readLine()) != null) {
String[] fields1 = line1.split("\\s\\s+", 4); // Split on at least 2 spaces.
if (fields1.length != 4) {
throw new IOException("Wrong number of fields for first line: " + line1);
}
String[] fields2 = line2.split("\\s\\s+", 3); // Split on at least 2 spaces.
if (fields1.length != 3) {
throw new IOException("Wrong number of fields for second line: " + line2);
}
String[] total = Arrays.copyOf(fields1, 7);
System.arraycopy(fields2, 0, total, 4, fields2.length);
;
data.add(total);
}
if (line1 != null && !line1.isBlank()) {
throw new IOException("Trailing single line: " + line1);
}
}
}
return data;
}
Substring is better, safer, than split.
Instead of String[] you might use record class (since java 14)
record Visitor(String no, String date, String name, String address,
String phone, String birthday, String need) { }
List<Visitor> data = new ArrayList<>();
data.add(new Visitor(fields1[0], fields1[1], fields1[2], fields1[3],
fields2[0], fields2[1], fields2[2]);
A record need little code, however cannot be changed, only replaced in the list.

java read from file. the last row overwrites the values of the previous lines in list

I have a file name 1.txt
Coupe 1 2
Coupe 3 4
and I have a code
br = new BufferedReader(new FileReader("1.txt"));
String tmp = "";
while ((tmp = br.readLine()) != null) {
String[] s = tmp.split("\\s");
comfortName = s[0];
tickets = Integer.parseInt(br.readLine());
baggage = Integer.parseInt(br.readLine());
// next code send to constructor of class from value of comfort name a parametrs.
Class[] e = new Class[]{Integer.TYPE,Integer.TYPE};
comfortName = "second.objects.carriages.type." + comfortName;
Class carriageClass = Class.forName(comfortName); Constructor constructor = carriageClass.getDeclaredConstructor(e);
passenger = (Passenger) constructor.newInstance((tickets),(baggage));
// the next line add to list a value from constructor
carriage.addPassenger(passenger);
add passenger code:
public boolean addPassenger(Passenger passenger) {
totalPassenger +=Passenger.getTickets();
totalBaggage+=Passenger.getBaggage();
return Carriage.getPassengerList() .add(passenger);
}
So the result when I send it to list have something like that:
Coupe 3 4
Coupe 3 4
But from debugger I see that values reading good. but always the last row
overwrites the values ​​of the previous lines in list .
So when I send only one row it's working
You have a typo in your code
I believe you should get tickets and baggage from the splitted array
tickets = Integer.parseInt(s[1]);
baggage = Integer.parseInt(s[2]);
instead of reading next lines, below will throw exception
tickets = Integer.parseInt(br.readLine());
baggage = Integer.parseInt(br.readLine());
The java.lang.Class.newInstance() creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized.
This sample code is working fine for me.
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new FileReader(System.getProperty("user.dir") + "/src/1.txt"));
String tmp = "";
while ((tmp = br.readLine()) != null) {
String[] s = tmp.split("\\s+");
String comfortName = s[0];
int tickets = Integer.parseInt(s[1]);
int baggage = Integer.parseInt(s[2]);
System.out.println(comfortName + tickets + baggage);
}
br.close();
}
on console :
Coupe12
Coupe34
Check your Passenger constructor that you create instance.
passenger = (Passenger) constructor.newInstance((tickets),(baggage));
The problem is at you constructor. If you constructor create new instance of Passenger object it will be ok. You constructor should be like that:
private Passenger(int tickets, int baggage){
this.tickets = tickets;
this.baggage = baggage;
}

Read mixed types of data from a file with Java

I am a bit stuck with this code. I have file file name.txt which contains following data:
BD1 // user ID
Bob Dillon // user full name
user#email.com // user Email
10.0 // amount of cash
100 // No.of Points
I can't read first and last name of user in the same string. Here is my code:
Scanner input_File = new Scanner(new File("customer.txt"));
int num_Customers = 0;
while(input_File.hasNext() && num_Customers < maxLoyalty_CardsQty)
{
//read ID
customerID[num_Customers] = input_File.next();
input_File.nextLine();
//Here is my problems begins
while(input_File.hasNextLine())
{
String line = input_File.nextLine();
Scanner line_Scan = new Scanner(line);
line_Scan.useDelimiter(" ");
fName[num_Customers] = input_File.next();
lName[num_Customers] = input_File.next();
line_Scan.close();
}
//read Email
email[num_Customers] = input_File.next();
//read Cash
spendToDate[num_Customers] = input_File.nextDouble();
//read Points
points[num_Customers] = input_File.nextInt();
num_Customers++;
}
input_File.close();
Consider using different data layout in the file. It's easier if you have all the required data in a single line, e.g. comma separated especially if you have information about multiple users (and I guess that's the case)
FS1, FirstName, LastName, foo#bar.baz, 10.0, 100
Then you can go with something like this
Scanner scanner = new Scanner(new File("customer.txt"));
while (scanner.hasNext()) {
String[] splitted = scanner.nextLine().split(", ");
// then you've data in a nice array arranged like below
// splitted[0] = user Id
// splitted[1] = first name
// etc.
fName[numCustomers] = splitted[1];
// ...
spendToDate[numCustomers] = splitted[3];
}
Or you can use Java 8 to simplify this to:
new Scanner(new File("customer.txt"))
.forEachRemaining(line -> {
String[] splitted = line.split(", ");
// etc.
});
or to:
Files.lines(Paths.get("customer.txt"))
.forEach(line -> {
String[] splitted = line.split(", ");
// splitted[0] = user Id
// splitted[1] = user name and surname
// etc.
});
Also, a friendly advice, make yourself acquainted with Java's naming conventions it'd make your code much cleaner and easier to read.

Parsing various values in Textfile (Java)

I have a textfile as such:
type = "Movie"
year = 2014
Producer = "John"
title = "The Movie"
type = "Magazine"
year = 2013
Writer = "Alfred"
title = "The Magazine"
What I'm trying to do is, first, search the file for the type, in this case "Movie" or "Magazine".
If it's a Movie, store all the values below it, i.e
Set the movie variable to be 2014, Producer to be "John" etc.
If it's a Magazine type, store all the variables below it as well separately.
What I have so far is this:
public static void Parse(String inPath) {
String value;
try {
Scanner sc = new Scanner(new FileInputStream("resources/input.txt"));
while(sc.hasNextLine()) {
String line = sc.nextLine();
if(line.startsWith("type")) {
value = line.substring(8-line.length()-1);
System.out.println(value);
}
}
} catch (FileNotFoundException ex) {
Logger.getLogger(LibrarySearch.class.getName()).log(Level.SEVERE, null, ex);
}
}
However, I'm already having an issue in simply printing out the first type, which is "Movie". My program seems to skip that one, and print out "Magazine" instead.
For this problem solely, is it because the line: line.startsWith("type")is checking if the current line in the file starts with type, but since the actual String called lineis set to the nextline, it skips the first "type"?
Also, what would be the best approach to parsing the actual values (right side of equal sign) below the type "Movie" and "Magazine" respectively?
I recommend you try the following:
BufferedReader reader = new BufferedReader(new FileReader(new File("resources/input.txt")));
String line;
while((line = reader.readLine()) != null) {
if (line.contains("=")) {
String[] bits = line.split("=");
String name = bits[0].trim();
String value = bits[1].trim();
if (name.equals("type")) {
// Make a new object
} else if (name.equals("year")) {
// Store in the current object
}
} else {
// It's a new line, so you should make a new object to store stuff in.
}
}
In your code, the substring looks suspect to me. If you do a split based on the equals sign, then that should be much more resilient.

Find a specific line in a file, write that line and the 2 after to a new file

I need to search for a specific line in a .txt file like someones name and then write the name and the next 2 lines after which is data about the person to a new file.
How it should work:
I enter a menu which lists employees taken from an arraylist and asks for my input for who I want a "report" for. I enter "John Doe" and the program creates a "report" called "JDoe.txt" and searches the arraylist for "John Doe" and writes his name in the new file along with his information (the next 2 lines after his name in the same file).
My code is creating the "report" and is writing data to it, but it just writing the data of what is first in the arraylist and not specifically the user who I entered. How do I write for the specific user I inputted?
Here is some of the code I have which is in the right direction but isn't producing what I want and I can't seem to figure out a fix:
import java.io.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
import javax.swing.JOptionPane;
public class Report { // FirstName LastName, Programmer
public Report() {
// code here the logic to create a report for the user
try {
String empList = "";
ArrayList<String> emps = new ArrayList<>();
String[] firstLine = new String[100], secondLine = new String[100], thirdLine = new String[100];
int index;
FileReader file = new FileReader("payroll.txt");
BufferedReader buffer = new BufferedReader(file);
String line;
for (index = 0; index < 100; index++) {
firstLine[index] = "";
secondLine[index] = "";
thirdLine[index] = "";
}
index = 0;
while ((line = buffer.readLine()) != null) {
firstLine[index] = line;
secondLine[index] = buffer.readLine();
thirdLine[index] = buffer.readLine();
emps.add(firstLine[index]);
index++;
}
buffer.close();
Collections.sort(emps);
for (String str : emps) {
empList += str + "\n";
}
String input = JOptionPane.showInputDialog(null, empList,
"Employee List", JOptionPane.PLAIN_MESSAGE);
index = 0;
// Iterate through the array containing names of employees
// Check if a match is found with the input got from the user.
// Break from the loop once you encounter the match.
// Your index will now point to the data of the matched name
if (emps.contains(input)) {
JOptionPane.showMessageDialog(null, "Report Generated.",
"Result", JOptionPane.PLAIN_MESSAGE);
String names[] = new String[2];
names = input.split(" ");
String fileName = names[0].charAt(0) + names[1] + ".txt";
// Create a FileWritter object with the filename variable as the
// name of the file.
// Write the necessary data into the text files from the arrays
// that
// hold the employee data.
// Since the index is already pointing to the matched name, it
// will
// also point to the data of the matched employee.
// Just use the index on the appropriate arrays.
File check1 = new File(fileName);
FileWriter file2;
if (check1.exists())
file2 = new FileWriter(fileName, true);
else
file2 = new FileWriter(fileName);
BufferedWriter buffer2 = new BufferedWriter(file2);
buffer2.write("Name: " + firstLine[index]);
buffer2.newLine();
buffer2.write("Hours: " + secondLine[index]);
buffer2.newLine();
buffer2.write("Wage: " + thirdLine[index]);
buffer2.newLine();
buffer2.close();
} else {
JOptionPane.showMessageDialog(null, input + " does not exist");
Report rpt = new Report();
}
} catch (IOException e) {
System.out.println(e);
}
}
public static void main(String[] args) {
new Report();
}
}
What the file it is reading from looks like:
There were two problems I found. The first was that after you when through and built your list of names, you were setting index = 0, and it never got changed.
for (String str : emps) {
empList += str + "\n";
}
//Make the JOptionPane...
index = 0;
This meant you were always getting the first value. If you set index to 1, you would get the second value. You can use the method indexOf(Object o) from the ArrayList library to get the correct index given the name that the user input. Example:
int i = emps.indexOf(input);
buffer2.write("Name: " + firstline[i]);
However, this would only partly solve the problem, as you sorted the list of employee names in emps, but not in the arrays you are using to write.
One solution to this is to not sort the emps array, which gives you the correct ordering to indexOf to work properly, but the employees listed in the JOptionPane will be listed in the order they were in the file.
However, since the values given for each employee are tied to that employee, I would suggest using a data structure that ties those values to the employee name, such as a Map. (Documentation is here: http://docs.oracle.com/javase/7/docs/api/java/util/Map.html ).
To initialize it, you can use it like this. I'm using a linkedHashMap because it came to my mind, you can select the proper type for your needs.
LinkedHashMap myMap<String, String[]> = new LinkedHashMap<String, String[]>();
The first field, String is your Key, which will be your employees name. The Value is the String[], and can be an array with the required two values. This ties these values to the name, ensuring they cannot get lost. To check for a name, just use myMap.containsKey(name), and compare to null to see if it exists, and then use myMap.get(name) to get the entry for that name.

Categories