728x90

SSH(Secure Shell)

 

- 네트워크 상의 다른 컴퓨터에 로그인하거나 원격 시스템에서 명령을 실행하고 다른 시스템으로 파일을

복사할 수 있도록 해주는 응용프로그램 또는 프로토콜을 가리킵니다.

- TCP 상에 보안 채널을 형성하여 기타 응용 프로토콜들이 안전하게 데이터 교환 가능하게 합니다.

- 특징

  - 암호화되지 않은 telnet, SMTP 등에 대한 패킷 스니핑 등의 보안 공격을 방지합니다.

  - TCP 통신만 가능합니다. 

 

Git에서 로컬이나 원격 저장소를 만들고 SSH통신을 통해 소스버전을 관리할 수 있습니다.

 

 

728x90

'네트워크' 카테고리의 다른 글

디지털 인증서(Certificate)와 SSL/TLS 기초  (1) 2023.07.11
728x90

스프링부트를 공부하는 중에 타임리프에 대해 알게 되었습니다.

전에 Spring을 사용해서 팀 프로젝트를 진행할 때는 JSP를 사용했는데요.

Springboot는 뷰 템플릿으로 Thymeleaf를 사용하는 것을 권장한다고 합니다.

그래서 이 글에서는 Thymeleaf란 무엇이고, 사용할 수 있는 몇 가지 문법에 대해 정리해보겠습니다.

 

Thymeleaf

타임리프는 내츄럴 템플릿(Natural Templates)이라고 부르기도 합니다.

내추럴 템플릿은 순수 HTML을 유지하면서 뷰 템플릿으로도 사용할 수 있는 것을 말합니다.

예를 들어 JSP는 서버를 거치지 않고 파일을 열 경우에 소스코드와 HTML 코드가 섞여 알아볼 수 없습니다.

하지만 Thymeleaf는 파일을 웹 브라우저에 열어 내용을 확인할 수 있고 서버를 통해 보면 동적인 결과도 얻을 수 있습니다.

또한 JSP는 .jsp로 저장이 되고 Thymeleaf는 html 파일로 저장됩니다.

 

HTML에서 타임리프를 사용하기 위한 선언은 아래와 같습니다.

<html xmlns:th="http://www.thymeleaf.org">

해당 선언을 해줄경우 HTML 내 태그에 있는 대부분의 속성들을 th:xxx 형식으로 바꿔 사용할 수 있습니다.

html 파일을 서버를 거치지 않고 그대로 볼 때에는 기존의 html 태그의 속성이 사용되고

서버를 거쳐 동적으로 뷰 템플릿을 거치게 되면 기존 태그의 속성을 th:xxx의 값으로 치환합니다.

 

예를 들어 input의 value값을 Thymeleaf로 표현하면 아래와 같습니다.

<!-- 기존 HTML -->
<input type="text" value="1"/>

<!-- Thymeleaf -->
<input type="text" value="1" th:value="${otherValue}"/>

이렇게 작성된 html은 html 파일을 웹브라우저로 켤 경우 input 박스의 값이 1로 보이고

서버를 거쳐 렌더링되어 웹브라우저에 보일 경우에는 otherValue에 담긴 값이 input박스의 값으로 나오게 됩니다.

 

간단하게 Thymeleaf에 대해 설명을 해봤습니다.

아래에는 타임리프의 간단한 문법을 정리해보겠습니다.

 

문법

URL 링크 표현식

<a href="/user" th:href="@{/user}"/>

타임리프에서 링크표현식은 @{...}을 사용합니다. 

링크 표현식은 표현식 안에 '()'를 사용해서 경로 변수와 쿼리 파라미터도 생성 가능합니다. 

<!-- 경로 변수
{itemId}를 item의 id로 대체합니다.
-->
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"

<!-- 쿼리 파라미터 -->
th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
<!-- 지정되지 않은 query는 쿼리 파라미터로 치환됩니다.
	 basic/items/1?query=test
-->

 

물론 간단히 아래처럼 사용할 수도 있습니다.

th:href="@{|/basic/items/${item.id}|}"

 

리터럴 대체

 

리터럴 대체는 |...|으로 사용합니다.

타임리프에서는 문자와 표현식 등이 분리되어 있기 때문에 + 연산자를 사용해야 합니다.

하지만 리터럴 대체 문법을 사용하면 연산자를 사용할 필요 없이 사용할 수 있습니다.

<!-- 리터럴 대체 사용 전 -->
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">

<!-- 리터럴 대체 사용 후 -->
<span th:text="|Welcome to our application, ${user.name}!|">

 

변수 표현식

 

변수 표현식은 ${...}으로 사용합니다.

모델에 포함된 값이나 타임리프 변수로 선언한 값을 조회할 수 있습니다.

프로퍼티 접근법을 사용합니다. 

${param}
// ex)
// localhost:8080/user?userId=kkkt
${param.userId} => kkkt

위 코드는 타임리프에서 쿼리 파라미터를 편리하게 조회할 수 있게 저장된 예약어입니다.

