반응형

최근 읽고 있는 '자바 최적화'라는 책을 보다가 몰랐었던 내용이 있어 기록할겸 남겨본다. 

자바7 이전 리소스 사용후 닫는 것은 온전히 개발자의 몫

    public void readFirstLineOld(File file) throws IOException {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(file));
            String FirstLine = reader.readLine();
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
    }

 

자바7 부터 언어 자체에 추가된 try-with-resources 생성자를 이용하면 try키워드 다음의 괄호 안에 리소스(AutoCloseable 인터페이스를 구현한 객체만 가능)를 지정해서 생성할 수 있다. 이로써 try 블록이 끝나는 지점에 개발자가 close() 메서드 호출을 깜빡 잊고 빠뜨려도 자동으로 호출된다. close() 메서드는 방금 전 예제와 똑같이 호출되고 비지니스 로직의 예외 발생 여부와 상관없이 무조건 실행된다.

    public void readFirstLineOld(File file) throws IOException {
        try( BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String FirstLine = reader.readLine();
        }
    }

코드가 훨씬 심플해졌다. 하지만 try-with-resources 생성자 사용시 자동으로 close() 를 호출해주는 것을 몰랐다면 finally 코드를 생성해 그 안에서 또 close를 호출하려고 하였을거다...

위의 예에서는 catch(IOException exception) {} 과 같이 별도로 에러처리를 안해주고 그냥 상위로 exception을 그냥 던져버리는데 사실 좋지 않다. 항상 에러 발생시 catch 내부에서 잡고 로깅해주고 처리해주는 것이 좋다.

이번 포스팅을 하며 느낀점은 기본에 좀 더 충실한 공부가 필요할 것 같다.

 

반응형
반응형

자바 스트림 Skip 사용시 java.lang.IllegalArgumentException: -number 형태의 에러가 나는 이유는

skip 메서드의 인자로 0보다 작은 값이 들어 갔기 때문이다.

따라서 어떤 수치를 계산해서 skip에 인자를 전달하고 있다면 해당 값이 0보다 작지 않은 지 확인해보자.

skip 내부 로직

반응형
반응형

이전에 어디선가 질문 받은게 갑자기 생각나 String.format을 사용하지 않고 메서드를 구현해보았다.

    @Test
    public void convertStringToNumberStyle(String money) {
        String[] strSplit = money.split("");
        String convertedNumberStyle = "";

        int size = strSplit.length - 1;

        int count = 1;
        for (int i = size; i >= 0; i--) {

            if (count == 1) {
                convertedNumberStyle += strSplit[i];
            } else if (((count % 3) == 0) && (i != 0)) {
                convertedNumberStyle = "," + strSplit[i] + convertedNumberStyle;
            } else {
                convertedNumberStyle = strSplit[i] + convertedNumberStyle;
            }

            count++;
        }

        System.out.println(convertedNumberStyle); // money : 5000000 => output : 5,000,000
    }

하지만 String.format을 쓰면 1줄이면 끝난다는거 ㅎㅎ

String.format("%,d", Integer.parseInt(money))

심심해서 구현해보았는데 보통 라이브러리들에 의존해서 이런 부분을 처리하다 보니 막상 코드로 옮기기가 쉽지 않았다.

방식도 다양하고 여러 방법이 있겠지만 나는 한글자씩 쪼개어서 string을 다시 합쳐가며 3자리 지점마다 ","를 붙여주는 방식을 택했다.

가끔 자바 관련 면접에도 나올 수 있기에 다양한 방법으로 직접 구현해 보면 좋을 것 같다.

반응형
반응형

스프링부트에서 JavamailSender사용시 sendMail부분에서 다음과 같은 에러가 발생

