728x90

홈화면 추가

웹브라우저 url에 http://localhost:8080을 입력해서 접속하면 위의 HomeController의 home메소드가 실행된다.

Return "home"은 tempaltes의 home.html을 반환한다는 의미이다.

 

 

home.html

members/new 로 가게하는 회원가입 과 /members 로 이동시키는 회원 목록 이 있다.

home.html 출력화면

위 파일 목록을 보면 원래 home의 디폴트는 static/index.html 이였다.

home의 디폴트가 home.html이 되었는데 그 이유는 요청이 들어올 때, 스프링 컨트롤러에서 해당 요청에 해당하는 메소드를 찾고 해당하는것이 없을 경우 static내에서 파일을 찾아 출력해주기 때문이다.

위의 코드에서는 컨트롤러에 @GetMapping("/") 에 해당하는 home 메소드가 존재하기 때문에 해당 메소드를 실행시켜 home.html을 반환하는 것이다.

 

회원가입

/member/new로 Get 요청이 들어오는 경우 실행되는 createForm 메소드이다. members/createMemberForm.html

을 반환한다.

createMemberForm.html

이름을 입력받아 회원가입하고 해당 데이터를 /member/new 경로에 post방식으로 전송한다.

createMemberForm.html 출력

등록을 누를경우 해당 데이터가 /member/new 경로로 post 데이터 전송을 한다.

name을 받기위해 Controller 디렉토리에 추가해준 MemberForm이다.

createMemberForm에서 name을 반환할 경우 해당 값을 받기 위한 클래스이다.

 

create메소드

 

createMemberForm에서 post보낸 데이터를 받는 메소드이다.

post 전송방식에 매핑하기 위해  @PostMapping을 이용하고 createMemberForm에서 받은 정보가 MemberForm form으로 전송되고 해당 정보를 새로 생성한 member객체에 넣어 memberService.join을 통해 메모리에 저장한다.

해당 동작이 끝나면 home 화면으로 redirect 시켜준다.

 

회뭔 목록 조회

memberService.findMembers를 통해 저장된 회원들의 정보를 모두 가져온다.

model 객체에 addAttribute를 통해 가져온 정보를 model객체에 저장하고 해당 정보를 members/memberList에 반환한다.

members/memberList

Thymeleaf를 사용해 모든 회원들의 정보를 출력시킨다.

${member.id}는 member.getId() 메소드의 결과를 반환하고, ${member.name}은 member.getName() 메소드의 결과를 반환하여 출력한다.

 

3명의 회원을 등록하고 members 로 get요청하여 memberList.html을 출력한 결과이다.

728x90

'Programming > Spring' 카테고리의 다른 글

스프링 빈과 의존관계  (0) 2022.01.06
회원 관리 예제  (0) 2021.12.30
웹개발 기초  (0) 2021.12.21
728x90

MemberController에서 MemberService를 통해 회원가입을 하고 MemberService를 통해서 데이터를 조회할 수 있어야 한다.

MemberController와 MemberService는 의존관계이다.

MemberController가 MemberService를 의존한다.

 

스프링이 시작할 때 스프링 컨테이너에 @Controller, @Service, @Repository 등의 어노테이션을 가진 클래스 객체들을 생성하고 스프링 빈으로 등록해서 관리한다.

 

스프링 컨테이너 : 스프링에서 객체를 관리하는 것. 객체의 생명주기를 관리하고 컨테이너에 담겨있는 객체들을 스프링 빈(Bean)이라고 부른다.

 

@Controller, @Service, @Repository 어노테이션이 존재하는 클래스들의 경우 객체를 스프링 빈(Bean)으로 등록해 스프링 컨테이너에서 관리한다.

 

위처럼 MemberService() 객체를 new로 할당해서 사용할 수도 있지만, Spring 컨테이너에 저장된 빈을 불러서 사용하면된다.

new를 통해 객체를 생성해서 할당할 경우, 모든 Controller가 각각의 Service 객체를 사용하게 된다.

-> 자원이 낭비되며 비효율 적이다.

스프링 컨테이너에는 각 객체들이 하나만 빈으로 등록되어 해당 빈을 공유하는 형태로 사용하기 때문에 new를통한 객체 할당을 통해서 사용하는것보다 훨씬 효율적으로 사용이 가능하다.

생성자에 @Autowired 어노테이션을 사용하면 스프링 컨테이너에 빈으로 존재하는 MemberService 객체를 연결해 준다.

-> MemberController가 memberService를 의존하게 된다. (Dependency Injection : 의존성 주입)

 

@Controller 어노테이션이 존재하는 MemberController 클래스는 스프링이 시작할 때 자동으로 스프링 컨테이너에 스프링 빈으로 등록된다.

@Autowired 어노테이션을 통해 스프링 빈을 연결하는 경우 해당 객체또한 스프링 컨테이너의 스프링빈으로 등록되어 있어야한다. 즉 위 코드에서는 memberService가 스프링 빈으로 등록되어 있어야 한다는 것이다.

@Service 어노테이션의 경우 @Controller와 마찬가지로 스프링이 시작할때 컴포넌트 스캔에 의해 스프링 빈으로 등록된다. MemberService에서도 생성자에서 @Autowired를 사용하는데 이때 사용되는 MemberRepository 또한 스프링 빈으로 등록되어 있어야한다.