그래서 param뒤에 쿼리 파라미터의 키를 넣으면 값을 손쉽게 사용할 수 있습니다.

 

th:each

 

반복문을 사용할 때 쓰는 문법입니다.

<tr th:each="item : ${items}">

위 코드처럼 사용하는데요 items에 담겨있는 데이터를 하나씩 item 변수에 담아 사용합니다

JSTL의 forEach 구문과 같은 역할을 합니다.

 

 

 

추가적인 문법이 알고 싶다면 타임리프 공식 사이트를 확인해보세요

https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#listing-seed-starter-data

728x90
728x90

 

웹 서비스를 만들기 위해서는 클라이언트와 서버 간에 데이터를 주고받을 필요가 있습니다.

그리고 REST API같은 HTTP API를 제작하기 위해서도 데이터의 처리방법에 대해 알 필요가 있습니다.

이전에 스프링을 사용하지 않고 이클립스 IDE만 사용해서 컨트롤러를 만들어 봤었습니다.

이때에는 매번 메서드에 HttpServletRequest를 매개변수로 사용하고 .getParameter()를 사용해

데이터를 처리하느라 좀 귀찮은 점이 있었습니다. 하지만 스프링을 사용하면 HTTP 요청과 응답 데이터를

쉽게 처리할 수 있습니다. 

 

우선 요청 데이터를 처리하는 것에 대해 정리하고 응답 데이터 처리에 대해 정리해보겠습니다.

응답 데이터 처리는 요청 데이터 처리와 중복되는 것이 많기 때문에 간략하게 해보겠습니다.

 

HTTP 요청 데이터 조회

 

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법은 주로 세 가지 방법을 사용합니다.

  • GET - 쿼리 파라미터
  • POST - HTML Form
  • HTTP message body

GET과 POST의 HTML Form 데이터를 처리하는 방법은 같습니다.

그전에 GET과 POST의 데이터 전달 방법에 대해 먼저 알아보겠습니다.

일단 GET의 데이터는 메시지 바디 없이 URL의 ?뒤에 Key=Value의 형식으로 전달합니다.

ex) localhost:8080/user?username=kim&userage=22

POST의  Form 데이터는 메시지 바디로 전달하지만 형식은 똑같이 쿼리 파라미터 형식으로 전달합니다.

username=kim&userage=22

이때 HTTP 헤더의 content-type은 application/x-www-form-urlencoded입니다.

 

이렇게 쿼리 파라미터 형식으로 전달하는 두 데이터를 처리하는 방법에는 크게 세 가지가 있습니다.

앞서 이야기했던 HttpServletRequest의 .getParameter를 사용하는 방법과

@RequestParam을 사용하는 방법 그리고 @ModelAttribute를 사용하는 방법이 있습니다.

 

@RequestParam

 

해당 어노테이션은 해당 어노테이션에 값으로 설정한 데이터가 들어오면 매핑해주는 역할을 합니다.

그리고 파라미터의 이름과 데이터의 Key값이 동일하면 어노테이션을 생략할 수 있습니다.

// URL -> http://localhost:8080/request-param?username=kim&age=24

// 1번 방법
@RequestMapping("/request-param")
public String requestParamV2(
	@RequestParam("username") String memberName,
	@RequestParam("age") int memberAge){...}

// 2번 방법 어노테이션 안의 값 생략
@RequestMapping("/request-param")
public String requestParamV2(
	@RequestParam String username,
 	@RequestParam int age){...}

// 3번 방법 어노테이션 자체를 생략
@RequestMapping("/request-param")
public String requestParamV2(String username,int age){...}

 

추가적으로 @RequestParam는 required 속성을 가지고 있습니다.

디폴트 값이 true인데 이럴 경우 해당하는 파라미터의 요청 데이터가 넘어오지 않으면 400 에러를 반환합니다.

하지만 ""처럼 빈 값을 넘겨도 아무런 에러를 반환하지 않습니다.

그리고 기본형 타입의 파라미터에 required=false를 설정했을 때는 값이 넘어오지 않으면 null값을 주는 것이 불가능하여

500 에러를 반환하기 때문에 이를 처리하는 것이 필요합니다.

이때에는 다른 속성인 defaultValue나 래퍼클래스를 사용하면 됩니다.

// required 속성 사용
@RequestMapping
public String requestParamDefault(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) int age) {...} 
        // age 해당하는 값이 안넘어 올 경우 500에러를 반환한다.

// defaultValue 속성 사용
@RequestMapping
public String requestParamDefault(
		@RequestParam(defaultValue = "guest") String username,
		@RequestParam(defaultValue = "-1") int age){...}
        
// 래퍼클래스 사용
@RequestMapping
public String requestParamDefault(
		@RequestParam String username,
		@RequestParam(required = false) Integer age){...}
        // 래퍼클래스를 사용해 null값 오류를 해결할 수 있다.

 

@ModelAttribute

 

