简体   繁体   中英

How to read multiple lines using FileReader only?

I have the following code:

public class Reader {
    public static void main(String[] args) throws IOException {
        try (FileReader in = new FileReader("D:/test.txt")) {
            // BufferedReader br = new BufferedReader(in);
            int line = in .read();
            for (int i = 0; i < line; i++) {
                //System.out.println(line);

                System.out.println((char) line);
                line = in .read();
            }
        }
    }
}

and a file Test.txt with the content:

 Hello Java 

When I run above code it only reads Hello . I would like to read multiple lines using FileReader only. I don't want to use BufferedReader or InputStreamReader etc. Is that possible?

I don't think this version of the code prints "Hello".

You are calling:

int line = in.read();

What does this do? Look in the Javadocs for Reader :

public int read()

throws IOException

Reads a single character . This method will block until a character is available, an I/O error occurs, or the end of the stream is reached.

(emphasis mine)

Your code reads the 'H' from 'Hello', which is 72 in ASCII.

Then it goes into your loop, with line==72, so it goes into the loop:

  for(int i=0;i<line;i++)

... making the decision "is 0 less than 72? Yes, so I'll go into the loop block".

Then each time it reads a character the value of line changes to another integer, and each time loop goes around i increments. So the loop says "Keep going for as long as the ASCII value of the character is greater than the number of iterations I've counted".

... and each time it goes around, it prints that character on a line of its own.

As it happens, for your input, it reads end-of-file (-1), and as -1 < i , the loop continue condition is not met.

But for longer inputs it stop on the first 'a' after the 97th character, or the first 'b' after the 98th character, and so on (because ASCII 'a' is 97, etc.)

H
e
l
l
o


J
a
v
a

This isn't what you want:

  • You don't want your loop to repeat until i >= "the character I just read". You want it to repeat until in.read() returns -1 . You have probably been taught how to loop until a condition is met.
  • You don't want to println() each character, since that adds newlines you don't want. Use print() .

You should also look at the Reader.read(byte[] buffer) method, and see if you can write the code to work in bigger chunks.


Two patterns you'll use over and over again in your programming career are:

  Type x = getSomehow();
  while(someCondition(x)) {
      doSomethingWith(x);
      x = getSomehow();
  }

... and ...

  Type x = value_of_x_which_meets_condition;
  while(someCondition(x)) {
      x = getSomehow();
      doSomethingWith(x);
  }

See if you can construct something with FileReader and the value you get from it, filling in the "somehows".

You will have to read the content char by char and parse for a new line sequence.

A new line sequence can be any of the following:

  1. a single cariage return '\\r'
  2. a single line feed '\\n'
  3. a carriage return followed by a line feed "\\r\\n"

EDIT

You could try the following:

public List<String> readLinesUsingFileReader(String filename) throws IOException {
    List<String> lines = null;
    try (FileReader fileReader = new FileReader(filename)) {
        lines = readLines(fileReader);
    }
    return lines;
}

private List<String> readLines(FileReader fileReader) throws IOException {
    List<String> lines = new ArrayList<>();
    boolean newLine = false;
    int c, p = 0;
    StringBuilder line = new StringBuilder();
    while(-1 != (c = fileReader.read())) {
        if(c == '\n' && p != '\r') {
            newLine = true;
        } else if(c == '\r') {
            newLine = true;
        } else {
            if(c != '\n' && c != '\r') {
                line.append((char) c);  
            }
        }
        if(newLine) {
            lines.add(line.toString());
            line = new StringBuilder();
            newLine = false;
        }
        p = c;
    }
    if(line.length() > 0) {
        lines.add(line.toString());
    }
    return lines;
}

Note that the code above reads the whole file into a List , this might not be well suited for large files! You may want in such a case to implement an approach which uses streaming, ie read one line at a time, for example String readNextLine(FileReader fileReader) { ... } .

Some basic tests:

Create test files to read

private final static String txt0 = "testnl0.txt";
private final static String txt1 = "testnl1.txt";
private final static String txt2 = "testnl2.txt";

@BeforeClass
public static void genTestFile() throws IOException {
    try (OutputStream os = new FileOutputStream(txt0)) {
        os0.write((
            "Hello\n" +
            ",\r\n" +
            "World!" +
            "").getBytes());
    }

    try (OutputStream os = new FileOutputStream(txt1)) {
        os.write((
            "\n" +
            "\r\r" +
            "\r\n" +
            "").getBytes());
    }

    try (OutputStream os = new FileOutputStream(txt2)) {
        os.write(( 
            "").getBytes());
    }
}

