공부한 내용을 정리한 글입니다.
틀린 정보가 있을 수 있습니다.

동작 파라미터화

  • 기존 문제 : 변화에 대응하기 힘들다
    • ex ) 특정 옵션에 대한 필터링 메서드가 존재할 때 필터링 옵션을 추가하거나 수정하고 싶다면 계속해서 코드를 수정해야한다. 이는 코드의 유연성이 떨어진다고 볼 수 있다.
/*
* 조건에 따라 코드가 계속해서 수정되어야 한다.
* 유언성이 떨어지는 코드라고 할 수 있다.
*/

/* 파란 사과 필터링 */
public List<Apple> filterBlueApples(List<Apple> inventory){
    List<Apple> result = new ArrayList<>();
    for(Apple apple : inventory){
        if(BLUE.equals(apple.color()))
            result.add(apple);
    }
    return result;
}

/* 입력된 색으로 사과 필터링 */
public List<Apple> filterApplesByColor(List<Apple> inventory, Color color){
    List<Apple> result = new ArrayList<>();
    for(Apple apple : inventory){
        if(color.equals(apple.color()))
            result.add(apple);
    }
    return result;
}

/* 색과 무게로 사과 필터링 */
public List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag){
    List<Apple> result = new ArrayList<>();
     boolean colorFilter = flag && color.equals(apple.color());
    boolean weightFilter = !flag && weight < apple.weight();
    for(Apple apple : inventory){
        if(colorFilter || weightFilter)
            result.add(apple);
    }
    return result;
}
  • 해결 : 동작 파라미터화
    • 어떻게 실행될지 결정되지 않은 코드 블럭. 이 코드 블럭의 실행은 나중으로 미뤄진다.
    • 즉, 제공된 코드 블럭에 따라 메서드의 동작이 바뀐다.
    • 위 코드처럼 조건에 따라 코드가 변화해야 할 때, 주어지는 조건(참 or 거짓을 반환하는 함수) 을 “predicate”라고 한다. ‘함수형 인터페이스’ 라고도 한다.
    • 함수형 인터페이스의 종류 : Predicate, Consumer, Supplier, Function, Comparator 등..
    • 함수형 인터페이스는 람다 함수로 일회성으로 간단히 구현되어 사용될 수 있다.
    • 전략 패턴 : 런타임에 동작 방식을 선택하는 디자인 패턴 기법
/* 선택 조건을 결정하는 인터페이스 : predicate */
public interface ApplePredicate{
    boolean test(Apple apple);
}

/* predicate 구현 */
public class AppleHeavyWeightPredicate implements ApplePredicate{
    @Ovverride
    public boolean test(Apple apple){
        return apple.weight() > 200;
    }
}

public class AppleBlueColorPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple){
        return BLUE.equals(apple.color());
    }
}

/* predicate 사용한 사과 필터링 구현 */
public List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate){
    List<Apple> result = new ArrayList<>();
    for(Apple apple : inventory){
        if(predicate.test(apple)) 
            result.add(apple);
    }
    return result;
}

/* predicate 사과 필터링 사용 예시 */
List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());
List<Apple> blueApples = filterApples(inventory, new AppleBlueColorPredicate());

/* 람다 함수로 함수 인터페이스를 구현한 사과 필터링 사용 예시 */
List<Apple> sweetApples = filterApples(inventory, (apple) -> apple.sugar() > 10); // 당도 10 이상
List<Apple> bigApples = filterApples(inventory, (apple) -> apple.radius() > 5); // cm
---

함수형 인터페이스 & 람다 함수

함수형 인터페이스

  • 추상화 메서드가 하나만 존재하는 인터페이스
  • ex ) Consumer, Supplier, Function, Comaparator, Predicate 등
  • Consumer : 입력값을 받아 소비하고 반환값이 없는 함수형 인터페이스
  • Supplier : 입력값 없이 값을 생성하여 반환하는 함수형 인터페이스
  • Comparator : 두 객체를 비교하여 정렬 기준을 제공하는 함수형 인터페이스
  • Predicate : 조건을 평가하여 true 또는 false 를 반환하는 함수형 인터페이스

람다 함수 (표현식)

  • 익명 클래스를 대신해 함수형 인터페이스를 간단히 구현하여 사용할 수 있는 문법
  • 인자 타입이 함수형 인터페이스가 아니면 람다를 인자로 전달할 수 없다.

@FunctionalInterface

  • 함수형 인터페이스임을 가리키는 어노테이션
  • 추상메서드가 한 개 이상인 인터페이스에 사용하면 컴파일 타임에 ‘Multiple nonoverriding abstract methods found in interface Foo’ 같은 에러가 발생할 수 있다.
  • 내 생각 : 함수형 인터페이스 형식을 강제하기 위한 수단으로 보인다. 함수형 인터페이스를 만들고 사용할 때 @FunctionalInterface 를 사용하면 실수를 방지할 수 있을 것 같다.

함수 디스크립터

  • 함수의 파라미터 리스트와 반환 타입을 보여주는 식이다.
    • (Apple apple) → apple.getColor().euqals(RED);
    • 위 람다 함수의 디스크립터는 ‘Apple → boolean’ 이다.

람다의 형식 검사, 형식 추론 (type)

  • 문제 : 람다는 함수형 인터페이스의 인스턴스와 같다. 하지만 람다식만으로는 어떤 함수형 인터페이스를 구현하는지 알 수 없다.
  • 해결 : 이는 람다가 사용되는 context 로 람다의 형식을 추론할 수 있기 때문이다.
  • 형식 검사 및 추론 :
    • 람다를 대입하는 ‘변수’, 람다가 전달되는 메서드의 ‘파라미터’, 사용된 ‘추상메서드’ 에 따라 람다의 형식이 결정된다.
  • 형식 검사 및 추론 과정 예시 :
    • 람다가 사용된 메서드 정의 확인 → 파라미터 Predicate 확인 → 추상메서드 test(Apple apple) 확인 → 람다의 함수 디스크립터 ‘Apple → boolean’ 결정

같은 람다로 여러 함수 인터페이스 구현

  • 형식 추론에 의해 같은 람다로 여러 함수 인터페이스를 구현할 수 있다.
  • 다음 두 람다 함수는 ‘() → int’ 로 함수 디스크립터가 같다.
    • Callable c = () → 42;
    • PrivilegedAction p = () → 42;

