IOUtils rework - #1865
Merged
Merged
IOUtils rework#1865
Conversation
maltek
approved these changes
Sep 23, 2026
maltek
left a comment
Contributor
There was a problem hiding this comment.
if you are motivated, you could also try these ideas (if not just merge):
contentFromBufferedSource- use
scala.collection.immutable.ArraySeq.newBuilderinstead ofList.newBuilder - replace with
Files.readAllLines(path, charset).asScalaand change the return type toscala.collection.Seq- not sure how invasive this is
- use
readEntireFile:Files.readString(path, charset).stripPrefix("\UFEFF")- should avoid keeping the whole file in memory twice during decoding
Contributor
Author
|
Good ideas, however:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A few clean-ups. No public API changes.
readEntireFile: single-pass readingStringBuilder→String) withFiles.readAllBytesfollowed by a singlenew String(bytes, offset, length, UTF_8)decode.Stringconstructor is documented to always replace malformed and unmappable input with the default replacement character, matching the previous explicitCharsetDecoderconfiguration. This also removes one use of a per-callCharsetDecoder(decoders are not thread-safe).readLinesInFile: simpler line splittingBufferedReader.lines()(Java Stream) →asScalaiterator adapter →toSeqpipeline with a plain
readLine()loop into a list builder. Same semantics.skipBOMIfPresent: cheaper and more correct BOM skippingSet[Char]lookup;a single
read()compared againstU+FEFFis sufficient.'\uefbb'and'\ufffe'entries from the BOM set. With a UTF-8 decoder these can never result from an actual BOM (a UTF-16 file decoded as UTF-8 yieldsU+FFFDreplacement chars, not these code points). They could only match genuinely encoded characters, in which case the old code silently stripped a legitimate first character. This is a bug fix.Docs/cleanup
Do not longer remove surrogates #1804) and an unused import.
Behavior changes
U+EFBB/U+FFFEthat is genuinely encoded in the file is now preserved instead of being stripped. Real BOMs (which decode toU+FEFF, e.g. the UTF-8 BOMEF BB BF) are still skipped, now consistently in both methods.Benchmark results
Corpus: 74,502 real source files, 412 MB (C 3,121 / Kotlin 46 / JS 43,549 / Swift 27,786), page-cache warm, 3 measured passes after warmup, alternating order.
Reading of all files produced byte-identical output between old and new implementation.
The
readEntireFilerewrite is an allocation win: 4.4x less garbage (no per-1KiB char arrays, no StringBuilder double-copy), but only ~4-13% faster. Pays off in the real use case:ForkJoinParallelCpgPasswith N threads doingreadEntireFileconcurrently. 4.4x less allocation rate means proportionally less GC pressure and allocation contention during parallel AST creation.readLinesInFileis performance-neutral. That change is simplicity only, not speed.