org.springframework.mail.MailSendException: Mail server connection failed; nested exception is javax.mail.NoSuchProviderException: No provider for SMTP. Failed messages: javax.mail.NoSuchProviderException: No provider for SMTP at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:446) ~[spring-context-support-5.1.2.RELEASE.jar!/:5.1.2.RELEASE] at
.
.
.
Caused by: javax.mail.NoSuchProviderException: No provider for SMTP
at javax.mail.Session.getProvider(Session.java:545) ~[javax.mail-1.6.2.jar!/:1.6.2] at 
.
.

 

소스코드

    @Bean
    public JavaMailSenderImpl mailSender() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

        javaMailSender.setProtocol("SMTP");
        javaMailSender.setHost("127.0.0.1");
        javaMailSender.setPort(25);

        return javaMailSender;
    }

문제의 원인은 javaMailSender.setProtocol("SMTP"); 이부분이였다.

setProtocol의 "SMTP"를 "smtp" 소문자로 변경해주면 해결된다.

해결책

"SMTP" => "smtp"

이유

JavaMailSenderImpl 파일을 들어가보면 알 수 있다. 내부에 DEFAULT_PROTOCOL 이 소문자로 할당되어있다.

public class JavaMailSenderImpl implements JavaMailSender {
    public static final String DEFAULT_PROTOCOL = "smtp";
    

항상 문제가 발생했을 때 문제 해결에 그치지 말고 이유에 대해서도 꼭 짚고 넘어가도록 하자!!!!

문제 해결보다 중요한게 원인 파악 이라고 생각한다.

반응형
반응형

스프링부트에서 메일 발송 기능 구현 중 다음과 같은 에러가 발생하였다.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mailService': Unsatisfied dependency expressed through field 'javaMailSender'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.mail.javamail.JavaMailSender' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE] 
.
.
.
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.mail.javamail.JavaMailSender' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1646) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1205) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1166) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]

 

소스코드

기본적인 maven dependency추가 후 @Autowired했음

 

문제해결

@Bean으로 다음과 같이 protocol, host, port 지정 해줌

    @Bean
    public JavaMailSenderImpl mailSender() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

        javaMailSender.setProtocol("smtp");
        javaMailSender.setHost("127.0.0.1");
        javaMailSender.setPort(25);

        return javaMailSender;
    }
반응형
반응형



HashMap을 생성하고 그 안에 put을 해도 HashMap자체의 해쉬코드(hashCode)값은 안변할 줄 알았것만.....


결론은 put을 할 때마다 hadhCode값이 변하게 됩니다.


[ 백문이불여일코딩 ]


[ 결과 hashcode 값 ]

get메서드사용할때는 당연히 안바뀌고 put으로 데이터를 넣을 때 마다 해쉬코드가 변하는 것을 확인!!!!

값은 key값에 데이터를 덮어씌울때도 hashcode가 변한다! 이유는??


[ 이유는??? ]

HashMap의 put메서드가 내부적으로 entry를 새로 만들기 때문!!!!

map에서의 hashcode의 값은 entry의 해시코드의 합으로 정의되는데 내부에서 

put메서드가 호출될때마다 새로운 entry를 생성하기 때문에 값이 달라짐

public int hashCode()
Returns the hash code value for this map. The hash code of a map is defined to be the sum of the hash codes of each entry in the map's entrySet() view. This ensures that m1.equals(m2) implies that m1.hashCode()==m2.hashCode() for any two maps m1 and m2, as required by the general contract of Object.hashCode().

This implementation iterates over entrySet(), calling hashCode() on each element (entry) in the set, and adding up the results.

Specified by:
hashCode in interface Map<K,V>
Overrides:
hashCode in class Object
Returns:
the hash code value for this map
See Also:
Map.Entry.hashCode(), Object.equals(Object), Set.equals(Object)


참조 : https://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#hashCode()


[ HashMap put메서드를 따라가보자! ]

1. put


2. putVal


3. putTreeVal


4. newTreeNode


5. new TreeNode



6. TreeNode의 super


Wow....


상식적으로 생각했을 때 Map이 한 번 생성되면 안에 내용이 변경되더라도 해시코드(hashcode)값은 변하지 않을 줄 알았는데....


변하는 것을 보고 한 번 끝까지 따라가 보았다.


뭔가 속이 후련한???


앞으로도 궁금한점이 생긴다면 대충 알고 넘어가는 것보다 한 번 끝까지 따라가 제대로 된 이유를 알아보는것을 습관화하도록 하자!






반응형
반응형


최근에 특정 모듈을 분석해서 다시 만들어야 하는 작업을 진행하였다.


해당 모듈이 약간 성능에 민감한 모듈이다 보니 가능한 한 빠르게 동작하도록 하였어야 했다.


그러다가 ArrayList.contains() 메서드로 된 부분이 있어 HashSet.continas으로 수정해 성능이 조금 높아 진 것을 확인했는데


기본적인 개념으로만 HashSet이 내부적으로 HashMap을 구현하고 있어서 빠를 것 같다는 생각을 했었는데

좀 더 자세히 알고 싶어 포스팅 하게 되었다.


[ HashSet.Contains() ]

내부적으로 HashSet은 HashMap Instance를 구현하고 있고 contains() 메서드를 호출하게되면 HashMap.containsKey(object)가 호출된다고 보면 된다. 자세한 설명은 밑을 참고하자.

Internally, the HashSet implementation is based on a HashMap instance. The contains() method calls HashMap.containsKey(object).

Here, it’s checking whether the object is in the internal map or not. The internal map stores data inside of the Nodes, known as buckets. Each bucket corresponds to a hash code generated with hashCode() method. So contains() is actually using hashCode() method to find the object’s location.

Now let’s determine the lookup time complexity. Before moving ahead, make sure you are familiar with Big-O notation.

On average, the contains() of HashSet runs in O(1) time. Getting the object’s bucket location is a constant time operation. Taking into account possible collisions, the lookup time may rise to log(n) because the internal bucket structure is a TreeMap.

This is an improvement from Java 7 which used a LinkedList for the internal bucket structure. In general, hash code collisions are rare. So we can consider the elements lookup complexity as O(1).



[ ArrayList.contains() ] 

ArrayList는 해당 값이 list에 있는지 판단하기 위해 내부적으로 indexOf(object) 메서드를 사용한다. indexOf(object) 메서드는 array 전체를 반복해서 돌고 각각의 element와 비교를 진행한다. 자세한 설명은 밑을 참고하자.

Internally, ArrayList uses the indexOf(object) method to check if the object is in the list. The indexOf(object)method iterates the entire array and compares each element with the equals(object) method.

Getting back to complexity analysis, the ArrayList.contains() method requires O(n) time. So the time we spend to find a specific object here depends on the number of items we have in the array. 



설명을 봤듯이 HashSet은 내부적으로 HashMap을 사용하고 ArrayList는 contains 메서드 내부에서 모든 element들을 돌며 비교작업을 진행하기 때문에 당연히 HashSet의 contains메서드가 빠를 수 밖에 없었던 것이다.


[ 테스트 코드 ]

1. 100만개의 String을 생성하여 HashSet, ArrayList를 만든다.

2. 500번 반복문을 돌며 Set과 List의 contains메서드를 사용해 확인한다.


HashSet Test

@Test
public void testHashSetContainsPerformance() {
Set<String> testSet = new HashSet<>();
String baseStr = "hellotheworld";
Random generator = new Random();

long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
testSet.add(baseStr + i);
}
long end = System.currentTimeMillis();
System.out.println("Setup Performance : " + (end - start));

long start2 = System.currentTimeMillis();
int matchingCount = 0;
for (int j =0; j < 500; j++) {
if (testSet.contains(baseStr + generator.nextInt(5000))) {
matchingCount++;
}
}
long end2 = System.currentTimeMillis();
System.out.println("HashSet Contains Performance : " + (end2 - start2));
}


ArrayList Test

@Test
public void testArrayListContainsPerformance() {
List<String> testList = new ArrayList<>();
String baseStr = "hellotheworld";
Random generator = new Random();

long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
testList.add(baseStr + i);
}
long end = System.currentTimeMillis();
System.out.println("Setup Performance : " + (end - start));

long start2 = System.currentTimeMillis();
int matchingCount = 0;
for (int j =0; j < 500; j++) {
if (testList.contains(baseStr + generator.nextInt(5000))) {
matchingCount++;
}
}
long end2 = System.currentTimeMillis();
System.out.println("ArrayList Contains Performance : " + (end2 - start2));
}


[ 결과 (시간단위 : ns) ]


결과를 보게되면 실제 set, list에 데이터를 쌓을 때는 hashset이 약 2배 정도 오래 걸리는 것을 알 수 있다. 

(아마 내부적으로 해쉬함수를 돌려 넣기 때문일 것으로 추정)

하지만 contains메서드의 hashset은 거의 시간이 안걸린다고 볼 수 있는 반면에 list는 set에 비해서는 꽤나 걸리는 것으로 볼 수 있다. 

따라서 한 번 데이터를 초기화해놓고 get이나 contains메서드를 사용할 일이 많은 시스템이라면 list보다는 set으로 자료구조를 가져가는 편이 좋다고 할 수 있을 것 같다.


포스팅을 마치도록 하겠습니다. 감사합니다.

도움이 되셨다면 광고 한 번 클릭해주시는 센스^_^


참고 : https://www.baeldung.com/java-hashset-arraylist-contains-performance



반응형
반응형

스프링부트(SpringBoot)로 카프카에서 데이터를 consume에 처리해야 하는 일이 있어 작업 중 발생한 에러에 대해 간단히 남겨본다...

나중에 같은 실수를 하지 않기 위해...


일단 현재 사용하고 있는 카프카 버전은 0.8.2.0이고 spring-integration-kafka를 사용하였다.

실제 클라우데라에 설치된 카프카 버전


카프카 낮은 버전을 사용하고 있어 요즘 spring-kafka 연동 가이드로는 에러가 발생해 삽질을 좀 하였다...

시간이 된다면 0.8.2.2 consume 모듈을 git에 올려서 링크 걸어보도록 하겠다.


다음과 같이 KafkaConfig를 설정해주고


[ 스프링 설정 및 코드 ]

@EnableIntegration
@Configuration
public class KafkaIntegration {

private static final String BOOTSTRAP_SERVER = "serverIp:2181";
private static final String ZOOKEEPER_CONNECT = "serverIp:2181";

@Getter
@Component
public static class KafkaConfig {
private String topic = "data_log";
private String brokerAddress = BOOTSTRAP_SERVER;
private String zookeeperAddress = ZOOKEEPER_CONNECT;

KafkaConfig(){}

public KafkaConfig(String t, String b, String zk) {
this.topic = t;
this.brokerAddress = b;
this.zookeeperAddress = zk;
}
}
}


Consumer 빈 등록

@Configuration
public class ConsumerConfiguration {

@Autowired
private KafkaIntegration.KafkaConfig kafkaConfig;

@Bean
public IntegrationFlow consumer() {

KafkaHighLevelConsumerMessageSourceSpec messageSourceSpec = Kafka.inboundChannelAdapter(
new ZookeeperConnect(this.kafkaConfig.getZookeeperAddress()))
.consumerProperties(props -> props.put("auto.offset.reset", "smallest")
.put("auto.commit.interval.ms", "100"))
.addConsumer("ectc_dsp_test", metadata -> metadata.consumerTimeout(100)
.topicStreamMap(m -> m.put(this.kafkaConfig.getTopic(), 1))
.maxMessages(10).valueDecoder(String::new));

Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer =
e -> e.poller(p -> p.fixedDelay(100));

return IntegrationFlows
.from(messageSourceSpec, endpointConfigurer)
.<Map<String, List<String>>>handle((payload, headers) -> {
payload.entrySet().forEach(
e -> System.out.println((e.getKey() + '=' + e.getValue())));
return null;
})
.get();
}

}