@Repository 어노테이션도 스프링 시작시 스프링 빈으로 등록시켜준다.

 

코드들은 위 그림과 같이 의존관계가 연결된다.

 

 

컴포넌트 스캔과 자동 의존관계 설정

@Controller, @Service, @Repository와 같이 어노테이션으로 스프링 빈을 등록하는 경우는 컴포넌트 스캔 방식에 해당한다. @Component 어노테이션이 스프링 빈으로 등록하는 어노테이션이지만, @Controller, @Service, @Repository 어노테이션의 내부에 @Component가 존재해 세가지 어노테이션 모두 스프링 빈으로 등록된다.

 

스프링이 시작할 때 @Component 어노테이션들을 스캔해서 스프링 빈으로 등록하는 것을 컴포넌트 스캔이라고 한다.

@Autowired는 스프링빈을 연결하는 역할을 한다.(의존 관계 설정)

 

@Component스캔의 범위는 @SpringBootApplication 어노테이션이 존재하는 클래스의 패키지와 해당 패키지의 하위패키지에서만 작동한다.

@SpringBootApplication 어노테이션 내부에 @ComponentScan 어노테이션이 존재한다.

 

**스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본적으로 싱글 톤 등록을 한다.**

싱글 톤 : 하나만 등록하여 해당 객체를 공유한다.

예를 들어 MemberController의 경우도 하나의 객체만 컨테이너에 스프링 빈으로 등록해서 공유한다. MemberController 객체가 여러개 스프링빈으로 등록되지않는다.

 

자바 코드로 직접 스프링 빈 등록하기

@Service, @Repository, @Autowired 어노테이션을 제거한 후 코드를 작성한다.

SpringConfig 파일을 새로 하나 생성한다.

@Configuration 어노테이션은 설정 파일을 만들기 위한 어노테이션이며, Bean을 등록하기 위한 어노테이션이다.

@Configuration 클래스 내부에 @Bean 어노테이션 작성시 해당 객체를 Bean에 등록한다는 의미를 가진다.

위 코드는 MemberService와 MemoryMemberRepository 객체를 Bean으로 등록하는 코드이다.

 

위코드를 통해 코드로 직접 스프링 빈을 등록한 경우에도 아래처럼 구조가 가능해진다.

스프링이 시작될 때, memberRepository와 memberService를 스프링 컨테이너에 올려 빈으로 등록한다.

MemberService 생성자에 파라미터로 memberRepository를 넣어주는데 이때 들어가는 memberRepository는 빈으로 등록된 memberRepository이다.

 

자바코드로 직접 등록할 때에도 Controller는 컴포넌트 스캔으로 올라가야 하기 때문에 @Controller 어노테이션을 꼭 써줘야한다. 또한 Controller 생성자에서 스프링 빈 객체를 가져올 때도 컴포넌트 스캔 방법인 @Autowired 어노테이션을 작성해주어야한다.

 

Dependency Injection의 3가지 방법

1.생성자 주입(생성자에 @Autowired 어노테이션 사용)

2.필드주입(필드에 @Autowired 어노테이션 사용)

3.Setter 주입(set 메소드가 public으로 사용되며 어디서든 호출이 가능해져 개발중 문제가 생길 수 있다.)

 

가장 좋은 방법은 생성자 주입이다.(생성자에 @Autowired 어노테이션을 통해 빈 객체를 연결하는것)

 

실무에서는 주로 Controller, Service, Repository와 같은 코드들은 컴포넌트 스캔을 사용한다고 한다. 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하는 경우 설정을 통해 코드로 직접 스프링 빈으로 등록한다고 한다.

 

MemberRepository의 경우 Memory를 사용하는 구현체에서 DB를 사용하는 구현체로 바꿀 예정이기 때문에 컴포넌트 스캔이아닌 코드로 직접 빈에 등록하는 코드를 남겨둔다.

 

@Autowired를 통해 Dependency Injection을 하는 경우 MemberController와 MemberService 와 같이 스프링이 관리하는 객체 즉 스프링 빈 객체들에 한해서만 가능하다. 스프링 빈으로 등록되지 않은 객체들은 @Autowired 사용이 불가능하다.

 

728x90

'Programming > Spring' 카테고리의 다른 글

웹 MVC 개발  (0) 2022.01.08
회원 관리 예제  (0) 2021.12.30
웹개발 기초  (0) 2021.12.21
728x90

비즈니스 요구사항 정리 및 설계

데이터 : 회원ID, 이름

기능 : 회원 등록, 조회

 

백엔드 계층 구조

컨트롤러 : MVC 컨트롤러 역할

서비스 : 도메인 객체를 이용한 핵심 비즈니스 로직이 구현됨

도메인 : 회원, 주문, 쿠폰 등과 같이 데이터베이스에 저장되고 관리되는 비즈니스 도메인 객체

리포지토리 : 데이터베이스에 접근하여 도메인 객체를 DB에 저장하고 관리하는곳

 

아래 프로그램에서는 DB없이 메모리를 저장공간으로 사용한다.

 

도메인

데이터인 회원 ID와 name이 들어간 Member객체이다.

