파일을 읽고 쓰는 기능
: 데이터가 메모리에 들어오고 나가는 것을 I/O 입출력이라고 합니다.
이것을 쉽게 이해하기 위해서 먼저 Stream을 살펴보겠습니다.
•
본 내용의 예제는 MDIS의 메타 데이터를 활용하여 데이터 분석을 했습니다
I/O(Input, Output)입출력
•
입력 : 키보드, 마우스, 바코드 등의 입력장치로부터 들어오는 데이터
(메모리에 들어오는 것)
•
출력 : 프로그램으로부터 화면, 파일 등으로 나가는 보여지는 데이터
(같은 메모리에 있어도 데이터가 나가면 출력/프로그램을 기준으로 생각)
I/O Stream
수 많은 입력장치로 들어오는 데이터를 표준화하여 드라이버를 사용해 번역하여 사용할 수 있도록 만들어졌다.
프로그래머는 드라이버가 번역한 내용으로 작업하는 것.
Stream (스트림)
: 스트림을 데이터를 가져오는 하나의 빨대라고 생각해 보겠습니다.
•
프로그램과 I/O 객체를 연결하여 데이터의 흐름을 연결해주는 통로 (빨대)
•
한 방향으로 연결!
•
데이터가 한꺼번에 들어오는것이 아니다.
(데이터가 줄 서서 1byte씩 들어온다고 생각)
•
try~catch 예외처리를 해줘야한다.
FileNotFoundException, IOException
Stream은 두 가지로 나뉜다
1.
바이트 스트림(Byte Stream)
•
1과 0으로 구성 (이미지, 음악 등 눈으로 보여지지 않는것)
•
1byte를 출력하기 때문에 일반적으로 영문자로 구성된 파일, 동영상, 음악 파일등에 적합한 스트림
2.
문자 스트림(Character Stream)
•
유니코드로 된 문자를 입, 출력하는 스트림
•
바이트 스트림 + Reader, Writer 클래스 제공
(2byte를 입출력 할 수 있는 문자 기반 스트림)
01.바이트 스트림
02.문자 스트림 구조 Reader / Writer
바이트 스트림과 문자 스트림 특징
01. 바이트 스트림은 다른 프로그램을 이용할 경우 문제가 발생할 수 있다.
•
바이트 스트림은 데이터를 있는 그대로 송수신 하는 스트림이다.
문자를 파일에 저장하는 것도 가능하며 저장된 데이터를 자바 프로그램을 이용해서 읽으면 문제되지 않지만 다른 프로그램을 사용할 경우 문제가 발생할 수 있다.
02. 바이트 스트림과 문자 스트림의 특징
•
운영체제별로 고유의 문자 표현 방식이 존재한다.
해당 운영체제에서 동작하는 프로그램은 해당 운영체제의 문자 표현 방식을 그대로 따른다
따라서 파일에 저장된 데이터는 해당 운영체제의 문자 표현 방식으로 저장되어 있어야 한다.
•
문자 스트림은 해당 운영체제의 문자 인코딩 기준을 따라 데이터 입출력 수행한다.
(윈도우에서는 윈도우, 리눅스에서는 리눅스 기분 문자 인코딩하여 입출력 수행)
대부분의 문자 스트림은 바이트 스트림과 1대 1의 대응 구조를 가지고있다.
바이트 스트림과 문자 스트림 대응표
예제) 메타 데이터 분석
•
2021 서울에서 가장 많이 이사간 지역은 어디인지 분석
•
MDIS 사이트 인구 이동 CSV 데이터 추출
◦
CSV : Comma Separated Values
쉼표로 구분된 값 파일(.csv)
01.BufferedReader / FileReader
•
FileReader를 통해 데이터를 읽어 온다.
//데이터 한줄씩 읽어오는 메서드
public List<PopulationMove> readByLine(String filename) throws IOException {
List<PopulationMove> pml = new ArrayList<>(); //리스트
BufferedReader br = new BufferedReader(
new FileReader(filename)
); // 파일 읽어올 준비
String str;
while ((str = br.readLine()) != null) {//데이터가 존재하지 않을때까지
PopulationMove pm = parse(str); //한 라인씩 데이터 읽어온다
pml.add(pm);//리스트에 넣어 준다
}
br.close();
return pml; //리스트 값 반환
}
Java
복사
02.읽어온 데이터 PopulationMove 객체화
객체 생성
public class PopulationMove {
private int fromSido;
private int toSido;
public PopulationMove() {
}
public PopulationMove(String fromSidoStr, String toSidoStr) {
this.fromSido = Integer.parseInt(fromSidoStr);
this.toSido = Integer.parseInt(toSidoStr);
}
public PopulationMove(int fromSido, int toSido) {
this.fromSido = fromSido;
this.toSido = toSido;
}
public int getFromSido() {
return fromSido;
}
public int getToSido() {
return toSido;
}
}
Java
복사
생성자 오버로딩
: case 01. 필드 생성자
public PopulationMove parse(String data) {//데이터 받기
String[] splitLine = data.split(",");
Integer fromSido = Integer.valueOf(splitLine[0]);//0번 전입 , 6번 전출
Integer toSido = Integer.valueOf(splitLine[6]);
return new PopulationMove(fromSido, toSido);//from, to
}
Java
복사
//기존 필드 생성자
public PopulationMove(int fromSido, int toSido) {
this.fromSido = fromSido;
this.toSido = toSido;
}
Java
복사
: case 02. 생성자에서 타입 변경
public PopulationMove parse(String data) {//데이터 받기
String[] splitLine = data.split(",");
return new PopulationMove(splitLine[0], splitLine[6]);//from, to
}
Java
복사
//데이터를 가공할 때 String을 그대로 받아서 생성자를 통해 Integer로 변경
public PopulationMove(String fromSidoStr, String toSidoStr) {
this.fromSido = Integer.parseInt(fromSidoStr);
this.toSido = Integer.parseInt(toSidoStr);
}
Java
복사
parsing(파싱)
•
웹 페이지에서 원하는 데이터를 추출하여 가공하기 쉬운 상태로 변경하는 것
◦
parser : 데이터를 다루기 쉬운 형태로 바꿔주는 역할
◦
parsing : 과정
03. 데이터 분석 (Count)
•
객체화하여 List로 받은 데이터로 이사간 count 구하기!
•
Map<key,value>은 키와 값을 하나의 쌍으로 저장하는 방식으로 키(key)는 값(value)을 찾기 위한 이름 역할을 한다.
Map<key (fromSido, toSido) : value (이사간 수 Count)>
public Map<String, Integer> getMoveCntMap(List<PopulationMove> pml) {
Map<String, Integer> moveCntMap = new HashMap<>();
for (PopulationMove pm : pml) {
String key = pm.getFromSido() + "," + pm.getToSido();
if (moveCntMap.get(key) == null) {
moveCntMap.put(key, 1);
}
moveCntMap.put(key, moveCntMap.get(key) + 1);
}
return moveCntMap;
}
Java
복사
•
결과
04. 파일 생성
•
main에서 createNewFile()메서드를 호출한다 (파일을 생성할 경로 전달)
String targetName = "each_sido_cnt.txt";
ps.createAFile(targetName);
Java
복사
•
createNewFile() : 매개변수로 받은 경로에 빈 파일 생성
public void createAFile(String filename) {
File file = new File(filename);
try {
file.createNewFile();//이때 파일이 생성되는 것
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Java
복사
파일 생성 결과
05. 생성한 파일에 write
// List<String>을 지정한 파일에 write
public void write(List<String> strs, String filename) {
File file = new File(filename);
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for (String str : strs) {
writer.write(str);
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Java
복사
Main
public static void main(String[] args) throws IOException {
String address = "./from_to.txt";
PopulationStatistice ps = new PopulationStatistice();
List<PopulationMove> pml = ps.readByLine(address);//파일 읽어온뒤 객체로 만들어서 리스트에 담기
Map<String, Integer> moveCntMap = ps.getMoveCntMap(pml);//리스트 Map에 담기
String targetName = "each_sido_cnt.txt";
ps.createAFile(targetName);
List<String> cntResult = new ArrayList<>();
for (String key : moveCntMap.keySet()) {
String[] fromTo = key.split(",");
String s = String.format("[%s, %s, %d]\\n", fromTo[0], fromTo[1], moveCntMap.get(key));
cntResult.add(s);
//System.out.printf("key:%s value:%d\\n", key, moveCntMap.get(key));
}
ps.write(cntResult, targetName);
}
Java
복사
Method 설계
Method란?
: 특정한 행동으로 정의하여 선언과 호출을 통해 그대로 실행되도록 구현하는 것
•
Method 설계
◦
소스 코드의 가독성을 위해 하나의 메서드 안에 모든 행동을 넣는 것이 아니라
비슷한 성격의 행동을 모아서 하나의 동작만 하도록 설계하여 구현하는 것이 좋다
◦
코드가 여러 번 반복되어 사용된다면
해당 메서드를 작은 단위로 하나의 동작으로 묶어 재사용이 가능하도록 설계하는 것이 좋다.
return 타입
: 메서드 선언시에는 리턴타입을 지정해준다 이때 리턴 타입에는 void와 자료형이 들어갈 수 있는데,
void는 return 받을 데이터가 없다는 것을 의미하고 자료형으로 선언한 경우 해당 자료형으로 값을 반환해줘야 한다.
디버거
: 버그는 프로그램이 의도하지 않은 방향으로 실행되거나 오류가 발생하는 등
프로그램의 임무를 정상적으로 실행하는 못하는 오작동을 의미한다.
•
버그를 잡는 행위 : 디버깅
•
디버깅을 사용할 때 사용하는 도구 : 디버거
1.
브레이크 포인터(빨간점)를 찍은 뒤 디버깅 모드 실행
2.
프로그램을 한 단계씩 실행하며 확인 할 수 있다.(에러가 나는 부분에 어떤 값이 들어오는지 확인)