람다의 지역변수 사용 (람다 캡쳐링)

  • 람다는 자신에게 주어진 인자를 제외하고도 외부에서 정의된 ‘한 번만 할당되는 지역변수’를 사용할 수 있다. 이를 람다 캡쳐링이라고 한다.
  • 즉, 외부 지역변수는 ‘final 이거나 선언 후 값이 바뀌지 않아야 한다.’
  • 인스턴스 변수 : 클래스 전역에 선언된 static (클래스 변수) 이 아닌 멤버 변수
  • 지역 변수 : 메서드 내부에 선언된 변수
  • 이유 :
    • 람다가 스레드A에서 실행되는 경우, 지역 변수를 다른 스레드B에서 할당했다면, 스레드A가 실행되는 동안 스레드B가 사라졌을 때 변수 할당이 해제될 수 있다. 따라서 람다는 지역 변수의 원본이 아닌 복사본을 사용한다.
    • 예를 들어, 새로운 스레드에서 수행할 동작을 람다 함수(Runnable)로 만들고 이 스레드 객체를 생성할 메서드를 다른 스레드에서 호출한다면 람다 함수 내부 로직과 람다 함수가 사용할 지역 변수가 소속된 스레드는 서로 달라진다. (스택의 스레드 영역이 다름)
      • 스레드1 : 람다함수 로직 실행
      • 스레드2 : 스레드1 객체 생성을 위한 메서드 호출, 지역 변수 포함
    • 따라서 람다 함수는 동기화 문제 방지를 위해 외부 지역변수의 복사본을 사용해야 하고, 이 복사본의 값이 바뀌지 않아야 하므로 한 번만 할당되는 지역변수를 사용해야 하는 것이다.
  • 이 말은 람다가 외부 지역 변수의 값을 바꿀 수 없다는 것과 같고, 람다는 변수가 아닌 ‘값’에 국한되어 어떤 동작을 수행한다는 것을 의미한다.
  • 인스턴스 변수는 스레드가 공유하는 힙에 존재하므로 특별한 제약이 없다.

람다 함수를 사용하여 함수형 인터페이스 구현 및 사용 예시

/* Consumer */
import java.util.function.Consumer;

Consumer<String> printConsumer = s -> System.out.println(s);
printConsumer.accept("Hello, Consumer!"); // Consumer 의 추상메서드 aceept 구현 및 호출

/* Supplier */
import java.util.function.Supplier;

Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get()); // Supplier 의 추상메서드 get 구현 및 호출

/* Comparator */
import java.util.function.Comparator;

String[] animals = {"cat", "elephant", "giraffe", "hippo"};
Arrays.sort(animals, (a, b) -> a.length() - b.length()); // sort() 내부에서 Comparator 사용
System.out.println(Arrays.toString(animals)); 

/* Predicate */
import java.util.function.Predicate;

Predicate<Integer> isEven = x -> x % 2 == 0;
System.out.println(isEven.test(4)); // Predicate 의 추상메서드 test 구현 및 호출 

동작 파라미터화 & 람다 연습

import java.util.*;
import java.util.function.Predicate;

public class Main{
    public static void main(String[] args){

        List<Apple> list = new ArrayList<>();
        list.add(new Apple(5, new Color("red"), "1"));
        list.add(new Apple(10, new Color("blue"), "2"));
        list.add(new Apple(15, new Color("green"), "3"));
        list.add(new Apple(20, new Color("red"), "4"));
        list.add(new Apple(13, new Color("blue"), "5"));
        list.add(new Apple(6, new Color("green"), "6"));

        Predicate<Apple> pred1 = apple -> apple.getRadius() > 10;
        Predicate<Apple> pred2 = apple -> apple.getName().equals("4");
        Predicate<Apple> pred3 = apple -> apple.getColor().getColor().equals("red");

        List<Apple> list2 = filter(list, pred3);

        for(Apple a : list2){
            System.out.print(a.getName() + "\t");
        }
        System.out.println();
    }

    // filter 함수
    public static List<Apple> filter(List<Apple> inventory, Predicate<Apple> pred){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            if(pred.test(apple))
                result.add(apple);
        }
        return result;
    }

    // apple 클래스
    static class Apple{
        private int radius;
        private Color color;
        private String name;
        public Apple(int radius, Color color, String name){
            this.radius = radius;
            this.color = color;
            this.name = name;
        }

        public int getRadius(){
            return radius;
        }

        public Color getColor(){
            return color;
        }

        public String getName(){
            return name;
        }
    }

    // color 클래스
    static class Color{
        private String color;
        public Color(String color){
            this.color = color;
        }

        public String getColor(){
            return color;
        }
    }
}

람다 연습: ExecutorService, Callable, 실행 스레드 이름 출력

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableThreadTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> threadNames = new ArrayList<>();
        for(int i = 0; i < 100; i++) {
            threadNames.add(executorService.submit(() -> {
                Thread.sleep(100); // 스레드 생성 확인을 위해 지연 발생
                return Thread.currentThread().getName();
                }));
        }
        for(Future<String> threadName : threadNames){
            try {
                System.out.println(threadName.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }
}

/* 실행 결과 : 지연발생 전에는 물리적 코어 개수 만큼만 스레드 생성
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-6
...
pool-1-thread-94
pool-1-thread-95
pool-1-thread-96
pool-1-thread-97
pool-1-thread-98
pool-1-thread-99
pool-1-thread-100
*/

동작 파라미터화 / 람다 리뷰

  • 동작 파라미터화 없이는 요구사항이 계속 변화함에 따라 중복된 메서드를 계속해서 추가해야 했다. (filterGreenApple, filterRedApple …)
  • 이런 중복 코드와 개발 비용을 줄이기 위해 동작 파라미터화를 도입했다.
  • 하지면 이것 역시 언어적 한계로 Predicate 같은 것을 클래스로 구현해서 인스턴스화한 뒤 메서드 인자로 넣어줘야했다.
  • 이렇게 되면 이전에 중복 코드를 계속 만들어주는 것과 비슷하게 새로운 Predicate 구현체를 만들어 줘야 하거나 보기에 좋지 않은 익명 클래스를 작성해야했다.
  • 이런 문제는 일회성 함수를 간단히, 가독성 좋게 만들 수 있는 람다 함수가 도입되어 해결됐다.
  • 이런 과정을 보면 결국 람다 함수의 등장이 필수적이었을 거라는 생각을 하게된다.
  • 만약 인자로 들어가야 할 함수가 이미 작성되어 있다면 람다 함수를 작성하는 것보다 간결하게 메서드 참조 (::) 를 사용해서 표현할 수 있다.

enum 객체 비교는 == 을 권장


이유 1. '==' 는 NullPointerException 을 발생시키지 않음

  • '==' 는 객체끼리 비교할 경우 메소드 호출 없이 객체의 참조만 비교하기 때문에 비교하는 대상이 null 이더라도 NPE 가 발생하지 않는다. if(obj == null) 이라면 NPE 가 발생하지 않고 false 가 반환된다. equals() 는 비교대상이 null 이면 NPE 를 발생시킴.

이유 2. '==' 는 컴파일 단계에서 타입 호환성을 검사한다.

  • '==' 로 다른 타입 객체를 비교하려고하면 컴파일 단계에서 에러를 발생시킨다. equals() 사용할 경우 컴파일 단계에서 검사하지 않고 그냥 false 를 반환한다.

이유 3. Enum 은 싱글톤 인스턴스기 때문에 '==' 사용해서 객체의 참조를 비교해도 다른 인스턴스를 가르키고 있을 가능성이 없음.

SOLID

로버트 마틴이 좋은 객체 지향 설계 원칙으로 정리한 5가지 원칙

  • SRP : 단일 책임 원칙 (SINGLE RESPONSIBILITY PRINCIPLE)
  • OCP : 개방 폐쇄 원칙 (OPEN CLOSED PRINCIPLE)
  • LSP : 리스코프 치환 원칙 (LISKOV SUBSTITUTION PRINCIPLE)
  • ISP : 인터페이스 분리 원칙 (INTERFACE SEGREGATION PRINCIPLE)
  • DIP : 의존 관계 역전 원칙 (DEPENDENCY INVERSION PRINCIPLE)

SRP 단일 책임 원칙

SINGLE RESPONSIBILITY PRINCIPLE

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 하나의 책임이라는 것은 모호하다.
    • 클 수도 있고, 작을 수도 있음
    • 문맥과 상황에 따라 다르다
  • 중요한 기준은 변경이다. 특정 기능이 변경될 때 코드에서 파급효과가 적으면 단일 책임 원칙을 잘 따랐다고 할 수 있다.
  • SRP 를 지키지 못한 예시
    • UI 하나 고치는데 쿼리문, 어플리케이션 코드 등등을 전부 고쳐야 함.

OCP 개방-폐쇄 원칙 (가장 중요함 !!)

OPEN - CLOSED PRINCIPLE

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀있어야 한다.
    • 예시: 다형성 활용
    • 인터페이스의 새로운 구현체를 만드는 것은 인터페이스가 바라보는 구현체를 교체해줘야하기 때문에 기존 코드를 변경해줘야한다. 즉, 확장에는 열려있지만 변경도 발생한다. (다형성을 활용했지만 OCP 를 지킬 수 없음)
    • 이 때, 객체를 생성하고 연관관계를 맺어주는 외부 조립, 설정자가 필요함. 이 역할을 Spring 이 해줄 수 있다.

LSP 리스코프 치환 원칙

LISKOV SUBSTITUTION PRINCIPLE

  • 객체는 프로그램의 정확성을 지키면서 하위 타입 인스턴스로 바꿀 수 있어야한다.
  • 다형성에서 하위 클래스는 인터페이스 규약을 전부 지켜야한다는 의미. 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 반드시 지켜져야한다.
  • 컴파일에 성공하는 것을 넘어서 기능적 원칙도 준수해야함.
    • 예를 들어, 자동차를 만든다면 액셀을 밟으면 느리더라도 앞으로 가야한다는 원칙을 지켜야한다.

ISP 인터페이스 분리 원칙

INTERFACE SEGREGATION PRINCIPLE

  • 특정 클라이언트를 위한 전문화된 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 자동차 인터페이스를 -> 운전자 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트를 -> 운전자 클라이언트, 정비사 클라이언트로 분리시킬 수 있음
  • 하나의 인터페이스가 변하더라도 관련 없는 클라이언트는 영향을 받지 않음
  • 인터페이스의 역할이 명확해지고 대체 가능성이 높아진다.

DIP 의존관계 역전 원칙 (가장 중요함 !! 2)

DEPENDENCY INVERSION PRINCIPLE

  • "프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다."
    • 의존성 주입(DEPENDENCY INJECTION)은 이 원칙을 따르는 방법 중 하나다.
  • 구현 클래스에 의존하지 말고 인터페이스에 의존하라.
  • OOP 관점에서 : 역할(ROLE) 에 의존하게 만들어야한다. 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경며 사용할 수 있다. 구현체에 의존한다면 변경이 아주 어려워진다.
  • 하지만, 인터페이스에 의존하는 코드가 구현체를 변경하려면 구현체에도 의존할 수 밖에 없다.
    • EX) MemberRepository mr = new MemoryMemberRepository();
    • MemoryMemberRepository 구현체를 변경하려면 의존할 수 밖에 없다.

위에서 예시를 들듯, 다형성만으로는 OCP, DIP 를 지킬 수 없다.

 

 

알고리즘 문제를 풀던 도중 UnsupportedOperationException 에러가 발생했다. 

 

처음보는 예외기도 하고 어떤 경우에 발생하는지 정리해놓기 위해 작성한다.

 

우선 Oracle Java Reference 에서 찾아보기로 했다. 

 

 

프로그래머스 Java 컴파일 옵션에 맞게 14 버전을 찾아보았고 설명은 위와 같다. 

 

요청된 작업이 지원되지 않는다는 것을 나타내기 위해 throw 된다는 것과 Collection 프레임워크에 대한 예외임을 알 수 있다. 

 

다른 사례들을 검색해본 결과 수정할 수 없는 ArrayList 자료형에 대해 작업을 수행하려다 이 예외가 발생하는 경우가 많았다. 

 

내 경우는 다음과 같다.

 

List<Character> copied = List.copyOf(charList);
for(Character c : copied){
    int index = copied.indexOf(c);
    List<Character> currentNum = new ArrayList<>();
    makeExistedNums(copied, index, currentNum);
}

 

 

위 코드에서 copied 라는 charList 의 복사본을 만들었고 makeExistedNums 라는 메서드에서 copied 리스트의 원소를 remove 한다.

 

이 때 UnsupportedOperationException 이 발생하는 것이다. 

 

원인을 찾아본 결과는 다음과 같다. 

 

List 인터페이스의 copyOf 메서드

 

ArrayList 의 상위 인터페이스인 List 의 copyOf 설명이다. 수정 불가능한 List 를 반환한다. 

 

내 경우 makeExistedNums 에서 깊은 복사된 리스트가 필요했기에 단순 대입이 아닌 자바 지원 메서드를 사용한 것이었는데 수정 불가능한 자료형을 반환한다는 것을 숙지하지 못했다. 

 

"unmodifiable List" 에 대한 설명을 더 찾아보자면 ... 

 

Unmodifiable Lists 특징

 

[간단 요약 번역]

List.of 와 List.copyOf 정적 팩토리 메서드는 수정 불가능한 리스트를 생성하기 위한 편리한 방법을 제공한다. 여기서 생성된 리스트들은 다음 특징을 가진다.

1. 수정 불가능함. 원소가 추가, 삭제, 대체 될 수 없다. 정해진 것 외의 메서드를 호출하면 "UnsupportedOperationException" 을 throw 한다.

2. null 원소를 허용하지 않는다. null 원소 생성을 시도하면 NullPointerException 을 throw 한다.

3. 모든 원소가 직렬화가 가능하다면 List 도 직렬화가 가능하다.

4. 리스트 원소의 순서는 제공된 인자 혹은 원소의 순서와 같다.

5. value based (값 기반) 클래스다. 여기에 사용된 팩토리 메서드는 새로운 인스턴스를 만들거나 이미 존재하는 인스턴스를 재사용할 수 있다. 그러므로 참조 비교(==), hash code, synchronization 과 같은 작업을 피해야한다.

(변수의 참조를 비교하거나 하지않고, collection 에 포함되어있는 각 값을 비교하는 것으로 이해)

6. 이들은 Serialized Form 페이지에 맞춰 직렬화된다.

 

 

위 설명에 따르면 수정 불가능한 리스트에 대해 remove 를 시도한 것도 잘못되었지만,

 

깊은 복사를 위해 List.copyOf 메서드를 사용한 의도 자체도 잘못되었다는 것을 알 수 있다. 

 

 

 

 

[해결법]

 

List 자료형의 깊은 복사를 위해서는 "new 연산자" 를 사용해야한다.

List<Character> copied = new ArrayList<>(charList);

 

잘못된 코드를 고치면 위와 같다.

 

개발용 서버로 사용 중이던 AWS 서버에서 업체 쪽 서버로 옮길 때가되었다. 

 

기존에 사용중이던 RDS 도 옮겨야되는 상황이 되었는데 RDS도, postgresql 도, DB 를 옮기는 것도 전부 처음이라 삽질을 마구하는 중이다..

 

나중에 필요할지 모르니 이것도 기록해둔다. 

 

 

 

필요 준비물 : 사용 중인 AWS RDS 인스턴스, 마이그레이션할 DB 서버, pgadmin4

 

1. pgadmin4 를 설치하고 AWS RDS 서버에 연결 : 

 

Server 아이콘을 보조클릭해주고 Register -> Server 선택

 

Server 아이콘을 보조클릭해주고 Register -> Server 선택

 

이름은 원하는대로 입력해준다

이름은 원하는대로 입력해준다

 

호스트 혹은 주소, 포트번호, posgresql 사용자, password 를 입력해준다

 

호스트 혹은 주소, 포트번호, posgresql 사용자, password 를 입력해준다. 그리고 save 를 클릭하면 rds 서버에 연결된다.

 

 

 

2. 데이터베이스 백업 옵션

 

사용 중인 데이터베이스를 보조클릭하고 Backup 을 선택

 

사용 중인 데이터베이스를 보조클릭하고 Backup 을 선택

 

 

filename 옆 폴더 아이콘으로 원하는 경로와 파일이름을 설정

 

filename 옆 폴더 아이콘으로 원하는 경로와 파일이름을 설정한다. Format 은 Custom, Encoding 은 UTF-8 로 설정해준다.

 

 

 

위 처럼 백업 옵션을 설정해준 뒤 Backup 버튼을 눌러 .sql 파일이 생성됐는지 확인한다.

 

 

 

3. 새로운 서버에 postgreSQL 설치 및 원격 접속 허용 설정

 

 

https://www.postgresql.org/download/linux/ubuntu/

 

PostgreSQL: Linux downloads (Ubuntu)

Linux downloads (Ubuntu) PostgreSQL is available in all Ubuntu versions by default. However, Ubuntu "snapshots" a specific version of PostgreSQL that is then supported throughout the lifetime of that Ubuntu version. Other versions of PostgreSQL are availab

www.postgresql.org

 

⬆️ 우분투 서버에 15 버전을 설치할 예정이기 때문에 위 가이드를 따라 설치했다. 14까지는 apt 저장소에서 설치 가능한듯 함.

 

 

sudo -u postgres psql

 

정상적으로 설치가 완료되고 서비스가 실행 중이라면 위 명령어로 DB 에 접속이 가능할 것이다. 

나는 설치할 때 super user 의 암호를 설정하라는 말이 없어서 직접 변경해줬다.

 

\password postgres

 

DB 에 접속한 상태에서 위 명령어를 입력하면 변경할 password 를 입력하라고 표시되는데, 

password 확인하는 것까지 총 두 번 입력하면 변경 완료됨.

하지만 이 상태로 종료 후 다시 접속하려하면 password 도 묻지않고 에러가 발생할 것이다. 설정을 변경해줘야 한다.

pg_hba.conf 라는 파일을 수정해줘야하는데 내 경우에 경로는 다음과 같았다. 

 

sudo vim /etc/postgresql/15/main/pg_hba.conf

 

위 경로로 접근하게되면 맨 밑에 아래와 같은 부분이 보인다.

 

 

 

 

수정해야할 부분은 "md5" 라고 표시된 부분이다. 

원래는 peer 라고 표시되어 있을텐데 md5 로 수정된 부분을 똑같이 변경해주면 된다.

 

맨 아래에 수정된 부분은 모든 곳에서 원격 접속을 허용하도록 설정한 것이다.

이렇게 총 두 부분을 추가/변경 해주면된다. 

설정 이후 DB에 접속할 때마다 password 를 물어볼 것이다.

 

 

 

sudo vim /etc/postgresql/15/main/postgresql.conf

 

위 설정 외에 원격 접속을 위한 설정이 하나 더 있는데 위에서 설정한 곳과 같은 경로에서 "postgresql.conf" 를 찾는다.

 