Test using the created files

@Test
public void readLinesUsingFileReader0() throws IOException {
    List<String> lines = readLinesUsingFileReader(txt0);
    Assert.assertEquals(3, lines.size());
    Assert.assertEquals("Hello", lines.get(0));
    Assert.assertEquals(",", lines.get(1));
    Assert.assertEquals("World!", lines.get(2));
}

@Test
public void readLinesUsingFileReader1() throws IOException {
    List<String> lines = readLinesUsingFileReader(txt1);
    Assert.assertEquals(4, lines.size());
    Assert.assertEquals("", lines.get(0));
    Assert.assertEquals("", lines.get(1));
    Assert.assertEquals("", lines.get(2));
    Assert.assertEquals("", lines.get(3));
}

@Test
public void readLinesUsingFileReader2() throws IOException {
    List<String> lines = readLinesUsingFileReader(txt2);
    Assert.assertTrue(lines.isEmpty());
}

Reading file character by character without any buffering stream is extremely ineffective. I would probably wrap FileReader in some BufferedReader or simply used Scanner to read condent of file, but if you absolutely want/need/have to use only FileReader then you can try with

int line = in.read();
while (line != -1) {
    System.out.print((char) line);
    line = in.read();
}

instead of your for (int i = 0; i < line; i++) {...} loop.

Read carefully slims answer . In short: reading condition shouldn't care if number of characters you read is less then numeric representation of currently read character ( i < line ). Like in case of

My name

is

not important now

This file has few characters which you normally will not see like \\r and \\n and in reality it looks like

My name\r\n 
\r\n 
is\r\n 
\r\n 
not important now

where numeric representation of \\r is 10 , so after you read My name\\r\\n (which is 9 characters because \\r and \\n are single character representing line separator) your i will become 10 and since next character you will try to read is \\r which is also represented by 10 your condition i<line will fail ( 10<10 is not true).

So instead of checking i<line you should check if read value is not EoF (End of File, or End of Stream in out case) which is represented by -1 as specified in read method documentation so your condition should look like line != -1 . And because you don't need i just use while loop here.

Returns:

The character read, or -1 if the end of the stream has been reached

If you have the new line character

    public static void main(String[]args) throws IOException{
        FileReader in = new FileReader("D:/test.txt");
        char [] a = new char[50];
        in.read(a); // reads the content to the array
        for(char c : a)
            System.out.print(c); //prints the characters one by one
        in.close();
    }

It will print

Hello 
Java

Reader.read() returns int code of single char or -1 if end of the file is reached:

http://docs.oracle.com/javase/7/docs/api/java/io/Reader.html#read()

So, read the file char by char and check LF (Line feed, '\\n', 0x0A, 10 in decimal), CR (Carriage return, '\\r', 0x0D, 13 in decimal)and end-of-line codes.

Note: Windows OS uses 2 chars to encode the end of line: "\\r\\n". The most of others including Linux, MacOS, etc. use only "\\n" to encode the end of line.

    final StringBuilder line = new StringBuilder(); // line buffer

    try (FileReader in = new FileReader("D:/test.txt")) {            
        int chAr, prevChar = 0x0A; // chAr - just read char, prevChar - previously read char
        while (prevChar != -1) { // until the last read char is EOF
            chAr = in.read(); // read int code of the next char
            switch (chAr) {
                case 0x0D: // CR - just
                    break; // skip
                case -1:   // EOF
                    if (prevChar == 0x0A) {
                        break; // no need a new line if EOF goes right after LF
                               // or no any chars were read before (prevChar isn't 
                               // changed from its initial 0x0A)
                    }
                case 0x0A: // or LF
                    System.out.println("line:" + line.toString()); // get string from the line buffer
                    line.setLength(0); // cleanup the line buffer
                    break;
                default: // if any other char code is read
                    line.append((char) chAr); // append to the line buffer
            }
            prevChar = chAr; // remember the current char as previous one for the next iteration
        }
    }

I solved the above problem by using this code

     public class Reader 
      {
    public static void main(String[]args) throws IOException{
         try (FileReader in = new FileReader("D:/test.txt")) {
         int line = in.read();

         while(line!=-1)
         {
           System.out.print((char)line);
              line = in.read();
          }   }
     }
    }

But there is one more question if I write for loop instead of while like this

for(int i=0;i<line;i++)

It prints only first line.Could anybody tell me why?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM