Category Archives: Java

Сравнение строк в Java

Недавно проходил собеседование по Java. В тестах был вопрос. Что возвращает следующий код?
public class Main
{
    public static void main(String[] args) 
    {
	String s1 = "abc";
	String s2 = "abc";

	String str1 = new String("abc");
	String str2 = new String("abc");

	if (s1 == s2){
	    System.out.println("s1 == s2");           
	} 
	else { 
	    System.out.println("s1 != s2");
	}

	if(s1 + s2 == s2 + s1){
	    System.out.println("s1 + s2 == s2 + s1");
	} 
	else {
	    System.out.println("s1 + s2 != s2 + s1");
	}

	if (str1 == str2) {
	    System.out.println("str1 == str2");           
	} 
	else {
	    System.out.println("str1 != str2");
	}
		
	if (s1 == str1){
	    System.out.println("str1 == s1");         
	}
	else {
	    System.out.println("str1 != s1");
        }
    }
}
Внимание, правильный ответ:
s1 == s2
s1 + s2 != s2 + s1
str1 != str2
str1 != s1
Для меня было неожиданно, что s1 == s2. Это связано с тем, что в java есть так называемый string intern pool (см. String interning), в который заносится адрес обьекта строковой константы, при ее первом обьявлении. А если дальше еще встречается эта константа, то используется адрес объекта String из пула, а не создается новый. Но если строка была создана, вот так:
String str1 = new String("abc");
то она автоматически в пул не помещается. Для того, что бы это произошло, нужно вызвать метод intern()

Стековая и регистровая архитектура виртуальной машины и Dalvik VM

Эта статья – перевод статьи

Виртуальная машина (VM) это абстракция над уровнем операционной системы, которая позволяет эмулировать физическую машину. Виртуальная машина позволяет запускать одну и ту же платформу под различными операционными системами и аппаратными архитектурами. Интерпретаторы Java и Python можно рассматривать как примеры, в которых код компилируется в специфический для их виртуальных машин байт код. Тоже можно наблюдать и в архитектуре Microsoft .Net, где код компилируется в промежуточный язык для CLR (Common Language Runtime).

Что должно входить в реализацию виртуальной машины? Она должна эмулировать операции физического процессора, а так же в идеале содержать следующие концепции:

  • компиляция исходного кода в специфический для данной виртуальной машины байткод
  • структуры данных для хранения инструкций и операндов (данные и процесс их обработки)
  • стек вызовов для выполнения операций в функции
  • “указатель инструкции” (IP) указывающий на следующию выполняемую инструкцию
  • виртуальный ЦП – обрабатывающего инструкции
    • доставляемые указателем инструкции
    • декодирование операндов
    • выполнение инструкции

Существует два основных способа реализации виртуальной машины: стековый и регистровый. Пример стековой виртуальной машины – виртуальная машина Java, .Net CLR, это широко используемый метод реализации виртуальной машины. В качестве регистровой виртуальной машины можно назвать Lua VM и Dalvik VM (которую мы кратко рассмотрим). Разница между этими двумя подходами в механизме используемом для записи и получения операндов и результатов выполнения команд.

Стековая виртуальная машина

Стековая виртуальная машина реализует основные, выше описанные свойства виртуальной машины, но в качестве структуры данных, куда помещаются операнды, используется стек. Операции получают данные из стека, обрабатывают их и заносят в стек результат по правилу LIFO (последний пришел, первый ушел). В стековой виртуальной машине, операция сложения двух чисел должна выполняться следующим способом (где 20, 7, и "результат" – операнды):

    POP   20
    POP   7
    ADD   20, 7, result
    PUSH  result

Из-за операций PUSH и POP для операции сложения требуется 4 инструкции. Преимущество стековой модели в том, что операнды задаются неявно указателем стека (на рисунке – SP). Это означает, что виртуальной машине не нужно явно указывать адреса операндов, указатель стека указывает на следующий операнд. В стековых виртуальных машинах все арифметические и логические операции выполняются посредством получения операндов и возврата результатов в стек.

Регистровые виртуальные машины

В регистровой реализации виртуальной машины структура данных, в которую помещаются операнды, основана на регистрах процессора. При этом не требуются операции PUSH или POP, но инструкции должны явно содержать адреса (регистры) в которых содержатся операнды. То есть, операнды для инструкций, в отличии от стековой модели, указываются явно. Например, операция сложения в регистровой виртуальной машине выглядит приблизительно так:

   ADD R1, R2, R3; # складывает содержимое R1 и R2, результат  заносит в R3

За счет отсутствия операций POP и PUSH команды в регистровой виртуальной машине выполняются быстрее аналогичных команд стековой виртуальной машины.

Другое преимущество регистровой модели в том что она позволяет провести оптимизацию, которая не может быть выполнена при стековом подходе. Например несколько раз встречающееся выражение при регистровом подходе может быть вычислено лиш однажды и сохранено в регистре для последующего использования, что экономит время необходимое для пересчета выражения.

Но с другой стороны в среднем инструкция регистровой машины длиннее чем в стековой машине, так как в ней тредуется явное указание операндов.

Интересная статья, в которой содержится простая реализация на С регистровой виртуальной машины. Если вы интересуетесь реализацией виртуальных машин и интерпретаторов то вас может заинтересовать книга автора ANTLR Теренса Парра "Language Implementation Pattern: Create your own domain-specific and general programming languages".

Виртуальная машина DALVIK

DALVIK – реализованная google виртуальная машина для Android и выполняющая функцию интерпретатора java кода на устройствах под управлением этой ОС. Для выполнения процесса Android создает отдельный экземпляр виртуальной машины. Это снижает вероятность краха системы при падении одного из приложений. Dalvik реализует регистровую модель и в отличаи от стандартного java байткода, который выполняет 8 битные инструкции на стековой JVM, использует 16 битные инструкции. Регистры реализованы в Dalvik в виде 4 битных полей.

Если мы хотим получить более детальную информацию о том как процесс получает экземпляр виртуальной машины, мы должны начать рассмотрение с момента загрузки ядра Linux в Android:

При загрузке системы, загрузчик операционной системы загружает ядро в память и инициализирует системные параметры. Вскоре после этого,

  • ядро запускает инициализирующую программу, которая является родительским процессом по отношении ко всем другим процессам.
  • инициализирующая программа запускает системные демоны и очень важный сервис "Зиготу".
  • процесс зиготы создает экземпляр Dalvik, являющийся прародителем всех экземпляров Dalvik в системе.
  • процесс зиготы так же запускает BSD сокет, который прослушивает входящие запросы.
  • при получении очередного запроса на создание нового экземпляра Dalvik VM, процесс зиготы разветвляет родительский Dalvik VM процесс и передает дочерний процесс запрашивающему приложению.

Это краткое описание того, как создается и используется виртуальная машина Dalvik в ОС Android.

Возвращаясь к теме виртуальных машин, Dalvik отличается от обычной виртуальной машины Java тем, что она выполняет байткод Dalvik, отличный от обычного java байткода. Промежуточный шаг между Java компилятором и Dalvik VM, на котором происходит преобразование Java байткода в байткод Dalvik берет на себя DEX компилятор. Раличие между JVM и Dalvik проилюстрировано на следующей диаграмме (заимствованной из книги – Learning Android).

DEX компилятор преобразует .class файлы java в .dex файлы, которые имеют меньший размер и оптимизированы для Dalvik VM.

В заключение …

Нельзя однозначно сказать, что стековая виртуальная машина лучше чем регистровая или наоборот. Этот вопрос остается дискуссионным и интересной областью для исследований. Вот интересная статья в которой автор реализует традиционную JVM как регистровую VM, и описывает некоторые полученные интересные результаты.

Инициализация java класса org.apache.http.client.HttpClient для работы с https

Ниже привожу пример инициализации HttpClient для https доверяющего всем сертификатам

import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.BasicClientConnectionManager;

import java.security.NoSuchAlgorithmException;
import java.security.KeyManagementException;
import java.security.cert.X509Certificate; 
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLContext; 
import javax.net.ssl.HostnameVerifier;


    
public MyHttpClient
{
    .......

    private HttpClient getHttpClient(boolean isHttps)
    {
        if(isHttps)
        {
            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[] { 
                new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() { 
                    return new X509Certificate[0]; 
                }
                
                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                
                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }};

            SSLContext sslContext;
            try {
                sslContext = SSLContext.getInstance("SSL");
            } catch (NoSuchAlgorithmException e) { //  if no Provider supports a TrustManagerFactorySpi implementation for the specified protocol.
                System.out.println(e.getMessage());
                return null;
            }
            
            try {
                sslContext.init(null, trustAllCerts, new SecureRandom());
            } catch (KeyManagementException e) {
                System.out.println(e.getMessage());
                return null;
            }
            
            HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
            SSLSocketFactory sf = new SSLSocketFactory(sslContext, (X509HostnameVerifier)hostnameVerifier);
            
            Scheme httpsScheme = new Scheme("https", 443, sf);
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(httpsScheme);

            ClientConnectionManager cm = new BasicClientConnectionManager(schemeRegistry);
            return new DefaultHttpClient(cm);
        } 
        else
        {
            return new DefaultHttpClient();
        }    
    }        
}