스프링은 객체에 요청 파라미터를 바인딩하는 기능도 가지고 있습니다.

이게 @ModelAttribute의 기능입니다. 해당 어노테이션도 생략이 가능합니다.

@RequestMapping
public String modelAttributeV1(@ModelAttribute HelloData helloData) {...}


// @ModelAttribute 생략
@RequestMapping
public String modelAttributeV1(HelloData helloData) {...}

위와 같이 작성 가능합니다.

스프링MVC는 @ModelAttribute가 있으면 다음을 실행합니다.

  • HelloData 객체를 생성합니다.
  • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾습니다.
  • 해당 프로퍼티의 setter를 호출해 파라미터의 값을 바인딩합니다.

때문에 객체로 사용할 클래스에는 setter 메서드를 만들어 둬야 합니다.

 

앞서 이야기한 @RequestParam과 @ModelAttribute는 둘 다 생략할 수 있습니다.

때문에 스프링은 생략 시 다음과 같은 규칙을 적용합니다.

  • String, int, Integer와 같은 단순 타입은  @RequestParam을 사용합니다.
  • argument resolver로 지정해둔 타입을 제외한 나머지는 @ModelAttribute 사용합니다.

 

HTTP Message Body

 

다음은 HTTP 메시지 바디에 담긴 단순 텍스트 데이터를 처리하는 방법입니다.

HTTP API에 주로 사용되고 JSON 형식이나 XML 형식을 주로 사용합니다.

 

이렇게 메시지 바디로 넘어오는 데이터는 앞서 이야기한 @RequestParam과 @ModelAttribute를 사용할 수 없습니다.

HTML form으로 넘어오는 데이터를 제외하고요.

요청 메시지 바디로 넘어오는 데이터를 조회하는 방법은 아래와 같습니다. 

  • InputStream
  • HttpEntity
  • RequestEntity - HttpEntity를 상속받은 클래스로 추가 기능을 제공합니다.
  • @RequestBody

 

이중에서는 @RequestBody를 사용하는 게 가장 편해 보였습니다.

스트림은 바이트 타입으로 넘어오기 때문에 다시 한번 타입에 맞춰 변경해주는 작업이 필요합니다.

그리고 HttpEntity나 @RequestBody는 HTTP 메시지 컨버터를 통해 바디의 내용을 문자나 객체 등으로 변환시켜줍니다.

하지만 HttpEntity는 getBody()로 한 번 더 꺼내는 과정이 필요합니다. 

// @RequestBody
@PostMapping("/request-body-json")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {...}

// HttpEntity
@PostMapping("/request-body-json")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {...}

 

HTTP 응답

 

스프링에서 응답 데이터를 만드는 방법은 크게 세 가지입니다.

  • 정적 리소스
  • 뷰 템플릿 사용
  • HTTP 메시지 사용

 

 

HTTP 메시지 사용

 

HTTP API나 비동기 통신을 통해 데이터의 특정 값만 제공해야 하는 경우에는

HTTP 바디에 직접 데이터를 실어서 보낼 필요가 있습니다.

 

만약 컨트롤러의 메서드가 전부 특정 데이터만 전달해야 하는 경우에는 클래스에 

@RestController나 @ResponseBody 어노테이션을 달아 사용할 수 있습니다. 

@RestController는 @Controller와 @ResponseBody 합쳐진 것이기 때문에 둘 중 무엇을 사용하든 상관없습니다.

 

스프링의 경우 String으로 반환하면 뷰 리졸버가 실행되어서 뷰를 찾고 렌더링 합니다.

하지만 @ResponseBody가 달린 클래스나 메서드는 반환 값을 HTTP 응답 메시지 바디에 직접 실어 보냅니다.

그리고 해당 어노테이션을 쓰지 않고 HttpEntity나 ResponseEntity를 사용해서 응답할 수도 있습니다.

ResponseEntity를 사용할 경우 HTTP 응답 코드를 설정할 수 있는데 @ResponseBody는 따로 설정할 수 없습니다.

하지만 @ResponseStatus를 사용한다면 @ResponseBody로 반환하는 메서드도 응답 코드를 설정할 수 있습니다.

 

// @ResponseBody와 @ResponseStatus 사용
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json")
public HelloData responseBodyJson() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);
    
    return helloData;
}

// ResponseEntity 사용
@GetMapping("/response-body-json")
public ResponseEntity<HelloData> responseBodyJson() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);
    
    return new ResponseEntity<>(helloData, HttpStatus.OK);
}

 

 

참조

HTTP 메시지 컨버터

argument resolver

728x90
728x90

최근에 스터디를 하면서 공부를 하고 있습니다.

그런데 금방 까먹는 듯한 기분이 들어 정리하는 시간을 가지려고 합니다.

이렇게 하면 더 오래 남지 않을까 하는 개인적인 기대가 있습니다.

이번 글은 인프런 김영한 님의 스프링 강의 - 스프링의 기본 기능 내용 정리입니다.

 