위 명령어로 파일을 열면 아래와 같은 부분이 보일것이다.

 

원격 접속 설정

 

이 곳에서 주석처리된 부분 밑의 listen_addresses = '*' 부분을 똑같이 작성해주고 저장한다.

 

위 설정을 모두 마치고 원격서버가 아닌 자신의 로컬환경으로 되돌아와서 pgadmin4 를 실행시킨다.

 

 

 

 

 

4. 백업파일 복원 및 옵션

 

AWS RDS 와 연결한 방식  그대로 원격 DB 서버에 pgadmin4 으로 접속한다.

 

사용자 이름과 비밀번호는 위에서 설정한 "postgres / 비밀번호 " 를 입력한다.

 

 

 

 

접속이 완료되었다면 데이터베이스를 보조클릭하고 Resotre 클릭

 

 

 

 

Filename 부분에 위에서 백업했던 파일의 경로를 찾아서 입력해준다. 

 

 

복원 옵션

복원 옵션을 위 이미지처럼 설정해준 뒤 Restore 클릭한다.

 

 

위 과정을 모두 마치면 사용하던 RDS 데이터베이스를 옮길 수 있다.

 

 

 

+ pgamdin4 에서 테이블 값 확인

 

 

 

사용해본 DBMS 가 졸업작품 만들 때 사용한 mariaDB 밖에 없어서 postgresql 은 생소했다 ... 

쿼리문을 입력해도 자꾸 에러가 발생해서 GUI 기능을 사용하기로 함.. 나중에 따로 공부해야할 듯

 

위 이미지에 나온대로 테이블을 보조클릭하고 "View/Edit Data -> All Rows" 를 클릭하면 저장된 데이터들이 보인다.

새로운 서버에서 이전에 사용하던 데이터들이 잘 보임. 다행이다.

 

 

+ 터미널의 pg_restore 에서 .sql 파일 복원하기 

 

위에서 백업한 .sql 파일을 pgAdmin 으로 복원할 수 없다면 pg_restore 을 사용해야한다.

아래 명령어로 복원이 가능하다

pg_store -U ${사용자이름} -d ${Database 이름} ${백업파일}
 

 

 

 

 

(내용 출처) 

https://www.youtube.com/watch?v=DOe7GkQgwPo&ab_channel=AmirEshaq

 

 

 

 

1. 기본적인 흐름

mediasoup은 producer로 미디어를 보내고 consumer로 미디어를 받는 기본적인 흐름을 가지고있다. producer와 consumer는 transport에 의해 만들어지며 각 transport당 여러개의 producer와 consumer를 가질 수 있다. producer와 consumer는 router를 통해 미디어를 주고받는다. transport는 router 당 여러개 만들어질 수 있다. router는 room(화상채팅방) 개념으로 이해할 수 있다. router는 worker에 의해 생성되며 각 worker당 여러개의 router를 가질 수 있다. worker는 cpu 코어 당 하나씩만 만들어질 수 있다.

 

구조 예시:

worker

|--router1

|       |--transport1

|       |         |--producer1

|       |         |--producer2

|       |--transport2

|       |         |--consumer1

|       |         |--consumer2

|--router2

...

 

2. 이번에 알아볼 것들

* connect 되었을 때 주고받는 parameter 들

* disconnect 되었을 때 무엇을 해야하는가?

 

 

3. socket 통신을 이용한 Client(producer/consumer) 와 Media Server 연결 과정

(1) 각 client에서 createDevice 메소드를 실행하여 producer peer와 consumer peer의 Device 객체 생성

(2) 각 producer/cosumer peer 는 서버에서 rtpCapabilities 를 받고 이를 인자로 받는 Device의 메소드 실행

(3) 서버에서 자신이 사용할 producer와 consumer용 webRtcTransport 두 개를 만든다. 만들 때 지정할 수 있는 옵션은 다음과 같다. listenIps: [{...}, {...}], enableUdp: true(v) / false, enableTcp: true(v) / false, preferUdp: true(v) / false

(4) 미디어 서버의 producer / consumer 용 각 webRtcTransport 는 각 client(producer/client) 로 여러 parameter를 보내고 producer client는 create SEND Transport를, consumer client는 create RECV Transport 를 수행한다. 아래는 미디어 서버가 client에게 보내는 parameter 들이다. Transport id, iceParameters, iceCandidates, dtlsParameters

(5) create SEND Transport / create RECV Transport 로 producer client와 consumer client 는 각각 자기의 local Transport 객체를 받는다.

(6-1) producer client 는 local Transport(SEND Transport) 객체의 produce 메소드를 실행하고 producer 객체를 받는다. produce 메소드는 local Transport 객체의 on Connect / on Produce 이벤트를 발생시킨다. produce 메소드는 아래 parameter를 받는다. encodins, codecOptions

(6-2) on Connect 이벤트는 dtlsParameters 를 반환하고 이를 미디어 서버로 보낸다. 미디어 서버는 자신의 local Transport (producer side webRtcTransport) 의 connect 메소드를 실행한다. connect 메소드는 dtlsParameters를 인자로 받는다. dtlsParameters

(6-3) on Produce 이벤트는 명확히 kind, rtpParameters 를 반환하고 callback, errback 메소드와 함께 이를 미디어 서버로 보낸다. 미디어 서버는 자신의 local Transport (producer side webRtcTransport) 의 produce 메소드를 실행한다. produce 메소드는 미디어 서버의 producer 객체를 반환한다. produce 메소드는 client에서 받은 parameter들을 인자로 받는다. kind, rtpParameters

(6-4) 우리는 미디어 서버측 producer의 producer id 를 필요로 한다. 미디어 서버는 producer id 를 producer client로 보낸다. producer client 는 producer id 를 받아서 callback 을 실행한다. callback 은 local Transport(SEND Transport)에게 '미디어 서버로 parameter 들을 잘 보냈다'는 사실을 알리고 producer id를 제공한다. 에러가 발생하면 errback 을 실행한다. * 이제 producer client 는 미디어 서버로 미디어를 스트리밍한다.

(7-1) 이제 consumer client 는 스트리밍 중인 미디어를 consume 해야한다. consumer client 의 Device 객체에서 rtpCapabilities 를 추출해서 미디어 서버로 보낸다.