회원 도메인과 리포지토리 만들기

MemberRepository interface이다.

리포지토리는 도메인 객체를 DB에 저장하고 관리하는곳이기 때문에 domain.Member를 import한다.

이후 Member들을 DB에 저장하거나 관리하는 메소드들을 선언한다.

DB에 따라 해당 인터페이스를 상속받아 구현한다.

 

Memory를 DB로 구현한 MemoryMemberRepository

MemberRepository를 상속받아 MemoryMemberRepository를 구현한다.

해당 인터페이스의 save, findById, findByName, findAll 메소드들을 해당 클래스에서 구현한다.

1씩 sequence를 더하면서 id값을 부여, store(Map)라는 임시저장소(Memory DB)에 member 저장

 

findById : id값과 일치하는 id를 store에서 탐색

Null이 반환될 수 있기 때문에 Optional.ofNullable로 탐색

 

 

findByName :  name값을 기반으로 store에 저장된 값 탐색

stream()의 경우 for문으로 모든원소에 접근하듯이 모든 원소들에대해 접근한다고 생각하면 된다.

filter를 통해 name과 일치하는 멤버들을 걸러내고, findAny()를 통해 걸러진 값을 반환한다.

즉 store에 저장된 값중 이름이 name과 일치하는 경우를 반환한다.

 

모든 값들을 Member List로 반환한다.

 

회원 리포지토리 Test코드 작성

개발한 기능들을 main메서드를 실행시켜 테스트하는 경우 시간이 오래걸리고, 한번에 모두 테스트하기 어렵다는 단점이 있기 때문에 테스트코드를 생성하여 메소드들을 테스트하는것이 좋다.

test내에 리포지토리 패키지를 생성하여 test클래스를 작성해준다.

@Test 어노테이션을 사용하여 해당 메소드가 테스트 메소드임을 명시해준다.

위처럼 해당 테스트 메소드를 구현한뒤 해당 메소드만 실행시켜 테스트가 가능하다.

save를 실행시킬경우 해당 테스트코드 내에서 오류가 발생하거나 실패를 하게되면 해당 실행에서 경고를 반환한다.

테스트코드가 성공할경우 아래처럼 정상 종료가 된다.

테스트코드는 항상 해당 코드를 실행시키고 검증까지 완료해야한다. save 메소드의 경우 Member 데이터 생성과 save함수 실행, 이후 findById를 통해 해당 id를 검색하고 assertThat(member).isEqualTo(result)를 통해 검증을 완료했다.

 

findByName 테스트 코드

findByName을 검증하기 위해 데이터를 만들고 findByName을 통해 result를 반환받고 assertThat(result).isEqualTo(member1)을 통해 검증했다.

 

findAll() 테스트 코드

2개의 member를 생성 및 save하고 findAll()을 실행한다.

이후 findAll()의 반환의 결과가 2개 인지 확인하여 검증한다.

 

테스트코드의 경우 모든 메소드들을 한꺼번에 실행하여 테스트하는것도 가능하다.

여러 메소드를 한꺼번에 실행할 경우 각 메소드들이 순서와 상관없이 실행된다.

각 메소드들이 실행되면서 같은 데이터를 save하는 경우 에러가 발생한다.(중복 저장 예외)

이러한 문제를 해결하기 위해 아래의 코드를 추가해준다. 

@AfterEach는 해당 클래스내의 각 메소드들이 실행을 끝낼때 마다 실행되게하는 어노테이션이다.

 

MemoryMemberRepository의 clearStore메소드

위 메소드를 통해 각 메소드들이 끝날 때마다 저장소를 비워준다.

-> 중복 저장 예외를 방지할 수 있다.

 

위의 개발과정은 MemoryMemberRepository를 모두 구현하고, 테스트 코드를 통해 해당 클래스의 메소드들을 검증 했다. 해당 개발 과정을 뒤집어 테스트 코드를 먼저 작성하고 검증이 완료된 후 메소드를 구현할 경우 테스트 주도개발(TDD)이라고 한다.

 

MemberService 메소드 작성

Join() 메소드

join 내부에서 member중복을 검사하는 코드를 작성한 뒤 외부의 새로운 메소드로 생성했다.

ifPresent를 통해 findByName의 반환값이 존재한다면 throw new IllegalStateExceptioon("이미 존재하는 회원입니다.")를 실행시킨다.

ifPresent는 값이 존재할 경우 내부 로직을 실행시키는 기능을 제공한다.

 

위와 같이 MemberService 코드를 구현했다.

회원가입, 전체 회원조회, 회원ID를 통한 회원 조회 등 비즈니스적인 메소드들이 구현된다.

 

MemberService 테스트 코드 구현

위처럼 테스트코드의 메소드명을 한글로 지정해도 상관없다.

 

테스트 코드를 구현할 때 given-when-then 구성으로 코드를 작성하는것이 좋다.

given : 주어지는 것

when : 실행했을 때

then : 결과

 

어떤 것이 주어진 상황에서 코드를 실행했을 때 결과가 이렇게 나와야 한다는 과정을 정리하여 작성하는 방법이다.

given-when-then 의 회원가입 테스트코드