로깅(Logging)

제가 프로젝트를 수행할 때는 어떠한 버그가 발생했을 때 이유를 알아보기 위해서 

시스템 콘솔에 데이터를 출력하는 System.out.println()을 사용했습니다.

하지만 실무에서 위의 방법을 사용할 경우에는 로그관리를 할 수 없기 때문에 별도의 로깅 라이브러리를 사용합니다.

스프링 부트는 기본적으로 slf4j라는 라이브러리를 사용하는데,

이는 Logback, Log4j, Log4j2 등 수많은 라이브러리를 통합해서 제공하는 라이브러리입니다.

 

로그의 선언에는 

private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j

위와 같은 방법을 사용할 수 있습니다. Logger와 LoggerFactory은 slf4j의 클래스입니다.

그리고 @Slf4j 어노테이션은 Lombok 라이브러리의 어노테이션으로 위의 두줄과 같이 직접적인 선언 없이

로그를 호출할 수 있게 해 줍니다. 이외에도 Lombok은 DTO 클래스에서 setter, getter의 작성을 쉽게 해주기도 합니다.

Lombok에 대한 자세한 내용은 다음에 다뤄보도록 하겠습니다.

 

 호출은 아래와 같이 할 수 있습니다.

String name = "Spring";
 
 log.trace("trace log={}", name);
 log.debug("debug log={}", name);
 log.info(" info log={}", name);
 log.warn(" warn log={}", name);
 log.error("error log={}", name);

로그의 출력은 개발 단계와 운영 단계에 맞춰 각각 출력하는 범위를 정할 수 있습니다.

레벨 순은 trace >> debug >> info >> warn >> error 순으로 디폴트 값은 info입니다.

그래서 출력을 info로 설정하면 하위 레벨인 debug와 trace는 출력되지 않습니다.

 

log.debug("String concat log=" + name);

그리고 로그의 출력을 위와 같이 + 연산자를 사용해서 출력할 수도 있습니다.

하지만 위와 같은 경우는 리소스를 낭비하여 사용하지 않아야 합니다.

출력이 info로 설정되어 있을 경우 debug는 대상이 아니라 그냥 넘어가야 하지만

그 작업이 이뤄지기 이전에 + 연산자의 로직이 실행돼 불필요한 자원을 소모하기 때문입니다.

 

로그 사용의 장점

  • 스레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있습니다.
  • 로그를 상황에 맞게 레벨을 조절하여 출력할 수 있습니다.
  • 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있습니다.
  • 파일로 남길 때에는 특정 기준에 맞춰 로그를 분할할 수 있습니다. 

 

매핑(Mapping)

스프링에서는 @Controller 어노테이션을 사용해 컨트롤러를 찾을 수 있습니다.

그리고 @RequestMapping 어노테이션을 사용해 해당하는 URL 호출이 오면 메서드가 실행되도록 매핑할 수 있습니다.

여기서는 @RequestMapping의 여러 매핑 방법에 대해 정리해보도록 하겠습니다.

 

@RequestMapping 방법 

  • HTTP 메서드 매핑
  • PathVariable 매핑
  • 특정 파라미터 조건 매핑
  • 특정 헤더 조건 매핑
  • 미디어 타입 조건 매핑

 

HTTP 메서드 매핑

 

HTTP 메서드 매핑은 @RequestMapping의 method 속성으로 매핑을 하는 방법입니다.

기본적으로 GET, POST, PUT, PATCH 등 모든 메서드에 대해 허용하게 됩니다.

method 속성을 사용하면 이를 특정 메서드의 요청에만 응답하도록 설정할 수 있습니다.

@RequestMapping(value = "/mapping", method = RequestMethod.GET)

위 URL에 POST 요청을 보내고 POST 요청에 해당하는 매핑이 없을 경우 

스프링 MVC는 HTTP 405(Method Not Allowed) 코드를 반환합니다.

덧붙여 스프링 부트는 해당 메서드 매핑의 축약형 어노테이션을 제공합니다.

@GetMapping(value="/mapping")
@PostMapping(value="/mapping")
@PutMapping(value="/mapping")

 

 

PathVariable 매핑

 

HTTP 리소스 경로에 식별자를 넣는 방법입니다.

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {}

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {}

위와 같이 사용할 수 있으며, 경로 변수와 파라미터의 이름이 같다면 @PathVariable의 이름을 생략할 수 있습니다.

 

특정 파라미터 조건 매핑

 

/**
 * 파라미터로 추가 매핑
 * params="mode",
 * params="!mode"
 * params="mode=debug"
 * params="mode!=debug" (! = )
 * params = {"mode=debug","data=good"}
 */
@GetMapping(value = "/mapping-param", params = "mode=debug")

특정 파라미터가 있거나 없는 요청에 대해서 매핑하는 방법입니다.

하지만 잘 사용되지는 않는다고 합니다.

 

 

특정 헤더 조건 매핑

 

/**
 * 특정 헤더로 추가 매핑
 * headers="mode",
 * headers="!mode"
 * headers="mode=debug"
 * headers="mode!=debug" (! = )
 */
@GetMapping(value = "/mapping-header", headers = "mode=debug")

파라미터 매핑과 비슷하지만 HTTP 헤더를 이용합니다.

 

 

미디어 타입 조건 매핑

 

미디어 타입 조건 매핑에는 HTTP 요청의 Content-Type과 Accept 해당하는 매핑이 있습니다.

Content-Type은 클라이언트가 서버에게 전송하는 데이터의 원래 미디어 유형을 말합니다.

Accept는 클라이언트가 이해할 수 있는 미디어 유형의 HTTP 요청 헤더입니다. 

 

특정 Content-Type 요청에는 consumes 속성을 사용하고, Accept 요청에는 produces 속성을 사용합니다.

제가 이 속성들을 사용할 때에는 주로 jQuery로 Ajax 통신을 할 때 사용했습니다.

특정한 값으로 전달받거나 전달하기 위해서 그렇습니다.

 

Content-Type

/**
 * Content-Type 헤더 기반 추가 매핑 Media Type
 * consumes="application/json"
 * consumes="!application/json"
 * consumes="application/*"
 * consumes="*\/*"
 * MediaType.APPLICATION_JSON_VALUE
 */
@PostMapping(value = "/mapping-consume", consumes = "application/json")

/**
 *
 * consumes 예시
 */
consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE

맞지 않으면 HTTP 415(Unsupported Media Type) 상태 코드를 반환합니다.

 

Accept

/**
 * Accept 헤더 기반 Media Type
 * produces = "text/html"
 * produces = "!text/html"
 * produces = "text/*"
 * produces = "*\/*"
 */
@PostMapping(value = "/mapping-produce", produces = "text/html")

/**
 *
 * produces 예시
 */
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"

맞지 않으면 HTTP 406(Not Acceptable) 상태코드를 반환합니다.

728x90
728x90

 

DFS/BFS 문제 유형의 한 문제입니다.

왜 우선 탐색 문제일까 고민하던 중에 스터디원이 준 힌트를 듣고 풀이 방법을 알아냈습니다.

트리로 보면 리프 노드를 구한다음 타깃과 같은 값을 가진 노드의 개수를 구하면 됩니다.

아래의 그림처럼 생각할 수 있습니다.

 

예를 들어 배열로 [4, 1, 2, 1]이 들어오고 타겟 넘버가 4일 경우의 수는 두 가지입니다.

 

import java.util.*;
class Solution {
    public int solution(int[] numbers, int target) {
        int answer = 0;
        Queue<Integer> answerList = new LinkedList<Integer>();
        answerList.add(0);
        
        for(int i: numbers){
            int size = answerList.size();
            for(int k = 0; k < size; k++){
                int pop = answerList.poll();
                answerList.add(pop + i);
                answerList.add(pop - i);
            }
        }
        
        for(int i :answerList){
            if(i == target){
                answer += 1;
            }
        }
        
        return answer;
    }
}

그래프를 그리면서 탐색할 Queue를 하나 만들어 주고 루트 노드로 0을 넣어줬습니다.

이후로는 해당 레벨의 갯수 만큼 반복하며 하위 노드를 만들고 부모 노드를 삭제하는 것을 반복합니다.

마지막에는 큐에 리프 노드의 숫자들만 남게 됩니다.

728x90
728x90

CODE

 전자의 이동부터 시작해서 현대의 컴퓨터 작동원리를 알려주고, 몇 개의 프로그래밍 언어를 소개하며 끝나는 책입니다.

다 읽는데 굉장히 오래 걸렸습니다. 모르는 단어도 많고 단어는 알아도 무슨 말하는지 모르겠는 게 허다했습니다. 특히 회로 부분과 메모리의 명령어는 아마 열 번은 읽어야 얼추 이해하지 않을까 싶습니다.

 

 그럼에도 책의 순서가 작은 부품에서 시작해서 그 부품들을 조립해 하나의 물건을 만드는 과정처럼 되어 있어서 끝까지 읽을 수 있었습니다. 먼저 전자에 대해 알려주고, 전자의 이동을 통해서 신호를 보내는 것을 설명합니다. 그리고는 신호의 방법과 이진수, 릴레이, 스위치, 논리, 회로를 거쳐 컴퓨터의 작동 방법에 이르렀습니다. 만약 이렇게 순서대로 하지 않았다면 회로에서 책을 덮고 오랜 시간 뒤에 다시 펼쳤을 것 같습니다. ㅎㅎ..ㅎ.

 

 책은 재밌고, 유용했습니다. 이전의 수업에서는 프로그래밍 위주의 학습이었습니다. 그래서 '십진수 1+1은 컴퓨터에서 어떤 과정을 거쳐서 답이 나오는 걸까'와 같은 기초적인 컴퓨터 동작 과정이 궁금했었는데 그에 대한 대답을 알 수 있었습니다. 그리고 그냥 사용하던 컴퓨터의 단위나 동작들이 다르게 보였습니다. RAM이나 하드디스크의 용량이 금세 커진 것을 다시 한번 느끼기도 하고, CPU에 Hz단위를 사용하는 것이나 32비트 운영체제에서 64비트 운영체제로 변경된 점 등 새로 알게 된 것도 많았습니다. 근데 또 이렇게 작성하는 와중에 이유가 뭐였지?라고 생각하니 생각이 어렴풋이 나는 것이 금세 까먹어 버린 듯합니다. 그래도 한 번 읽어 뒀으니 다음에는 더 많은 부분을 이해할 수 있겠지 생각하며 가끔씩 꺼내봐야겠습니다.

 

 

 

 

 

 

 

 

 

