25~28일차 배운 내용(이거 완전 짱 중요한 부분!!!!! 여기 모르면 말짱 도로묵,,,,,, 도로묵 맛이찌....)
1. IO 기반 입출력 및 네트워킹
1) IO 패키지 소개
2) 입력 스트림과 출력 스트림
3) 콘솔 입출력
4) 파일 입출력
5) 보조 스트림
6) 네트워크 기초
7) TCP 네트워킹
8) UDP 네트워킹
[ IO 기반 입출력 및 네트워킹 ]
[ 1 ] IO(Input Output) 패키지 소개
- 자바에서 모든 입출력은 스트림(Stream)을 통해 이루어진다.
스트림은 단일 방향으로 연속적으로 흘러가는 것을 말하는데, 물이 높은 곳에서 낮은 곳으로 흐르듯이
데이터는 출발지에서 나와 도착지로 들어간다는 개념이다.
입출력 장치는 매우 다양하기에 장치에 따라 입출력 부분을 일일이 다르게 구현을 하면 프로그램 호환성이
떨어질 수밖에 없다. 이러한 문제를 해결하기 위해 입출력 장치와 무관하고 일관성 있게
프로그램을 구현할 수 있도록 일종의 가상 스트림을 제공하는 것이다.
자료를 읽어 들이려는 소스(source)와 자료를 쓰려는 대상(target)에 따라 각각 다른 스트림 클래스를 제공한다.
[ 2 ] 입력 스트림과 출력 스트림
- 프로그램이 출발지냐 또는 도착지냐에 따라서 스트림의 종류가 결정된다.
- 입력 스트림 : 어떤 대상으로부터 자료를 읽어 들일 때 사용하는 스트림
출력 스트림 : 화면에 사용자가 쓴 글을 파일에 저장할 때 사용하는 스트림
- 스트림의 특성이 단방향이므로 하나의 스트림으로 입력과 출력을 모두 할 수 없다.
- 항상 프로그램을 기준으로 데이터가 들어오면 입력 스트림이고,
데이터가 나가면 출력 스트 림이라는 것을 명심해야 한다!!
종류 | 예시 | |||
입력 스트림 | FileInputStream | FileReader | BufferedInputStream | BufferedReader |
출력 스트림 | FileOutputStream | FileWriter | BufferedOutputStream | BufferedWriter |
- 자바의 기본적인 데이터 입출력(IO: Input/Output) API는 java.io 패키지에서 제공하고 있 다.
- 바이트 기반 스트림 vs 문자 기반 스트림
- 바이트 기반 스트림 (바이트 = 1byte)
: 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 받고 보내는 것이 가능하다.
1byte씩 데이터를 처리하는 기반이다.
- 문자 기반 스트림 (문자 = 2bytes)
: 문자만 받고 보낼 수 있도록 특화되어 있다.
2bytes씩 데이터를 처리하는 기반이다.
바이트 기반 스트림에서도 문자를 다룰 수 있는데 문자가 워낙 많아서
문자 기반 스트림을 따로 만들었다.
종류 | 예시 | |||
바이트 스트림 | FileInputStream | FileOutputStream | BufferedInputStream | BufferedOutputStream |
문자 스트림 | FileReader | FileWriter | BufferedReader | BufferedWriter |
1. InputStream (입력 스트림)
- 바이트 기반 입력 스트림의 최상위 클래스이면서 추상 클래스이다.
* 여기서 추상 클래스란,,? *
"추상 클래스"
- 구성 멤버
1) 필드 2) 생성자 3) 일반 메서드 4) 추상 메서드
- 추상클래스는 생성자가 있긴 하지만 추상클래스 자체를 가지고 객체를 생성할 수는 없다.
추상클래스를 상속받은 클래스들을 가지고 객체를 생성한다.
- 목적
: "통일", 자식클래스의 필드와 메서드명 일치시킴
"인터페이스"
- 구성 멤버
1) 상수 2) 추상 메서드 3) 디폴트 메서드 4) 정적 메서드
- 목적
: "다형성", 객체교환성을 높여 다양한 실행 결과를 얻음
*
import java.io.FileInputStream;
import java.io.InputStream;
public class ReadExample1 {
public static void main(String[] args) throws Exception {
// 바이트 기반의 파일 입력스트림 생성
InputStream is = new FileInputStream("C:/dev/test.txt");
// FileInputStream 은 파일을 읽어올 때 사용하는 바이트 기반 파일 입력스트림이다.
int readByte;
while(true) {
readByte = is.read();
// read() : 매개변수가 없는 경우
// .read()는 FileInputStream으로부터 데이터를 1바이트씩 읽어오는 메서드
// readByte에는 읽어온 데이터가 저장되게 된다.
System.out.println(readByte);
if(readByte == -1) { // readByte == -1이라는 이야기는 더이상 읽어올 데이터가 없단 얘기
break;
}
System.out.println((char)readByte);
// readByte는 4byte인데 char는 2byte이니까 강제형변환
}
is.close();
}
}
import java.io.FileInputStream;
import java.io.InputStream;
public class ReadExample2 {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("C:/dev/test.txt");
int readByteNo;
byte[] readBytes = new byte[3];
String data = "";
while(true) {
readByteNo = is.read(readBytes); // 3bytes씩 데이터를 읽어온다. 즉, 배열의 크기만큼 읽어오는 것
// read(readBytes) : 매개변수 하나인 경우
// 읽어온 데이터는 readBytes 배열에 저장되고 읽은 데이터의 개수는 readByteNo에 담겨진다.
System.out.println(readByteNo);
if(readByteNo == -1)
break;
data += new String(readBytes, 0, readByteNo);
// readBytes 배열의 인덱스 0번부터 readByteNo의 개수만큼..
}
System.out.println(data.toString());
System.out.println(data); // 위의 data.toString() 출력한것과 동일하게 출력됨. "C:/dev/test.txt"
is.close();
}
}
import java.io.FileInputStream;
import java.io.InputStream;
public class ReadExample3 {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("C:/dev/test.txt");
int readByteNo;
byte[] readBytes = new byte[8];
readByteNo = is.read(readBytes, 2, 3);
// read(readBytes, 2, 3) : 매개변수 두개 이상인 경우
// readBytes 배열의 2번 인덱스부터 3개의 데이터 읽어와서 넣어줌
// 즉, readBytes 배열에 3개의 데이터를 넣어주는데 2번 인덱스부터 넣는다.
// readByteNo는 읽어온 데이터의 갯수 이므로 3이다.
for(int i=0; i<readBytes.length; i++) {
System.out.println(readBytes[i]);
}
is.close();
}
}
2. OutputStream (출력 스트림)
- 바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스
import java.io.FileOutputStream;
import java.io.OutputStream;
public class WriteExample1 {
public static void main(String[] args) throws Exception {
OutputStream os = new FileOutputStream("C:/dev/test1.txt");
byte[] data = "ABC".getBytes();
// .getBytes() : String 문자열을 1바이트씩 가져와서 바이트 배열에 저장
// .getBytes() : String을 byte[] 로 만든다.
for(int i=0; i<data.length; i++) {
os.write(data[i]);
// write(int) : 매개변수 int값으로 하나인 경우
// .write(byte[] b) : 출력 스트림에 매개값으로 주어진 바이트 배열 b의 모든 바이트를 보낸다.
// 즉, 한 바이트씩 OutputStream으로 전송한다.
}
os.flush();
// flush() : 버퍼에 잔류하는 즉 남아있을 수 있는 모든 바이트를 출력한다.
os.close();
}
}
import java.io.FileOutputStream;
import java.io.OutputStream;
public class WriteExample2 {
public static void main(String[] args) throws Exception {
OutputStream os = new FileOutputStream("C:/dev/test2.txt");
byte[] data = "ABC".getBytes();
os.write(data);
// write(byte[] b) : 매개변수 바이트 배열로 하나인 경우
// byte[] 로 OutputStream으로 한꺼번에 전송한다.
os.flush();
os.close();
}
}
import java.io.FileOutputStream;
import java.io.OutputStream;
public class WriteExample3 {
public static void main(String[] args) throws Exception {
OutputStream os = new FileOutputStream("C:/dev/test3.txt");
byte[] data = "ABC".getBytes();
os.write(data, 1, 2);
// write(data, 1, 2) : 매개변수 2개 이상인 경우
// write(data, 1, 2) : data 배열에서 1번 인덱스부터 2개의 데이터 값 가져와서 쓰기
os.flush();
os.close();
}
}
3. Reader (입력 스트림)
- 문자 기반 입력 스트림의 최상위 클래스로 추상 클래스이다.
FileReader, BufferedReder, InputStreamReader 클래스는 모두 Reader 클래스를 상속하고 있다.
import java.io.FileReader;
import java.io.Reader;
public class ReadExample1 {
public static void main(String[] args) throws Exception {
Reader reader = new FileReader("C:/dev/test.txt");
int readData;
while( true ) {
readData = reader.read();
// read() : 매개변수 없는 경우
// read() : 입력 스트림으로부터 한개의 문자(2byte씩)를 읽고 리턴
if(readData == -1) // readData == -1 이라는 얘기는 더이상 읽어 올 데이터가 없다는 이야기
break;
System.out.print((char)readData);
}
reader.close();
// close() : 사용한 시스템 자원을 반납하고 입력 스트림을 닫는다.
}
}
import java.io.FileReader;
import java.io.Reader;
public class ReadExample2 {
public static void main(String[] args) throws Exception {
Reader reader = new FileReader("C:/dev/test.txt");
int readCharNo;
char[] cbuf = new char[2];
String data = "";
while( true ) {
readCharNo = reader.read(cbuf);
// read(char[] cbuf) : 매개값이 char 배열 하나인 경우
// 입력 스트림으로부터 읽은 문자들을 매개값으로 주어진 문자 배열
// cbuf에 저장하고 실제로 읽은 문자의 개수를 리턴
if(readCharNo == -1)
break;
data += new String(cbuf, 0, readCharNo);
}
System.out.println(data);
reader.close();
}
}
import java.io.FileReader;
import java.io.Reader;
public class ReadExample3 {
public static void main(String[] args) throws Exception {
Reader reader = new FileReader("C:/dev/test.txt");
int readCharNo;
char[] cbuf = new char[4];
readCharNo = reader.read(cbuf, 1, 2);
for(int i=0; i<cbuf.length; i++) {
System.out.println(cbuf[i]);
}
reader.close();
}
}
4. Writer (출력 스트림)
- 문자 기반 출력 스트림의 최상위 클래스로 추상 클래스
import java.io.FileWriter;
import java.io.Writer;
public class WriteExample1 {
public static void main(String[] args) throws Exception {
Writer writer = new FileWriter("C:/dev/test.txt");
char[] data = "홍길동".toCharArray();
// toCharArray() : String 문자열을 char 배열로 변환시키는 메서드
for(int i=0; i<data.length; i++) {
writer.write(data[i]);
}
writer.flush();
writer.close();
}
}
import java.io.FileWriter;
import java.io.Writer;
public class WriteExample2 {
public static void main(String[] args) throws Exception {
Writer writer = new FileWriter("C:/dev/test.txt");
char[] data = "홍길동".toCharArray();
writer.write(data);
writer.flush();
writer.close();
}
}
import java.io.FileWriter;
import java.io.Writer;
public class WriteExample3 {
public static void main(String[] args) throws Exception {
Writer writer = new FileWriter("C:/dev/test.txt");
char[] data = "홍길동".toCharArray();
writer.write(data, 1, 2);
writer.flush();
writer.close();
}
}
import java.io.FileWriter;
import java.io.Writer;
public class WriteExample4 {
public static void main(String[] args) throws Exception {
Writer writer = new FileWriter("C:/dev/test.txt");
String data = "안녕 자바 프로그램";
//writer.write(data);
writer.write(data, 3, 2);
writer.flush();
writer.close();
}
}
* 추가 내용 *
[표준 입출력 ]
- 자바에서는 화면에 출력하고 입력받는 표준 입출력 클래스를 미리 정의해 두었다.
--> 이 표준 입출력 클래스는 프로그램이 시작될 때 생성되므로 따로 만들 필요 X.
- 우리가 지금까지 화면 출력을 위해 사용한 "System.out" 은 표준 출력을 위한 객체였다!!
- 표준 입출력은 콘솔 화면에 입출력된다고 해서 콘솔 입출력 이라고도 한다.
- 표준 입출력을 위한 System 클래스는 다음과 같이 세개의 변수를 가진다.
[ 3 ] 콘솔 입출력
- 콘솔 (Console) : 시스템을 사용하기 위해 키보드로 입력을 받고 화면으로 출력하는 소프트웨어
• Unix, Linux: 터미널
• Windows 운영체제: 명령 프롬프트
• 이클립스: Console 뷰
1. System.in 필드
- InputStream 타입의 입력 스트림 ( InputStream 변수 대입 가능 )
- 콘솔에서 데이터를 입력받는 스트림,,? * 콘솔에 내가 직접 데이터를 입력할 수 있다.*
- 읽은 byte는 키보드의 아스키 코드(ascii code) * 1byte씩 데이터를 읽어온다. *
- 아스키 코드로부터 문자 변환
- System.int 필드의 예제 : 키보드로부터 입력된 한글 읽기
- read()메소드는 1바이트씩만 읽음 --> 오류 발생
- 한글과 같이 2바이트를 필요로 하는 유니코드는 read() 메소드로 읽을 수 없다.
* InputStream의 read() 메서드 --> 바이트 기반 --> 1바이트씩 읽을 수 있음 *
- 전체 내용을 바이트 배열로 받아 String 객체 생성 후 읽기
import java.io.InputStream;
public class SystemInExample1 {
public static void main(String[] args) throws Exception {
System.out.println("== 메뉴 ==");
System.out.println("1. 예금 조회");
System.out.println("2. 예금 출금");
System.out.println("3. 예금 입금");
System.out.println("4. 종료 하기");
System.out.print("메뉴를 선택하세요: ");
InputStream is = System.in;
// System.in : 콘솔에서 바이트기반의 입력스트림을 생성한다.
char inputChar = (char) is.read();
// read() 메서드는 읽어온 데이터를 4바이트 int형으로 반환한다.
switch(inputChar) {
case '1':
System.out.println("예금 조회를 선택하셨습니다.");
break;
case '2':
System.out.println("예금 출금를 선택하셨습니다.");
break;
case '3':
System.out.println("예금 입금를 선택하셨습니다.");
break;
case '4':
System.out.println("종료 하기를 선택하셨습니다.");
break;
}
}
}
import java.io.InputStream;
public class SystemInExample2 {
public static void main(String[] args) throws Exception {
InputStream is = System.in;
// InputStream : 바이트 기반의 입력 스트림
byte[] datas = new byte[100];
System.out.print("이름: ");
int nameBytes = is.read(datas);
// datas 배열에 읽은 데이터를 저장하고 읽어온 데이터의 개수를 nameBytes에 대입
String name = new String(datas, 0, nameBytes-2);
// nameBytes개수에서 -2를 하는 이유
// 문자열의 맨 마지막의 두개는 엔터키값으로 그걸 제외한게 진짜 내가 구하고자 하는 길이이다.
System.out.print("하고 싶은 말: ");
int commentBytes = is.read(datas);
String comment = new String(datas, 0, commentBytes-2);
System.out.println("입력한 이름: " + name);
System.out.println("하고 싶은 말: " + comment);
}
}
2. System.out 필드
- PrintStream 타입의 출력 스트림 ( OutputStream으로 타입 변환 가능 )
* 데이터를 바이트 기반으로 콘솔에 출력 *
- 아스키 코드를 출력하면 콘솔에는 문자가 출력한다.
- 문자열을 출력하려면 바이트 배열을 얻어야 한다.
import java.io.OutputStream;
public class SystemOutExample {
public static void main(String[] args) throws Exception {
OutputStream os = System.out;
// 콘솔에 outputStream을 만드는 것
// byte 단위로 적는다!!
for(byte b = 48; b < 58; b++) { // 아스키코드 48에서 57까지의 문자 출력
os.write(b); // 0123456789 출력
}
os.write(10);
// 라인피드(10)을 출력하면 다음 행으로 넘어간다.
for(byte b = 97; b < 123; b++) { // 아스키코드 97에서 122까지의 문자 출력
os.write(b); // abcdefghijklmnopqrstuvwxyz 출력
}
os.write(10);
String hangul = "가나다라마바사아자차카타파하";
byte[] hangulBytes = hangul.getBytes();
os.write(hangulBytes);
os.flush();
os.close();
}
}
3. Console 클래스 ( 그냥 한번 훑고 지나가신 부분,, 별로 안중요한 걸지도,,?)
- 자바6부터 콘솔에서 입력된 문자열을 쉽게 읽을 수 있도록 제공하고 있다.
- 기본형
: Console console = System.console();
- 주의할 점은 이클립스에서 System.console() 메소드는 null 리턴하기 때문에
반드시 명 령 프롬프트에서 실행해야 한다.
import java.io.Console;
public class ConsoleExample {
public static void main(String[] args) {
Console console = System.console();
System.out.print("아이디: ");
String id = console.readLine();
System.out.print("패스워드: ");
char[] charPass = console.readPassword();
String strPassword = new String(charPass);
System.out.println("---------------------");
System.out.println(id);
System.out.println(strPassword);
}
}
4. Scanner 클래스
- Console 클래스의 단점
: 문자열은 읽을 수 있지만 기본 타입(정수, 실수) 값을 바로 읽을 수 없다. (문자열만 읽을 수 있음)
---> 이걸 보완(?)한게 Scanner 클래스!!
- java.util.Scanner
: 콘솔로부터 기본 타입의 값은 물론 다양한 타입의 값을 읽을 수 있다.
import java.util.Scanner;
public class ScannerExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 콘솔에서 받은 데이터를 매개변수로 받는것,,?
System.out.print("문자열 입력 > ");
String inputString = scanner.nextLine();
// .nextLine() : String 타입 반환
System.out.println(inputString);
System.out.println();
System.out.print("정수 입력 > ");
int inputInt = scanner.nextInt();
// .nextInt() : int 타입 반환
System.out.println(inputInt);
System.out.println();
System.out.print("실수 입력 > ");
double inputDouble = scanner.nextDouble();
// .nextDouble() : double 타입 반환
System.out.println(inputDouble);
}
}
[ 4 ] 파일 입출력
1. File 클래스
- File 클래스는 파일 시스템의 파일을 표현하는 클래스이다.
- 파일 크기, 파일 속성, 파일 이름 등의 정보 제공
- 파일 생성 및 삭제 기능 제공
- 디렉토리 생성, 디렉토리에 존재하는 파일 리스트 얻어내는 기능
* \\T 로 적은 이유
: \T 만 적으면 특수기호로 알아보니깐 특수기호가 아니라는 것을 알려주기 위해
앞에 \를 하나 더 붙인것이다.
* - Unix - Windows
: File Separator( / ) : File Separator(\)
ex) /home/user2/etc ex) c:\dev\workspace\
- 파일 및 디렉토리 생성 및 삭제 메서드
- 파일 및 디렉토리의 정보를 리턴하는 메소드
import java.io.File;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileExample {
public static void main(String[] args) throws Exception {
File dir = new File("C:/Temp/Dir");
File file1 = new File("C:/Temp/file1.txt");
File file2 = new File("C:/Temp/file2.txt");
File file3 = new File(new URI("file:///C:/Temp/file3.txt"));
// 파일 객체 4개 생성함
if(dir.exists() == false) { dir.mkdirs(); }
if(file1.exists() == false) { file1.createNewFile(); }
if(file2.exists() == false) { file2.createNewFile(); }
if(file3.exists() == false) { file3.createNewFile(); }
File temp = new File("C:/Temp");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm");
File[] contents = temp.listFiles();
// listFiles() : 파일은 뭐가 있고 디렉토리는 뭐가 있는지 반환..
System.out.println("날짜 시간 형태 크기 이름");
System.out.println("-------------------------------------------------------------------");
for(File file : contents) {
System.out.print(sdf.format(new Date(file.lastModified())));
if(file.isDirectory()) {
System.out.print("\t<DIR>\t\t\t" + file.getName());
} else {
System.out.print("\t\t\t" + file.length() + "\t" + file.getName());
}
System.out.println();
}
}
}
2. FileInputStream
- 파일로부터 바이트 단위로 읽어 들일 때 사용하는 스트림
(내가 원하는 주어지는 파일을 바이트 단위로 읽는 스트림)
- 그림, 비디오, 오디오, 텍스트 파일 등 모든 종류의 파일을 읽을 수 있다.
- 객체 생성 방법
- FileInputStream 객체가 생성될 때 파일과 직접 연결된다.
- 만약 파일이 존재하지 않으면 FileNoFoundException 예외가 발생한다.
- 예외가 발생하면 try-catch문으로 예외 처리한다.
- InputStream의 하위 클래스로 사용 방법이 InputStream과 동일하다.
* 두번째 방법
: 파일 객체를 만든 다음 FileInpurStream을 만들고 거기에 매개변수로 파일 객체를 넣어준것 *
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class FileInputStreamExample {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\dev\\workspace\\java\\chap18\\src\\sec04\\exam02_fileinputstream\\FileInputStreamExample.java");
int data;
while ((data = fis.read()) != -1) {
System.out.write(data); // System.out : PrintStream(바이트 기반의 표준화면(콘솔) 출력 스트림)
}
} catch (Exception e) { // 주어지는 파일이 없을 수 있는 FileNotFoundException 예외가 생길 수 있으니 예외 처리를 해줘야한다.
e.printStackTrace();
}
}
}
3. FileOutputStream
- 파일에 바이트 단위로 데이터를 저장할 때 사용
- 그림, 오디오, 비디오, 텍스트 등 모든 종류의 데이터를 파일로 저장
- 객체 생성 방법
- 파일이 이미 존재할 경우, 데이터를 출력하게 되면 파일을 덮어쓰게 되어 기존 파일 내용은 사자리게 된다.
- 기존 파일 내용 끝에 데이터 추하가는 경우에는 FileOutputStream 생성자의 두번째 매개값을 true로 주면 된다.
ex) FileOutputStream fos = new FileOutputStream("C:/Temp/data.txt", true);
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class FileOutputStreamExample {
public static void main(String[] args) throws Exception {
String originalFileName = "C:\\dev\\workspace\\java\\chap18\\src\\sec04\\exam03_fileoutputstream\\house.jpg";
String targetFileName = "C:/Temp/house.jpg";
FileInputStream fis = new FileInputStream(originalFileName);
FileOutputStream fos = new FileOutputStream(targetFileName);
int readByteNo;
byte[] readBytes = new byte[100];
while((readByteNo = fis.read(readBytes)) != -1) {
fos.write(readBytes, 0, readByteNo);
}
fos.flush();
fos.close();
fis.close();
System.out.println("복사가 잘 되었습니다.");
}
}
4. FileReader
- 텍스트 파일로부터 데이터를 읽어 들일 때 사용
- 문자 단위로 읽기 때문에 텍스트가 아닌 그림, 오디오, 비디오 등의 파일은 읽을 수 없다.
import java.io.FileReader;
public class FileReaderExample {
public static void main(String[] args) throws Exception {
// 문자기반의 입력 스트림
FileReader fr = new FileReader("C:\\dev\\workspace\\java\\chap18\\src\\sec04\\exam04_file_reader\\FileReaderExample.java");
int readCharNo;
char[] cbuf = new char[100];
while((readCharNo = fr.read(cbuf)) != -1) {
String data = new String(cbuf, 0, readCharNo);
System.out.print(data);
}
fr.close();
}
}
5. FileWriter
- 텍스트 파일에 문자 데이터를 저장할 때 사용
- 텍스트가 아닌 그림, 오디오, 비디오 등의 데이터를 파일로 저장 불가
- 객체 생성 방법
- 파일이 이미 존재할 경우, 데이터를 출력하게 되면 파일을 덮어쓰게 되므로 기존의 파일의 내용은 사라지게 된다.
- 기존 파일 내용 끝에 데이터를 추가할 경우에는 FileWriter 생성자에 두 번째 매개값으로 true를 주면 된다.
ex) FileWriter fw = new FileWriter("C:/Temp/file.txt", true);
import java.io.File;
import java.io.FileWriter;
public class FileWriterExample {
public static void main(String[] args) throws Exception {
File file = new File("C:/Temp/file.txt");
FileWriter fw = new FileWriter(file, true);
// FileWriter : 문자기반의 파일 출력 스트림
// FileWriter(file, true) : 기존의 파일 내용 끝에 데이터를 추가하는 경우
fw.write("FileWriter는 한글로된 " + "\r\n"); // "\r\n" : 줄바꿈
fw.write("문자열을 바로 출력할 수 있다. " + "\r\n");
fw.flush();
fw.close();
System.out.println("파일에 저장되었습니다.");
}
}
[ 5 ] 보조 스트림 (* 프로그램 내에서 사용하기 편하게 보조 스트림을 사용한다. *)
- 다른 스트림과 연결 되어 다음과 같은 여러 가지 편리한 기능을 제공해주는 스트림이다.
- 문자 변환
: byte기반을 문자형으로 변환하는 것, InputStream --> Reader 로 변환
즉, 바이트기반의 입력 스트림을 문자기반의 입력스트림으로 변환하는 것을 의미한다.
- 입출력 성능 향상
: buffer를 사용해서 성능 향상시킨다.
- 기본 데이터 타입 입출력
- 객체 입출력
- 보조 스트림은 다음의 그림과 같이 또 다른 보조 스트림에도 연결되어 스트림 체인을 구성할 수 있다.
ex) 문자 변환 보조 스트림인 InputStreamReader를 다시 성능 향상 보조 스트림인 BufferedReader에
연결하는 코드는 다음과 같다.
--> InputStream은 바이트 기반의 입력 스트림이다.
InputStreamReader 문자기반의 입력 보조 스트림
1. 문자 변환 보조 스트림
- 소스 스트림이 바이트 기반 스트림이지만 데이터가 문자인 경우에 문자 변환 보조 스트림을 사용한다.
- Reader, Writer는 문자 단위로 입출력한다 --> 바이트 기반 스트림보다 편리
- 문자셋의 종류를 지정할 수 있기 때문에 다양한 문자 입출력이 가능하다.
( 1 ) InputStreamReader (문자로 변환해주는 보조 스트림)
//* 콘솔 입력 *//
InputStream is = System.in; // InputStream : 바이트 스트림
Reader reader = new InputStreamReader(is);
// 바이트 기반 스트림으로 만들어진 객체 is가 문자기반 reader로 변환되는 것
//* 파일 입력 *// ( 파일에서 입력 받는 것,,?)
FileInputStream fis = FileInputStream("C:/Temp/file.txt");
// 파일 C:/Temp/file.txt 에 있는 데이터를 바이트 기반으로 읽어온다.
Reader reader = new InputStreamReader(fis);
// 보조스트림 InputStreamReader를 사용해서 바이트 기반으로 읽어온것을 문자기반으로 바꾼다!
( 2 ) OutputStreamWriter ( 데이터를 문자로 입력하면 바이트로 변환시켜서 전송해주는 보조 스트림 )
//* 파일 출력 *//
FileOutputStream fos = FileOutputStream("C:/Temp/file.txt");
Writer writer = new OutputStreamWriter(fos); // 문자 -> 바이트로 변환해서 출력
2. 성능 향상 보조 스트림
* CPU가 데이터를 처리하는 시간 + CPU를 기다리는 시간 + 디스크(Disk) IO 시간 + 네트워크 IO 시간 + 서비스 IO 시간
이 모든 시간들을 합한게 응답시간이다. 이 응답시간의 대부분은 디스크 시간이 차지한다.
바이트 단위의 데이터 10개 있으면 10초가 걸린다고 하자. (1바이트에 1초)
이 데이터들을 처리하는 시간을 줄이는 방법에는 무엇이 있을까 ?
메모리 버퍼에 데이터를 모아놓았다가 한번에 디스크에 보내면 속도처리가 현저히 줄어든다.
--> 성능을 향상 시킨다는 것
따라서 버퍼를 사용하면 이와같이 성능을 향상 시킬 수 있다.
- 입출력 성능에 영향을 미치는 입출력 소스 : 하드 디스크, 느린 네트워크
- 버퍼를 이용한 해결 : 입출력 소스와 직접 작업하지 않고 버퍼(buffer)와 작업 --> 실행 성능 향상
( 1 ) BufferedInputStream 과 BufferedReader
* 데이터를 읽을 때도 1바이트 기준으로 읽는데 시간이 오래 걸리니깐
4바이트 10바이트 등 이렇게 덩어리로 읽어오면 더 빠르게 읽을 수 있다. --> 성능 향상
BufferedInputStream bis = new BufferedInputStream(바이트입력스트림);
BufferedReader br = new BufferedReader(문자입력스트림);
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class BufferedInputStreamExample {
public static void main(String[] args) throws Exception {
long start = 0;
long end = 0;
// 보조스트림 BufferedInputStream 사용하지 않은 경우
FileInputStream fis1 = new FileInputStream("C:\\dev\\workspace\\java\\chap18\\src\\sec05\\exam03_bufferedinputstream\\forest.jpg");
// FileInputStream : 바이트기반의 입력스트림
// 파일 시스템은 "절대 경로"로 적어져야 읽어올 수 있다.
// 절대 경로(path) ex) C:\\dev\\workspace\\...\\forest.jpg
// 상대 경로(path) ex) forest.jpg
start = System.currentTimeMillis();
while(fis1.read() != -1) {}
end = System.currentTimeMillis();
System.out.println("사용하지 않았을 때: " + (end-start) + "ms");
fis1.close();
// 보조스트림 BufferedInputStream 사용한 경우
FileInputStream fis2 = new FileInputStream("C:\\dev\\workspace\\java\\chap18\\src\\sec05\\exam03_bufferedinputstream\\forest.jpg");
BufferedInputStream bis = new BufferedInputStream(fis2);
start = System.currentTimeMillis();
while(bis.read() != -1) {}
end = System.currentTimeMillis();
System.out.println("사용했을 때: " + (end-start) + "ms");
bis.close();
fis2.close();
}
}
[ BufferedInputStream 코드 ]
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
public class BufferedReaderExample {
public static void main(String[] args) throws Exception {
// InputStream : 바이트 기반의 입력 스트림
InputStream is = System.in;
Reader reader = new InputStreamReader(is);
// 문자변환용 보조스트림 : 문자기반 <-- 바이트기반
BufferedReader br = new BufferedReader(reader);
// 성능향상용 보조스트림
System.out.print("입력 : ");
String lineString = br.readLine();
// .readLine() : 문자열로 변환시켜 읽어온다.
System.out.println("출력 : "+lineString);
}
}
[ BufferedReader 코드 ]
( 2 ) BufferedOutputStream 과 BufferedWriter
BufferedOutputStream bos = new BufferedOutputStream(바이트출력스트림);
BufferedWriter bw = new BufferedWriter(문자출력스트림);
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class BufferedOutputStreamExample {
public static void main(String[] args) throws Exception {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
int data = -1;
long start = 0;
long end = 0;
fis = new FileInputStream("C:\\dev\\workspace\\java\\chap18\\src\\sec05\\exam05_bufferedoutputstream\\forest.jpg");
bis = new BufferedInputStream(fis);
fos = new FileOutputStream("C:/Temp/forest.jpg");
//"C:/Temp/forest.jpg" 파일을 바이트기반 으로 만들겠다는 것
start = System.currentTimeMillis();
while((data = bis.read()) != -1) {
fos.write(data);
}
fos.flush();
end = System.currentTimeMillis();
fos.close(); bis.close(); fis.close();
System.out.println("사용하지 않았을 때: " + (end-start) + "ms");
fis = new FileInputStream("C:\\dev\\workspace\\java\\chap18\\src\\sec05\\exam05_bufferedoutputstream\\forest.jpg");
bis = new BufferedInputStream(fis);
fos = new FileOutputStream("C:/Temp/forest.jpg");
bos = new BufferedOutputStream(fos);
start = System.currentTimeMillis();
while((data = bis.read()) != -1) {
bos.write(data);
}
bos.flush();
end = System.currentTimeMillis();
bos.close(); fos.close(); bis.close(); fis.close();
System.out.println("사용했을 때: " + (end-start) + "ms");
}
}
[ BufferedOutputStream 코드 ]
3. 기본 타입 입출력 보조 스트림
- 바이트 스트림은 바이트 단위로 입출력하기 때문에 자바의 기본 데이터 타입인 boolean, char, short, int, long, float, double 단위로 입출력할 수 없다. 그러나 DataInputStream 과 DataOutputStream 보조 스트림을 연결하면 기본 데이터 타입으로 입출력이 가능하다.
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class DataInputOutputStreamExample {
public static void main(String[] args) throws Exception {
// FileOutputStream : 바이트기반의 파일 출력스트림
FileOutputStream fos = new FileOutputStream("C:/Temp/primitive.dat");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeUTF("홍길동");
// .writeUTF("홍길동") : UTF-8인코딩을 사용하여 출력 스트림에 문자열을 적는다.
dos.writeDouble(95.5);
// Double형으로 적었지만 바이트 단위로 파일에 적힌다.
dos.writeInt(1);
dos.flush();
dos.clo
4. 프린트 보조 스트림
- PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print(), println() 메소드를 가 지고 있는 보조 스트림이다.
import java.util.Date;
public class PrintfExample {
public static void main(String[] args) {
// PrintStream : OutputStream의 보조스트림
System.out.printf("상품의 가격:%d원\n", 123); // 상품의 가격:123원
System.out.printf("상품의 가격:%6d원\n", 123);// 상품의 가격: 123원
System.out.printf("상품의 가격:%-6d원\n", 123);// 상품의 가격:123 원
System.out.printf("상품의 가격:%06d원\n", 123);// 상품의 가격:000123원
System.out.printf("반지름이 %d인 원의 넓이:%10.2f\n", 10, Math.PI*10*10);
// 반지름이 10인 원의 넓이: 314.16
System.out.printf("%6d | %-10s | %10s\n", 1, "홍길동", "도적");
// 1 | 홍길동 | 도적
Date now = new Date();
System.out.printf("오늘은 %tY년 %tm월 %td일 입니다\n", now, now, now);
// 오늘은 2022년 12월 07일 입니다
System.out.printf("오늘은 %1$tY년 %1$tm월 %1$td일 입니다\n", now);
// 오늘은 2022년 12월 07일 입니다
System.out.printf("현재 %1$tH시 %1$tM분 %1$tS초 입니다\n", now);
// 현재 17시 35분 43초 입니다
}
}
[ Printf() 코드 ]
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamExample {
public static void main(String[] args) throws Exception {
// FileOutputStream : 바이트기반의 파일 출력스트림
FileOutputStream fos = new FileOutputStream("C:/temp/file.txt");
PrintStream ps = new PrintStream(fos);
ps.println("[프린터 보조 스트림]");
// println() :
ps.print("마치 ");
ps.println("프린터가 출력하는 것처럼 ");
ps.println("데이터를 출력합니다.");
// 바이트기반이라서 바이트로 데이터를 넣어야하는데 보조스트림 PrintStream을
// 사용해서 그에 대한 메서드를 사용함으로 문자열을 집어넣고 문자열을 출력하게 한다.
ps.flush();
ps.close();
fos.close();
}
}
[ PrintStream() 코드 ]
5. 객체 입출력 보조 스트림
* 파일에다 객체로 저장하고 싶다,, 객체를 넣고 보조 스트림이 바이트로 변환해서
파일에 넣어주는것,,,?
- 객체를 보낼 때 사용하는 보조 스트림이다.
- 객체를 파일 또는 네트워크로 입출력할 수 있는 기능 제공
* 파일에다 객체로 저장하고 싶다,, 객체를 넣고 보조 스트림이 바이트로 변환해서
파일에 넣어주는것,,,? 객체를 보낼 때 사용하는 보조 스트림이다.
- 객체 직렬화(serialization)
- 객체는 문자가 아니므로 바이트 기반 스트림으로 출력해야 한다.
- 객체를 출력하기 위해서는 객체의 데이터(필드값)를 일렬로 늘어선 연속적인 바이트로 변경해야 한다.
* 객체는 필드에 있다. 객체를 바이트로 변환하기 위해서는 그 객체들을 배열처럼 일렬로 만든다.
이것을 객체 직렬화 라고 한다. 직렬화를 시켜야 객체를 바이트만큼 쪼개서 전송할 수 있기 때문이다.
( 1 ) ObjectInputStream / ObjectOutputStream
- ObjectOutputStream : 바이트 출력 스트림과 연결되어 객체를 직렬화하는 역할
ObjectInputStream : 바이트 입력 스트림과 연결되어 객체로 역직렬화하는 역할
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectInputOutputStreamExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
// FileOutputStream : 바이트기반의 파일출력시스템
ObjectOutputStream oos = new ObjectOutputStream(fos);
// ObjectOutputStream : 객체 입출력 보조 스트림 중 출력보조스트림
oos.writeObject(new Integer(10));
oos.writeObject(new Double(3.14));
oos.writeObject(new int[] { 1, 2, 3 });
oos.writeObject(new String("홍길동"));
// writeObject(Object obj) : Object 타입의 값을 매개변수로 넣는다.
// 만들어진 객체들을 "C:/Temp/Object.dat" 파일에 보낸다.
oos.flush(); oos.close(); fos.close();
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Integer obj1 = (Integer) ois.readObject();
Double obj2 = (Double) ois.readObject();
int[] obj3 = (int[]) ois.readObject();
String obj4 = (String) ois.readObject();
ois.close(); fis.close();
System.out.println(obj1); // 10
System.out.println(obj2); // 3.14
System.out.println(obj3[0] + "," + obj3[1] + "," + obj3[2]); // 1,2,3
System.out.println(obj4); // 홍길동
}
}
// 읽을때도 쓸때도 바이트로 했다...?
[ 다양한 객체 쓰고 읽기 ]
( 2 ) 직렬화가 가능한 클래스 (Serializable)
- 자바에서는 Serializable 인터페이스를 구현한 클래스만 직렬화 할 수 있도록 제한했다.
--> transient 필드는 직렬화에서 제외된다.
- 객체 직렬화 할 때는 private 필드도 포함한 모든 필드를 바이트로 변환이 가능하다.
import java.io.Serializable;
public class ClassA implements Serializable {
int field1;
ClassB field2 = new ClassB();
static int field3;
transient int field4;
}
import java.io.Serializable;
public class ClassB implements Serializable {
int field1;
}
import java.io.Serializable;
public class ClassC implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5993977944878930441L;
// 클래스마다 있는 serialVersionUID! 내가 final로 지정해놓으면 이 클래스에
// 멤버를, 내용을 변경해도 오류없이 파일 입출력이 된다.
// serialVersionUID 를 가지고 파일을 가져온다.
int field1;
int field2;
//int field2;
// 이렇게 field2를 추가해서 클래스 내용을 변경하고 저장을 하면
// 새로운 클래스 파일이 만들어진다. 그 이전에 field1만 있던 ClassC와
// field1과 field2가 있는 ClassC 이렇게 두개가 생기게 되는 것이다.
// 파일을 읽어오는 애는 field1만 있는 ClassC를 불러오게 되는 것이고
// 이 ClassC 파일은 마지막으로 변경한 field1과 field2가 있는 ClassC 파일이
// 되니깐 서로 입출력 하는 클래스가 달라서 java.io.InvalidClassException 오류가 난다.
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ClassA classA = new ClassA();
classA.field1 = 1;
classA.field2.field1 = 2;
classA.field3 = 3;
classA.field4 = 4;
oos.writeObject(classA);
oos.flush(); oos.close(); fos.close();
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ClassA v = (ClassA) ois.readObject();
System.out.println("field1: " + v.field1);
System.out.println("field2.field1: " + v.field2.field1);
System.out.println("field3: " + v.field3);
System.out.println("field4: " + v.field4);
// 0 출력
// field4에는 transcient 키워드가 붙어있어서 객체 직렬화가 안된다.
}
}
[ Serializable 예시 코드 ]
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableWriter {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ClassA classA = new ClassA();
classA.field1 = 1;
classA.field2.field1 = 2;
classA.field3 = 3;
classA.field4 = 4;
oos.writeObject(classA);
oos.flush(); oos.close(); fos.close();
}
}
[ SerializableWriter 예시 코드 ]
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableReader {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ClassA v = (ClassA) ois.readObject();
System.out.println("field1: " + v.field1);
System.out.println("field2.field1: " + v.field2.field1);
System.out.println("field3: " + v.field3);
System.out.println("field4: " + v.field4);
}
}
[ SerializableReader 예시 코드 ]
( 3 ) serialVersionUID 필드
- 직렬화된 객체를 역직렬화 할 때는 직렬화 했을 때와 같은 클래스를 사용해야 한다.
* 직렬화된 객체를 바이너리한것을 역직렬화라고 한다,,,?
* 내가 보낸 클래스와 받는 클래스가 같아야 역직렬화가 된다.
이때 클래스 이름만 같다는 것이 아니라 클래스의 내용도 같아야 같은 클래스라는 것이다.
만일 객체의 이름 즉 클래스의 이름이 같은데 내용이 달라지거나 변경되면 같은것으로 보지 않아서
역직렬화가 되지 않는다. 이때 변경이 되었어도 같다는 것을 알려주는 아이가 있다.
--> serialVersionUID !!!
객체를 보낼 때는 serialVersionUID를 꼭 적어줘야한다!!
- 클래스의 이름이 같더라도 클래스의 내용이 변경되면, 역직렬화는 실패한다.
- serialVersionUID
- 같은 클래스임을 알려주는 식별자 역할을 한다.
- Serializable 인터페이스 구현 : 컴파일 시 자동적으로 serialVersionUID 정적 필드를 추가해준다.
- 다만, 재컴파일하면 serialVersionUID의 값이 변경된다.
* serialVersionUID를 안적어줘도 컴파일이 자동적으로 추가해주기는 하는데
컴파일이 또 재컴파일 될 때마다 달라지므로 객체가 직렬화가 안됨!!!
그래서 그냥 우리가 직접 박아주는게 제일 좋다!!
EX) static final long serialVersionUID = 정수값;
--> 이렇게 fianl로 적어주면 변경 절대 안되므로 변경될일이 없으니까 객체 보내줘도 이상 생길 일 없다!
- 불가피하게 수정을 해야 하는 경우 명시적으로 serialVersionUID를 선언해주면 오류가 발생하지 않는다.
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerialVersionUIDExample1 {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
// FileOutputStream : 바이트기반의 파일 출력 스트림
ObjectOutputStream oos = new ObjectOutputStream(fos);
// ObjectOutputStream : 객체 입출력 보조 스트림
ClassC classC = new ClassC();
classC.field1 = 1;
oos.writeObject(classC);
oos.flush(); oos.close(); fos.close();
}
}
[ SerialVersionUID 예시1 ]
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class SerialVersionUIDExample2 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ClassC classC = (ClassC) ois.readObject();
// 직렬화된 객체를 역직렬화 한 것
System.out.println("field1: " + classC.field1);
}
}
[ SerialVersionUID 예시2 ]
( 4 ) writeObject()와 readObject() 매소드
- 부모 클래스가 Serializable 구현하지 않고 자식 클래스가 Serializable 구현한 경우
--> 부모 필드는 직렬화에서 제외된다.
- 위의 경우, 부모 클래스의 필드를 직렬화 하려면
- 방법 1 : 부모 클래스가 Serializable 인터페이스를 구현하도록 한다.
* 부모 클래스 변경하는 경우
- 방법 2 : 자식 클래스에서 writeObject( ) 와 readObject( ) 메서드를 선언해서 부모 객체의 필드를 직접 출력시킨다.
* 부모 클래스는 안건드리고 자식 클래스를 변경한 경우
- writeObject( ) 메서드는 직렬화될 때 자동으로 호출되고, readObject( ) 메서드는 역직렬화될 때 자동으로 호출된다.
주의할 점!! 접근 제한자가 private이 아니면 자동으로 호출되지 않기 때문에 반드시 private을 붙여줘야한다.
public class Parent {
public String field1;
}
[ Serializable 구현하지 않은 부모 클래스 ]
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
// Serializable 를 구현한 Child 구현 클래스이면서 Parent 클래스의 자식 클래스
// writeObject()와 readObject() 매서드
public class Child extends Parent implements Serializable {
public String field2;
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(field1);
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
field1 = in.readUTF();
in.defaultReadObject();
}
}
[ 직렬화되지 않은 부모 클래스의 필드 처리 ]
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class NonSerializableParentExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
// 바이트기반의 파일 입출력스트림
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 객체입출력 보조스트림
Child child = new Child();
child.field1 = "홍길동";
child.field2 = "홍삼원";
oos.writeObject(child);
// Child 클래스를 구현클래스가 아닌 그냥 일반클래스로 만든다면
// oos.writeObject(child); 는 NotSerializableException 발생한다.
// 객체를 바이트로 쪼개려면 Serializable를 구현한 구현 클래스로 만들어진 객체여야한다.
// 그냥 일반 클래스로 객체를 만들면 바이트로 쪼갤 수 없다는 것이다.
// 따라서 그냥 일반 클래스로 객체를 만든다면 ObjectOutputStream 사용할 수 없다.
oos.flush(); oos.close(); fos.close();
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Child v = (Child) ois.readObject();
System.out.println("field1: " + v.field1);
System.out.println("field2: " + v.field2);
ois.close(); fis.close();
}
}
// 바이트기반의 출력 스트림을 만들고 보조 스트림을 사용한다.
// Child 객체를 만든다.
// 그 Child 클래스에 있는 필드에 값을 지정해서 넣어준다.
// writeObject()에 그 Child 객체를 넣는다.
// readObject() 로 그 Child 객체를 읽는다.
[ 직렬화 및 역직렬화 ]
[ 6 ] 네트워크 기초
1. 서버와 클라이언트 ( 요청과 응답으로 이루어져 있다.)
- 서버 : 서비스를 제공하는 프로그램
- 웹 서버, FTP서버, DBMS, 메신저 서버
- 클라이언트의 연결을 수락하고, 요청 내용 처리한 후 응답 보내는 역할
- 클라이언트 : 서비스를 받는 프로그램
- 웹 브라우저, FTP 클라이언트, 메신저(카카오톡,,)
- 네트워크 데이터를 필요로 하는 모든 애플리케이션이 해당(모바일 앱 포함)
2. IP 주소와 포트(Port)
- IP (Internet Protocol) 주소
- 네트워크상에서 컴퓨터를 식별하는 번호
- 네트워크 어댑터(랜 (Lan) 카드) 마다 할당된다.
- IP 주소 확인하는 방법 : 명렬 프롬포트 (cmd.exe) 사용
- XXX.XXX.XXX.XXX 의 형식으로 표현된다. (XXX 는 0 ~ 255 사이의 정수)
- 포트 (Port)
- 같은 컴퓨터 내에서 프로그램을 식별하는 번호
- 클라이언트는 서버 연결 요청 시 IP 주소와 Port를 같이 제공한다.
- 0 ~ 65535 범위의 값을 가진다.
- 포트의 범위는 다음의 그림과 같이 세가지로 구분된다.
3. InetAddress 로 IP 주소 얻기
- java.net.InetAddress
- IP 주소를 표현한 클래스이다.
- 로컬 컴퓨터의 IP 주소이다.
- 도메인 이름을 DNS에서 검색한 후 IP 주소를 가져오는 기능을 제공한다.
* IP 주소 얻는 순서
: DNS(Domain Name Server) 에서 내가 가고 싶은 서버의 이름을 검색한 후 IP 주소를 얻는다.
얻은 IP 주소를 가지고 내가 가고 싶은 서버에 간다.
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
InetAddress local = InetAddress.getLocalHost();
System.out.println("내컴퓨터 IP주소: " + local.getHostAddress());
// getHostAddress() : String 타입으로 내 컴퓨터의 IP 주소를 반환해준다.
InetAddress[] iaArr = InetAddress.getAllByName("www.naver.com");
// getAllByName() : 내 컴퓨터에 있는 도메인 네임 서버에 가서 내가 가고싶은
// 이름을 검색해서 IP 주소를 얻어서 그걸로 서버를 찾아가는 것이다.
for(InetAddress remote : iaArr) {
System.out.println("www.naver.com IP주소: " + remote.getHostAddress());
}
} catch(UnknownHostException e) {
e.printStackTrace();
}
}
}
[ 7 ] TCP 네트워킹
- 연결 지향적 프로토콜 --> 시간이 소요 된다.
- 통신 선로 고정 --> 전송 속도가 느려질 수 있다.
- 데이터를 정확하고 안정적으로 전달한다.
- TCP 네트워킹을 위해 java.net.ServerSocket, java.net.Socket 클래스를 제공하고 있다.
1. ServerSocket 과 Socket의 용도
2. ServerSocket 생성과 연결 수락
- ServerSocket 생성과 포트 바인딩
- 생성자에 바인딩 포트를 대입하고 객체를 생성한다.
- 연결 수락
- accept( ) 메서드는 클라이언트가 연결 요청하기 전까지 블로킹한다. --> 대기시킨다.
- 연결된 클라이언트의 IP 주소를 얻는다.
- ServerSocket 포트 언바인딩
- 더이상 클라이언트의 연결 수락이 필요 없는 경우 언바인딩 한다.
/* ServerSocket 생성과 포트 바인딩 */
ServerSocket serverSocket = new ServerSocket(5001); // 포트를 5001로 지정해서 ServerSocket 생성
// ServerSocket serverSocket = new ServerSocket( );
// serverSocket.bind(new InetSocketAddress("localhost", 5001));
// 아래 두줄과 맨 위의 한줄은 같은 것이다.
/* 연결 수락 */
Socket socket = serverSocket.accept( );
/* ServerSocket 포트 언바인딩 */
serverSocket.close( );
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerExample {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 5001));
// serverSocket = new ServerSocket(5001); 이 한줄이 위의 두줄과 같은 것
// localhost : 내 자신의 IP 주소 , 5001 : 포트
while(true) {
System.out.println("[연결 기다림]");
Socket socket = serverSocket.accept(); // 실행대기.. socket에 요청이 들어오기 전까지 대기
InetSocketAddress isa = (InetSocketAddress)socket.getRemoteSocketAddress();
// InetSocketAddress : SocketAddress의 자식 클래스로 강제 형변환 해줌
// getRemoteSocketAddress() 메서드가 SocketAddress 타입으로 리턴하기때문
System.out.println("[연결 수락함] " + isa.getHostName()); // getHostName : 127.0.0.1
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
[ 연결 수락 ]
3. Socket 생성과 연결 요청 ( 클라이언트 연결 )
- Socket 생성 및 연결 요청
- java.net.Socket 이용한다.
- 서버의 IP 주소와 바인딩 포트 번호를 제공하면 생성과 동시에 사용이 가능하다.
- 연결 끊기
- Exception 처리가 필요하다.
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
public class ClientExample {
public static void main(String[] args) {
Socket socket = null;
socket = new Socket();
System.out.println( "[연결 요청]");
try {
socket.connect(new InetSocketAddress("192.168.30.199", 5001));
System.out.println( "[연결 성공]");
} catch(Exception e) {
e.printStackTrace();
}
if(!socket.isClosed()) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
[ 연결 요청 ]
4. Socket 데이터 통신
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
public class ClientExample {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket();
System.out.println( "[연결 요청]");
socket.connect(new InetSocketAddress("192.168.30.199", 5001));
System.out.println( "[연결 성공]");
byte[] bytes = null;
String message = null;
OutputStream os = socket.getOutputStream(); // 데이터 전달, 출력하기
message = "안녕하세요 저는 길현지입니다 :0";
bytes = message.getBytes("UTF-8"); // 문자열 -> 바이트 배열 : 인코딩
os.write(bytes);
os.flush();
System.out.println( "[데이터 보내기 성공]");
InputStream is = socket.getInputStream(); // 데이터 받기
bytes = new byte[100];
int readByteCount = is.read(bytes);
message = new String(bytes, 0, readByteCount, "UTF-8");
System.out.println("[데이터 받기 성공]: " + message);
os.close();
is.close();
} catch(Exception e) {}
if(!socket.isClosed()) {
try {
socket.close();
} catch (IOException e1) {}
}
}
}
[ 데이터 보내고 받기 ]
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerExample {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 5001));
while(true) {
System.out.println( "[연결 기다림]");
Socket socket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("[연결 수락함] " + isa.getHostName());
byte[] bytes = null;
String message = null;
InputStream is = socket.getInputStream(); // 데이터 받는
bytes = new byte[100];
int readByteCount = is.read(bytes);
message = new String(bytes, 0, readByteCount, "UTF-8"); // 바이트 -> 문자열 : 디코딩
System.out.println("[데이터 받기 성공]: " + message);
OutputStream os = socket.getOutputStream(); // 데이터 출력, 보내는
message = "Hello Client";
bytes = message.getBytes("UTF-8"); // 문자열 -> 바이트 배열 : 인코딩
os.write(bytes);
os.flush();
System.out.println( "[데이터 보내기 성공]");
is.close();
os.close();
socket.close();
}
} catch(Exception e) {}
if(!serverSocket.isClosed()) {
try {
serverSocket.close();
} catch (IOException e1) {}
}
}
}
[ 데이터 받고 보내기 ]
5. 스레드 병렬 처리 ( 스레드별로 소켓을 만들어서,,? 여러개의 클라이언트를 스레드로 처리하는 것 )
- Accept( ), connect( ), read( ), write( )는 별도의 작업 스레드를 생성한다.
- 스레드풀을 사용해서 스레드 수 관리
- 스레드풀은 스레드의 수를 제한해서 사용한다.
- 갑작스러운 클라이언트의 폭증은 작업 큐의 작업량만 증가시킨다.
6. 채팅 서버 구현
( 1 ) 서버 클래스 구조
( 2 ) startServer( ) 메서드
- Executor Service 생성, ServerSocket 생성 및 포트 바인딩, 연결 수락 코드가 있다.
( 3 ) stopServer( ) 메서드
- 연결된 모든 Socket 닫기, ServerSocket 닫기, ExecutorService 종료 코드가 있다.
( 4 ) Client 클래스
- 다수의 클라이언트 관리 --> 연결 수락할때 마다 Client 인스턴스를 생성해서 관리하는 것이 좋다.
( 5 ) UI 생성 코드
[ 8 ] UDP 네트워킹
- UDP(User Datagram Protocol)의 특징
- 비연결 지향적 프로토콜
- 연결 절차를 거치지 않고 발신자가 일방적으로 데이터를 발신하는 방식
- TCP 보다 전송시간이 빠르다.
- 통신 선로가 고정적이지 않다.
- 데이터 패킷들이 서로 다른 통신 선로 통해서 전달될 수 있다.
- 먼저 보낸 패킷이 느린 선로를 통해 전송될 경우, 나중에 보낸 패킷보다 늦게 도착할 수 있다.
- 데이터 손실 발생 가능성이 있다.
- 일부 패킷은 잘못된 선로로 전송되어 유실 가능
- 데이터 전달의 신뢰성이 떨어진다.
* 패킷을 라우터 라는 애가 받아서 어디로 데이터를 보낼지 판단해서 보내주는 아이이다.
근데 이 라우터가 하나만 있는게 아니라 여러개라는 것이다!
즉, 라우터가 통신 선로라고 생각하면 통신선로가 여러개이니깐 서로 다른 통신선로로 통해서
데이터가 전달되다 보니 데이터 패킷들이 다같은 시간에 보내지는 것이 아니라서 시간차이가 있게
데이터가 도착할 수 있고, 중간에 데이터가 어디로 갔는지 알 수 없어서 유실될 경우도 생긴다.
- java.net API
- DatagramSocket : 발신자는 DatagramSocket을 만들어서 데이터 패킷을 보낸다.
- DatagramPacket : 수신자는 DatagramPacket을 만들어서 데이터 패킷을 받는다.
1. 발신자 구현
- 소켓을 통해 데이터 패킷을 전송한다.
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UdpSendExample {
public static void main(String[] args) throws Exception {
/* 발신자 구현 : 소켓 통해 데이터 패킷 전송
* 1) DatagramSocket 생성
* 2) DatagramPacket 생성
* 3) DatagramPacket 발송
* 4) DatagramSocket 닫기
*/
DatagramSocket datagramSocket = new DatagramSocket();
System.out.println("[발신 시작]");
for(int i = 1; i < 3; i++) {
String data = "메세지" + i + "from 길현지";
byte[] byteArr = data.getBytes("UTF-8");
// 보내는 데이터를 UTF-8 로 인코딩(문자 -> 바이트)
// UTF-8은 가변형(한글-3bytes, 영자-1byte) 문자형
DatagramPacket packet = new DatagramPacket
(byteArr, byteArr.length,
new InetSocketAddress("192.168.30.199", 5001));
datagramSocket.send(packet);
System.out.println("[보낸 바이트 수]: " + byteArr.length + "bytes");
}
System.out.println("[발신 종료]");
datagramSocket.close();
}
}
2. 수신자 구현
- 바인딩한 특정 포트로 데이터를 받아 저장한다.
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceiveExample extends Thread {
public static void main(String[] args) throws Exception {
/* 수신자 구현 : 바인딩한 특정 포트로 데이터 받아서 저장
* 1) DatagramSocket 생성
* 2) DatagramPacket 생성
* 3) DatagramPacket 수신
* 4) DatagramScoket 닫기
*/
DatagramSocket datagramSocket = new DatagramSocket(5001);
// Thread 하위 클래스로부터 작업 스레드 생성 : 익명 자식 객체 사용
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("[수신 시작]");
try {
while(true) {
DatagramPacket packet = new DatagramPacket(new byte[100], 100);
// 공간 100개짜리 바이트 배열 만들어서 packet에 넣음
datagramSocket.receive(packet);
// 소켓이 들어올 때까지 즉, 데이터가 입력 될 때까지 대기한다.
String data = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
// packet에서 데이터를 가져오는데 0번째 인덱스부터 packet의 길이만큼 가져오고
// 데이터를 가져올 때 "UTF-8"로 디코딩(byte 바이트->String 문자열)해서 가져온다.
System.out.println("[받은 내용: " + packet.getSocketAddress()+ "] " + data);
}
}catch(Exception e) {
System.out.println("[수신 종료]");
}
}
};
thread.start();
// 작업 스레드를 병렬로 실행시킨다.
Thread.sleep(10000);
// 10000mill = 10초 동안 메인스레드를 일시정지 상태로 만든다.
datagramSocket.close();
}
}
'kh-정보교육원' 카테고리의 다른 글
23,24일차_배운 내용(여전히 코생아) (2) | 2022.12.06 |
---|---|
22일차(2)_배운 내용(어김없이 코생아) (0) | 2022.12.04 |
22일차(1)_배운 내용(어김없이 코생아) (1) | 2022.12.01 |
21일_배운 내용(오늘도 코생아) (0) | 2022.12.01 |