File Handling & XNews
Java I/O streams, FileReader/FileWriter, BufferedReader, the File class, reading & writing text files, and the XNews file-processing project.
After this you should be able to
- Explain the core idea in File Handling & XNews
- Apply File Handling & XNews in a representative exercise
My study notes on reading/writing files with java.nio.file.Files, working with CSV,
append modes & open options, and the foundations for building XNews (a personalized news fetcher using
Jackson + OkHttp + NewsAPI).
Notebook Summary — copy by hand#
Telegraphic, high-density. Each block is sized to transcribe fast. Tables for comparisons, "doodle:" lines for sketches. Everything here is the compressed form of Section II.
FILE HANDLING (java.nio.file.Files) why files? → data survives app close / power off (persistence) examples → media player reads video, spreadsheet opens CSV, app logs Path → Paths.get("output.txt") // points at a file, no I/O yet Files METHODS (simple → complex) readAllBytes(path) → byte[] all bytes readAllLines(path) → List<String> all lines (UTF-8 default) write(path, byte[]) → write bytes (default: CREATE+WRITE+TRUNCATE) write(path, lines) → write text lines newBufferedReader(p) → BufferedReader efficient line reads newBufferedWriter(p) → BufferedWriter efficient text writes newInputStream(p) → InputStream read raw bytes (stream) newOutputStream(p) → OutputStream write raw bytes (stream) newByteChannel(p) → SeekableByteChannel random access (seek) rule of thumb: small text → readAllLines / write big or streaming → Buffered* / *Stream / channel doodle: ruler L→R: [readAllLines]──[Buffered]──[Stream]──[Channel] left=easy/whole-file, right=powerful/streaming
APPENDING TO A FILE append → newBufferedWriter(path, StandardOpenOption.APPEND) if file missing → NoSuchFileException fix → add CREATE: newBufferedWriter(path, CREATE, APPEND) // create if absent, then append StandardOpenOption (key flags)
| Option | Effect |
|---|---|
APPEND | write at end of existing file |
CREATE | create if not present (keep if present) |
CREATE_NEW | create, FAIL if already exists |
TRUNCATE_EXISTING | wipe contents to 0 then write |
WRITE | open for writing |
defaults: write()/newBufferedWriter() w/ NO option = CREATE + WRITE + TRUNCATE → OVERWRITES! doodle: file box; APPEND = pen adds at bottom; TRUNCATE = eraser clears box first
ACT#1: count Lines / Words / Chars / Paragraphs → output.txt read file → loop lines lines++ per line read words+= line.split("\\s+").length (skip empty) chars+= line.length() (+1 per newline if counting them) paragraphs++ on blank-line separator (blank line = para break) out format: Total Lines Count - N Total Words Count - N Total Characters Count - N Total Paragraphs Count - N para def: block of text separated by ≥1 empty line
CSV = Comma-Separated Values (tabular data as plain text) 1 line = 1 record fields separated by commas field WITH comma → wrap in "double quotes" " INSIDE a value → write it twice "" (escape) e.g. She said ""hi"" → "She said ""hi""" doodle: grid table → flatten into rows of a,b,c (spreadsheet → text lines) ACT#2: total price per item IN : ITEM,DESCRIPTION,PRICE,QUANTITY OUT: ITEM,DESCRIPTION,TOTAL_PRICE TOTAL_PRICE = PRICE × QUANTITY steps: read lines → skip header → split "," → compute → write new CSV naive split(",") breaks on quoted commas — fine for simple data
| Need | Read | Write |
|---|---|---|
| whole small text | readAllLines | write(p,lines) |
| raw bytes (img/zip) | readAllBytes | write(p,bytes) |
| large / line-by-line | newBufferedReader | newBufferedWriter |
| stream of bytes | newInputStream | newOutputStream |
| random access | newByteChannel | |
XNEWS = serve curated news by user prefs (topic, language…) FLOW: prefs.json ──read(File I/O)──> Jackson parse ──> build request │ ▼ OkHttp GET → NewsAPI ──> JSON response │ ▼ Jackson deserialize → Article objects → pretty print to user doodle: prefs.json → [Jackson] → [OkHttp]──HTTP──>(NewsAPI) → [Jackson] → printed articles TOOLS: File I/O → read prefs.json Jackson → JSON ⇄ Java objects (parse + deserialize) OkHttp → make REST API call (HTTP GET) NewsAPI → external news source (returns JSON) serialize = Java obj → JSON text deserialize = JSON text → Java obj
Full explanations + runnable code: jump to Detailed Notes →
Detailed Study Notes#
One numbered concept per subsection. + marks anything I added or corrected beyond the core material — enrichment, fixes, and "good to know" extras.
1. Why files exist: persistence#
A note-taking app remembers what I typed even after I close it and power off the phone. That only works because the text was written to a file on disk, not just held in RAM. RAM (variables, objects) is wiped the moment the process ends or power is lost; a file on storage stays put until something deletes it. This idea is called persistence.
Everyday file I/O (input/output) is everywhere: a media player reading a video file, a spreadsheet program opening a CSV, an application writing log lines as it runs. + Other common cases: saving game progress, app settings/config files, caching downloaded data, exporting reports, and databases (which are ultimately structured files on disk).
2. Path and Paths.get — naming a file before touching it#
Path is just a typed reference to a location; creating one does no
disk I/O.+ Before reading or writing, I describe where the file is using Paths.get("output.txt"), which returns a Path object. This
is purely a label — the file need not exist yet, and nothing is opened. The actual reading/writing happens
when I hand that Path to a Files method. Think of Path as writing an address on an envelope; Files methods are
the postal service that actually delivers.
+ On modern Java you'll also see Path.of("output.txt") —
it's the newer equivalent of Paths.get(...) (added in Java 11) and is preferred
going forward. Both produce the same kind of object.
3. The Files method family, simple → complex#
java.nio.file.Files is a utility class of static helpers; pick the
one matching your data size and access pattern.These methods sit on a spectrum from "easiest, loads everything" to "most powerful, most control":
| Method | What it does | Returns |
|---|---|---|
readAllBytes | Read every byte of a file at once | byte[] |
readAllLines | Read every line of text at once | List<String> |
write (bytes) | Write a byte array to a file | Path |
write (lines) | Write lines of text to a file | Path |
newBufferedReader | Open file → efficient text reader | BufferedReader |
newBufferedWriter | Open/create file → efficient text writer | BufferedWriter |
newInputStream | Open file → byte input stream | InputStream |
newOutputStream | Open/create file → byte output stream | OutputStream |
newByteChannel | Open/create → seekable channel (random access) | SeekableByteChannel |
The split that matters: readAll* and the line/byte write overloads load the whole file into memory in one shot — dead simple,
perfect for small files. The Buffered* and *Stream/Channel variants process data incrementally, so memory stays low even for huge
files.
+ "Buffered" means the reader/writer keeps a small in-memory chunk (a buffer) so it talks to the slow disk in big gulps instead of one character at a time — far fewer system calls, much faster. Analogy: carrying water with a bucket (buffer) instead of one cupped handful per trip.
+ Bytes vs text: use the byte methods (readAllBytes,
newInputStream, newOutputStream) for binary files like
images, PDFs, or zips; use the text/line methods for human-readable text. + readAllLines defaults to UTF-8 encoding in Java 8+.
readAll*): simplest
code, but loads everything into RAM — an OutOfMemoryError waiting to happen on large files.
Streaming/Buffered: constant memory and faster for big data, but more code and you must remember to
close resources.readAllBytes on a 4 GB log will crash; a buffered loop won't. Interviewers love asking "how
would you process a file too big to fit in memory?" — answer: stream it.java.nio ("new I/O") vs the older
java.io: Files + Path is the modern, more convenient API. + NIO also brings non-blocking I/O and channels; for plain file reads, the headline
win is concise one-liners like Files.readAllLines.Runnable demos of each → Code: Files method tour.
4. Appending data & StandardOpenOption#
StandardOpenOption.APPEND.To append text instead of replacing it:
Files.newBufferedWriter(Paths.get("output.txt"), StandardOpenOption.APPEND);
But there's a trap: if output.txt doesn't exist yet, this throws NoSuchFileException — because APPEND alone says "open the
existing file at its end," and there is no existing file. The fix is to combine it with CREATE:
Files.newBufferedWriter(Paths.get("output.txt"), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Now it creates the file if absent, then appends — safe either way.
+ The full set of common open options worth knowing:
| Option | Meaning |
|---|---|
WRITE | Open the file for writing. |
APPEND | Bytes go to the end of the file (requires the file to be writable). |
CREATE | Create the file if it doesn't exist; if it does, just open it. |
CREATE_NEW | Create the file, but fail with an exception if it already exists (useful to avoid clobbering). |
TRUNCATE_EXISTING | If the file exists, wipe it to length 0 before writing. |
+ Important default: when you call Files.write(...)
or newBufferedWriter(...) with no options, Java uses CREATE + WRITE + TRUNCATE_EXISTING
— i.e., it creates-or-overwrites. That's why a second write silently erases the first unless you append.
APPEND by itself on a missing file →
NoSuchFileException. + And APPEND combined with
TRUNCATE_EXISTING is contradictory and throws IllegalArgumentException — never
pair them.APPEND means every run nukes yesterday's
logs.Runnable: → Code: append vs overwrite.
5. BufferedReader & BufferedWriter#
Files.newBufferedReader(path) hands back a BufferedReader
you read with readLine() in a loop (returns null at
end-of-file). Files.newBufferedWriter(path) gives a BufferedWriter you write with write(...) / newLine(). They're the workhorses for processing text files of any size without
loading it all at once.
+ Always wrap them in a try-with-resources block so they close automatically
— closing a BufferedWriter also flushes any buffered bytes to disk.
Forgetting to close/flush can mean your last lines never actually get written.
Runnable: → Code: BufferedReader/Writer.
6. Activity #1 — count lines, words, characters, paragraphs#
output.txt
in a fixed format.Required output format:
Total Lines Count - 10 Total Words Count - 10 Total Characters Count - 10 Total Paragraphs Count - 10
The approach: read the file line by line with a BufferedReader, and for each line
update counters.
- Lines — increment once per line read.
- Words — split the line on whitespace (
line.split("\\s+")) and add the count; + guard against empty/blank lines so they don't count as one word. - Characters — add
line.length()per line. + Decide explicitly whether newline characters count; the line string fromreadLine()excludes the\n, so add it manually if your spec counts newlines. - Paragraphs — + a paragraph is a block of text separated by one or more blank lines; increment the paragraph count when you transition from a blank line back to a non-blank line (or count non-empty blocks).
Then write all four counters to output.txt with a BufferedWriter.
Full solution: → Code: FileCount · also Lab Ex.3.
7. CSV — comma-separated values#
CSV is how spreadsheets and tables travel as simple text. The rules:
- One line = one record (one row of the table).
- Fields are separated by commas.
- A field that contains a comma must be wrapped in double quotes, so the comma inside isn't
mistaken for a separator:
"Smith, John",42. - A double quote inside a value is written as two double quotes (doubled to escape it):
"She said ""hi"""representsShe said "hi".
+ These conventions are standardized as RFC 4180. + Real-world CSV also varies: some files use semicolons or tabs as separators (regional locales), and the first line is often a header row of column names.
| Value to store | Written in CSV as |
|---|---|
Plain text | Plain text |
Boston, MA | "Boston, MA" |
She said "hi" | "She said ""hi""" |
line1⏎line2 (newline) | "line1⏎line2" (quoted) |
"," breaks the moment a
field contains a quoted comma — "Boston, MA" becomes two fields. Fine for clean numeric data;
for messy real data use a proper CSV parser (e.g., OpenCSV, Apache Commons CSV). +
Runnable parsing: → Code: CSV read/compute/write.
8. Activity #2 — total price per item (CSV → CSV)#
Input format: ITEM,DESCRIPTION,PRICE,QUANTITY
Output format: ITEM,DESCRIPTION,TOTAL_PRICE
For each data row, TOTAL_PRICE = PRICE × QUANTITY, and the QUANTITY column is dropped. Steps: read all lines, skip (or re-emit) the header,
split each row on commas, parse PRICE and QUANTITY as
numbers, multiply, then write ITEM,DESCRIPTION,TOTAL_PRICE to the new file.
split(",") is fine here because
the sample data is clean numbers; + if descriptions could contain commas you'd need
quoting-aware parsing (see §7 pitfall).Full solution: → Code: TotalPrice · also Lab Ex.4.
9. XNews — what I'm building#
It works like searching Google for news on a topic, but as my own program. The end-to-end pipeline:
- File handling — read the user's preferences from a JSON file.
- Jackson — parse that JSON into Java objects.
- OkHttp — make a REST API request (HTTP GET) to NewsAPI using those preferences.
- Jackson — deserialize the API's JSON response into Java objects.
- Finally, print the articles neatly for the user.
+ Jackson is the standard Java library for converting between JSON and Java objects. Serialize = Java object → JSON text; deserialize = JSON text → Java object. + OkHttp is a popular HTTP client library for making web requests from Java. + REST API = a web service you call over HTTP (usually returning JSON); NewsAPI is one such service that returns news articles as JSON and typically requires a free API key.
Diagram (data flow):
prefs.json ──read (File I/O)──> Jackson parse ──> Java prefs object │ ▼ build request OkHttp HTTP GET ───> (NewsAPI) │ JSON response <──────────┘ │ Jackson deserialize │ ▼ List<Article> → printed to user
Runnable skeleton: → Code: XNews pipeline.
★ Key Takeaways#
- Files persist data beyond a program's lifetime — that's the whole point of file I/O.
Pathjust names a location;Filesmethods do the actual work.- Match the method to the data:
readAll*/writefor small whole files,Buffered*/*Stream/Channelfor large or streaming data. - Writing overwrites by default. Use
StandardOpenOption.APPENDto add, and pair it withCREATEto avoidNoSuchFileExceptionon a missing file. - Always close writers (try-with-resources) so buffers flush.
- CSV = one record per line, comma-separated; quote fields with commas, double the inner quotes (RFC 4180).
- XNews pipeline: read JSON prefs (File I/O) → Jackson parse → OkHttp call to NewsAPI → Jackson deserialize → print. Serialize = obj→JSON, deserialize = JSON→obj.
Code Samples — runnable#
Standard JDK only (target Java 17, an LTS). Each block has a filename, run command,
inline comments, expected output, and a "why" note. Single-file programs run directly with java File.java (source-launch mode, JDK 11+).
III-1 Files method tour ↔ §3#
import java.nio.file.*; import java.util.List; import java.io.*; public class FilesTour { public static void main(String[] args) throws IOException { Path p = Paths.get("sample.txt"); // names a file; no I/O yet // --- WRITE (whole file). Default = CREATE+WRITE+TRUNCATE (overwrites) --- Files.write(p, List.of("line one", "line two")); // writes 2 lines // --- readAllLines: whole file into a List<String> (UTF-8) --- List<String> lines = Files.readAllLines(p); System.out.println("lines = " + lines); // --- readAllBytes: whole file as raw bytes (good for binary) --- byte[] bytes = Files.readAllBytes(p); System.out.println("byte count = " + bytes.length); // --- BufferedReader: efficient line-by-line for any size --- try (BufferedReader br = Files.newBufferedReader(p)) { String l; while ((l = br.readLine()) != null) // readLine()==null at EOF System.out.println("read: " + l); } } }
Why: Files.write(p, List.of(...)) with no options overwrites because the
default open options are CREATE+WRITE+TRUNCATE_EXISTING. byte count = 18 =
"line one"(8) + "line two"(8) + 2 newlines. readLine() strips the
newline, which is why each printed line has none.
III-2 Append vs overwrite ↔ §4#
import java.nio.file.*; import static java.nio.file.StandardOpenOption.*; import java.io.*; public class AppendDemo { public static void main(String[] args) throws IOException { Path p = Paths.get("log.txt"); // CREATE + APPEND: makes file if missing, then adds to the END. // APPEND alone on a missing file would throw NoSuchFileException. try (BufferedWriter w = Files.newBufferedWriter(p, CREATE, APPEND)) { w.write("run at " + System.currentTimeMillis()); w.newLine(); // platform line separator } // close() flushes buffer to disk // Run this program twice: the file keeps BOTH lines (proof of append). Files.readAllLines(p).forEach(System.out::println); } }
Why: Without APPEND, the second run's default TRUNCATE would erase the
first line. CREATE prevents the missing-file crash. The try-with-resources close()
flushes — drop it and buffered text may never hit disk.
III-3 BufferedReader / BufferedWriter ↔ §5#
import java.nio.file.*; import java.io.*; public class BufferedCopy { public static void main(String[] args) throws IOException { Path in = Paths.get("in.txt"); Path out = Paths.get("out.txt"); Files.write(in, java.util.List.of("apple", "banana", "cherry")); // Stream large files line-by-line without loading the whole thing. try (BufferedReader r = Files.newBufferedReader(in); BufferedWriter w = Files.newBufferedWriter(out)) { String line; int n = 0; while ((line = r.readLine()) != null) { w.write((++n) + ": " + line.toUpperCase()); w.newLine(); } } Files.readAllLines(out).forEach(System.out::println); } }
Why: Both resources are declared in one try-with-resources, so both close (and the writer flushes) even if an exception fires mid-loop. Memory use stays flat regardless of file size because only one line is held at a time.
III-4 Activity #1 — FileCount ↔ §6#
import java.nio.file.*; import java.io.*; public class FileCount { public static void main(String[] args) throws IOException { Path in = Paths.get("input.txt"); // seed an input file: 2 paragraphs separated by a blank line Files.write(in, java.util.List.of("Hello world here", "second line", "", "new paragraph now")); long lines = 0, words = 0, chars = 0, paragraphs = 0; boolean prevBlank = true; // start "blank" so first text block counts try (BufferedReader br = Files.newBufferedReader(in)) { String line; while ((line = br.readLine()) != null) { lines++; // 1 per line chars += line.length() + 1; // +1 counts the newline boolean blank = line.trim().isEmpty(); if (!blank) { words += line.trim().split("\\s+").length; // split on whitespace if (prevBlank) paragraphs++; // blank→text = new para } prevBlank = blank; } } try (BufferedWriter w = Files.newBufferedWriter(Paths.get("output.txt"))) { w.write("Total Lines Count - " + lines); w.newLine(); w.write("Total Words Count - " + words); w.newLine(); w.write("Total Characters Count - " + chars); w.newLine(); w.write("Total Paragraphs Count - " + paragraphs); } Files.readAllLines(Paths.get("output.txt")).forEach(System.out::println); } }
Why: Words = 3 (line1) + 2 (line2) + 3 (line4) = 8; the blank line adds none.
Paragraphs = 2 because there are two text blocks split by the blank line — the prevBlank flag
is what prevents counting consecutive blanks as multiple breaks. Chars includes a +1 per line for the
newline (16+11+0+17 text chars + 4 newlines... here counted as length+1 each = 48); if your spec excludes
newlines, drop the + 1.
III-5 CSV read / compute / write ↔ §7#
import java.nio.file.*; import java.util.*; public class CsvDemo { public static void main(String[] args) throws Exception { Path in = Paths.get("items.csv"); Files.write(in, List.of("ITEM,DESCRIPTION,PRICE,QUANTITY", // header row "Pen,Blue ink,10,3", "Notebook,A5 ruled,50,2")); List<String> lines = Files.readAllLines(in); List<String> outRows = new ArrayList<>(); outRows.add("ITEM,DESCRIPTION,TOTAL_PRICE"); // new header for (int i = 1; i < lines.size(); i++) { // i=1 skips header String[] f = lines.get(i).split(","); // naive split (clean data) double total = Double.parseDouble(f[2]) * Double.parseDouble(f[3]); outRows.add(f[0] + "," + f[1] + "," + total); } Files.write(Paths.get("out.csv"), outRows); outRows.forEach(System.out::println); } }
Why: split(",") works only because no field here contains a comma.
Double.parseDouble gives 30.0 (note the trailing .0) — use
int parsing or formatting if you want whole numbers. This is the core of the second exercise; see the fuller
version below.
III-6 Activity #2 — TotalPrice ↔ §8#
import java.nio.file.*; import java.util.*; public class TotalPrice { public static void main(String[] args) throws Exception { Path in = Paths.get(args.length > 0 ? args[0] : "input.csv"); Path out = Paths.get(args.length > 1 ? args[1] : "output.csv"); if (!Files.exists(in)) // seed demo data if absent Files.write(in, List.of("ITEM,DESCRIPTION,PRICE,QUANTITY", "Pen,Blue ink,10,3", "Mug,Ceramic,120,2")); List<String> rows = new ArrayList<>(); rows.add("ITEM,DESCRIPTION,TOTAL_PRICE"); List<String> src = Files.readAllLines(in); for (int i = 1; i < src.size(); i++) { String[] c = src.get(i).split(","); int price = Integer.parseInt(c[2].trim()); int qty = Integer.parseInt(c[3].trim()); rows.add(c[0] + "," + c[1] + "," + (price * qty)); // derived col } Files.write(out, rows); System.out.println("Wrote " + (rows.size()-1) + " item rows to " + out); rows.forEach(System.out::println); } }
Why: Parsing as int (not double) keeps prices whole
(30, not 30.0). .trim() guards against stray spaces around numbers.
Accepting file names via args makes it reusable from the command line.
III-7 XNews pipeline (skeleton) ↔ §9#
XNews uses third-party libraries (Jackson, OkHttp) which aren't in the standard library, so the network/JSON
parts can't run with bare java File.java. + Below is (a) a
pure-JDK version that proves the same pipeline shape with no dependencies, and (b) the real
Jackson/OkHttp shape annotated as reference.
import java.nio.file.*; import java.net.http.*; // JDK 11+ HttpClient — no OkHttp needed import java.net.URI; import java.util.List; public class XNewsLite { public static void main(String[] args) throws Exception { // 1) FILE I/O: read user preferences from a JSON-ish file Path prefs = Paths.get("prefs.json"); if (!Files.exists(prefs)) Files.write(prefs, List.of("{ \"topic\": \"java\", \"language\": \"en\" }")); String json = Files.readString(prefs); // whole file as String System.out.println("prefs = " + json.trim()); // 2) PARSE (real XNews uses Jackson; here a tiny manual extract) String topic = json.replaceAll("(?s).*\"topic\"\\s*:\\s*\"([^\"]+)\".*", "$1"); // 3) HTTP GET to an API (real XNews uses OkHttp + NewsAPI) HttpClient client = HttpClient.newHttpClient(); HttpRequest req = HttpRequest.newBuilder().uri(URI.create("https://example.com/?topic=" + topic)) .GET().build(); System.out.println("would GET news for topic = " + topic); // (omitting the live send so this runs offline) // 4) DESERIALIZE response → 5) print articles (shown in full version) System.out.println("pipeline OK: file -> parse -> request -> (parse) -> print"); } }
Why: This mirrors XNews's 5 stages using only the JDK (Files.readString,
java.net.http.HttpClient) so it actually runs. The regex "parse" is a stand-in for Jackson —
fine for a demo, never for real JSON.
// Dependencies (Maven): jackson-databind, okhttp import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.*; import java.nio.file.*; public class XNews { record Prefs(String topic, String language) {} // maps prefs.json record Article(String title, String url) {} // maps API JSON public static void main(String[] a) throws Exception { ObjectMapper m = new ObjectMapper(); // 1) File I/O + 2) Jackson: JSON file -> Prefs object (deserialize) Prefs prefs = m.readValue(Files.readAllBytes(Paths.get("prefs.json")), Prefs.class); // 3) OkHttp: REST GET to NewsAPI (key from env, never hard-coded) [+] String key = System.getenv("NEWS_API_KEY"); OkHttpClient http = new OkHttpClient(); Request req = new Request.Builder().url("https://newsapi.org/v2/everything?q=" + prefs.topic() + "&language=" + prefs.language() + "&apiKey=" + key).build(); try (Response resp = http.newCall(req).execute()) { String body = resp.body().string(); // raw JSON // 4) Jackson: deserialize the "articles" array var root = m.readTree(body); for (var node : root.get("articles")) { // 5) print neatly System.out.println("• " + node.get("title").asText() + "\n " + node.get("url").asText()); } } } }
Why: ObjectMapper.readValue(bytes, Prefs.class) is Jackson
deserialization (JSON→object). The OkHttp Response is in try-with-resources because it
holds a network connection that must close. The API key comes from an environment variable — + hard-coding secrets in source is a real security mistake.
Hands-On Lab — practice#
Six graded exercises. Predict the output before you run, then verify. Solutions are collapsed — write yours first.
# 1. Check Java is installed (need JDK 11+ for `java File.java`; 17 LTS ideal) java -version javac -version # 2. Make a working folder mkdir -p ~/workspace/file-handling-lab cd ~/workspace/file-handling-lab # 3. Create a source file, e.g. Ex1.java, paste your code, then: java Ex1.java # single-file source-launch, no separate compile # --- OR the classic two-step (any JDK) --- javac Ex1.java # compile -> Ex1.class java Ex1 # run (note: no .java / .class extension)
Write then read three lines#
Goal: Write three lines to hello.txt using Files.write, then read them back with Files.readAllLines
and print them.
- Build a
Pathforhello.txt. - Write
List.of("a","b","c")withFiles.write. - Read with
readAllLinesand print each line.
Solution
import java.nio.file.*; import java.util.List; public class Ex1 { public static void main(String[] a) throws Exception { Path p = Paths.get("hello.txt"); Files.write(p, List.of("a", "b", "c")); // overwrites each run Files.readAllLines(p).forEach(System.out::println); } }
Answer: Still 3 lines. Files.write with no options
defaults to TRUNCATE_EXISTING, so each run wipes and rewrites — it does not accumulate.
Append a timestamp every run#
Goal: Append a line to runs.txt on each execution so the
file grows. Make it not crash on the first run when the file is missing.
- Open a
BufferedWriterwith the correct open options. - Write the current time, then a newline.
- Run it 3 times; confirm 3 lines.
APPEND alone and the file doesn't exist yet? Which single option fixes it?Solution
import java.nio.file.*; import static java.nio.file.StandardOpenOption.*; import java.io.*; public class Ex2 { public static void main(String[] a) throws Exception { try (BufferedWriter w = Files.newBufferedWriter(Paths.get("runs.txt"), CREATE, APPEND)) { w.write("run @ " + java.time.LocalTime.now()); w.newLine(); } Files.readAllLines(Paths.get("runs.txt")).forEach(System.out::println); } }
Answer: APPEND alone → NoSuchFileException on a
missing file. Adding CREATE fixes it.
FileCount (Exercise 1)#
Goal: Count lines, words, characters, and paragraphs of an input file and write the
four totals to output.txt in the required format.
- Seed an
input.txtwith at least 2 paragraphs (blank line between). - Loop lines; update the four counters (use a
prevBlankflag for paragraphs). - Write the four
Total ... Count - Nlines tooutput.txt.
Solution
Use the full FileCount.java from Section III-4. The key idea:
only bump paragraphs on a blank→non-blank transition, so consecutive
blank lines never over-count.
Self-test: With paragraphs of 3 and 2 words split by blank line(s), you
should see Total Words Count - 5 and Total Paragraphs Count - 2
regardless of how many blank lines separate them.
TotalPrice (Exercise 2)#
Goal: Read ITEM,DESCRIPTION,PRICE,QUANTITY, compute TOTAL_PRICE = PRICE×QUANTITY, write ITEM,DESCRIPTION,TOTAL_PRICE to a new CSV.
- Read all lines; keep line 0 logic separate (it's the header).
- Write a new header
ITEM,DESCRIPTION,TOTAL_PRICE. - For each data row:
split(","), parse price & qty, multiply, append. - Write the result CSV.
10 and quantity
3, do you want output 30 or 30.0? Which numeric type gives which?Solution
Use TotalPrice.java (Section III-6). Parse with Integer.parseInt for clean 30; use Double.parseDouble if prices can be decimals (you'll get 30.0).
Self-test: Output header must be exactly
ITEM,DESCRIPTION,TOTAL_PRICE (QUANTITY column dropped), and row count = input rows
− 1 header.
Quote-aware CSV split#
Goal: Parse a CSV row where a description may contain a quoted comma, e.g. Mug,"Big, blue",120,2, without breaking the field on the inner comma.
- Don't use naive
split(",")— it splits inside the quotes. - Walk the string char-by-char, toggling an
inQuotesflag on"; only split on commas when!inQuotes. - Verify the description stays intact as one field.
Mug,"Big, blue",120,2 produce (wrongly)? How many should it be?Solution
import java.util.*; public class Ex5 { static List<String> parse(String row) { List<String> out = new ArrayList<>(); StringBuilder cur = new StringBuilder(); boolean inQ = false; for (char ch : row.toCharArray()) { if (ch == '"') inQ = !inQ; // toggle quote state else if (ch == ',' && !inQ) { // split only outside quotes out.add(cur.toString()); cur.setLength(0); } else cur.append(ch); } out.add(cur.toString()); return out; } public static void main(String[] a) { System.out.println(parse("Mug,\"Big, blue\",120,2")); } }
Answer: Naive split yields 5 fields (wrong); correct is 4.
The parser keeps Big, blue as one field. Note it doesn't yet un-double
""; production code should also collapse doubled quotes and ideally use a real CSV
library.
Mini XNews pipeline (offline)#
Goal: Reproduce the XNews shape with only the JDK: read a prefs.json file, extract the topic, and print what request you'd make — proving
the file→parse→request flow end to end.
- Create
prefs.jsonwith atopicfield if missing. - Read it with
Files.readString. - Extract
topic(regex is fine for the drill; Jackson in real code). - Print the URL you'd GET.
HttpClient in the production XNews — and which one does both
serialize and deserialize?Solution
Use XNewsLite.java (Section III-7). It reads prefs.json, pulls out the topic, and prints the request it would make —
all offline with the JDK.
Answer: Jackson replaces manual JSON parsing (and it handles both serialize and deserialize), and OkHttp replaces the HTTP client for the NewsAPI call.
✓ Self-check rubric — how to know you've got it#
- Method picker: Given a task ("process a 5 GB log", "read a small config", "save an image"), I
can name the right
Filesmethod without looking. - Append vs overwrite: I can explain why a re-run erased my file, and fix it with
CREATE, APPEND— and I know whyAPPENDalone can throw. - Resource safety: I always wrap readers/writers in try-with-resources and can say why (auto-close + flush).
- Counting logic: My paragraph count survives multiple consecutive blank lines (the
prevBlankflag works). - CSV rules: I can hand-write the CSV encoding of
She said "hi", ok?correctly (quotes doubled, field wrapped). - XNews flow: I can draw the 5-stage pipeline from memory and define serialize vs deserialize, naming Jackson and OkHttp's roles.