728x90

'잡담 > 리뷰' 카테고리의 다른 글

Clean Code 책 후기  (0) 2022.09.24
728x90

import java.util.*;
class Solution {
    public int[] solution(String[] genres, int[] plays) {
        int[] answer = {};
        HashMap<String, Integer> genresMax = new HashMap<String, Integer>();
        HashMap<String, Integer[]> genTop2 = new HashMap<String, Integer[]>();
        for(int i = 0; i < plays.length ; i++){
            genresMax.put(genres[i],genresMax.getOrDefault(genres[i],0) + plays[i]);
        }
        // 장르 최대값
        int size = genresMax.size();
        String[] ge = new String[size];
        for(int i = 0; i < size; i++){
            int max = 0;
            String maxName = null;
            for(String s: genresMax.keySet()){
                if(genresMax.get(s) > max){
                    max = genresMax.get(s);
                    maxName = s;
                }
            }
            ge[i] = maxName;
            genresMax.remove(maxName);
        }
        // 값 담을 리스트
        List<Integer> list = new ArrayList<>();
        for(String s: ge){
            int rank1 = -1;
            int index1 = 0;
            int rank2 = -1;
            int index2 = 0;
            for(int i = 0; i < plays.length; i++){
                if(genres[i].equals(s)){
                    if(plays[i] > rank1){
                        rank2 = rank1;
                        index2 = index1;
                        rank1 = plays[i];
                        index1 = i;
                    }else if(plays[i] > rank2){
                        rank2 = plays[i];
                        index2 = i;
                    }
                }
            }
            list.add(index1);
            if(rank2 != -1){
                list.add(index2);
            }
        }
        answer = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            answer[i] = list.get(i);
        }
        return answer;
    }
}
728x90
728x90

 

해시 문제라 HashSet을 사용하긴 했는데 전체적인 사고방식은 수학 풀이 같습니다.

일단 N 마리의 포켓몬 중에서 N/2 마리의 포켓몬을 선택할 수 있고, 포켓몬의 종류 개수를 P라고 가정하겠습니다.

그렇다면 P가 N/2보다 크다면 가져갈 수 있는 포켓몬의 최대 종류의 수는 N/2의 값이되고

P가 N/2보다 작거나 같다면 가져갈 수 있는 포켓몬의 최대 종류의 수는 P가 됩니다.

 

nums = [피카츄, 피카츄, 파이리, 꼬부기]
hashset = [피카츄, 파이리, 꼬부기]

 때문에 위의 예시처럼 가져갈 수 있는 포켓몬의 수인 2마리보다 포켓몬의 종류 개수가 많다면

그냥 가져갈 수 있는 수를 리턴하면 됩니다. 

 

nums = [피카츄, 피카츄, 피카츄, 파이리, 파이리, 파이리]
hashset = [피카츄, 파이리]

반대로 위처럼 가져갈 수 있는 포켓몬의 숫자가 3마리인데 포켓몬의 종류가 그 숫자보다 적다면

포켓몬 종류의 수를 리턴하면 됩니다.

 

import java.util.*;
class Solution {
    public int solution(int[] nums) {
        int length = nums.length;
        
        Set<Integer> ponketmon = new HashSet<Integer>();
        for(int i: nums){
            ponketmon.add(i);
        }
        
        if((int)length/2 < ponketmon.size()){
            return (int)length/2;
        }else{
            return ponketmon.size();
        }
    }
}

위에 코드는 제 풀이 코드입니다.

728x90
728x90

 

 

 

 

 

 이번 주 공부할 내용인 해시 테이블 자료구조에 대해 정리해보겠습니다. 먼저 해시 테이블과 해싱(Hashing)이 무엇인지 알아보겠습니다. 그다음 자바에서 어떻게 적용되어 있는지 알아본 후 대표적인 클래스와 그 메서드를 알아보겠습니다.

 

해시 테이블(Hash Table)

 해시 테이블이란 키와 값의 쌍으로 이루어진 자료구조입니다. 해당 자료구조에는 해싱이라는 과정을 통해서 데이터가 저장됩니다. 그리고 원리적으로는 데이터 양이 아무리 많아지더라도 자료의 검색과 삭제에 O(1)의 시간이 걸립니다. 때문에 커다란 데이터에서 특정한 값을 검색할 때 해시 테이블을 사용하는 것이 유리합니다.

 여기서 해싱(Hashing)이란 해시 함수(Hash Function)를 이용해 데이터를 저장하고 검색하는 기법을 말합니다. 해시 함수는 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수를 말합니다. 예를 들어 해시 함수 중 하나인 SHA-256의 경우 해당 함수에 한 글자를 넣던 백 글자를 넣던 입력값의 길이에 상관없이 256비트의 결괏값이 출력됩니다. 좀 더 간단히는 아래의 그림과 같이 표현할 수 있습니다.

 해시 테이블은 자료 검색에 사용하기 위한 자료구조라 키값에 중복이 있으면 안 됩니다. 하지만 해시 함수는 어떤 값을 입력으로 넣든 간에 같은 길이의 결괏값을 반환하기 때문에 낮은 확률일지라도 서로 다른 값을 입력으로 넣었지만 같은 값이 출력되는 경우가 있습니다. 

 위의 그림과 같으며 이를 해시 충돌이라고 합니다. 해시 충돌을 방지하기 위해서 체이닝이나 이중 해시같은 방법이 사용됩니다. 다음과 같이 간단히 해시에 대해 알아보았습니다. 다음으로는 자바에서의 해시에 대해 알아보겠습니다.

 

자바에서의 해싱

 자바에서 해싱을 구현한 클래스로는 HashSet, HashMap, Hashtable 등이 있습니다. 이중 컬렉션 프레임워크가 등장하기 전에 사용하던 Hashtable은 이전 소스와의 호환성 문제로 남겨두고 있으니 이제는 HashMap을 사용하는 것이 좋습니다. 그리고 HashSet은 리스트와 달리 저장 순서를 유지하지 않습니다. 저장 순서를 유지하고자 한다면 LinkedHashSet을 사용하면 됩니다.

 자바에서 해싱을 사용하는 자료구조는 배열과 LinkedList의 조합으로 되어 있습니다. 해시 함수를 통해 나온 해시 코드는 키로써 배열의 인덱스가 되고 값은 해당 배열에 연결된 LinkedList에 저장됩니다. 때문에 키값으로 배열의 해당 인덱스에 있는 연결 리스트에 있는 값을 빠르게 찾을 수 있습니다.

 해시 함수에서 해시 충돌이라는 것에 대해서 이야기를 했었습니다. 자바에서는 해시 함수로 Object 클래스에 정의된 hashCode() 메서드를 사용합니다. 해당 메서드는 객체의 주소를 이용하는 알고리즘이라 모든 객체에 대해 유일한 값을 반환합니다. 때문에 해시 충돌의 걱정을 덜 수 있습니다. 그리고 String 클래스의 경우에는 오버라이딩한 hashCode() 메서드를 사용합니다. 이 메서드는 같은 내용의 문자열을 가졌다면 같은 해시 코드를 반환합니다. 이상으로 해싱은 마치고 클래스와 메서드에 대해 정리하겠습니다.

 

HashSet

  • 중복된 요소를 저장하지 않습니다.
  • 저장 순서를 유지하지 않습니다.

메서드 설명

boolean add(Obeject o) 새로운 객체를 저장합니다.
boolean addAll(Collection c) 주어진 컬렉션에 저장된 모든 객체를 추가합니다.
void clear() 저장된 모든 객체를 삭제합니다.
Object clone() 얕은 복사해 반환합니다.
boolean contains(Object o) 지정된 객체를 포함하고 있는지 알려줍니다.
boolean containsAll(Collection c) 컬렉션 c에 저장된 모든 객체를 포함하는지 알려줍니다.
boolean isEmpty() 비어있는지 알려줍니다.
Iterator iterator() iterator를 반환합니다.
boolean remove(Object o) 지정된 객체를 삭제합니다.
boolean removeAll(Collection c) 주어진 컬렉션에 저장된 모든 객체와 동일한 것을 삭제합니다.
boolean retainAll(Collection c) 주어진 컬렉션에 저장된 객체와 동일한 것만 남기고 삭제합니다.
int size() 저장된 객체의 수를 반환합니다.
Object[] toArray() 저장된 객체들을 객체 배열의 형태로 반환합니다.
Object[] toArray(Object[] a) 저장된 객체들을 주어진 객체 배열 a에 담습니다.

 

HashMap

메서드 설명