멤버가 given으로 주어지고 join메소드를 실행했을 때 검증을 위해 findOne을 통해 나오는 결과가 무엇인지 확인하는 과정이다.

 

중복 회원가입 예외 테스트코드

assertThrows는 두번째 인자로 주어진 로직 실행시 첫번째 인자에 해당하는 예외가 발생하는지 검사하는 함수입니다.

첫번째 인자가 IllegalStateException이 아닌 NullPointerException이라면 assertThrows에서 에러를 반환한다.

해당 로직에서 정상적으로 IllegalStateException 을 발생시키면 해당 예외를 예외객체 e에 저장하고,

assertThat을 통해 해당 예외 메세지를 "이미 존재하는 회원입니다." 와 비교하여 검증을 완료한다.

 

Service테스트 코드에서도 테스트별로 멤버들을 생성하고 저장하기 때문에 아래 코드를 넣어준다.

 

MemberService의 생성자 코드

MemberService의 생성자 인자로 memberRepository가 주어지며 각 MemberService마다 memberRepository를 별도로 갖게 된다.

 

테스트코드에서의 MemberService

@BeforEach를 통해 모든 메소드들이 실행되기전 해당 코드가 실행되도록 했다.

테스트코드 메소드들이 실행될 때 마다 MemoryMemberRepository()를 생성하고 서비스를 따로 생성해주며 서비스 객체 생성때마다 memberRepository를 생성해준다.

즉 각 테스트 메소드들이 각각의 memberService와 memberRepository를 갖게된다.

728x90

'Programming > Spring' 카테고리의 다른 글

웹 MVC 개발  (0) 2022.01.08
스프링 빈과 의존관계  (0) 2022.01.06
웹개발 기초  (0) 2021.12.21
728x90

인프런의 김영한님 Spring 강의 복습 및 정리

 

웹개발의 3가지 방법

1.정적 컨텐츠 : 서버의 동작 없이 요청한 HTML 파일을 그대로 내려주는것

2.MVC와 템플릿 엔진 : 서버에서 일부 동작을 통해 HTML을 가공하여 파일을 내려주는것 (JSP, PHP)

3.API : HTML로 파일을 내리는 것이아니라 JSON 형태의 데이터를 반환하는 방법

 

정적컨텐츠

스프링에서도 정적컨텐츠(Static Content) 기능을 제공한다.

 

서버 실행후 해당 파일 url 연결

해당 html 파일의 내용이 그대로 출력된다.

정적컨텐츠의 경우 요청한 html파일을 가공없이 그대로 출력해준다. 또한 동적 프로그래밍이 불가능하다.

 

MVC와 템플릿 엔진

MVC : Model, View, Controller

MVC 모델이 도입되기 이전에는 View에서 데이터가공까지 모두 처리하였다. ex) JSP, PHP

View는 화면과 관련된 일, 비즈니스 로직이나 서버 뒷단에 관련된 동작들은 Controller나 Back-end 비즈니스 로직에서 , View와 Controller, Back-end 에서 주고받는 데이터를 Model이라고 하는 데이터에 담아서 주고받는 구조로 동작한다.

 

Hello-mvc로 Parameter name을 가지고 GET 요청을 통해 서버에 요청하면 model객체에 addAttribute를 통해 파라미터로 받은 name값을 담아서 hello-template으로 전송한다. return의 목적지로 model 객체가 전송된다.

 

데이터를 전송할 때 사용하는 Model 객체의 경우 메소드의 파라미터로 선언만 해주면 Spring에서 만들어준다. Model 객체를 사용하기 위해 따로 메모리를 할당할 필요가 없으며 Spring에서 만들어준것을 사용하기만 하면된다.

addAttribute를 통해 저장된 값은 JSON형태이며 "name"이 key이고 파라미터로 전달받은 name이 value가 된다.

 

spring코드에서 반환되는 데이터가 전달되는 hello-template.html 이다.

Model 객체로 전달된 데이터의 key가 name인 value를 출력한다.

붉은색 선으로 밑줄 쳐져있는 name이 넘겨주는 파라미터 이름이고 푸른색 선이 가리키는 spring은 해당 파라미터의 값이된다.

전달받은 값을 addAttribute("name", name) 으로 Model 객체에 넣어주는데, key가 "name"이 되고 파라미터로 전달받은 값이 name에 들어가게된다. 즉 key : name, value : spring이 된다.

@RequestParam 어노테이션에 require=false를 넣어주는것은 해당 파라미터에 값이 없어도 실행되게 하는것이다.

파라미터 값을 채우지 않을 경우 default text인 hello! empty가 출력된다.

 

API

@ResponseBody 어노테이션을 사용할 경우 return에 의한 반환값이 그대로 HTTP response의 body에 삽입되어 HTML에 출력된다.

return "hello "+name 에 의해 name파라미터로 전송된 pw4ngc0가 삽입되어 출력됨을 알 수 있다.

 

API를 통한 객체 반환

Hello 객체에 name값을 넣어 반환한다.

객체를 반환할 경우 해당 객체의 데이터를 JSON형태로 출력해준다.

프론트에서 해당 객체를 가공하여 사용하는 것 같다.

 

@ResponseBody 어노테이션의 메소드가 객체를 반환할 경우 해당 객체를 JSON형태로 변환하여 HTTP 응답으로 넘겨준다.

728x90

'Programming > Spring' 카테고리의 다른 글

웹 MVC 개발  (0) 2022.01.08
스프링 빈과 의존관계  (0) 2022.01.06
회원 관리 예제  (0) 2021.12.30
728x90

바이트기반 보조스트림

FilterInputStream, FilterOutputStream

FilterInputStream과 FilterOutputStream은 InputStream/OutputStream의 자손이면서 모든 보조스트림의 조상이다.

보조스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반 스트림을 필요로한다.

protected(FilterInputStream(InputStream in)

public FilterOutputStream(OutputStream out)

 

FilterI/OStream의 모든 메서드는 기반스트림의 메서드를 그대로 호출하고 Filter보조스트림은 아무런 일을 하지않는다. FilterInputStream, FilterOutputStream은 상속을 통해 원하는 작업을 수행하도록 읽고쓰는 메서드를 오버라이딩 해야한다.

 

FilterInputStream의 자손 BufferedInputStream, DataInputStream, PushbackInputStream 등

FilterOutputStream의 자손 BufferedOutputStream, DataOutputStream, PrintStream 등

 

BufferedInputStream, BufferedOutputStream

버퍼 보조스트림은 입출력의 효율을 높이기 위해 사용된다. 버퍼를 이용해서 한번에 여러 바이트를 입출력하는것이 빨라지기 떄문이다.

BufferedInputStream(InputStream in, int size) : InputStream인스턴스와 버퍼 사이즈를 지정하여 버퍼 InputStream을 생성한다.

BufferedInputStream(InputStream in) : InputStream인스턴스만을 인자로 넣어주며 버퍼의 사이즈를 지정해주지 않는경우 8192byte크기의 버퍼를 갖게된다.

 

BufferedInputStream은 입력소스로부터 데이터를 buffer크기만큼 데이터를 읽어 내부 버퍼에 저장해놓는다. 프로그램에서는 외부가 아닌 내부 버퍼에 저장된 데이터를 읽어들이기 때문에 효율적인 데이터 처리가 가능하다.

내부 버퍼에 저장된 모든 데이터를 읽어들인 이후 read메서드가 호출되면 BufferedInputStream은 입력 소스로부터 다시 버퍼크기만큼의 데이터를 읽어다 버퍼에 저장해 놓는다.

 

BufferedOutputStream(OutputStream out, int size) : OutputStream인스턴스와 버퍼 사이즈를 인자로 던져 내부버퍼를 갖는 BufferedOutputStream 인스턴스를 생성한다.

BufferedOutputStream(OutputStream out) : OutputStream인스턴스를 인자로 주며 사이즈를 지정해주지 않을 경우 기본 버퍼사이즈는 8192byte크기가 된다.

flush() : 버퍼의 모든 내용을 출력하여 버퍼를 비운다.

close() : flush()를 호출해서 버퍼의 모든 내용을 출력하고 BufferedOutputStream인스턴스가 사용하던 모든 자원을 반환한다.

 

BufferedOutputStream 도 버퍼를 이용하여 출력작업을 하게 된다. write()메소드를 이용하여 BufferedOutputStream의 데이터를 내부 버퍼에 복사하고 버퍼로부터 출력한다. 버퍼의 모든내용을 출력한 후 write()호출 시 버퍼를 비우고 추가적인 데이터들을 내부 버퍼에 복사한다. 

출력소스가 버퍼에 남아있는 채로 프로그램이 종료될 수 있기 때문에, BufferedOutputStream을 사용완료한 후에는 반드시 BufferedOutputStream의 close()메소드나 flush() 메소드를 호출해 버퍼의 모든내용이 출력되도록 해야한다.

버퍼의 사이즈가 5기때문에 처음 write호출시 1부터 5까지만 write되고 6,7,8,9는 버퍼에 존재한다.

버퍼를 비우기위해 bos.close()를 호출하여 버퍼에 존재하는 6,7,8,9가 출력되고 버퍼를 비운다.

 

DataInputStream, DataOutputStream

DataInputStream은 FilterInputStream의 자손이며 DataInput인터페이스를 구현한 클래스이다.

DataOutputStream은 FilterOutputStream의 자손이며 DataOutput인터페이스를 구현한 클래스이다.

DataStream을 사용하면 byte뿐만아니라 8가지 기본 자료형의 값들도 읽기,쓰기가 가능하다. DataStream을 이용하여 기본 자료형 값을 출력한경우 이값을 읽기위해서는 똑같이 DataStream의 기본자료형을 이용해야한다.

sample.dat파일을 HxD로 열었을 경우이다. 4바이트중 10의 16진수인 A가 출력되고 20.0f가 4바이트로 41A00000이 저장되어있다. 마지막으로 ture값인 1이 저장된것을 알 수 있다.

 

 

sample.dat파일을 각각 기본자료형으로 읽어 출력하는 프로그램이다.

DataOutputStream으로 저장한 데이터 기본자료형에 맞추어 read를 해주면 정상적으로 파일을 읽을 수 있다.

데이터를 변환없이 저장하고 읽을때 사용하면 편하다.

 

try내부에서 파일을 while(true)로 계속해서 읽는다. 파일의 끝에 도달하여 read를 하면 EOFException이 발생하여 sum을 출력하고 try-catch부분이 끝나는경우 finally의 close()가 실행되어 스트림을 모두 비워준다. 

 

JDK1.7이후부터 try-with-resource문을 이용하여 close()를 호출하지 않아도 자동호출되도록 할 수 있다.

위처럼 try의 내부()에 자원을 적어주는것이 try-with-resources문이다. 위처럼 처리해줄경우 close()를 직접 호출하지않아도 ()내부의 자원이 자동으로 close()된다.

 

SequenceInputStream

입력스트림을 연속적으로 연결해서 하나의 스트림으로부터 데이터를 읽을 수 있는것

SequenceInputStream(Enumeration e) : 저장된 입력스트림을 하나의 스트림으료 연결한다.

SequenceInputStream(InputStream s1, InputStream s2) : 두개의 입력스트림을 하나로 연결한다.

 

위처럼 사용할 수 있다.

 

728x90

'Programming > JAVA' 카테고리의 다른 글

바이트 기반 스트림  (0) 2021.09.10
자바의 입출력  (0) 2021.09.10
그룹화와 분할 - groupingBy(), partitioningBy()  (0) 2021.09.02
collect()  (0) 2021.09.02
스트림의 최종연산  (0) 2021.09.01
728x90

바이트 기반 스트림

InputStream과 OutputStream

InputStream의 메서드

int available() : 스트림으로부터 읽어 올 수 있는 데이터의 크기를 반환한다.

void close() : 스트림을 닫음으로써 사용하고 있던 자원을 반환한다.

void mark(int readlimit) : 현재위치를 표시해 놓는다. 후에 reset()에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다. readlimit은 되돌아갈 수 있는 byte의 수이다.

boolean markSupported() : mark()와 reset()을 지원하는지 알려준다.

abstract int read() : 1byte를 읽어온다. 더이상 읽어올 데이터가 없으면 -1을 반환한다.

int read(byte[] b) : 배열 b의 크기만큼 읽어서 배열을 채우고 읽어온 데이터 수를 반환한다.

int read(byte[] b, int off, int len) : len개의 byte를 읽어서, 배열 b의 지정된 위치(off)부터 저장한다. 실제로 읽어오는 데이터의 수는 len보다 작을 수 있다.

void reset() : 스트림에서의 위치(offset)를 마지막으로 mark()이 호출되었던 위치로 되돌린다.

long skip(long n) : 스트림에서 주어진 길이(n)만큼을 건너뛴다.

 

OutputStream 메서드

void close() : 입력소스를 닫음으로써 사용하고 있던 자원을 반환한다.

void flush() : 스트림의 버퍼에있는 모든 내용을 출력소스에 쓴다.

abstract void write(int b) : 주어진 값을 출력소스에 쓴다.

void write(byte[ ] b) : 주어진 배열 b에 저장된 모든 내용을 출력소스에 쓴다.

void write(byte[ ] b, int off, int len) : 주어진 배열 b에 저장된 내용 중에서 off번째부터 len개 만큼만을 읽어서 출력소스에 쓴다.

 

flush()는 버퍼가 있는 출력스트림에서만 의미가 있다. 

프로그램이 종료될 때, 사용하고 close하지 않은 스트림을 JVM이 자동적으로 닫아주기는 하지만 스트림을 사용 완료한 이후에는 close()를 호출하여 반드시 닫아주는 것이 좋다. 

ByteArrayInputStream처럼 메모리를 사용하는 스트림, System.in, System.out과 같은 표준 입출력 스트림은 닫아주지 않아도 된다.

 

ByteArrayInputStream과 ByteArrayOutput

바이트배열에 데이터를 입출력하는데 사용되는 스트림이다. 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용된다.

스트림의 종류가 달라도 읽고 쓰는 방법은 동일하다.

 

ByteArrayInputStream()에 읽을 데이터를 매개변수로 주어 읽기 스트림을 주고 ByteArrayOutputStream을 생성해주어 각InputStream에 저장된 데이터를 하나씩 입력받아 출력 스트림에 쓴다. 이후 스트림에 적은내용을 배열에 옮긴다.

배열을 스트림을 통해 복사하는 방법이다.

 

배열 크기 복사

InputStream의 offset 0부터 temp.length만큼 데이터를 읽어 temp에 저장한다.

이후 temp에 저장된 내용을 temp의 offset 5부터 길이 5만큼 outputStream에 쓴다.

available()을 통해 스트림에 읽을 데이터가 남아있는경우 반복문이 계속 실행되게하고 내부에서는 데이터를 temp의 크기(4byte)만큼씩 temp에 저장하고 output.write를 통해 temp 에 저장된 데이터를 outputStream에 temp크기만큼씩 쓴다.

tmep의 마지막 저장결과를 보면 4바이트씩 읽다가 마지막에 입력스트림에 2바이트밖에 남지않아 2바이트를 읽은 경우이다. 기존의 데이터를 가지고있는상태에서 2바이만 새로읽어 뒷내용이 유지된다. 이후 OutputStream의 결과처럼 temp의 모든내용이 저장된다.

 

위의 예제에서의 temp에서 저장된 데이터가 변하지 않는 문제점을 해결한 예제이다. 읽은 크기만큼만 write하기 때문에 마지막에 2byte를 읽은경우 2byte만 write하여 배열이 그대로 복사된다.

728x90

'Programming > JAVA' 카테고리의 다른 글

바이트기반 보조스트림  (0) 2021.09.11
자바의 입출력  (0) 2021.09.10
그룹화와 분할 - groupingBy(), partitioningBy()  (0) 2021.09.02
collect()  (0) 2021.09.02
스트림의 최종연산  (0) 2021.09.01
728x90

입출력

입출력 : I/O Input과 Output의 약자이다. 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 의미한다. 키보드로부터 입력을 받고 모니터 화면에 출력을 하는 행위도 I/O행위이다.

 

스트림(Stream)

자바에서의 입출력 즉 데이터를 한쪽에서 다른쪽으로 전달하려면 두 개의 대상을 연결하고 데이터를 전송할 수 있는 것이 필요하다. 이것을 스트림(Stream)이라한다.

이전에 작성한 스트림과는 다른 개념이다.

 

입출력에서 사용되는 스트림은 데이터를 운반하는 연결통로를 의미한다.

 

스트림은 한쪽방향 즉 단방향 통신만 가능하다. 그렇기 때문에 입력과 출력을 수행하기 위해서는 입력스트림(input Stream), 출력스트림(output stream) 2개의 스트림이 각각 필요하다.

 

스트림은 먼저보낸 데이터를 먼저 받는 FIFO 구조이다. 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다.

 

바이트기반 스트림 InputStream, OutputStream

스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 다른 스트림을 사용한다.

파일

입력스트림 - FileInputStream

출력스트림 - FileOutputStream

메모리(byte 배열)

입력스트림 - ByteArrayInputStream

출력스트림 - ByteArrayOutputStream

프로세스(프로세스간 통신)

입력스트림 - PipedInputStream

출력스트림 - PipedOutputStream

오디오 장치

입력스트림 - AudioInputStream

출력스트림 - AudioOutputStream

 

입출력 대상에따라 입출력스트림 클래스가 정의되어있으며 각각 읽고 쓰는데 필요한 추상메서드들을 입출력대상에 맞게 구현해 놓았다.

 

읽기 쓰기 메서드

입력을 받는 InputStream에는 데이터를 읽는 read()메서드가 정의되어있다.

abstract int read()

int read(byte[] b)

int read(byte[] b, int off, int len)

 

int read(byte[] b) 메서드는 내부적으로 int read(byte[] b, int off, int len) 를 호출하고  int read(byte[] b, int off, int len) 는 내부적으로 read()를 호출한다. 추상메서드 read() 는 입력스트림으로 부터 1byte를 읽어서 반환하는 메서드로 구현되어있다.

 

출력 스트림 OutputStream에는 데이터를 출력하는 write()메서드가 정의되어있다.

abstract void write(int b)

void write(byte [] b)

void write(byte[] b, int off, int len)

 

write()메서드도 마찬가지로 아래 두개의 메서드가 내부적으로 write(int b)를 사용한다.

write(int b)는 b를 출력하는 함수이다. 각각 데이터를 출력스트림으로 출력한다.

 

보조스트림

스트림의 기능을 보완하기 위한 보조스트림이 제공된다. 보조스트림은 데이터를 입출력하는것이아닌 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있는 역할을 한다. 보조스트림을 사용하기위해서는 입출력 스트림을 먼저 생성한 후 이것을 이용하여 보조스트림을 생성해야한다.

 

FileInputStream fis = new FileInputStream("test.txt");

BufferedInputStream bis = new BufferedInputStream(fis);

bis.read();

 

test파일을 읽는 코드이다.

파일 입력 스트림인 FileInputStream에 성능 향상을 위한 보조스트림 BufferedInputStream을 생성해준 경우이다. 보조스트림역시 InputStream과 OutputStream의 자손이므로 입출력방법이 동일하다.

 

보조스트림의 종류

FilterInputStream, FilterOutputStream : 필터를 이용한 입출력 처리

BufferedInputStream. BufferedOutputStream : 버퍼를 이용한 입출력 처리

DataInputStream, DataOutputStream : int, float와 같은 기본형 단위(primitive type)로 데이터를 처리하는 기능

SequenceInputStream : 두개의 데이터를 하나로 연결

LineNumberInputStream : 읽어 온 데이터의 라인 번호를 카운트

ObjectInputStream, ObjectOutputStream : 데이터를 객체단위로 읽고 쓰는데 사용. 주로 파일을 이용하며 객체 직렬화

PrintStream : 버퍼를 이용하며, 추가적인 print관련 기능

PushbackInputStream : 버퍼를 이ㅛㅇ해서 읽어온 데이터를 다시 되돌리는 기능(unread, push back to buffer)

 

문자기반 스트림 Reader, Writer

자바에서는 문자형인 char형이 2byte이기 때문에 바이트 기반의 스트림으로 2byte인 문자를 처리하는데 어려움이 있다.

2byte문자를 처리하기 위한 스트림이 문자기반 스트림 Reader와 Writer이다.

문자데이터를 입출력 할때는 바이트기반 스트림 대신 문자기반 스트림을 사용하는것이 좋다.

 

문자열 파일

입력스트림 - FileReader

출력스트림 - FileWriter

문자열 메모리(char 배열)

입력스트림 - CharArrayReader

출력스트림 - CharArrayWriter

프로세스(프로세스간 문자열 통신)

입력스트림 - PipedReader

출력스트림 - PipedWriter

문자열 버퍼 스트림

입력스트림 - StringReader

출력스트림 - StringWriter

 

Reader메서드

int read()

int read(char[] cbuf)

abstract int read(char[] cbuf, int off, int len)

 

InputStream의 read와 비슷하지만 Reader의 read()는 char배열을 인자로받아 처리한다.

 

Writer메서드

void write(int c)

void write(char[] cbuf)

abstract void write(char[] cbuf, int off, int len)

void write(String str)

void write(String str, int off, int len)

 

Writer의 write메서드의 인자로 char[]과 String을 받아서 처리한다.

 

문자열 기반 보조스트림

BufferedReader, BufferedWriter : 문자열기반 버퍼를 사용한다.

FilterReader, FilterWriter : 문자열기반 필터를 사용한다.

LineNumberReader : 읽어온 문자 데이터 라인 번호를 카운트한다.

PrintWriter : 버퍼를 이용하여, 추가적인 출력기능

pushbackReader : 버퍼를 이용해서 읽어온 읽어온 데이터를 다시 되돌린다.

 

728x90

'Programming > JAVA' 카테고리의 다른 글

바이트기반 보조스트림  (0) 2021.09.11
바이트 기반 스트림  (0) 2021.09.10
그룹화와 분할 - groupingBy(), partitioningBy()  (0) 2021.09.02
collect()  (0) 2021.09.02
스트림의 최종연산  (0) 2021.09.01
728x90

그룹화와 분할

그룹화 : 스트림의 요소를 특정 기준으로 그룹화 하는것

분할 : 스트림의 요소를 두가지, 지정한 조건에 일치하는 그룹과 일치하지 않는 그룹으로 분할하는것

 

그룹화 메서드 groupingBy()

Collector groupingBy(Function classifier)

Collector groupingBy(Function classifier, Collecctor downstream)

Collector grooupingBy(Function classifier, Supplier mapFactory, Collector downstream)

 

분할 메서드 partitioningBy()

Collector partitioningBy(Predicate predicate)

Collector partitioningBy(Predicate predicate, Collecctor downstream)

 

그룹화인 groupingBy()의 경우 Function을 매개변수로 받고 분할인 partitioningBy()의 경우 Predicate를 매개변수로 받는다. 스트림을 두개의 그룹으로 나눠야하는 경우 partitioningBy()가 더 빠르다.

그룹화와 분할의 결과는 Map에 담겨 반환된다.

 

partitioningBy()에 의한 분류

위처럼 코드가 있을경우 partitioningBy() 사용예시이다.

Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale));