으로 등록 후 어플리케이션을 실행하면 다음과 같은 에러메시지가 발생하였다.


[ 발생한 에러메세지 ]

kafka.common.KafkaException: fetching topic metadata for topics [Set(dsp_log)] from broker [ArrayBuffer(id:245,host:eedkaf-dmp001.svr.net,port:9092, id:94,host:eedkaf-dmp002.svr.net,port:9092, id:95,host:eedkaf-dmp003.svr.net,port:9092)] failed

at kafka.client.ClientUtils$.fetchTopicMetadata(ClientUtils.scala:72) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.client.ClientUtils$.fetchTopicMetadata(ClientUtils.scala:93) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.consumer.ConsumerFetcherManager$LeaderFinderThread.doWork(ConsumerFetcherManager.scala:66) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.utils.ShutdownableThread.run(ShutdownableThread.scala:60) [kafka_2.11-0.8.2.0.jar:na]

Caused by: java.nio.channels.ClosedChannelException: null

at kafka.network.BlockingChannel.send(BlockingChannel.scala:100) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.producer.SyncProducer.liftedTree1$1(SyncProducer.scala:73) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.producer.SyncProducer.kafka$producer$SyncProducer$$doSend(SyncProducer.scala:72) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.producer.SyncProducer.send(SyncProducer.scala:113) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.client.ClientUtils$.fetchTopicMetadata(ClientUtils.scala:58) ~[kafka_2.11-0.8.2.0.jar:na]

... 3 common frames omitted


분명 로그를 봐도 내가 KafkaConfig에서 설정해준 서버 IP와 정상적인 커넥션을 맺었는데 왜이런단 말인가???

2018-11-20 12:12:05.668  INFO 87935 --- [161.26.70:2181)] org.apache.zookeeper.ClientCnxn          : Session establishment complete on server 내가지정한서버IP:2181, sessionid = 0x366e7594a3918e9


[ 해 결 ]

주키퍼(Zookeeper) 내부적으로 클러스터간 통신시 혹은 zookeeper to kafka간 통신시 서버의 IP보다는 호스트명으로 서로를 인지한다는 말?을 들은적이 있어 로컬 host파일에 서버의 호스트와 IP를  등록하고 다시 실행해보았더니 정상적으로 카프카에서 메세지를 consume하는 것을 확인할 수 있었다.


혹시나 다음과 같은 문제가 발생한다면 host파일에 서버의 호스트명과 IP를 등록 후 다시 해보길...


정확한 원인은 나중에 관련한 문서나 Zookeeper를 좀더 깊게 공부하게 되어 발견하게 된다면 추후 또 포스팅해보도록 하겠습니다. 


ref : https://spring.io/blog/2015/04/15/using-apache-kafka-for-integration-and-data-processing-pipelines-with-spring

반응형
반응형

일반적인 map.size() 말고 실제 메모리에 올라가는 map byte사이즈를 알고 싶은경우~


다음과 같이 사용하면 될듯


public static void size(Map map) {
try {
System.out.println("Index Size: " + map.size());
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
System.out.println("Data Size: " + baos.size());
} catch(IOException e){
e.printStackTrace();
}
}


baos.size() 메소드를 들어가보면 다음과 같이 bytes를 리턴한다.

/**
* Returns the current size of the buffer.
*
* @return the value of the <code>count</code> field, which is the number
* of valid bytes in this output stream.
* @see java.io.ByteArrayOutputStream#count
*/
public synchronized int size() {
return count;
}


확인할일이 있어서 보다가 정리

반응형

+ Recent posts