On Wed, 9 Sep 2026 13:30:25 GMT, Jaikiran Pai <[email protected]> wrote:

>> Thank you for your experiments, Jaikiran! Suspending myself until you're 
>> done.
>
> Hello Markus, I've run the experiments that I had in mind. I am a bit 
> preoccupied with a few other things and will need some more days to make to a 
> conclusion of those experiments.

I have gone through the usages of `InputStreamReader` and very specifically 
sub-classes of `InputStreamReader` which override the `read` methods. The 
`InputStreamReader` class and its read() methods (which take different 
parameters) have been around for several decades. In the experiment results, I 
see that there aren't too many sub-classes of this class that override these 
read methods. However, there are some library classes which override these 
methods and have some non-trivial code in those implementations. For example, 
in some cases, after calling `super.read(...)` these overridden methods do 
validations against the decoded characters to either report errors (for some 
specific characters) or to replace the read character with some pre-defined 
replacement character.

In the current implementation of `InputStreamReader` in JDK mainline, it is 
guaranteed that any read operation, be it through the read(...) methods or 
`readAllAsString()` or `readAllLines()` or even `skip()`, the sub-class' 
read(...) method will be invoked and it will have a chance to run the 
library/application specific code. However, with the proposed change in this PR 
we now have a situation that the `readAllAsString()` implementation may no 
longer call any of the read(...) methods of the sub-class and instead would 
directly return a `String` constructed out of the decoded characters. 

`readAllAsString()` is a newly introduced method (in Java 25) and it's very 
likely that no sub-class of `InputStreamReader` overrides it currently (in 
fact, in the usage search I ran, I found no such override which isn't a 
surprise). What that then means is that, for such sub-class of 
`InputStreamReader` constructed using a `Charset` with the new proposed 
implementation, it's now possible for application code to see a different 
string value against the same underlying byte stream depending on whether the 
code calls `readAllAsString()` or some other read method.

For example, consider this code:


import java.io.*;

public class Bar {

    private static final String INPUT = "hello,world";

    public static void main(final String[] args) throws IOException {
        final String expected = "hello/world"; // we expect the SomeReader to 
replace "," with "/"
        // readAllLines to read the underlying stream and decode the bytes to 
characters
        final String firstLine = 
newInputStreamReader().readAllLines().getFirst();
        if (!expected.equals(firstLine)) {
            throw new AssertionError("FAILED: unexpected result from 
readAllLines: " + firstLine + " expected: " + expected);
        }
        System.out.println("PASSED: readAllLines() returned " + firstLine);
        // readAllAsString to read the underlying stream and decode the bytes 
to characters
        final String allAsString = newInputStreamReader().readAllAsString();
        if (!expected.equals(allAsString)) {
            throw new AssertionError("FAILED: unexpected result from 
readAllAsString: " + allAsString + " expected: " + expected);
        }
        System.out.println("PASSED: readAllAsString() returned " + allAsString);
    }

    private static Reader newInputStreamReader() {
        return new SomeReader(new ByteArrayInputStream(INPUT.getBytes()));
    }

    private static final class SomeReader extends InputStreamReader {

        private SomeReader(InputStream in) {
            super(in);
        }
    
        @Override
        public int read(char[] cbuf, int off, int len) throws IOException {
            int n  = super.read(cbuf, off, len);
            if (n == -1) {
                return -1;
            }
            for (int i = 0; i < n; i++) {
                if (cbuf[off + i] == ',') {
                    cbuf[off + i] = '/';
                }
            }
            return n;
        }
    }

}

What this tries to demonstrate is that depending on which API of Reader gets 
used by the application, the same underlying byte stream would now generate a 
different sequence of characters for such sub-classes. When run against the 
proposed PR, the application fails due to unexpected output from the 
readAllAsString() method.

Given this, I think skipping the call to the read(...) method of the 
`InputStreamReader` sub-class only from the readAllAsString() would introduce 
too big an inconsistency. Perhaps we should just add this proposed optimization 
only to instances which are of exact `InputStreamReader` type? So something 
like this (untested) change in `InputStreamReader`:


@Override
public String readAllAsString() throws IOException {
    // optimized implementation for InputStreamReader to
    // try and avoid unnecessary buffer copies in super.readAllAsString()
    if (this.getClass() == InputStreamReader.class) {
        String s = sd.tryReadAllAsString();
        return (s != null) ? s : super.readAllAsString();
    }
    return super.readAllAsString();
}

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/32264#discussion_r4029014544

Reply via email to