void clear() 저장된 모든 객체를 삭제합니다.
Object clone() 얕은 복사해 반환합니다.
boolean containsKey(Object key) 지정된 키가 있는지 알려줍니다.
boolean containsValue(Object value) 지정된 값이 있는지 알려줍니다.
boolean remove(Object key) 지정된 키로 된 저장된 값을 삭제합니다.
Object replace(Object key, Object value) 지정된 키의 값을 value 값으로 대체합니다.
boolean replace(Object key, Object oldValue, Object newValue) 지정된 키와 oldValue가 일치하는 경우에만 newValue 값으로 대체합니다.
int size() 저장된 객체의 수를 반환합니다.
Collection values() 저장된 모든 값을 컬렉션의 형태로 반환합니다.
Object put(Object key, Object value) 지정된 키와 값을 저장합니다.
void putAll(Map m) 맵에 저장된 모든 요소를 HashMap에 저장합니다.
Set keySet() HashMap에 저장된 모든 키를 Set으로 반환합니다.
boolean isEmpty() 비어있는지 알려줍니다.
Object get(Object key) 지정된 키의 값을 반환합니다. 못찾을 경우 null을 반환합니다.
Object getOrDefault(Object key, Object defaultValue) 지정된 키의 값을 반환합니다. 못찾을 경우 기본값으로 지정된 객체를 반환합니다.
728x90
728x90

 

 팀 프로젝트를 진행하던 중에 자주 들었던 생각이 있습니다. ‘내가 제대로 코드를 짜고 있는 걸까?’ 기능적으로는 원하는 방향으로 작동했기 때문에 문제는 없었지만 팀과 같이 협업할 때와 유지보수를 해야 할 때 문제가 없을까 하는 의문도 같이 들었습니다. 이런 의문을 해결하고자 찾아보았던 책이 클린 코드와 마틴 파울러의 ‘리팩토링’이란 책이었습니다. 다른 주제를 다루는 것 같으면서도 비슷한 내용 같아 무슨 책을 먼저 구매할까 고민하다가 표지가 마음에 들어 클린 코드를 먼저 구매했습니다.

 

 다 읽은 후에는 팀 프로젝트를 마무리할 즈음에 구매한 책이라 설계 단계부터 적용을 하지 못해 아쉽다는 생각이 들었습니다. 작게는 이름을 정하는 규칙부터 크게는 어떻게 구조적으로 코드를 짜야 하는지까지 알려주는 책입니다. 그리고 실제로 저자가 코드를 수정하고 어떤 사고의 과정을 거쳐 그렇게 수행했는지 알려주기 때문에 이해하는데 더욱 쉬웠습니다. 저자가 많은 원칙을 강조하는데 그중 단일 책임 원칙과 OCP 그리고 TDD가 기억에 남습니다. TDD는 원칙은 아니지만 일단 ㅎㅎ.

 

 단일 책임 원칙이 기억나는 이유는 프로젝트를 진행하면서 클래스를 제대로 구분하지 않아 한 가지가 아닌 여러 이유에서 같은 클래스를 수정한 경험 때문에 그렇습니다. 팀 프로젝트를 리팩토링 할 예정인데 거기서 이 원칙에 알맞게 수정을 해봐야겠습니다. OCP는 추상화를 잘 사용하는 방법인 것 같아서, 그 방법을 자유자재로 사용하고 싶은 마음에 기억에 남습니다. 하지만 이 방법을 잘 사용하려면 추상화 단계를 구분하여 클래스나 모듈을 구성해야 하는데, 책에 쓰여있는 것처럼 딱 봐서는 추상화 단계를 구분하기가 어려워 이해하는데 시간이 좀 걸릴 것 같습니다. TDD는 책에서 정말로 강조를 많이 하기 때문에 궁금함이 생겼습니다. ‘왜 이렇게 강조를 할까?’라는 생각이 들었는데 테스트 코드를 작성하는 방법도 찾아보고 해서 적용해봐야겠습니다.

 

 그런데 책이 이해하기 쉽다고는 했지만 제가 모르는 원칙이나 개념들이 많아서 그런 단어들이 나오면 찾아보면서 읽느라 좀 오래 걸리긴 했습니다. 단어를 찾아봐도 정확히 이해하지 못하는 부분들도 있었습니다. 때문에 일정 주기를 두고 계속해서 읽어 봐야 할 책 같습니다.

 

 그리고 공감하기 어려운 부분도 있었습니다. 책에서 클래스나 변수, 메서드의 이름을 짓는 부분은 영어를 사용하는 문화에서는 바로 이해해도 한국에서는 바로 와 닿기는 힘들겠다 생각했습니다. 예를 들어 함수가 하는 일을 잘 표현하도록 서술적인 이름을 사용하라 하면서 includesetupAndTeardownPage라는 이름의 함수를 짓습니다. 근데 한국어로 하면 ‘설정과 해제 페이지를 넣어라’라는 뜻인데 이게 영어로 작성되어 있을 경우 영어를 한 번 인식하고 한국어로 해석하는 과정을 거치기 때문에 책에 작성된 것처럼은 공감하기 어려웠습니다.

 

 하지만 개발에서는 영어가 주요 언어이고 많은 공식 문서나 커뮤니티 또한 영어를 사용하기 때문에 제가 공부를 해야겠다고 생각이 들었습니다. 허헣..

728x90

'잡담 > 리뷰' 카테고리의 다른 글

[독후감] CODE  (0) 2022.10.02

+ Recent posts