(7-2) 미디어 서버는 consumer client가 보낸 rtpCapabilities 와 자신의 router로 consumer client가 자신의 producer 가 스트리밍하는 미디어를 잘 consume 할 수 있는지 producer id 와 함께 체크한다. consumer client 가 consume 할 수 있다고 판단되면 미디어 서버는 자신의 local Transport(consumer side webRtcTransport) 의 consume 메소드를 실행한다. consume 메소드는 미디어 서버측의 consumer 객체를 반환한다.

(7-3) 미디어 서버는 consumer 객체에서 parameter 들을 추출해서 consumer client 로 보낸다. consumer client는 parameter들을 받아 local Transport(RECV Transport) 의 consume 메소드를 실행한다. consumer client 의 consume 메소드는 consumer 객체를 반환하고 on Connect 이벤트를 발생시킨다.

(7-4) on Connect 이벤트는 dtlsParameters 를 반환하고 미디어 서버로 보낸다. 미디어 서버는 dtlsParameters 를 받고 이를 인자로 받는 local Transport(consumer side webRtcTransport) 의 connect 메소드를 호출한다. * 이제 consumer client 는 미디어 서버로부터 스트리밍 미디어를 consume 한다.

 

 

 

4. producer client 가 연결을 종료했을 때 ... 

* mediasoup은 연결이 종료된걸 자동으로 감지하지 않기때문에 socket.io 를 사용해서 disconnect 이벤트가 발생하면 그 후속조치를 취해줘야한다.

(1) Producer client가 connection 닫고 disconnect 이벤트 발생

(2) 미디어 서버의 producer side webRTCTransport 로 transport.close() 호출하고 producer.close() 호출한다.

(3) producer.close() 가 호출되면 미디어 서버의 consumer side webRtcTransport 에게 close 이벤트가 발생한다. 그 때 transport.close() 호출한다.

(4) 미디어 서버의 consumer side webRtcTransport 가 close 되면 consumer client의 local transport(RECV Transport)에게 transport.close() 를 호출하고 그 후 consumer.close() 를 호출한다.

 

5. consumer client 가 연결을 종료했을 때 ... 

* mediasoup은 연결이 종료된걸 자동으로 감지하지 않기때문에 socket.io 를 사용해서 disconnect 이벤트가 발생하면 그 후속조치를 취해줘야한다.

(1) consumer client 가 connection 닫고 disconnect 이벤트 발생

(2) 미디어 서버의 consumer side webRtcTransport 로 transport.close() 호출하고 consumer.close() 호출한다.

https://sprout13.tistory.com/42

 

Wine을 이용하여 Linux에서 Windows 소프트웨어 실행

Linux란 컴퓨터 오픈소스 운영체제의 분류 중 하나로, 리누스 토르발스가 개발한 리눅스 커널에 그 기반을 두고 있다. 오픈 소스 운영체제이기 때문에 매우 다양한 배포판이 있어, 사용자의 취향

sprout13.tistory.com

(리눅스 민트 와인 설치)

 

https://velog.io/@duboo/Ubuntu-20.04-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95-%ED%95%9C%EA%B8%80%EA%B9%A8%EC%A7%90-%EB%AC%B8%EC%A0%9C

 

Ubuntu 카카오톡 설치 / 23.08 수정

우분투에서 카카오톡을 사용하는 방법 중 가장 많은 분들이 사용하는 방법인 와인을 이용한 설치 방법 및 한글 깨짐 문제를 해결하는 방법입니다.

velog.io

(리눅스 카카오톡 한글 설치)

Linux mint 를 사용 중인 노트북에서 HDMI 포트로 외장모니터를 사용하려니 모니터에 맞는 해상도가 없었다.

 

처음 보는 상황이라 Linux mint 에서 원하는 해상도를 추가하는 방법을 찾아보았다.

 

내가 사용하는 모니터는 '한성 TFG34Q14W 1500R' 고, 21:9 비율 울트라와이드 모니터다. 

 

https://forums.linuxmint.com/viewtopic.php?t=389338

 

Ultrawide resolution not working [SOLVED] - Linux Mint Forums

Forum rules Before you post read how to get help. Topics in this forum are automatically closed 6 months after creation. thimor Level 1 Posts: 2 Joined: Fri Jan 13, 2023 11:49 am Post by thimor » Fri Jan 13, 2023 12:05 pm Just upgraded from linux mint 18

forums.linuxmint.com

mint 커뮤니티를 돌아다니던 중 위 게시글을 찾게 되었다. 여기서 찾은 정보는 다음과 같다.

xrandr --newmode "2560x1080_60.00" 230.00 2560 2720 2992 3424 1080 1083 1093 1120 -hsync +vsync
xrandr --addmode HDMI-2 "2560x1080_60.00"
1. 관련 질문자가 자기 device 정보를 남길 때 'inxi -Fxxxrz' 라는 명령어를 사용한다.
- Inxi is a command line tool that can be used to find the complete system and hardware details such as;
Hardware,CPU,Drivers,Xorg,Desktop,Kernel,GCC version,Processes,RAM usage,and other useful information.
- CPU / Drivers / Xorg / Desktop / Kernel ... 등 하드웨어와 전체 시스템 정보를 찾기위해 사용하는 툴
2. xrandr 라는 명령어로 화면 해상도를 설정하는 듯 보인다.
- Xrandr is used to set the size, orientation and/or reflection of the outputs for a screen. It can also set the screen size. ( 화면 크기, 방향 등 설정 명령어 ) 

 

나머지는 대충 알겠는데 '230.00 2560 2720 2992 3424 1080 1083 1093 1120 -hsync +vsync' 이 부분이 뭔지 모르겠다. 

 

https://itslinuxfoss.com/set-custom-resolution-using-xrandr/

 

How to Set Custom Resolution Using xrandr?

The “xrandr” is a built-in command line tool to customize screen resolution, orientation, and refresh rates and fix monitor-related bugs. This tool is also beneficial for setting a system-supported custom...

itslinuxfoss.com

 

위 링크로 들어가보면 원하는 해상도를 설정하고 적용하는 방법을 알 수 있다.

 

위에서 궁금했던 부분에 대해서도 알 수 있는데 ... '새 모드에 대한 해상도와 타이밍' 등을 나타낸다고 한다. 

 

이는 다음 명령어로 확인할 수 있다. (General Transfer Format의 약자다.)

 

gtf 3440 1440 60 

3440 1440 은 내 모니터 해상도고 60은 refresh rate 다. 내 모니터는 144hz 까지 지원하지만 노트북에 내장그래픽이라 그냥 60으로 했다. 실행하면 다음 사진처럼 결과가 나온다.

 

 