List<Student> maleStudent = stuBySex.get(true);

List<Student> femaleStudent = stuBySex.get(flase);

각각 남자학생일경우, 여학생일 경우 List이다. Student객체에 boolean으로 초기화 되어있는 값을 확인하여 partitioningBy에서 분할한다.

 

성별 학생수 구하기

Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, counting());

System.out.println("남학생 수 : "+ stuNmBySex.get(true));

System.out.println("여학생 수 : "+ stuNmBySex.get(false));

 

성별 1등학생 구하기

Map<Boolean, Optional<Stuudent>> topScoreBySex = stuStream

                                       .collect(partitioningBy(Student::isMale,maxBy(comparingInt(Student::getScore)));

System.out.println("남학생 수 : "+ topScoreBySex.get(true));

System.out.println("여학생 수 : "+ topScoreBySex.get(false));

 

150점 아래 학생들만 출력

Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream

                      .collect(partitioningBy(Student::isMale, partitioningBy(s -> s.getScore() < 150)));

List<Student> failedMaleStu = failedStuBySex.get(true).get(true);

List<Student> failedFemaleStu = failedStuBySex.get(false).get(true);

 

groupingBy()에 의한 분류

학생들을 반별로 그룹짓기

Map<Integer, List<Student>> stuByBan = stuStream.collect(groupingBy(Student::getBan));

 

groupingBy()로 그룹화를 하면 기본적으로 List<T>에 값을 담는다.

toList()를 매개변수로 넣어주어도되고 필요에 따라 toSet()이나 toCollection(HashSet::new) 등을 사용할 수 있다.

ex)

Map<Integer, List<Student>> stuuByBan = stuStream.collect(groupingBy(Student::getBan, toList()));

Map<Integer, HashSet<Student>> stuByHak = stuStream

                                            .collect(groupingBy(Studuent::getHak,toCollection(HashSet::new)));

분할과 그룹화는 많이써봐야할것같다. 이해는 되지만 사용하지않은 상황에서 응용이 어렵게 느껴진다.

728x90

'Programming > JAVA' 카테고리의 다른 글

바이트 기반 스트림  (0) 2021.09.10
자바의 입출력  (0) 2021.09.10
collect()  (0) 2021.09.02
스트림의 최종연산  (0) 2021.09.01
Optional  (0) 2021.08.25

+ Recent posts