여기서 Modeline 뒤에서부터 "3440 ... +Vsync" 까지 복사하고 다음 명령어에 사용한다. 여기까지 완료하면 실질적으로 해상도 추가는 완료된다. (두번째 명령어에서 나는 HDMI-1 를 사용했는데 inxi 명령어로 사용할 포트가 어떤건지 확인하고 입력하자)

xrandr --newmode "3440x1440_60.00" 419.11 3440 3688 4064 4688 1440 1441 1444 1490 -HSync +Vsync
xrandr --addmode HDMI-1 "3440x1440_60.00"

 

아마 여기까지만해도 Display 설정에 추가된 해상도가 보일테지만 제대로 적용이 안된다. 이후 로그아웃 했더니 잘 동작했다. 어떤 것이든 설정 후에 뭔가가 안되면 로그아웃을 해보자...

 

아무 인자 없이 xrandr를 실행해보면 아래 HDMI-1 항목에 3440x1440 이 추가된 것을 볼 수 있다. 

 

 

 

 

 

(추가) 로그아웃 / 리부팅 이후 Display Resolution 삭제되는 현상 

처음 설정에서 제대로 적용이 되지 않아서 logout을 해보라고 했었다. 이 땐 로그아웃 이후에도 설정이 유지되어서 원래 그런줄 알았는데 시간이 지나고보니 설정이 삭제되어 있었다. 아무래도 추가한 설정이 유지되지 않는 모양이다. 바로 위에 링크해두었던 페이지에 설정을 저장할 수 있는 방법이 설명되어있다. 다만 나의 경우 zsh를 사용하므로 좀 다르게 설정해야했다. 

 

일단 기본적인 방법은 본인 쉘 설정 파일에 아래와 같은 코드를 추가하는 것이다. 보면 아시겠지만 앞에서 설정할 때 사용한 명령어 두줄이다. 본인의 상황에 맞게 입력해주면 된다. 

xrandr --newmode "3440x1440_60.00"  419.11  3440 3688 4064 4688  1440 1441 1444 1490  -HSync +Vsync
xrandr --addmode HDMI-1 "3440x1440_60.00"

 

첫 번째 경우 : bash 사용자 

위 링크에서 설명하는대로 "~/.profile" 파일에 xrandr 명령어를 추가한다. 

"~/.bash_profile" 이 존재하는 경우에는 이 파일에 추가한다.

 

두 번째 경우 : zsh 사용자 (내 경우다)

"~/.zshrc" 파일에 자신의 xrandr 명령어 두 줄을 추가한다.

 

파일의 변경사항을 저장한 후 로그아웃/리부팅 하면 추가한 설정이 없어지지 않고 유지되어있다.

 

 

 

 

 

* 주의 사항 *

1. HDMI를 연결해도 모니터가 켜지지 않을때 : 아무리 뺐다껴도 반응이 없더니 inxi 명령어를 사용한 후 모니터카 켜졌다. ( 켜지고나니 해상도가 달라서 이 글을 작성하게 되었지만... ) 디바이스 정보를 표시하기위해 refresh 하면서 켜지는거 아닌가... 싶다.

2. 본인이 사용할 디스플레이 포트를 잘 확인하자 : 첫 번째 링크에서는 HDMI-2, 두 번째 링크에서는 wayland0 라고 표시되어있다. 나 같은 경우 inxi -Fxxxrz 명령어로 확인해보니 외장모니터용으로 HDMI-1 포트를 사용 중이었기 때문에 xrandr 명령어의 인자로 HDMI-1 을 사용했다.

 

 

 

생각해보니 똑같은 모니터를 사용하는 데스크탑에서도 똑같은 버전을 사용중인데 왜 노트북에서만 이런건지 잘 모르겠다.. 다른 글도 보다보니 같은 4.x 커널 안에서도 이전버전에서는 해상도가 지원되었다가 다음 버전에서 해상도가 지원이 안되는 경우도 있는걸 보면 자주 있는 일 같기는 하다.

미디어 서버와 클라이언트의 연결을 수립하고 난 이후 router(방) 을 생성하여 여러 인원이 채팅방을 만들었다고 가정. 

 

Q. 권한을 가진 유저가 버튼을 클릭해서 '특정 유저의 비디오/오디오 송출을 막으려면' 어떻게 해야할까?

1. 권한을 가진 유저가 특정 유저의 미디어 송출을 막을 순 있지만 직접적으로 상대방의 미디어를 키고 끌 수 있게 컨트롤하면 안됨.
2. 권한을 가진 유저가 특정 유저의 미디어 송출을 막았을 때 해당 유저가 마음대로 다시 송출할 수 있으면 안됨.
3. 송출하지 못하게 할 것인가, 송출하더라도 다른 유저에게 전달되지 못하게 할 것인가?

A. 

1번의 경우는 클라이언트 부분을 조작하는게 아닌 미디어 서버부분을 조작해서 송출을 막는 것으로 해결하기로 했다. 
-> 미디어 서버에서 특정 유저의 producer 를 제거 중지하면 미디어는 송출될 수 없고 클라이언트를 조작할 필요도 없다. ( producer.pause() )

2번의 경우는 특정 유저의 미디어 송출이 block 된 후 유저가 다시 버튼을 눌러 미디어 서버에 producer를 생성 재개하려 할 수 있다. 이 때, 미디어 서버에서 peer의 peerState 부분에 block 당했는지 식별할 수 있는 속성을 하나 추가하고 이걸 확인한 후 아직 block 당한 상태라면 producer 를 생성 재개하지 않기로 했다.  
-> router 의 각 peer마다 block 상태를 가지고있고 producer 생성 재개할 때 이를 검사하도록 조정 ( producer.resume() )
-> socket.emit()으로 클라이언트에서 서버로 이벤트와 함께 userId 배열과 콜백함수를 방출하고 미디어서버에서 socket.on으로 이벤트를 감지한 직후 producer.pause() 로 유저를 block한다.  위에서 말한 과정을 끝내고 전달받은 콜백함수를 호출하면 클라이언트에 구현되어있는 콜백함수가 실행되며 모든 유저의 View 화면에 특정 유저가 차단당했다는 표시를 해준다. 

3번의 경우는 이해가 완벽하지 않아 애매하다. 송출하지 못하게하려는 목적으로 미디어 서버의 producer를 제거 중지시키기로 했지만 나머지 유저들의 consumer를 제거/중지하는 방법도 유효한지 모르겠음. 대신 한 명의 유저를 조작하는게 더 쉽기 때문에 block 당한 유저의 producer 를 제거 중지시키는 쪽으로 채택했다. 

 

 

* 참고 * 

1. 미디어 서버의 producer는 video 와 audio 로 구분될 수 있고, 코드 상에서 producer.kind 로 구분 가능

2. 처음에는 미디어 서버의 producer를 close() 하려고 했다. 하지만 이 방법은 미디어 서버에서 닫은 producer를 다시 활성화 할 수 없고 재생성하는 방법 밖에 없었다. 공식문서에서 producer.pause()와 producer.resume()을 찾았고 이 메소드를 사용하기로 결정했다.

3. pause() / resume() 을 사용할 때 주의할 점: 클라이언트가 미디어를 on/off 하는 동작을 통해 producer를 close() 했다가 다시 생성한다면 중지되었던 producer는 사라졌기 때문에 다시 미디어를 송출할 가능성 있음. 따라서 peer의 block 상태가 true라면 resume 뿐만아니라 transport.produce() 도 못하게 막아야함.

 

 

 

 

 

 

 

 

https://bird-liver-ecc.notion.site/StringTokenizer-VS-String-split-7133dade7c804f46a48994859fc514b0?pvs=4

 

StringTokenizer VS String.split()

StringTokenizer 와 String.split() 의 속도 차이와 그 이유

bird-liver-ecc.notion.site

↑ 원본링크 

 

 

- 참조 -

StringTokenizer 와 String.split() 의 속도 차이와 그 이유

StringTokenizer는 느릴까? 실험하기

StringTokenizer 관련 StackOverflow 게시글

 

 

무엇이 더 빠른가? 🏃‍♂️

  • 알고리즘 문제를 풀면서 각종 해설에서 StringTokenizer를 사용한다. 그래서 당연히 String.split() 은 속도를 생각하면 사용하면 안되는 메소드로 생각하고 있었다. 그런데 생각보다 그렇게 단순히 결정짓고 넘어갈 문제가 아니었다.
  • 결론 :
    1. 그때그때 다르다.
    2. StringTokenizer는 레거시코드다. (deprecated 되지는 않았다)
    3. String.split() 은 비교적 일정한 속도를 내지만 StringTokenizer는 경우에 따라 편차가 크다.
    4. 즉 상황에 맞춰서 써라. ( 자바 버전을 업그레이드 했을 때 문제 생길지는 미지수 jdk17까지는 사용할 수 있는듯 )

속도 차이가 왜 생기나? 🤷‍♂️

  • String.split()은 정규표현식을 사용해 문자열을 나누고 이는 속도가 느린 원인이 된다.
    • 실제로 알고리즘 문제를 풀다보면 속도차이가 꽤 생기는 경우 발생
    • 하지만 속도가 꽤나 균일하다
  • StringTokenizer는 구분자(delimeter)와 문자열을 전부 다 비교한다.
  • StringTokenizer는 ‘구분자가 유니코드, hasMoreTokens나 nextToken 호출’ 시에도 문자열과 구분자 전체를 비교하기 때문에 효율이 좋지 못하다.
    • 즉 위에서 말한 조건이 만족되는 경우가 많을수록 속도가 급속도로 느려진다.
    • 예를들어… 아스키코드에 존재하지 않는 “뷁” 같은 문자를 구분자로 사용하며 그 수가 많고, 여러 문자를 나누어 hasMoreTokens()나 nextToken() 을 반복해서 사용한다면 StringTokenizer를 사용하는건 좋은 선택이 아니다.

실험 결과 비교하기

  1. 첫번째 실험 : 구분자를 “,” 사용 / hasMoreTokens() 사용 안함 / nextToken() 한번 사용
    1. 결과 :
    splits ====== time : 271, clientIp : 192.168.1.1
    tokenizer ====== time : 80, clientIp : 192.168.1.1
    [출처] [StringTokenizer VS String.split] 누가 더 빠른가|작성자 평범한개발자
    
  2. 두번째 실험 : 구분자를 아스키코드가 아닌 문자 사용 / hasMoreTokens(), nextToken() 반복 사용
    1. 코드 :
    StringTokenizer 와 String.split() 의 속도 차이와 그 이유b. 결과 : 차이가 갑자기 확 좁혀졌다
  3. splits ====== time : 257, clientIp : 127.0.0.3 tokenizer ====== time : 220, clientIp : 127.0.0.3 [출처] [StringTokenizer VS String.split] 누가 더 빠른가|작성자 평범한개발자
  4. // 위 링크에서 코드 발췌 @Test public void getClientIpSplitTest() { String clientIp = null; long start = System.currentTimeMillis(); for(int i = 0; i < 1000000; i++) { String xForwardedFor="192.168.1.1뛟127.0.0.1뛟127.0.0.2뛟127.0.0.3"; String[] splits = xForwardedFor.split("뛟"); if(splits.length < 1) clientIp = ""; else { for(String str : splits) { clientIp = str; } } } long end = System.currentTimeMillis(); log.info("splits ====== time : {}, clientIp : {}", end-start, clientIp); } @Test public void getClientIpStringTokenizerTest() { String clientIp = null; long start = System.currentTimeMillis(); for(int i = 0; i < 1000000; i++) { String xForwardedFor="192.168.1.1뛟127.0.0.1뛟127.0.0.2뛟127.0.0.3"; StringTokenizer tokenizer = new StringTokenizer(xForwardedFor, "뛟"); while(tokenizer.hasMoreTokens()) { clientIp = tokenizer.nextToken(); } if(clientIp == null) clientIp = ""; } long end = System.currentTimeMillis(); log.info("tokenizer ====== time : {}, clientIp : {}", end-start, clientIp); } [출처] [StringTokenizer VS String.split] 누가 더 빠른가|작성자 평범한개발자
  5. 세번째 실험 : 두번째 실험에서 구분자 “하나” 추가 / hasMoreTokens(), nextToken() 반복 사용
    1. 결과 : 역전 당했다. 효율이 정말 좋지 않아보인다.
    "192.168.1.1뛟127.0.0.1뛟127.0.0.2뛟127.0.0.3뛟127.0.0.4";
    
    splits ====== time : 270, clientIp : 127.0.0.4
    tokenizer ====== time : 319, clientIp : 127.0.0.4 
    [출처] [StringTokenizer VS String.split] 누가 더 빠른가|작성자 평범한개발자
    

+ Recent posts