<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>proft.me - Блог о Linux, Python, Vim и другом здоровом образе жизни</title><link>http://proft.me/feed/latest/</link><description>proft.me - Блог о Linux, Python, Vim и другом здоровом образе жизни: новости блога</description><atom:link href="http://proft.me/feeds/latest/" rel="self"></atom:link><language>ru-ru</language><lastBuildDate>Wed, 06 Sep 2017 00:00:00 +0300</lastBuildDate><item><title>Просмотр своих и чужих фотографий в Instagram под Android</title><link>http://proft.me/2017/09/6/prosmotr-fotografij-v-instagram-android/</link><description>&lt;p&gt;&lt;img src="http://en.proft.me/media/android/android_instagram.png" alt="Просмотр своих и чужих фотографий в Instagram под Android" class="right" width="120"&gt;&lt;/p&gt;
&lt;p&gt;Так как у &lt;em&gt;Instagram&lt;/em&gt; нету родного API под Android платформу для авторизации и просмотра чужих и своих фотографий, то в этой статье мы реализуем 5 классов, с помощью которых сможем авторизовать пользователя и отобразить список его фоток в &lt;code&gt;GridView&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Для начала нам нужен обычный аккаунт в &lt;em&gt;Instagram&lt;/em&gt; из которого мы сделаем аккаунт для разработчиков &lt;a href="https://www.instagram.com/developer/"&gt;тут&lt;/a&gt;. Дальше нам нужно выбрать &lt;em&gt;Register new Client ID&lt;/em&gt; и заполнить все поля. После этого у нас будет &lt;em&gt;Application&lt;/em&gt; в &lt;a href="https://www.instagram.com/developer/sandbox/"&gt;Sandbox&lt;/a&gt; режиме. Это означает, что мы можем пригласить еще 9 человек (смотри вкладку &lt;em&gt;Sandbox&lt;/em&gt; в &lt;em&gt;Manage Client&lt;/em&gt;) и тестировать &lt;a href="https://www.instagram.com/developer/endpoints/"&gt;возможности&lt;/a&gt; &lt;em&gt;Instagram REST API&lt;/em&gt; между ними. После этого можно можно пройти &lt;a href="https://www.instagram.com/developer/review/"&gt;рецензию&lt;/a&gt; и получить нужные права для того что-бы все другие пользователи могли использовать &lt;em&gt;Instagram REST API&lt;/em&gt; а не только 9 человек из &lt;em&gt;Sandbox&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Для общения с &lt;em&gt;Instagram REST API&lt;/em&gt; будет использоваться &lt;a href="http://en.proft.me/2016/12/21/how-do-get-and-post-requests-android-using-okhttp/"&gt;OKHttp&lt;/a&gt;, а для загрузки фоток будет использоваться &lt;a href="http://en.proft.me/2016/09/12/load-process-and-cache-image-android-using-glide/"&gt;Glide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Дальше создаем пакет &lt;code&gt;instagram&lt;/code&gt; в корне нашего проекта и начинаем наполнять его вспомогательными классами. Первым из них будет класс &lt;code&gt;Instagram&lt;/code&gt;, его основная функция - инициализация &lt;em&gt;Instagram&lt;/em&gt; клиента.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// file instagram/Instagram.java
public class Instagram {
    public static final String AUTH_URL = "https://instagram.com/oauth/authorize/?";
    public static final String ACCESS_TOKEN_URL = "https://api.instagram.com/oauth/access_token";
    public static final String API_BASE_URL = "https://api.instagram.com/v1";

    private Context context;

    private InstagramDialog dlg;
    private InstagramAuthListener listener;
    private InstagramSession session;

    private String clientId;
    private String clientSecret;
    private String redirectUri;

    public Instagram(Context context, String clientId, String clientSecret, String redirectUri) {
        this.context = context;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUri = redirectUri;

        String authUrl = AUTH_URL + "client_id=" + clientId + "&amp;amp;redirect_uri=" + redirectUri + "&amp;amp;response_type=code&amp;amp;scope=basic+public_content";

        session = new InstagramSession(context);
        dlg = new InstagramDialog(context, authUrl, redirectUri, new InstagramDialog.InstagramDialogListener() {
            @Override
            public void onSuccess(String code) {
                retrieveAccessToken(code);
            }

            @Override
            public void onError(String error) {
                listener.onError(error);
            }

            @Override
            public void onCancel() {
                listener.onCancel();
            }
        });
    }

    public void authorize(InstagramAuthListener listener) {
        this.listener = listener;
        dlg.show();
    }

    public void resetSession() {
        session.reset();
        dlg.clearCache();
    }

    public InstagramSession getSession() {
        return session;
    }

    private void retrieveAccessToken(String code) {
        new AccessTokenTask(code).execute();
    }

    public class AccessTokenTask extends AsyncTask&amp;lt;URL, Integer, Long&amp;gt; {        
        ProgressDialog progressDlg;
        InstagramUser user;
        String code;

        public AccessTokenTask(String code) {
            this.code = code;
            progressDlg = new ProgressDialog(context);
            progressDlg.setMessage("Getting access token...");          
        }

        protected void onCancelled() {
            progressDlg.cancel();
        }

        protected void onPreExecute() {
            progressDlg.show();
        }

        protected Long doInBackground(URL... urls) {         
            long result = 0;

            try {
                HashMap&amp;lt;String, String&amp;gt; params = new HashMap&amp;lt;String, String&amp;gt;(5);

                params.put("client_id", clientId);
                params.put("client_secret", clientSecret);
                params.put("grant_type", "authorization_code");
                params.put("redirect_uri", redirectUri);
                params.put("code", code);

                InstagramRequest request = new InstagramRequest();
                String response = request.post(ACCESS_TOKEN_URL, params);

                if (!response.equals("")) {
                    JSONObject jsonObj = (JSONObject) new JSONTokener(response).nextValue();
                    JSONObject jsonUser = jsonObj.getJSONObject("user");

                    user = new InstagramUser();
                    user.accessToken = jsonObj.getString("access_token");
                    user.id = jsonUser.getString("id");
                    user.username = jsonUser.getString("username");
                    user.fullName = jsonUser.getString("full_name");
                    user.profilePicture = jsonUser.getString("profile_picture");
                }                       
            } catch (Exception e) { 
                e.printStackTrace();
            }

            return result;
        }

        protected void onProgressUpdate(Integer... progress) {}

        protected void onPostExecute(Long result) {         
            progressDlg.dismiss();

            if (user != null) {
                session.store(user);
                listener.onSuccess(user);
            } else {
                listener.onError("Failed to get access token");
            }
        }                
    }

    public interface InstagramAuthListener {
        public abstract void onSuccess(InstagramUser user);
        public abstract void onError(String error);
        public abstract void onCancel();
    }

    public ArrayList&amp;lt;String&amp;gt; getUserMedia(String token, int count, String userID) {
        ArrayList&amp;lt;String&amp;gt; photoList = new ArrayList&amp;lt;String&amp;gt;();

        try {
            HashMap&amp;lt;String, String&amp;gt; params = new HashMap&amp;lt;String, String&amp;gt;(1);
            params.put("count", String.valueOf(count));

            InstagramRequest request = new InstagramRequest(token);
            String response = request.createRequest("GET", "/users/" + userID + "/media/recent", params);

            if (!response.equals("")) {
                JSONObject jsonObj = (JSONObject) new JSONTokener(response).nextValue();
                JSONArray jsonData= jsonObj.getJSONArray("data");

                int length = jsonData.length();

                if (length &amp;gt; 0) {
                    photoList = new ArrayList&amp;lt;String&amp;gt;();

                    for (int i = 0; i &amp;lt; length; i++) {
                        JSONObject jsonPhoto = jsonData.getJSONObject(i).getJSONObject("images").getJSONObject("low_resolution");
                        photoList.add(jsonPhoto.getString("url"));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return photoList;
    }
}
&lt;/pre&gt;

&lt;p&gt;Следующий по списку класс для инициализации &lt;code&gt;WebView&lt;/code&gt; с помощью которого будет происходить авторизация пользователя в &lt;em&gt;Instagram&lt;/em&gt;.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// file instagram/InstagramDialog.java
@SuppressLint({ "NewApi", "SetJavaScriptEnabled" })
public class InstagramDialog extends Dialog {

    private ProgressDialog spinner;
    private WebView webView;
    private LinearLayout content;
    private TextView tvTitle;

    private String authUrl;
    private String redirectUri;

    private InstagramDialogListener listener;

    static final FrameLayout.LayoutParams FILL = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);

    static final int MARGIN = 8;
    static final int PADDING = 2;

    static final String TAG = "DBG";

    public InstagramDialog(Context context, String authUrl, String redirectUri, InstagramDialogListener listener) {
        super(context);
        this.authUrl = authUrl;
        this.listener = listener;
        this.redirectUri = redirectUri;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        spinner = new ProgressDialog(getContext());
        spinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
        spinner.setMessage("Loading...");

        content = new LinearLayout(getContext());
        content.setOrientation(LinearLayout.VERTICAL);

        setUpTitle();
        setUpWebView();

        Display display = getWindow().getWindowManager().getDefaultDisplay();
        Point outSize = new Point();

        int width = 0;
        int height = 0;

        double[] dimensions = new double[2];

        if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.HONEYCOMB_MR2) {
            display.getSize(outSize);

            width = outSize.x;
            height = outSize.y;
        } else {
            width = display.getWidth();
            height = display.getHeight();
        }

        if (width &amp;lt; height) {
            dimensions[0] = 0.87 * width;
            dimensions[1] = 0.82 * height;
        } else {
            dimensions[0] = 0.75 * width;
            dimensions[1] = 0.75 * height;
        }

        addContentView(content, new FrameLayout.LayoutParams((int) dimensions[0], (int) dimensions[1]));
    }

    private void setUpTitle() {
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        //Drawable icon = getContext().getResources().getDrawable(R.drawable.icon);

        tvTitle = new TextView(getContext());

        tvTitle.setText("Instagram");
        tvTitle.setTextColor(Color.WHITE);
        tvTitle.setTypeface(Typeface.DEFAULT_BOLD);
        tvTitle.setBackgroundColor(0xFF163753);
        tvTitle.setPadding(MARGIN + PADDING, MARGIN, MARGIN, MARGIN);
        tvTitle.setCompoundDrawablePadding(MARGIN + PADDING);
        //title.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);

        content.addView(tvTitle);
    }

    private void setUpWebView() {
        webView = new WebView(getContext());

        webView.setVerticalScrollBarEnabled(false);
        webView.setHorizontalScrollBarEnabled(false);
        webView.setWebViewClient(new InstagramWebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.loadUrl(authUrl);
        webView.setLayoutParams(FILL);
        //webView.setLayoutParams(FILL);

        WebSettings webSettings = webView.getSettings();

        webSettings.setSavePassword(true);
        webSettings.setSaveFormData(false);

        content.addView(webView);
    }

    public void clearCache() {
        webView.clearCache(true);
        webView.clearHistory();
        webView.clearFormData();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        clearCache();
        listener.onCancel();
    }

    private class InstagramWebViewClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "Redirecting URL " + url);

            if (url.startsWith(redirectUri)) {
                if (url.contains("code")) {
                    String temp[] = url.split("=");
                    listener.onSuccess(temp[1]);
                } else if (url.contains("error")) {
                    String temp[] = url.split("=");
                    listener.onError(temp[temp.length-1]);
                }

                InstagramDialog.this.dismiss();
                return true;
            }

            return false;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {   
            super.onReceivedError(view, errorCode, description, failingUrl);
            listener.onError(description);
            InstagramDialog.this.dismiss();
            Log.d(TAG, "Page error: " + description);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
            spinner.show();
            Log.d(TAG, "Loading URL: " + url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);

            String title = webView.getTitle();
            if (title != null &amp;amp;&amp;amp; title.length() &amp;gt; 0) {
                tvTitle.setText(title);
            }
            spinner.dismiss();
        }
    }

    public interface InstagramDialogListener {
        public abstract void onSuccess(String code);
        public abstract void onCancel();
        public abstract void onError(String error);
    }
}
&lt;/pre&gt;

&lt;p&gt;Класс &lt;code&gt;InstagramRequest&lt;/code&gt; используется для выполнения GET и POST запросов к &lt;em&gt;Instagram REST API&lt;/em&gt;. Этот класс использует &lt;code&gt;OkHttp&lt;/code&gt; для общения с &lt;em&gt;Instagram REST API&lt;/em&gt;. Как настроить и использовать &lt;code&gt;OkHttp&lt;/code&gt; читай в &lt;a href="http://en.proft.me/2016/12/21/how-do-get-and-post-requests-android-using-okhttp/"&gt;How to do GET and POST requests in Android using OkHttp &lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Для сохранения токена после авторизации и других данных о пользователе создадим класс &lt;code&gt;InstagramSession&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// file instagram/InstagramSession.java
public class InstagramSession {
    private Context context;
    private SharedPreferences sp;

    private static final String SHARED = "Instagram_Preferences";
    private static final String USERID  = "userid";
    private static final String USERNAME = "username";
    private static final String FULLNAME = "fullname";
    private static final String PROFILEPIC = "profilepic";
    private static final String ACCESS_TOKEN = "access_token";

    public InstagramSession(Context context) {
        this.context = context;
        this.sp = context.getSharedPreferences(SHARED, Context.MODE_PRIVATE);
    }

    public void store(InstagramUser user) {
        Editor editor = sp.edit();

        editor.putString(ACCESS_TOKEN, user.accessToken);
        editor.putString(USERID, user.id);
        editor.putString(USERNAME, user.username);
        editor.putString(FULLNAME, user.fullName);
        editor.putString(PROFILEPIC, user.profilePicture);

        editor.commit();
    }

    public void reset() {
        Editor editor = sp.edit();

        editor.putString(ACCESS_TOKEN, "");
        editor.putString(USERID, "");
        editor.putString(USERNAME, "");
        editor.putString(FULLNAME, "");
        editor.putString(PROFILEPIC, "");

        editor.commit();

        CookieSyncManager.createInstance(context);

        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.removeAllCookie();
    }

    public InstagramUser getUser() {
        if (sp.getString(ACCESS_TOKEN, "").equals("")) {
            return null;
        }

        InstagramUser user = new InstagramUser();

        user.id = sp.getString(USERID, "");
        user.username = sp.getString(USERNAME, "");
        user.fullName = sp.getString(FULLNAME, "");
        user.profilePicture = sp.getString(PROFILEPIC, "");
        user.accessToken = sp.getString(ACCESS_TOKEN, "");

        return user;
    }

    public String getAccessToken() {
        return sp.getString(ACCESS_TOKEN, "");
    }

    public boolean isActive() {
        return (sp.getString(ACCESS_TOKEN, "").equals("")) ? false : true;
    }
}
&lt;/pre&gt;

&lt;p&gt;Класс &lt;code&gt;InstagramUser&lt;/code&gt; будет хранить в себе информацию о авторизированном пользователе.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
public class InstagramUser {
    public String id;
    public String username;
    public String fullName;
    public String profilePicture;
    public String accessToken;

    @Override
    public String toString() {
        return "InstagramUser{" +
                "id='" + id + '\'' +
                ", username='" + username + '\'' +
                ", fullName='" + fullName + '\'' +
                ", profilPicture='" + profilePicture + '\'' +
                '}';
    }
}
&lt;/pre&gt;

&lt;p&gt;Создадим Activity, в которой соберем все вспомогательные классы в рабочую цепочку.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
public class InstagramActivity extends AppCompatActivity {
    private InstagramSession instagramSession;
    private Instagram instagram;

    private ProgressBar pbLading;
    private GridView gvPhotos;
    private LinearLayout llInstagramOff;
    private RelativeLayout rlInstagramOn;
    private Button btnLogout, btnLogin;

    private static final String CLIENT_ID = "XXX";
    private static final String CLIENT_SECRET = "XXX";
    private static final String REDIRECT_URI = "XXX";
    String TAG = "VVV";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_instagram);

        llInstagramOff =  (LinearLayout) findViewById(R.id.llInstagramOff);
        rlInstagramOn =  (RelativeLayout) findViewById(R.id.rlInstagramOn);
        btnLogin =  (Button) findViewById(R.id.btnLogin);
        btnLogout =  (Button) findViewById(R.id.btnLogout);

        btnLogout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                instagramSession.reset();
                toggleInstagram();
            }
        });

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                instagram.authorize(mAuthListener);
            }
        });

        instagram = new Instagram(this, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
        instagramSession = instagram.getSession();
        //toggleInstagram();

        loadInstagram();
    }


    private Instagram.InstagramAuthListener mAuthListener = new Instagram.InstagramAuthListener() {
        @Override
        public void onSuccess(InstagramUser user) {
            loadInstagram();
        }

        @Override
        public void onError(String error) {
            Toast.makeText(InstagramActivity.this, error, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCancel() {
            Toast.makeText(InstagramActivity.this, "OK. Maybe later?", Toast.LENGTH_SHORT).show();
        }
    };


    private void loadInstagram() {
        if (instagramSession.isActive()) {
            InstagramUser instagramUser = instagramSession.getUser();
            Log.d(TAG, "loadInstagram: " + instagramUser);

            pbLading = (ProgressBar) findViewById(R.id.pbLading);
            gvPhotos = (GridView) findViewById(R.id.gvPhotos);

            ((TextView) findViewById(R.id.tv_name)).setText(instagramUser.fullName);
            ((TextView) findViewById(R.id.tv_username)).setText(instagramUser.username);

            ImageView userIv = (ImageView) findViewById(R.id.iv_user);
            Glide.with(this).load(instagramUser.profilePicture).into(userIv);
            //Log.d(TAG, "instagramUser.profilPicture: " + instagramUser.profilPicture);

            new DownloadTask().execute();
        }
        toggleInstagram();
    }


    private void toggleInstagram() {
        if (instagramSession.isActive()) {
            llInstagramOff.setVisibility(View.GONE);
            rlInstagramOn.setVisibility(View.VISIBLE);
        } else {
            llInstagramOff.setVisibility(View.VISIBLE);
            rlInstagramOn.setVisibility(View.GONE);
        }
    }


    public class DownloadTask extends AsyncTask&amp;lt;URL, Integer, Long&amp;gt; {
        ArrayList&amp;lt;String&amp;gt; photoList;

        protected void onCancelled() {}

        protected void onPreExecute() {}

        protected Long doInBackground(URL... urls) {
            long result = 0;
            //String userId = instagramSession.getUser().id;
            String userId = "6000222747";
            photoList = instagram.getUserMedia(instagramSession.getAccessToken(), 10, userId);
            return result;
        }

        protected void onProgressUpdate(Integer... progress) {
        }

        protected void onPostExecute(Long result) {
            pbLading.setVisibility(View.GONE);

            if (photoList == null) {
                Toast.makeText(getApplicationContext(), "No photos available", Toast.LENGTH_LONG).show();
            } else {
                Log.d(TAG, "onPostExecute: " + photoList);

                DisplayMetrics dm = new DisplayMetrics();
                getWindowManager().getDefaultDisplay().getMetrics(dm);

                int width   = (int) Math.ceil((double) dm.widthPixels / 3);
                width = width - 20;
                int height  = width;

                PhotoListAdapter adapter = new PhotoListAdapter(InstagramActivity.this);
                adapter.setData(photoList);
                adapter.setLayoutParam(width, height);
                gvPhotos.setAdapter(adapter);
            }
        }
    }

}
&lt;/pre&gt;

&lt;p&gt;Layout для этой активити такой.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"&amp;gt;

    &amp;lt;!-- OFF --&amp;gt;

    &amp;lt;LinearLayout
        android:id="@+id/llInstagramOff"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="visible"
        android:gravity="center"&amp;gt;

        &amp;lt;Button
            android:id="@+id/btnLogin"
            android:layout_width="175dp"
            android:layout_height="wrap_content"
            android:text="Log in" /&amp;gt;
    &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;!-- ON --&amp;gt;

    &amp;lt;RelativeLayout
        android:id="@+id/rlInstagramOn"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        android:padding="5dp"&amp;gt;

        &amp;lt;ImageView
            android:id="@+id/iv_user"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:contentDescription="@string/app_name"
            android:src="@drawable/c19" /&amp;gt;

        &amp;lt;LinearLayout
            android:layout_toRightOf="@+id/iv_user"
            android:layout_alignTop="@+id/iv_user"
            android:layout_alignBottom="@+id/iv_user"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:orientation="vertical"
            android:gravity="center_vertical"&amp;gt;

            &amp;lt;TextView
                android:id="@+id/tv_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="#000000"
                android:textStyle="bold"
                android:textSize="17sp"
                android:text="Lorensius Londa"/&amp;gt;

            &amp;lt;TextView
                android:id="@+id/tv_username"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="lorensiuswlt"/&amp;gt;

        &amp;lt;/LinearLayout&amp;gt;

        &amp;lt;Button
            android:id="@+id/btnLogout"
            android:layout_below="@+id/iv_user"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="Log out" /&amp;gt;

        &amp;lt;RelativeLayout
            android:layout_below="@+id/btnLogout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp" &amp;gt;

            &amp;lt;GridView
                android:id="@+id/gvPhotos"
                android:verticalSpacing="5dp"
                android:horizontalSpacing="5dp"
                android:padding="5dp"
                android:stretchMode="columnWidth"
                android:numColumns="3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/&amp;gt;

            &amp;lt;ProgressBar
                style="?android:attr/progressBarStyleLarge"
                android:id="@+id/pbLading"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:layout_centerInParent="true"/&amp;gt;

        &amp;lt;/RelativeLayout&amp;gt;

    &amp;lt;/RelativeLayout&amp;gt;

&amp;lt;/LinearLayout&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Вспомогательный класс &lt;code&gt;PhotoListAdapter&lt;/code&gt; для адаптера для &lt;code&gt;GridView&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
public class PhotoListAdapter extends BaseAdapter {
    private Context context;
    private ArrayList&amp;lt;String&amp;gt; photos;
    private int width;
    private int height;

    public PhotoListAdapter(Context context) {
        this.context = context;
    }

    public void setData(ArrayList&amp;lt;String&amp;gt; data) {
        photos = data;
    }

    public void setLayoutParam(int width, int height) {
        this.width  = width;
        this.height = height;
    }

    @Override
    public int getCount() {
        return (photos == null) ? 0 : photos.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageIv;

        if (convertView == null) {
            imageIv = new ImageView(context);

            imageIv.setLayoutParams(new GridView.LayoutParams(width, height));
            imageIv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            imageIv.setPadding(0, 0, 0, 0);
        } else {
            imageIv = (ImageView) convertView;
        }

        Glide.with(context).load(photos.get(position)).into(imageIv);

        return imageIv;
    }
}
&lt;/pre&gt;

&lt;p&gt;Результат &lt;/p&gt;
&lt;div style="text-align:center; margin-bottom: 20px;"&gt;
&lt;img alt="android_instagram_example.png" src="http://en.proft.com.ua/media/android/android_instagram_example.png"&gt;
&lt;/div&gt;

&lt;p&gt;Информацию о пользователе в виде json можно получить добавив к url &lt;code&gt;?__a=1&lt;/code&gt;, например, https://www.instagram.com/proftua/?__a=1.&lt;/p&gt;</description><pubDate>Wed, 06 Sep 2017 00:00:00 +0300</pubDate><guid>http://proft.me/2017/09/6/prosmotr-fotografij-v-instagram-android/</guid></item><item><title>Получение прогноза погоды в Android с помощью Retrofit</title><link>http://proft.me/2017/05/5/poluchenie-prognoza-pogody-android-retrofit/</link><description>&lt;p&gt;&lt;img src="http://en.proft.me/media/android/android_openweathermap.png" alt="Получение прогноза погоды в Android с помощью Retrofit" class="right" width="120"&gt;&lt;/p&gt;
&lt;p&gt;В этой статье вы узнаете как получить погоду с &lt;em&gt;openweathermap.org&lt;/em&gt; по координатам пользователя для текущего дня и прогноз на ближайщие 5 дней с помощью библиотеки Retrofit.&lt;/p&gt;
&lt;p&gt;Retrofit это REST клиент для Android написанный в Square. Эта библиотека позволяет легко получить и загрузить JSON (или любую другую структуру данных) через &lt;a href="https://ru.wikipedia.org/wiki/REST"&gt;REST&lt;/a&gt;. Retrofit позволяет настроить какой конвертор использовать для сериализации/десериализации данных. Например, для JSON можно использовать &lt;a href="http://en.proft.me/2016/12/17/simple-gson-examples/"&gt;GSON&lt;/a&gt;, также можно определить произвольный конвертор для парсинга других структур данных (например, для XML). Retrofit использует &lt;a href="http://en.proft.me/2016/12/21/how-do-get-and-post-requests-android-using-okhttp/"&gt;OkHttp&lt;/a&gt; библиотеку для HTTP запросов.&lt;/p&gt;
&lt;p&gt;Побробней познакомится с возможностями Retrofit можно в статье &lt;a href="http://en.proft.me/2016/06/22/how-get-remote-resource-retrofit-android/"&gt;How to get remote resource with Retrofit in Android&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;В качестве источника погоды для&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;одного дня будет использоватся &lt;a href="https://openweathermap.org/current"&gt;Current weather data API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;для прогноза на 5 дней &lt;a href="https://openweathermap.org/forecast5"&gt;5 day / 3 hour forecast&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Перед использованием OpenWeatherMap API необходимо зарегистрироваться и &lt;a href="http://openweathermap.org/appid"&gt;получить ключ&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;В файл &lt;em&gt;AndroidManifest.xml&lt;/em&gt; добавим права на Internet и гео-локацию&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
&amp;lt;uses-permission android:name="android.permission.INTERNET" /&amp;gt;
&amp;lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /&amp;gt;
&lt;/pre&gt;

&lt;p&gt;В файл &lt;em&gt;build.gradle (Module:app)&lt;/em&gt; добавим Retrofit, Glide и GSON-конвертор&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
dependencies {
    ...
    compile 'com.google.code.gson:gson:2.8.0'
    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    compile 'com.squareup.retrofit2:converter-gson:2.2.0'
    compile 'com.github.bumptech.glide:glide:3.7.0'
}
&lt;/pre&gt;

&lt;p&gt;Опишем модель данных для прогноза погоды на текущий день как того желает Retrofit.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// файл WeatherDay.java

public class WeatherDay {
    public class WeatherTemp {
        Double temp;
        Double temp_min;
        Double temp_max;
    }

    public class WeatherDescription {
        String icon;
    }

    @SerializedName("main")
    private WeatherTemp temp;

    @SerializedName("weather")
    private List&amp;lt;WeatherDescription&amp;gt; desctiption;

    @SerializedName("name")
    private String city;

    @SerializedName("dt")
    private long timestamp;

    public WeatherDay(WeatherTemp temp, List&amp;lt;WeatherDescription&amp;gt; desctiption) {
        this.temp = temp;
        this.desctiption = desctiption;
    }

    public Calendar getDate() {
        Calendar date = Calendar.getInstance();
        date.setTimeInMillis(timestamp * 1000);
        return date;
    }

    public String getTemp() { return String.valueOf(temp.temp); }

    public String getTempMin() { return String.valueOf(temp.temp_min); }

    public String getTempMax() { return String.valueOf(temp.temp_max); }

    public String getTempInteger() { return String.valueOf(temp.temp.intValue()); }

    public String getTempWithDegree() { return String.valueOf(temp.temp.intValue()) + "\u00B0"; }

    public String getCity() { return city; }

    public String getIcon() { return desctiption.get(0).icon; }

    public String getIconUrl() {
        return "http://openweathermap.org/img/w/" + desctiption.get(0).icon + ".png";
    }
}
&lt;/pre&gt;

&lt;p&gt;Опишем модель данных для прогноза погоды на 5 дней, тут проще, так-как это список из объектов &lt;code&gt;WeatherDay&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// файл WeatherForecast.java

public class WeatherForecast {
    @SerializedName("list")
    private List&amp;lt;WeatherDay&amp;gt; items;

    public WeatherForecast(List&amp;lt;WeatherDay&amp;gt; items) {
        this.items = items;
    }

    public List&amp;lt;WeatherDay&amp;gt; getItems() {
        return items;
    }
}
&lt;/pre&gt;

&lt;p&gt;Опишем сам API, т.е. укажем ключ, входные URL для каждого из прогнозов и параметры которые передаются для получения погоды.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
public class WeatherAPI {
    public static String KEY = "KEY";
    public static final String BASE_URL = "http://api.openweathermap.org/data/2.5/";
    private static Retrofit retrofit = null;

    public interface ApiInterface {
        @GET("weather")
        Call&amp;lt;WeatherDay&amp;gt; getToday(
            @Query("lat") Double lat,
            @Query("lon") Double lon,
            @Query("units") String units,
            @Query("appid") String appid
        );

        @GET("forecast")
        Call&amp;lt;WeatherForecast&amp;gt; getForecast(
                @Query("lat") Double lat,
                @Query("lon") Double lon,
                @Query("units") String units,
                @Query("appid") String appid
        );
    }

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}
&lt;/pre&gt;

&lt;p&gt;Макет &lt;em&gt;activity_main.xml&lt;/em&gt; для &lt;code&gt;Activity&lt;/code&gt; простой. В первом &lt;code&gt;LinearLayout&lt;/code&gt; (с &lt;code&gt;id = llToday&lt;/code&gt;) будет отображаться погода для текущего дня, а во втором &lt;code&gt;LinearLayout&lt;/code&gt; (с &lt;code&gt;id = llForecast&lt;/code&gt;) будет отображаться прогноз погоды на ближайшие 5 дней (наполнятся програмно), оставляем его пустым.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"&amp;gt;

    &amp;lt;LinearLayout
        android:id="@+id/llToday"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="30dip"&amp;gt;

        &amp;lt;TextView
            android:id="@+id/tvTemp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50dp"
            android:text="Weather... " /&amp;gt;

        &amp;lt;ImageView
            android:id="@+id/ivImage"
            android:layout_width="wrap_content"
            android:layout_height="60dp" /&amp;gt;
        &amp;lt;/LinearLayout&amp;gt;


    &amp;lt;LinearLayout
        android:id="@+id/llForecast"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="30dip"&amp;gt;
        &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Get"
        android:onClick="getWeather"/&amp;gt;
&amp;lt;/LinearLayout&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Непосредственно сама &lt;code&gt;Activity&lt;/code&gt; ниже. Изображения для погоды подгружается с помощью &lt;a href="http://en.proft.me/2016/09/12/load-process-and-cache-image-android-using-glide/"&gt;Glide&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
public class MainActivity extends AppCompatActivity {
    String TAG = "WEATHER";
    TextView tvTemp;
    ImageView tvImage;
    LinearLayout llForecast;
    WeatherAPI.ApiInterface api;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvTemp = (TextView) findViewById(R.id.tvTemp);
        tvImage = (ImageView) findViewById(R.id.ivImage);
        llForecast = (LinearLayout) findViewById(R.id.llForecast);

        api = WeatherAPI.getClient().create(WeatherAPI.ApiInterface.class);
    }

    public void getWeather(View v) {
        Double lat = 49.22;
        Double lng = 28.409;
        String units = "metric";
        String key = WeatherAPI.KEY;

        Log.d(TAG, "OK");

        // get weather for today
        Call&amp;lt;WeatherDay&amp;gt; callToday = api.getToday(lat, lng, units, key);
        callToday.enqueue(new Callback&amp;lt;WeatherDay&amp;gt;() {
            @Override
            public void onResponse(Call&amp;lt;WeatherDay&amp;gt; call, Response&amp;lt;WeatherDay&amp;gt; response) {
                Log.e(TAG, "onResponse");
                WeatherDay data = response.body();
                //Log.d(TAG,response.toString());

                if (response.isSuccessful()) {
                    tvTemp.setText(data.getCity() + " " + data.getTempWithDegree());
                    Glide.with(MainActivity.this).load(data.getIconUrl()).into(tvImage);
                }
            }

            @Override
            public void onFailure(Call&amp;lt;WeatherDay&amp;gt; call, Throwable t) {
                Log.e(TAG, "onFailure");
                Log.e(TAG, t.toString());
            }
        });

        // get weather forecast
        Call&amp;lt;WeatherForecast&amp;gt; callForecast = api.getForecast(lat, lng, units, key);
        callForecast.enqueue(new Callback&amp;lt;WeatherForecast&amp;gt;() {
            @Override
            public void onResponse(Call&amp;lt;WeatherForecast&amp;gt; call, Response&amp;lt;WeatherForecast&amp;gt; response) {
                Log.e(TAG, "onResponse");
                WeatherForecast data = response.body();
                //Log.d(TAG,response.toString());

                if (response.isSuccessful()) {
                    SimpleDateFormat formatDayOfWeek = new SimpleDateFormat("E");
                    LayoutParams paramsTextView = new LayoutParams(
                         LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
                    LayoutParams paramsImageView = new LayoutParams(convertDPtoPX(40, MainActivity.this), 
                         convertDPtoPX(40, MainActivity.this));

                    int marginRight = convertDPtoPX(15, MainActivity.this);
                    LayoutParams paramsLinearLayout = new LayoutParams(LayoutParams.WRAP_CONTENT, 
                         LayoutParams.WRAP_CONTENT);
                    paramsLinearLayout.setMargins(0, 0, marginRight, 0);

                    llForecast.removeAllViews();

                    for (WeatherDay day : data.getItems()) {
                        if (day.getDate().get(Calendar.HOUR_OF_DAY) == 15) {
                            String date = String.format("%d.%d.%d %d:%d",
                                    day.getDate().get(Calendar.DAY_OF_MONTH),
                                    day.getDate().get(Calendar.WEEK_OF_MONTH),
                                    day.getDate().get(Calendar.YEAR),
                                    day.getDate().get(Calendar.HOUR_OF_DAY),
                                    day.getDate().get(Calendar.MINUTE)
                            );
                            Log.d(TAG, date);
                            Log.d(TAG, day.getTempInteger());
                            Log.d(TAG, "---");

                            // child view wrapper
                            LinearLayout childLayout = new LinearLayout(MainActivity.this);
                            childLayout.setLayoutParams(paramsLinearLayout);
                            childLayout.setOrientation(LinearLayout.VERTICAL);

                            // show day of week
                            TextView tvDay = new TextView(MainActivity.this);
                            String dayOfWeek = formatDayOfWeek.format(day.getDate().getTime());
                            tvDay.setText(dayOfWeek);
                            tvDay.setLayoutParams(paramsTextView);
                            childLayout.addView(tvDay);

                            // show image
                            ImageView ivIcon = new ImageView(MainActivity.this);
                            ivIcon.setLayoutParams(paramsImageView);
                            Glide.with(MainActivity.this).load(day.getIconUrl()).into(ivIcon);
                            childLayout.addView(ivIcon);

                            // show temp
                            TextView tvTemp = new TextView(MainActivity.this);
                            tvTemp.setText(day.getTempWithDegree());
                            tvTemp.setLayoutParams(paramsTextView);
                            childLayout.addView(tvTemp);

                            llForecast.addView(childLayout);
                        }
                    }
                }
            }

            @Override
            public void onFailure(Call&amp;lt;WeatherForecast&amp;gt; call, Throwable t) {
                Log.e(TAG, "onFailure");
                Log.e(TAG, t.toString());
            }
        });

    }

    public int convertDPtoPX(int dp, Context ctx) {
        float density = ctx.getResources().getDisplayMetrics().density;
        int px = (int)(dp * density);
        return px;
    }

}
&lt;/pre&gt;

&lt;p&gt;Результат&lt;/p&gt;
&lt;div style="text-align:center; margin-bottom: 20px;"&gt;
&lt;img alt="android_retrofit_weather.png" src="http://en.proft.com.ua/media/android/android_retrofit_weather.png"&gt;
&lt;/div&gt;</description><pubDate>Fri, 05 May 2017 00:00:00 +0300</pubDate><guid>http://proft.me/2017/05/5/poluchenie-prognoza-pogody-android-retrofit/</guid></item><item><title>Про Java Collections Framework: почти все и сразу</title><link>http://proft.me/2017/04/10/pro-java-collections-framework-pochti-vse-i-srazu/</link><description>&lt;p&gt;&lt;a href="http://docs.oracle.com/javase/tutorial/collections/intro/index.html"&gt;Java Collections Framework&lt;/a&gt; это коллекция интерфейсов и классов, которые используются для сохранения и обработки данных.&lt;/p&gt;
&lt;div style="text-align:center;"&gt;
&lt;img alt="java_collection.png" src="http://proft.com.ua/media/java/java_collection.png" style="width:100%;"&gt;&lt;br/&gt;
&lt;/div&gt;

&lt;p&gt;Java Collections Framework состоит из следующих компонентов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;List&lt;/code&gt; это упорядоченный список объектов (иногда еще называют последовательностью объектов). Элементы списка могут вставляться или извлекаться по их индексу в списке (индексация начинается с 0). В эту группу входят: &lt;code&gt;ArrayList&lt;/code&gt;, &lt;code&gt;LinkedList&lt;/code&gt;, &lt;code&gt;Vector&lt;/code&gt;, &lt;code&gt;Stack&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Set&lt;/code&gt; это не упорядоченная коллекция. Главная особенность множеств - уникальность элементов, то есть один и тот же элемент не может содержаться в множестве дважды. Есть такие имплементации &lt;code&gt;Set&lt;/code&gt; интерфейса: &lt;code&gt;HashSet&lt;/code&gt;, &lt;code&gt;TreeSet&lt;/code&gt;, &lt;code&gt;LinkedHashSet&lt;/code&gt;, &lt;code&gt;EnumSet&lt;/code&gt;. &lt;code&gt;HashSet&lt;/code&gt; хранит элементы в хэш-таблице, что предполагает эффективную реализацию, но не гарантирует порядок итерации элементов. &lt;code&gt;TreeSet&lt;/code&gt; хранит элементы в &lt;a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D0%B0%D1%81%D0%BD%D0%BE-%D1%87%D1%91%D1%80%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE"&gt;красно-чёрном дереве&lt;/a&gt; и упорядочивает элементы по их значениям, эта коллекция существено медленее чем &lt;code&gt;HashSet&lt;/code&gt;. &lt;code&gt;LinkedHashSet&lt;/code&gt; реализована как хэш-таблица и хранит элементы в связаном списке, в порядке котором они были вставленны.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Map&lt;/code&gt; (иногда можно встретить названия: отображения, словарь, ассоциативный массив, карты) отображает ключи в значения и не может иметь дубликаты ключей. Есть три основных имплементации &lt;code&gt;Map&lt;/code&gt; интерфейса &lt;code&gt;HashMap&lt;/code&gt;, &lt;code&gt;TreeMap&lt;/code&gt; и &lt;code&gt;LinkedHashMap&lt;/code&gt;. &lt;code&gt;HashMap&lt;/code&gt; не гарантирует воспроизводимость порядка вставки элементов. &lt;code&gt;TreeMap&lt;/code&gt; хранит элементы в &lt;a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D0%B0%D1%81%D0%BD%D0%BE-%D1%87%D1%91%D1%80%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE"&gt;красно-чёрном дереве&lt;/a&gt; и упорядычивает элементы по их ключам и медленее чем &lt;code&gt;HashMap&lt;/code&gt;. &lt;code&gt;LinkedHashMap&lt;/code&gt; упорядычивает элементы в порядке их вставки.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Iterator&lt;/code&gt;/&lt;code&gt;ListIterator&lt;/code&gt; используются для итерации элементов коллекции. Основное отличие между &lt;code&gt;Iterator&lt;/code&gt; и &lt;code&gt;ListIterator&lt;/code&gt; в том, что первый позволяет итерировать только в одном направлении, а второй в обоих направлениях.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Выше сказаное можно резюмировать в такой таблице&lt;/p&gt;
&lt;div style="text-align:center;"&gt;
&lt;img alt="java_collection_summary.png" src="http://proft.com.ua/media/java/java_collection_summary2.png" style="width:90%;"&gt;&lt;br/&gt;
&lt;/div&gt;

&lt;p&gt;Производительность методов каждого класса можно записать через &lt;em&gt;O&lt;/em&gt;-нотацию&lt;/p&gt;
&lt;table border="0" class="redtable-striped"&gt;
&lt;thead&gt;
&lt;tr&gt;
    &lt;th&gt;Метод&lt;/th&gt;
    &lt;th&gt;Тип&lt;/th&gt;
    &lt;th&gt;Время&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
    &lt;td&gt;get, set&lt;/td&gt;
    &lt;td&gt;ArrayList&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;add, remove&lt;/td&gt;
    &lt;td&gt;ArrayList&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;contains, indexOf&lt;/td&gt;
    &lt;td&gt;ArrayList&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;get, put, remove, containsKey&lt;/td&gt;
    &lt;td&gt;HashMap&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;add, remove, contains&lt;/td&gt;
    &lt;td&gt;HashSet&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;add, remove, contains&lt;/td&gt;
    &lt;td&gt;LinkedHashSet&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;get, set, add, remove (с любого конца)&lt;/td&gt;
    &lt;td&gt;LinkedList&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;get, set, add, remove (по индексу)&lt;/td&gt;
    &lt;td&gt;LinkedList&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;contains, indexOf&lt;/td&gt;
    &lt;td&gt;LinkedList&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;peek&lt;/td&gt;
    &lt;td&gt;PriorityQueue&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;add, remove&lt;/td&gt;
    &lt;td&gt;PriorityQueue&lt;/td&gt;
    &lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;remove, get, put, containsKey&lt;/td&gt;
    &lt;td&gt;TreeMap&lt;/td&gt;
    &lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td&gt;add, remove, contains&lt;/td&gt;
    &lt;td&gt;TreeSet&lt;/td&gt;
    &lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;

&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;b class="lb"&gt;List&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ArrayList&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ArrayList&lt;/code&gt; это массив с изменяемым количесвтом элементов (стандартный &lt;code&gt;array&lt;/code&gt; имеет фиксированый размер), его элементы могут быть доступны непосредственно по индексу. Он реализует все операции списка и позволят вставлять все объекты, включая &lt;code&gt;null&lt;/code&gt;. Контейнер &lt;code&gt;ArrayList&lt;/code&gt;, оптимизированный для произвольного доступа к элементам, но с относительно медленными операциями вставки/удаления элементов в середине списка.&lt;/p&gt;
&lt;p&gt;В случае переполнения массива появляется необходимость в новом, имеющем больше места. Размещение и перемещение всех элементов будет занимать O(n) времени. Также, необходимо добавление и удаление элементов для передвижения существующих элементов в массиве. Это, возможно, самое большое неудобство в использовании &lt;code&gt;ArrayList&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Поиск в &lt;code&gt;ArrayList&lt;/code&gt; проходит за O(1). Удаление первого элемента (худший случай) происходит за O(n), для последнего элемента (лучший случай) за O(1). Вставка происходит за O(n).&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
ArrayList&amp;lt;String&amp;gt; lst = new ArrayList&amp;lt;&amp;gt;();

// добавление элементов
lst.add("Mercury");
lst.add("Venus");
lst.add("Earth");
lst.add("Saturn");
lst.add("Neptune");

// добавлене несколько элементов сразу
lst.addAll(Arrays.asList("PlanetX", "PlanetY", "PlanetZ"));

// отображение массива
System.out.println(lst);

// добавление элемента в определеную позицию
lst.add(0, "Jupiter");
lst.add(1, "Uranus");

// получение True если значение есть в массиве, иначе False
boolean isExist = lst.contains("Earth")

// получение True если все значения есть в массиве, иначе False
ArrayList&amp;lt;String&amp;gt; favorite = new ArrayList&amp;lt;&amp;gt;();
favorite.add("Earth");
favorite.add("Saturn");
boolean isContains = planets.containsAll(favorite);

// удаление элемента по значению
lst.remove("Saturn");
lst.remove("Neptune");

// оставить в списке только указаные элементы
ArrayList&amp;lt;String&amp;gt; favorite = new ArrayList&amp;lt;&amp;gt;();
favorite.add("Earth");
favorite.add("Saturn");
planets.retainAll(favorite);

// отображение массива
System.out.println(lst);

// удаление элемента по индексу
lst.remove(1);

// удалить все элементы в списке
ArrayList&amp;lt;String&amp;gt; favorite = new ArrayList&amp;lt;&amp;gt;();
favorite.add("Earth");
favorite.add("Saturn");
planets.removeAll(favorite);

// удалить элементы списка, которые соответсвуют условию Predicate
class SamplePredicate&amp;lt;T&amp;gt; implements Predicate&amp;lt;T&amp;gt; {
    T varc1;
    public boolean test(T varc) {
        if(varc1.equals(varc)) {
            return true;
        }
        return false;
    }
}

SamplePredicate&amp;lt;String&amp;gt; filter = new SamplePredicate&amp;lt;&amp;gt;();
filter.varc1 = "Saturn";
planets.removeIf(filter);

// обновление значения по индекску
lst.set(1, "Neptune");

// получить индекс элемента по значению, если не найденно то -1
int pos = lst.indexOf("Jupiter");

// получение элемента по индексу
String planet = lst.get(3)

// размер массива
int amount = lst.size()

// получить часть массива
ArrayList&amp;lt;String&amp;gt; lst2 = new ArrayList&amp;lt;&amp;gt;(lst.subList(1, 3));

// обэдинение массивов
lst.addAll(lst2)

// удалить все элементы
lst.clear()

// отсортировать список в обратном порядке
List reversedList = Collections.reverse(planets);

// заменить значение каждого элемента списка на результат оператор
// оператор
class MyOperator&amp;lt;T&amp;gt; implements UnaryOperator&amp;lt;T&amp;gt;{
    T varc1;
    public T apply(T varc){
        return varc1;
    }
}

MyOperator&amp;lt;String&amp;gt; operator = new MyOperator&amp;lt;&amp;gt;();
operator.varc1 = "Earth";
planets.replaceAll(operator);
System.out.println(planets);
// [Earth, Earth, Earth, Earth]


// выполнить действие над каждым элементом списка
// действие
class MyConsumer&amp;lt;T&amp;gt; implements Consumer&amp;lt;T&amp;gt;{
    public void accept(T planet){
        System.out.println("We are flying to " + planet);
    }
}

MyConsumer&amp;lt;String&amp;gt; action = new MyConsumer&amp;lt;&amp;gt;();
planets.forEach(action);
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;LinkedList&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;LinkedList&lt;/code&gt; — это связанный список ссылок на элементы. Таким образом, для доступа к элементу в центре, приходится производить поиск с самого начала и до конца листа. С другой стороны, добавление и удаление элемента в &lt;code&gt;LinkedList&lt;/code&gt; быстрее (чем в &lt;code&gt;ArrayList&lt;/code&gt;) по той причине, что эти операции лишь изменяют сам список.&lt;/p&gt;
&lt;p&gt;Контейнер &lt;code&gt;LinkedList&lt;/code&gt;, оптимизированный для последовательного доступа, с быстрыми операциями вставки/удаления в середине списка. Произвольный доступ к элементам &lt;code&gt;LinkedList&lt;/code&gt; выполняется относительно медленно, т.к. требует полного перебора элементов. Класс представляет структуру данных связного списка и реализует интерфейсы &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;Dequeue&lt;/code&gt;, &lt;code&gt;Queue&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Поиск в LinkedList проходит за O(n). Удаление происходит за O(1). Вставка происходит за O(1).&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
LinkedList&amp;lt;String&amp;gt; lst = new LinkedList&amp;lt;&amp;gt;();

// добавление элементов
lst.add("Mercury");
lst.add("Earth");
lst.addLast("Saturn");
lst.addFirst("Neptune");
lst.add(1, "Venus");

// вставка в начало
lst.push("Kopernik")

// отображение списка
System.out.println(lst);

// добавлене несколько элементов сразу
lst.addAll(Arrays.asList("PlanetX", "PlanetY", "PlanetZ"));

// изменить элемент списка по индексу
String planet = lst.set(1, "PlanetA")

// получение True если значение есть в списке, иначе False
boolean isExist = lst.contains("Earth")

// получение элемента по индексу
String planet = lst.get(3)

// получение первого элемента
String planet = lst.getFirst()

// получение последнего элемента
String planet = lst.getLast()

// получить все элементы списка в виде массива
String[] planets = lst.toArray()

// удалить все элементы
lst.clear()

// получить индекс первого элемента с указаным значением, если не найденно то -1
int pos = lst.indexOf("Jupiter");

// получить индекс последнего элемента с указаным значением, если не найденно то -1
int pos = lst.lastIndexOf("Jupiter");

// удаление первого элемента со значением
lst.remove("PlanetX");

// удалить первый элемент из списка и вернуть значение
String planet = lst.removeFirst();

// удалить первый элемент из списка и вернуть значение
String planet = lst.poll();

// удалить первый элемент из списка и вернуть значение
String planet = lst.pop();

// удалить последний элемент из списка и вернуть значение
String planet = lst.removeLast();

// размер списка
int amount = lst.size()

// перебор элементов списка
for(String str: lst) {
  System.out.println(str);
}

// быстрый способ добавить/объединить списки
String[] morePlanets = {"Planet1", "Planet2", "Planet3"};
Collections.addAll(lst, moreInts);

// конвертирование LinkedList в ArrayList
List&amp;lt;String&amp;gt; arr = new ArrayList&amp;lt;&amp;gt;(lst);
&lt;/pre&gt;

&lt;p&gt;Сравнение &lt;code&gt;LinkedList&lt;/code&gt; и &lt;code&gt;ArrayList&lt;/code&gt; по скорости операций:&lt;/p&gt;
&lt;table border="0" class="redtable"&gt;
&lt;thead&gt;
&lt;tr&gt;
    &lt;th&gt;Метод&lt;/th&gt;
    &lt;th&gt;Arraylist&lt;/th&gt;
    &lt;th&gt;&amp;nbsp;LinkedList&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
    &lt;td&gt;get(index)&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="highlight"&gt;
    &lt;td&gt;add(E)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;add(E, index)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="highlight"&gt;
    &lt;td&gt;remove(index)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;Iterator.remove()&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="highlight"&gt;
    &lt;td&gt;Iterator.add(E)&lt;/td&gt;
    &lt;td&gt;O(n)&lt;/td&gt;
    &lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Сравнеие производительности &lt;code&gt;Arraylist&lt;/code&gt; и &lt;code&gt;LinkedList&lt;/code&gt;&lt;/p&gt;
&lt;div style="text-align:center;"&gt;
&lt;a href="http://www.programcreek.com/2013/03/arraylist-vs-linkedlist-vs-vector/"&gt;&lt;img alt="java_arraylist_vs_linkedlist.png" src="http://proft.me/media/java/java_arraylist_vs_linkedlist.png"&gt;&lt;/a&gt;&lt;br/&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Vector&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Vector&lt;/code&gt; это динамический массив, похожий на &lt;code&gt;ArrayList&lt;/code&gt;, за исключением двух моментов: &lt;code&gt;Vector&lt;/code&gt; синхронизирован (потоко-безопасен) и &lt;code&gt;Vector&lt;/code&gt; содержит много legacy-методов. По умолчанию, &lt;code&gt;Vector&lt;/code&gt; удваивает свой размер когда заканчивается выделенная под элементы память. &lt;code&gt;ArrayList&lt;/code&gt; же увеличивает свой размер только на половину.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stack&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Стэк является подкласом &lt;code&gt;Vector&lt;/code&gt; и работает по принципу LIFO (last-in-first-out rule) - последним пришел, первым ушел. Обратите внимание, что &lt;code&gt;Stack&lt;/code&gt; и &lt;code&gt;Vector&lt;/code&gt; оба потокобезопасны. Используйте стэк если вы хотите вставлять и удалять элементы только с вершины стэка, что полезно в рекурсивных алгоритмах.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
Stack&amp;lt;String&amp;gt; planets = new Stack&amp;lt;&amp;gt;();

// добавление элементов
planets.push("Mercury");
planets.push("Earth");
planets.push("Saturn");

// вывод стэка
System.out.println(planets);

// пустой стэк?
boolean isEmpty = planets.empty()

// получить вершину стэка, без удаления
String top = planets.peek();
System.out.println(top);

// получить вершину стэка, с удалением
String saturn = planets.pop();
System.out.println(saturn);

// поиск элемента в стэке (вызов метода equals() для каждого элемента)
int index = planets.search("Earth");
System.out.println(index);
&lt;/pre&gt;

&lt;p&gt;&lt;b class="lb"&gt;Set&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HashSet&lt;/code&gt;, &lt;code&gt;TreeSet&lt;/code&gt; и &lt;code&gt;LinkedHashSet&lt;/code&gt; относятся к семейству &lt;code&gt;Set&lt;/code&gt;. В множествах &lt;code&gt;Set&lt;/code&gt; каждый элемент хранится только в одном экземпляре, а разные реализации &lt;code&gt;Set&lt;/code&gt; используют разный порядок хранения элементов. В &lt;code&gt;HashSet&lt;/code&gt; порядок элементов определяется по сложному алгоритму и не гарантирует порядок в котором элементы вставлялись. Если порядок хранения для вас важен, используйте контейнер &lt;code&gt;TreeSet&lt;/code&gt;, в котором объекты хранятся отсортированными по возрастанию в порядке сравнения или &lt;code&gt;LinkedHashSet&lt;/code&gt; с хранением элементов в порядке добавления.&lt;/p&gt;
&lt;p&gt;Множества часто используются для проверки принадлежности, чтобы вы могли легко проверить, принадлежить ли объект заданному множеству, поэтому на практике обычно выбирается реализация &lt;code&gt;HashSet&lt;/code&gt;, оптимизированная для быстрого поиска.&lt;/p&gt;
&lt;p&gt;Сравним времени на операцию &lt;code&gt;add()&lt;/code&gt; для &lt;code&gt;HashSet&lt;/code&gt;, &lt;code&gt;TreeSet&lt;/code&gt;, &lt;code&gt;LinkedHashSet&lt;/code&gt;.&lt;/p&gt;
&lt;div style="text-align:center;"&gt;
&lt;a href="http://www.programcreek.com/2013/03/hashset-vs-treeset-vs-linkedhashset/"&gt;&lt;img alt="java_hashset_treeset_linkedhashset.png" src="http://proft.me/media/java/java_hashset_treeset_linkedhashset.png"&gt;&lt;/a&gt;&lt;br/&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;HashSet&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HashSet&lt;/code&gt; расширяет &lt;code&gt;AbstractSet&lt;/code&gt; и реализует интерфейс &lt;code&gt;Set&lt;/code&gt;. &lt;code&gt;HashSet&lt;/code&gt; создает коллекцию, которая использует хеш-таблицу для сохранения своих значений. Класс &lt;code&gt;Object&lt;/code&gt; и его наследники имеют метод &lt;code&gt;hashCode()&lt;/code&gt;, который используется классом &lt;code&gt;HashSet&lt;/code&gt; для эффективного размещения объектов, заносимых в коллекцию. Ключ используется для определения уникальности элемента и называется хеш-кодом.&lt;/p&gt;
&lt;p&gt;Особености &lt;code&gt;HasSet&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;HashSet&lt;/code&gt; не поддерживает порядок своих элементов, а это значит, что элементы будут возвращены в любом порядже.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HashSet&lt;/code&gt; не разрешает хранить дубликаты. Если вы добавите существующий элемент, то старое значение будет переписано.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HashSet&lt;/code&gt; разрешает добавить в колекцию &lt;code&gt;null&lt;/code&gt; значение, но только одно значение.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HashSet&lt;/code&gt; не синхронизировано.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
HashSet&amp;lt;String&amp;gt; planets = new HashSet&amp;lt;&amp;gt;();

// добавление элементов
planets.add("Mercury");
planets.add("Earth");
planets.add("Saturn");
planets.add("Neptune");
planets.add("Venus");

// отображение множества, обратите внимание на порядок вывода
System.out.println(planets);

// получение True если значение есть в множестве, иначе False
boolean isExist = planets.contains("Earth")

// пустое множество?
boolean isEmpty = planets.isEmpty()

// удаление элемента по значению
planets.remove("Saturn");

// удалить все элементы
planets.clear()

// размер множества
int amount = planets.size()

// перебор элементов множества
for(String str: planets) {
  System.out.println(str);
}

// конвертирование HashSet в ArrayList
List&amp;lt;String&amp;gt; arr = new ArrayList&amp;lt;&amp;gt;(planets);

// конвертирование List в Set
Set&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;(list);

// конвертирование List в Set, если надо учитывать сравнение элементов
Set&amp;lt;Integer&amp;gt; set = new TreeSet&amp;lt;&amp;gt;(aComparator);
set.addAll(list);
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;LinkedHashSet&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Класс &lt;code&gt;LinkedHashSet&lt;/code&gt; расширяет класс &lt;code&gt;HashSet&lt;/code&gt;, не добавляя никаких новых методов. Класс поддерживает связный список элементов множества в том порядке, в котором они вставлялись.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
LinkedHashSet&amp;lt;String&amp;gt; planets = new LinkedHashSet&amp;lt;&amp;gt;();

// добавление элементов
planets.add("Mercury");
planets.add("Earth");
planets.add("Saturn");
planets.add("Neptune");

// отображение множества, обратите внимание на порядок вывода
System.out.println(planets);
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;TreeSet&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TreeSet&lt;/code&gt; реализует интерфейс &lt;code&gt;SortedSet&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Класс &lt;code&gt;TreeSet&lt;/code&gt; создаёт коллекцию, которая для хранения элементов использует дерево. Объекты сохраняются в отсортированном порядке по возрастанию.&lt;/p&gt;
&lt;p&gt;Время доступа к элементам небольшое, что делает &lt;code&gt;TreeSet&lt;/code&gt; отличным выбором для сохранения большого количкства отсортироыанных данных и к которым должен быть обеспечен быстрый доступ.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TreeSet&lt;/code&gt; не поддерживает хранение элементов типа &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
TreeSet&amp;lt;String&amp;gt; planets = new TreeSet&amp;lt;&amp;gt;();

// добавление элементов
planets.add("Mercury");
planets.add("Earth");
planets.add("Saturn");
planets.add("Neptune");
planets.add("Venus");

// добавлене несколько элементов сразу
planets.addAll(Arrays.asList("PlanetX", "PlanetY", "PlanetZ"));

// отображение множества, обратите внимание на порядок вывода
System.out.println(planets);

// получение True если значение есть в множестве, иначе False
boolean isExist = planets.contains("Earth")

// пустое множество?
boolean isEmpty = planets.isEmpty()

// получить первый элемент
String planet = planets.first();

// получить последний элемент
String planet = planets.last();

// получить все элементы перед указаным
Set planetsBefore = planets.headSet("Neptune");

// получить все элементы после указаного
Set planetsAfter = planets.tailSet("Neptune");

// получить все элементы между двумя указаными
Set planetsSub = planets.subSet("Mercury", "Venus");

// удаление элемента по значению
planets.remove("Saturn");

// удалить все элементы
planets.clear()

// размер множества
int amount = planets.size()

// перебор элементов множества
for(String str: planets) {
  System.out.println(str);
}

// получить индекс элемента
int pos = planets.headSet("Neptune").size()

// определим произволный Comparator для TreeSet
Set&lt;String&gt; planets2 = new TreeSet&amp;lt;&amp;gt;(new Comparator&amp;lt;String&amp;gt;() {
    @Override
    public int compare(String o1, String o2) {
        // define comparing logic here
        return Integer.compare(o1.length() , o2.length());
    }
});

planets2.add("Earth");
planets2.add("Saturn");
planets2.add("Neptune");

System.out.println(planets2);
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;NavigableSet&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Интерфейс &lt;code&gt;NavigableSet&lt;/code&gt; наследуется от &lt;code&gt;SortedSet&lt;/code&gt; и предоставляет возможность навигации по множеству в обоих направлениях.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
NavigableSet&amp;lt;String&amp;gt; planets = new TreeSet&amp;lt;&amp;gt;();

// добавление элементов
planets.add("Mercury");
planets.add("Earth");
planets.add("Saturn");
planets.add("Neptune");
planets.add("Venus");

// печать множества
System.out.println(planets);

// обратный порядок
NavigableSet&amp;lt;String&amp;gt; planetsReverse = planets.descendingSet();
System.out.println(planetsReverse);

// получить два последних элемента
NavigableSet&amp;lt;String&amp;gt; twoLast = planets.tailSet("Saturn", true);
System.out.println(twoLast);

// получить элемент, который перед указанным
String lower = planets.lower("Saturn");
System.out.println(lower);

// получить элемент, который после указаного
String higher = planets.higher("Saturn");
System.out.println(higher);

// получить первый элемент и удалить его из множества
String first = planets.pollFirst();

// получить последний элемент и удалить его из множества
String last = planets.pollLast();
&lt;/pre&gt;

&lt;p&gt;&lt;b class="lb"&gt;Queue&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Очереди предствляют собой упорядоченый список элементов, т.к. реализуют интерфейс &lt;code&gt;List&lt;/code&gt;, но есть особености использования. Элементы вставляются в конец очереди, а извлекаются из начала очереди. Обычно, но не обязательно, очереди работают по принципу FIFO — первым пришел, первым ушел.&lt;/p&gt;
&lt;p&gt;Используйте очередь если вы хотите обрабатывать поток элементов в том же порядке в котором они поступают. Хорошо для списка заданий и обработки запросов.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Queue&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;В Java Collections API интерфейс &lt;code&gt;Queue&lt;/code&gt; реализован в&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LinkedList&lt;/code&gt; - реализует принцип FIFO.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PriorityQueue&lt;/code&gt; - хранит элементы либо в естественном порядке (natural order) либо в соответсвии с &lt;code&gt;Comparator&lt;/code&gt;, переданым в конструктор.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
Queue&amp;lt;String&amp;gt; planets = new LinkedList&amp;lt;&amp;gt;();

// добавление элементов
planets.add("Mercury");
planets.add("Earth");
planets.add("Saturn");

// добавить элемент, в случаи проблемы - выбросить exception
planets.add("Neptune");

// добавить элемент, в случаи проблемы - вернуть false
planets.offer("Venus");

// получить последний элемент очереди, без удаления
String top = planets.element();
System.out.println(top);

// получить последний элемент очереди, с удалением
String saturn = planets.remove();
System.out.println(saturn);

// итерация элементов
for(String planet : planets) {
    System.out.println(planet);
}
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Deque&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Deque&lt;/code&gt; (Double Ended Queues) это двухсторонняя очередь, которая позволяет вставлять/удалять элементы как с конца очереди, так и с начала. &lt;code&gt;Deque&lt;/code&gt; интерфейс расширяет &lt;code&gt;Queue&lt;/code&gt; интерфейс.&lt;/p&gt;
&lt;p&gt;В Java Collections API интерфейс &lt;code&gt;Deque&lt;/code&gt; реализован в&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ArrayDeque&lt;/code&gt; - базируется на массиве и используется при реализации стэка (принцип LIFO).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LinkedList&lt;/code&gt; - базируется на двух-связном списке и используется при реализации очереди (принцип FIFO).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
Deque&amp;lt;String&amp;gt; planets = new LinkedList&amp;lt;&amp;gt;();

planets.addLast("Mercury");
planets.offerLast("Earth");
planets.offerLast("Saturn");
planets.offerLast("Venus");
System.out.println(planets);

// пустая очередь?
boolean isEmpty = planets.isEmpty()

// итерация по очереди и извлечение по одному элементу
while (planets.peekFirst() != null) {
  System.out.println("Первый элемент: " + planets.peekFirst());
  planets.removeFirst();
  System.out.println("Очередь: " + planets);
}

// получить первый элемент очереди, без удаления
String top = planets.peekFirst();
System.out.println(top);

// получить первый элемент очереди, с удалением
String saturn = planets.pollFirst();
System.out.println(saturn);
&lt;/pre&gt;

&lt;p&gt;&lt;b class="lb"&gt;Map&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;В контейнерах &lt;code&gt;Map&lt;/code&gt; (отображения, словарь, ассоциативный массив, карты) хранятся два объекта: ключ и связанное с ним значение. &lt;code&gt;Map&lt;/code&gt; позволяет искать объекты по ключу. Объект, ассоциированный с ключом, называется значением. И ключи, и значения являются объектами. Ключи могут быть уникальными, а значения могут дублироваться. Некоторые отображения допускают пустые ключи и пустые значения.&lt;/p&gt;
&lt;p&gt;Интерфейс &lt;code&gt;Map&lt;/code&gt; соотносит уникальные ключи со значениями. Ключ - это объект, который вы используете для последующего извлечения данных. Задавая ключ и значение, вы можете помещать значения в объект отображения. После того как это значение сохранено, вы можете получить его по ключу.&lt;/p&gt;
&lt;p&gt;Основные методы - &lt;code&gt;get()&lt;/code&gt; и &lt;code&gt;put()&lt;/code&gt;, чтобы получить или поместить значения в отображение.&lt;/p&gt;
&lt;p&gt;Интерфейс &lt;code&gt;Map&lt;/code&gt; предоставляет три представления (view) хранящихся данных:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;множество всех ключей&lt;/li&gt;
&lt;li&gt;множество всех значений&lt;/li&gt;
&lt;li&gt;множество объектов &lt;code&gt;Entry&lt;/code&gt;, содержащих в себе и ключ и значение&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Отображения не поддерживают реализацию интерфейса &lt;code&gt;Iterable&lt;/code&gt;, поэтому нельзя перебрать карту через цикл &lt;code&gt;for&lt;/code&gt; в форме &lt;code&gt;for-each&lt;/code&gt;. Можно перебрать отдельно ключи или значения или объекти &lt;code&gt;Entry&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Интерфейс &lt;code&gt;SortedMap&lt;/code&gt; расширяет интерфейс &lt;code&gt;Map&lt;/code&gt; и гарантирует, что элементы размещаются в возрастающем порядке значений ключей.&lt;/p&gt;
&lt;p&gt;Интерфейс &lt;code&gt;NavigableMap&lt;/code&gt; расширяет интерфейс &lt;code&gt;SortedMap&lt;/code&gt; и определяет поведение отображения, поддерживающее извлечение элементов на основе ближайшего соответствия заданному ключу или ключам.&lt;/p&gt;
&lt;p&gt;Интерфейс &lt;code&gt;Map.Entry&lt;/code&gt; позволяет работать с элементом отображения, в частности используется при переборе элементов. Основные мотеды интерфейса &lt;code&gt;Map.Entry&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;equals(Object o)&lt;/code&gt; сравнение на равенство двух элементов&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getKey()&lt;/code&gt; получить ключ элемента&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getValue()&lt;/code&gt; получить значение элемента&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hashCode()&lt;/code&gt; получить хэш-код элемента&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setValue(V value)&lt;/code&gt; замена значения элемента на value&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;HashMap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HashMap использует хэш-таблицу для реализации &lt;code&gt;Map&lt;/code&gt; интерфейса, что позволяет операциям &lt;code&gt;get()&lt;/code&gt; и &lt;code&gt;put()&lt;/code&gt; выполнятся за константное время даже для больших наборов. &lt;code&gt;HashMap&lt;/code&gt; обеспечивает максимальную скорость выборки, а порядок хранения его элементов не очевиден.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HashMap&lt;/code&gt; подобен &lt;code&gt;Hashtable&lt;/code&gt; с нескольками исключениямия:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HashTable&lt;/code&gt; потокобезопасна, а &lt;code&gt;HashMap&lt;/code&gt; нет&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HashTable&lt;/code&gt; не может содержать элементы &lt;code&gt;null&lt;/code&gt;, тогда как &lt;code&gt;HashMap&lt;/code&gt; может содержать один ключ &lt;code&gt;null&lt;/code&gt; и любое количество значений &lt;code&gt;null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Итератор у &lt;code&gt;HashMap&lt;/code&gt;, в отличие от перечислителя &lt;code&gt;HashTable&lt;/code&gt;, работает по принципу &lt;em&gt;fail-fast&lt;/em&gt; (выдает исключение при любой несогласованности данных)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
HashMap&amp;lt;String, Double&amp;gt; planets = new HashMap&amp;lt;&amp;gt;();

// добавление элементов
planets.put("Mercury", new Double(2439.7));
planets.put("Earth", new Double(6371));
planets.put("Saturn", new Double(58232));
planets.put("Neptune", new Double(24622));
planets.put("Venus", new Double(6051.8));

// отображение множества, обратите внимание на порядок вывода
System.out.println(planets);

// получить значение по ключу
Double radius = planets.get("Earth");

// получение True если ключ есть в отображении, иначе False
boolean isExist = planets.containsKey("Earth")

// получение True если значение есть в отображении, иначе False
boolean isExist = planets.containsValue("Earth")

// пустое отображение?
boolean isEmpty = planets.isEmpty()

// удаление элемента по ключу
planets.remove("Saturn");

// список ключей
Set keys = planets.keySet();

// список значений
Collection&amp;lt;Double&amp;gt; values = planets.values();
ArrayList&amp;lt;Double&amp;gt; arr = new ArrayList&amp;lt;&amp;gt;(planets.values());

// множество элементов ввиде объектов интерфейса Map.Entry
Set entries = planets.entrySet();

// перебор элементов Entry отображения с помощью for
for (Map.Entry me : planets.entrySet()) {
  System.out.println("Key: "+me.getKey() + " &amp;amp; Value: " + me.getValue());
}

// перебор элементов отображения с помощью while
Set entries = planets.entrySet();
Iterator i = entries.iterator();

while(i.hasNext()) {
    Map.Entry me = (Map.Entry)i.next();
    System.out.print(me.getKey() + ": ");
    System.out.println(me.getValue());
}

// размер отображения
int amount = planets.size()

// удалить все элементы
planets.clear()

// конвертирование Map в ArrayList: список ключей
List keyList = new ArrayList(planets.keySet());

// конвертирование Map в ArrayList: список значений
List valueList = new ArrayList(planets.valueSet());

// конвертирование Map в ArrayList: список ключ-значения
List entryList = new ArrayList(planets.entrySet());

// упорядочивание Map по значениям
List list = new ArrayList(planets.entrySet());
Collections.sort(list, new Comparator() {
   @Override
  public int compare(Entry e1, Entry e2) {
    return e1.getValue().compareTo(e2.getValue());
  }
});
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;TreeMap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TreeMap&lt;/code&gt; реализует &lt;code&gt;Map&lt;/code&gt; интерфейс с помощью структуры &lt;em&gt;красно-чёрного дерева&lt;/em&gt; и хранит ключи отсортированными по возрастанию (естественный порядок, natural order). Переопределить сортировку можно предоставив экземпляр класса &lt;code&gt;Comparator&lt;/code&gt;, метод &lt;code&gt;compare&lt;/code&gt; которого и будет использован для сортировки ключей. Обратите внимание, что все ключи добавленные в словарь должны реализовывать интерфейс &lt;code&gt;Comparable&lt;/code&gt; (это необходимо для сортировки).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TreeMap&lt;/code&gt; предоставляет быстрый доступ к своим элементам.&lt;/p&gt;
&lt;p&gt;Для вставки, удаления и поиска элементов в &lt;code&gt;Map&lt;/code&gt; лучше использовать &lt;code&gt;HashMap&lt;/code&gt;. Если же нужно последовательно обходить карты в неком порядке то лучше использовать &lt;code&gt;TreeMap&lt;/code&gt;. Иногда, в зависимости от размера коллекции, лучше добавить элементы в &lt;code&gt;HashMap&lt;/code&gt;, а потом сконвертировать в &lt;code&gt;TreeMap&lt;/code&gt; для упорядоченого обхода элементов.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
TreeMap&amp;lt;String, Double&amp;gt; planets = new TreeMap&amp;lt;&amp;gt;();

// добавление элементов
planets.put("Mercury", new Double(2439.7));
planets.put("Earth", new Double(6371));
planets.put("Saturn", new Double(58232));
planets.put("Neptune", new Double(24622));
planets.put("Venus", new Double(6051.8));

// отображение множества, обратите внимание на порядок вывода
System.out.println(planets);

// получить значение по ключу
Double radius = planets.get("Earth");

// получение True если ключ есть в отображении, иначе False
boolean isExist = planets.containsKey("Earth")

// получение True если значение есть в отображении, иначе False
boolean isExist = planets.containsValue("Earth")

// пустое отображение?
boolean isEmpty = planets.isEmpty()

// удаление элемента по ключу
planets.remove("Saturn");

// список ключей
Set keys = planets.keySet();

// список значений
Collection&amp;lt;Double&amp;gt; values = planets.values();
ArrayList&amp;lt;Double&amp;gt; arr = new ArrayList&amp;lt;&amp;gt;(planets.values());

// множество элементов ввиде объектов интерфейса Map.Entry
Set entries = planets.entrySet();

// перебор элементов отображения с помощью for
for (Map.Entry me : planets.entrySet()) {
  System.out.println("Key: "+me.getKey() + " &amp;amp; Value: " + me.getValue());
}

// перебор элементов отображения с помощью while
Set entries = planets.entrySet();
Iterator i = entries.iterator();

while(i.hasNext()) {
    Map.Entry me = (Map.Entry)i.next();
    System.out.print(me.getKey() + ": ");
    System.out.println(me.getValue());
}

// размер отображения
int amount = planets.size()

// удалить все элементы
planets.clear()

// сортировка элементов по значению
// компаратор
public static &amp;lt;K, V extends Comparable&amp;lt;V&amp;gt;&amp;gt; Map&amp;lt;K, V&amp;gt;  sortByValues(final Map&amp;lt;K, V&amp;gt; map) {
    Comparator&amp;lt;K&amp;gt; valueComparator = new Comparator&amp;lt;K&amp;gt;() {
        public int compare(K k1, K k2) {
            int compare =  map.get(k1).compareTo(map.get(k2));
            if (compare == 0)
                return 1;
            else
                return compare;
        }
    };

    Map&amp;lt;K, V&amp;gt; sortedByValues = new TreeMap&amp;lt;K, V&amp;gt;(valueComparator);
    sortedByValues.putAll(map);
    return sortedByValues;
}

Map sortedMap = sortByValues(planets);
for (Map.Entry me : sortedMap.entrySet()) {
    System.out.println("Key: "+me.getKey() + " &amp;amp; Value: " + me.getValue());
}
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;LinkedHashMap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;LinkedHashMap&lt;/code&gt; реализует связанный список элементов отображения и хранит ключи в порядке вставки. Также позволяет сортировать элементы в порядке последнего доступа. &lt;code&gt;LinkedHashMap&lt;/code&gt; не обеспечивает скорость поиска как &lt;code&gt;HashMap&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
LinkedHashMap&amp;lt;String, Double&amp;gt; planets = new LinkedHashMap&amp;lt;&amp;gt;();

// добавление элементов
planets.put("Mercury", new Double(2439.7));
planets.put("Earth", new Double(6371));
planets.put("Saturn", new Double(58232));
planets.put("Neptune", new Double(24622));
planets.put("Venus", new Double(6051.8));

// отображение множества, обратите внимание на порядок вывода
System.out.println(planets);

// получить значение по ключу
Double radius = planets.get("Earth");

// получение True если ключ есть в отображении, иначе False
boolean isExist = planets.containsKey("Earth")

// получение True если значение есть в отображении, иначе False
boolean isExist = planets.containsValue("Earth")

// перебор элементов отображения с помощью for
for (Map.Entry me : planets.entrySet()) {
  System.out.println("Key: "+me.getKey() + " &amp;amp; Value: " + me.getValue());
}

// еще один вариант перебора элементов отображения с помощью for
for (Map.Entry&lt;String, Double&gt; me : planets.entrySet()) {
    String key = me.getKey();
    Double value = me.getValue();
    System.out.println("Key: " + key + " &amp; Value: " + value);
}
&lt;/pre&gt;

&lt;p&gt;&lt;b class="lb"&gt;Различия между Iterator и ListIterator?&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Iterator может использоваться для перебора элементов &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;List&lt;/code&gt; и &lt;code&gt;Map&lt;/code&gt;. В отличие от него, &lt;code&gt;ListIterator&lt;/code&gt; может быть использован только для перебора элементов коллекции &lt;code&gt;List&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Iterator&lt;/code&gt; позволяет перебирать элементы только в одном направлении, при помощи метода &lt;code&gt;next()&lt;/code&gt;. Тогда как &lt;code&gt;ListIterator&lt;/code&gt; позволяет перебирать список в обоих направлениях, при помощи методов &lt;code&gt;next()&lt;/code&gt; и &lt;code&gt;previous()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;При помощи &lt;code&gt;ListIterator&lt;/code&gt; вы можете модифицировать список, добавляя/удаляя элементы с помощью методов &lt;code&gt;add()&lt;/code&gt; и &lt;code&gt;remove()&lt;/code&gt;. &lt;code&gt;Iterator&lt;/code&gt; не поддерживает данного функционала.&lt;/li&gt;
&lt;/ul&gt;</description><pubDate>Mon, 10 Apr 2017 00:00:00 +0300</pubDate><guid>http://proft.me/2017/04/10/pro-java-collections-framework-pochti-vse-i-srazu/</guid></item><item><title>Как получить координаты маршрута между двумя городами в Python</title><link>http://proft.me/2017/03/18/koordinaty-marshruta-dvumya-gorodami-v-python/</link><description>&lt;p&gt;Представим, что нам нужно получить координаты маршрута между Винницей и Одессой. Для этого воспользуемся &lt;a href="https://developers.google.com/maps/documentation/directions/"&gt;Google Directions API&lt;/a&gt; и модулем &lt;a href="https://github.com/googlemaps/google-maps-services-python"&gt;googlemaps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Установка&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
pip install -U googlemaps
&lt;/pre&gt;

&lt;p&gt;Предварительно нужно &lt;a href="https://console.developers.google.com/"&gt;получить API KEY&lt;/a&gt; для используемого проекта.&lt;/p&gt;
&lt;p&gt;Запрос к Directions API для получения маршрута.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
import googlemaps
from datetime import datetime

now = datetime.now()
gmaps = googlemaps.Client(key='API_KEY')
result = gmaps.directions("Vinnytsia, Ukraine", "Odessa, Ukraine", mode="transit", departure_time=now)
raw = result[0]['overview_polyline']['points']
&lt;/pre&gt;

&lt;p&gt;Но вот незадача, &lt;a href="https://developers.google.com/maps/documentation/utilities/polylinealgorithm?csw=1"&gt;координы хранятся&lt;/a&gt; не в виде широты и долготы, а в виде следующей последовательности &lt;em&gt;}wjiGtd&lt;/em&gt;. Нам поможет модуль &lt;a href="https://github.com/hicsail/polyline"&gt;polyline&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Установка&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
pip install -U polyline
&lt;/pre&gt;

&lt;p&gt;Использование&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
import polyline
points = polyline.decode(raw)
&lt;/pre&gt;

&lt;p&gt;В &lt;code&gt;points&lt;/code&gt; список кортежей с координатами, который можем передать в &lt;a href="https://developers.google.com/maps/documentation/static-maps/"&gt;Google Maps Static&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
coordinates = "|".join(["{0},{1}".format(p[0], p[1]) for p in points])
url = "http://maps.googleapis.com/maps/api/staticmap?size=800x800&amp;amp;path={0}".format(coordinates)
&lt;/pre&gt;

&lt;div style="text-align:center; margin-bottom: 20px;"&gt;
&lt;img alt="python_direction_api.png" src="http://proft.com.ua/media/python/python_direction_api.png" style="width:60%;"&gt;&lt;br/&gt;
&lt;/div&gt;</description><pubDate>Sat, 18 Mar 2017 00:00:00 +0200</pubDate><guid>http://proft.me/2017/03/18/koordinaty-marshruta-dvumya-gorodami-v-python/</guid></item><item><title>pathlib: удобное формирование путей в файловой системе</title><link>http://proft.me/2016/07/19/pathlib-udobnoe-formirovanie-putej-v-fajlovoj-sist/</link><description>&lt;p&gt;В python 3 есть полезный модуль &lt;a href="https://docs.python.org/3/library/pathlib.html"&gt;pathlib&lt;/a&gt; для формирование путей в файловой системе.&lt;/p&gt;
&lt;p&gt;Сравните пример &lt;/p&gt;
&lt;pre class="prettyprint"&gt;
# python 2

import os

directory = "/home/user/temp/"
filepath = os.path.join(directory, "data.csv")

if os.path.exists(filepath):
    print('exist')

# python 3

from pathlib import Path

directory = Path("/home/user/temp/")
filepath = directory / "data.csv"

if filepath.exists():
    print('exist')
&lt;/pre&gt;

&lt;p&gt;Для python 2 можно установить командой &lt;code&gt;pip2 install pathlib&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Получить домашний каталог текущего пользователя &lt;/p&gt;
&lt;pre class="prettyprint"&gt;
Path.home()
&lt;/pre&gt;

&lt;p&gt;Разбить путь на части&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
from pathlib import PurePath
PurePath("/home/user/temp/").parts
# ('/', 'home', 'user', 'temp')
&lt;/pre&gt;

&lt;p&gt;Записать строку в файл и закрыть файл&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
p = Path('hw.txt')
p.write_text('Hello world')
&lt;/pre&gt;

&lt;p&gt;Прочитать строку из файла&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
p = Path('hw.txt')
line = p.read_text()
&lt;/pre&gt;

&lt;p&gt;Еще пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
&amp;gt; from pathlib import Path
&amp;gt; p = Path("/usr/lib/python2.5/gopherlib.py")
&amp;gt; p.parent
PosixPath('/usr/lib/python2.5')
&amp;gt; p.name
'gopherlib.py'
&amp;gt; p.suffix
'.py'
&amp;gt; p.stem
'gopherlib'
&lt;/pre&gt;

&lt;p&gt;Описание &lt;a href="https://docs.python.org/3/library/pathlib.html"&gt;тут&lt;/a&gt;.&lt;/p&gt;</description><pubDate>Tue, 19 Jul 2016 00:00:00 +0300</pubDate><guid>http://proft.me/2016/07/19/pathlib-udobnoe-formirovanie-putej-v-fajlovoj-sist/</guid></item><item><title>Локальная отладка рассылки с помощью MailDev</title><link>http://proft.me/2016/05/14/lokalnaya-otladka-rassylki-s-pomoshyu-maildev/</link><description>&lt;p&gt;&lt;a href="http://danfarrelly.nyc/MailDev/"&gt;MailDev&lt;/a&gt; позволяет локально поднять SMTP сервер для тестирования и отладки почтовой рассылки. Присутствуют и другие приятные бонусы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;веб-интерфейс для просмотра входящих&lt;/li&gt;
&lt;li&gt;просмотр текстового и html вида письма, а также заголовков&lt;/li&gt;
&lt;li&gt;просмотр аттачей&lt;/li&gt;
&lt;li&gt;автоматическое обновлений в веб-интерфейсе&lt;/li&gt;
&lt;li&gt;настройка с командной строки ;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="text-align:center"&gt;
&lt;img alt="MailDev" src="https://dl.dropboxusercontent.com/u/50627698/maildev/screenshot-2015-03-29.png" width="600" /&gt;
&lt;/div&gt;

&lt;p&gt;Установка и запуск&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
sudo npm install -g maildev
maildev
&lt;/pre&gt;

&lt;p&gt;Веб-морда доступна по адресу &lt;em&gt;http://0.0.0.0:1080&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Настройки для Django&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
EMAIL_HOST = '0.0.0.0'
EMAIL_PORT = '1025'
&lt;/pre&gt;

&lt;p&gt;Настройки для MeteorJS&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
# через переменную окружения
export MAIL_URL='smtp://0.0.0.0:1025'

# или в серверной части
Meteor.startup( function() {
    process.env.MAIL_URL = 'smtp://0.0.0.0:1025';
});
&lt;/pre&gt;</description><pubDate>Sat, 14 May 2016 00:00:00 +0300</pubDate><guid>http://proft.me/2016/05/14/lokalnaya-otladka-rassylki-s-pomoshyu-maildev/</guid></item><item><title>Пример работы с перечислениями (enum) в Java</title><link>http://proft.me/2015/03/29/primer-raboty-s-perechisleniyami-enum-v-java/</link><description>&lt;blockquote&gt;Информация в посте приведена на основе книги Эккель Б. Философия Java (4-е издание)&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="http://en.proft.me/media/java/java_enum.jpg" alt="java_enum.jpg" class="right" width="150"&gt;&lt;/p&gt;
&lt;p&gt;Ключевое слово &lt;code&gt;enum&lt;/code&gt; создает новый тип с ограниченным набором именованных значений, и работать с этими значениями можно как с обычными компонентами программы.&lt;/p&gt;
&lt;p&gt;Мы можем получить список констант перечисления, вызвав для &lt;code&gt;enum&lt;/code&gt; метод &lt;code&gt;values()&lt;/code&gt;. Метод &lt;code&gt;values()&lt;/code&gt; создает массив констант перечисления, следующих в порядке их объявления, так что полученный массив может использоваться в цикле &lt;a href="http://en.proft.me/2016/08/19/different-ways-iterate-over-collections-java/"&gt;foreach&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;При создании перечисления компилятор генерирует соответствующий класс. Этот класс автоматически наследует от класса &lt;code&gt;java.lang.Enum&lt;/code&gt;. Элементы &lt;code&gt;enum&lt;/code&gt; это статически доступные экземпляры enum-класса. Некоторые полезные возможности базового класса продемонстрированы в следующем примере:&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
class Example1{  
    public enum Season {WINTER, SPRING, SUMMER, FALL};

    Season w = Season.WINTER;

    public static void main(String[] args) {  
        for (Season s : Season.values())  
            System.out.println(s.ordinal() + ": " + s);  
    }  
}
&lt;/pre&gt;

&lt;p&gt;Метод &lt;code&gt;ordinal()&lt;/code&gt; возвращает значение &lt;em&gt;int&lt;/em&gt;, определяющее порядок объявления экземпляров перечисления начиная с нуля. Экземпляры всегда можно безопасно сравнивать с оператором &lt;em&gt;==&lt;/em&gt;, a методы &lt;code&gt;equals()&lt;/code&gt; и &lt;code&gt;hashCode()&lt;/code&gt; автоматически создаются за вас. Класс &lt;code&gt;Enum&lt;/code&gt; реализует интерфейс &lt;code&gt;Comparable&lt;/code&gt;, поэтому в нем присутствует метод &lt;code&gt;compareTo( )&lt;/code&gt;, кроме того, реализуется интерфейс &lt;code&gt;Serializable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Метод &lt;code&gt;name()&lt;/code&gt; возвращает имя точно в таком виде, в каком оно было объявлено; эту же информацию возвращает метод &lt;code&gt;toString()&lt;/code&gt;. Статический метод &lt;code&gt;valueOf()&lt;/code&gt; возвращает экземпляр перечисления, соответствующий переданному строковому имени, или возбуждает исключение при отсутствии подходящего экземпляра.&lt;/p&gt;
&lt;p&gt;Пример получения элемент &lt;code&gt;enum&lt;/code&gt; по его строковому представлению&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
String name = "SUMMER"; 
Season season = Season.valueOf(name);
&lt;/pre&gt;

&lt;p&gt;Перечисление не может использоваться при наследовании, но в остальном оно работает как обычный класс. Это означает, что в перечисление &lt;em&gt;можно добавлять новые методы&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Допустим, вы решили определить для перечисления другое описание - вместо реализации &lt;code&gt;toString()&lt;/code&gt; по умолчанию, которая просто выдает имя экземпляра перечисления. Для этого можно предоставить конструктор для сохранения расширенной информации,
a также дополнительные методы для передачи расширенного описания:&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
class Example2{  
    public enum Season {
        WINTER("Cold season"), 
        SPRING("Cool season"), 
        SUMMER("Hot season"), 
        FALL("Cool season");

        private String description;

        private Season(String description) {
            this.description = description;
        }

        public String getDescription() {return description;}
    };

    public static void main(String[] args) {  
        for (Season s : Season.values())  
            System.out.println(s + ": " + s.getDescription());  
    }  
}
&lt;/pre&gt;

&lt;p&gt;Обратите внимание: если вы собираетесь определять методы, то последовательность экземпляров перечисления должна завершаться символом &lt;code&gt;;&lt;/code&gt;. Кроме того, по правилам Java перечисление должно начинаться с определения экземпляров. При попытке определить их после методов или полей компилятор выдает ошибку.&lt;/p&gt;
&lt;p&gt;Конструктор и методы строятся пo той же схеме, что и в обычном классе‚ потому что перечисление (с некоторыми ограничениями) и является обычным классом.&lt;/p&gt;
&lt;p&gt;Одна из самых удобных возможностей перечисления связана с их использованием в команде &lt;code&gt;switch&lt;/code&gt;. &lt;/p&gt;
&lt;pre class="prettyprint"&gt;
class Example3{  
    public enum Season {WINTER, SPRING, SUMMER, FALL};

    public static void main(String[] args) {  
        Season w = Season.WINTER;
        switch(w) {
            case WINTER:
            case SPRING:
            case FALL:
                System.out.println("Working ...");  
                break;
            case SUMMER:
                System.out.println("Relaxation ...");  
                break;

        }
    }  
}
&lt;/pre&gt;</description><pubDate>Sun, 29 Mar 2015 00:00:00 +0200</pubDate><guid>http://proft.me/2015/03/29/primer-raboty-s-perechisleniyami-enum-v-java/</guid></item><item><title>Разница между ArrayList и LinkedList</title><link>http://proft.me/2014/10/29/raznica-mezhdu-arraylist-i-linkedlist/</link><description>&lt;p&gt;Контейнер &lt;code&gt;List&lt;/code&gt; гарантирует хранение списка элементов в определенной последовательности. Интерфейс &lt;code&gt;List&lt;/code&gt; добавляет в &lt;a href="http://en.proft.me/2013/11/3/java-collection-framework-cheat-sheet/"&gt;Collection&lt;/a&gt; методы вставки и удаления элементов в середине списка.&lt;/p&gt;
&lt;p&gt;Существуют две основные разновидности &lt;code&gt;List&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Базовый контейнер &lt;code&gt;ArrayList&lt;/code&gt; с превосходной скоростью произвольного доступа к элементам, но относительно медленными операциями вставки и удаления элементов в середине.&lt;/li&gt;
&lt;li&gt;Связанный список &lt;code&gt;LinkedList&lt;/code&gt; с оптимальным последовательным доступом и низкозатратными операциями вставки и удаления в середине списка. Операции произвольного доступа &lt;code&gt;LinkedList&lt;/code&gt; выполняет относительно медленно, но обладает более широкой функциональностью, чем &lt;code&gt;ArrayList&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="http://techinerd.com/stack-and-heap-memory/"&gt;
&lt;img alt="java_al_ll.png" src="http://proft.me/media/android/java_al_ll.png"/&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Пример работы с &lt;code&gt;ArrayList&lt;/code&gt;&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
ArrayList al = new ArrayList();

al.add("C");
al.add("A");
al.add("E");
al.add("F");
al.add(1, "A2");

System.out.println("Содержимое al: " + al);

al.remove("F");
al.remove(2);
&lt;/pre&gt;

&lt;p&gt;Класс &lt;code&gt;LinkedList&lt;/code&gt;, как и &lt;code&gt;ArrayList&lt;/code&gt;, реализует базовый интерфейс &lt;code&gt;List&lt;/code&gt;, но при этом выполняет некоторые операции (вставка и удаление в середине списка) более эффективно, чем &lt;code&gt;ArrayList&lt;/code&gt;. И наоборот, с операциями произвольного доступа он работает менее эффективно.&lt;/p&gt;
&lt;p&gt;В &lt;code&gt;LinkedList&lt;/code&gt; также добавляются методы, которые позволяют использовать его как стек, очередь или двустороннюю очередь (&lt;em&gt;дек&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Некоторые из этих методов представляют собой синонимы или небольшие видоизменения для создания имен, более знакомых в контексте конкретного применения (прежде всего &lt;em&gt;Queue&lt;/em&gt;). Например, методы &lt;code&gt;getFirst()&lt;/code&gt; и &lt;code&gt;element()&lt;/code&gt; идентичны - они возвращают начало (первый элемент) списка без его удаления и выдают исключение &lt;code&gt;NoSuchElementException&lt;/code&gt;, если список пуст. &lt;/p&gt;
&lt;p&gt;Метод &lt;code&gt;addFirst()&lt;/code&gt; вставляет элемент в начало списка.&lt;/p&gt;
&lt;p&gt;Метод &lt;code&gt;offer()&lt;/code&gt; делает то же, что &lt;code&gt;add()&lt;/code&gt; и &lt;code&gt;addLast()&lt;/code&gt;. Все эти методы добавляют элемент в конец списка.&lt;/p&gt;
&lt;p&gt;Метод &lt;code&gt;removeLast()&lt;/code&gt; удаляет и возвращает последний элемент списка.&lt;/p&gt;
&lt;p&gt;Пример работы с &lt;code&gt;LinkedList&lt;/code&gt;&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
LinkedList ll = new LinkedList();

ll.add("F");
ll.add("B");
ll.add("D");
ll.add("E");
ll.add("C");
ll.addLast("Z");
ll.addFirst("A");
ll.add(1, "A2");
System.out.println("Содержимое ll: " + ll);

// удаление элементов
ll.remove("F");
ll.remove(2);
System.out.println("Содержимое ll: " + ll);

// удалить первый и второй элемент 
ll.removeFirst();
ll.removeLast();
System.out.println("Содержимое ll: " + ll);
&lt;/pre&gt;</description><pubDate>Wed, 29 Oct 2014 00:00:00 +0200</pubDate><guid>http://proft.me/2014/10/29/raznica-mezhdu-arraylist-i-linkedlist/</guid></item><item><title>Почти все банкоматы Украины на карте вашего Android'a</title><link>http://proft.me/2014/07/26/pochti-vse-bankomaty-ukrainy-na-karte-vashego-andr/</link><description>&lt;p&gt;&lt;img src="http://proft.me/media/android/sm_logo.jpg" alt="sm_logo.jpg" class="right" width="100"&gt;&lt;/p&gt;
&lt;p&gt;Наверное у многих была ситуация, когда нужно снять деньги с карточки а вы не знаете где ближайший банкомат. Если вас такая ситуация застала в родном городе то особо не беда, можно добраться до ближайшего банкомата/банка про который вы знаете. А если такая ситуация возникла в городе, в которым вы плохо ориентируетесь?&lt;/p&gt;
&lt;p&gt;Вот из такой цепочки соображений возникла идея для сайта &lt;a href="http://safemoney.com.ua/"&gt;safemoney.com.ua&lt;/a&gt; и &lt;a href="https://play.google.com/store/apps/details?id=proft.me.atms.app"&gt;Android-приложения&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Мое описание с &lt;a href="https://play.google.com/store/apps/details?id=proft.me.atms.app"&gt;Google play&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Список адресов банкоматов и филиалов украинских банков. Есть возможность отобразить все адреса на карте и показать банкоматы возле Вас.&lt;/p&gt;
&lt;p&gt;На данный момент доступны банкоматы и филиалы для всех областных центров (кроме Луганска, Донецка и Крым АР). Всего 51 банк с 12652 банкоматами и филиалами.&lt;/p&gt;
&lt;p&gt;Проект развивается на бесплатной основе, потому приветствуются желающие помочь в наполнении базы :).&lt;/p&gt;    
&lt;/blockquote&gt;

&lt;ul class="gallery"&gt;
    &lt;li&gt;&lt;a title="" rel="prettyPhoto[sm]" href="http://proft.me/media/android/sm_www.png"&gt;&lt;img alt="Банкоматы Украины" src="http://proft.me/media/android/sm_www.png" width="120"/&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="" rel="prettyPhoto[sm]" href="http://proft.me/media/android/sm_android1.jpg"&gt;&lt;img alt="Банкоматы Украины" src="http://proft.me/media/android/sm_android1.jpg" width="120"/&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="" rel="prettyPhoto[sm]" href="http://proft.me/media/android/sm_android2.jpg"&gt;&lt;img alt="Банкоматы Украины" src="http://proft.me/media/android/sm_android2.jpg" width="120"/&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="" rel="prettyPhoto[sm]" href="http://proft.me/media/android/sm_android3.jpg"&gt;&lt;img alt="Банкоматы Украины" src="http://proft.me/media/android/sm_android3.jpg" width="120"/&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div style="clear:both;padding-bottom: 10px"&gt;&lt;/div&gt;

&lt;p&gt;На стороне сервера трудиться &lt;em&gt;Django&lt;/em&gt; + &lt;em&gt;Django REST framework&lt;/em&gt;. Почему Django, а не, скажем Flask, Tornado, etc.? Тут было интересней разобраться со связкой Android-приложение + REST, чем заниматься оптимизацией backend'a и что не мало важно в Django уже есть из коробки админка, orm, sitemap. &lt;/p&gt;
&lt;p&gt;База используется PostgreSQL, хотя это не принципиально. Что-то особого из гео-кодирования пока не используется, только координаты широты и долготы. Сами координаты получаю с помощью замечательного модуля &lt;a href="https://github.com/geopy/geopy"&gt;geopy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Пример&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
from geopy.geocoders import GoogleV3
geolocator = GoogleV3()
address, (latitude, longitude) = geolocator.geocode("Украина, Винница, ул. Пирогова 1")
print(address, latitude, longitude)
(u"Pyrohova street, 1, Vinnytsia, Vinnyts'ka oblast, Ukraine", 49.232661, 28.457728)
&lt;/pre&gt;

&lt;p&gt;Если есть желающие сделать &lt;strong&gt;iOS клиент&lt;/strong&gt; и у вас есть возможность опубликовать в App Store, то пишите тут в  комментариях или напрямую через &lt;a href="http://proft.me/contacts/"&gt;контакты&lt;/a&gt;. По сути, приложение это набор ListView с запросами к REST и отображение результатов запроса на Google Map. Желательно кэшировать результат запроса в локальное хранилище устройства, что бы не дергать сервер на каждый чих или если проблемы с доступом к интернету. Для желающих доступ к API предоставлю.&lt;/p&gt;</description><pubDate>Sat, 26 Jul 2014 00:00:00 +0300</pubDate><guid>http://proft.me/2014/07/26/pochti-vse-bankomaty-ukrainy-na-karte-vashego-andr/</guid></item><item><title>Пробую Mac OS X ... кисловато</title><link>http://proft.me/2014/07/7/probuyu-mac-os-x-kislovato/</link><description>&lt;style&gt;
.nomargin li {margin: 5px 0 7px 0;}
&lt;/style&gt;

&lt;p&gt;&lt;img src="http://proft.me/media/mac/osx.png" alt="osx.png" class="right" width="100"&gt;
Раньше я особо не высказывался по поводу продукции Apple, т.к. она была мне мало интересна до недавнего момента, пока у меня не появился Mac mini. Вокруг меня много людей с полярными взглядами на яблочную продукцию, кто-то фанатеет до потери пульса и пускает слюни на каждый новый гаджет или софт :), а кто-то не упускает момента что-бы не кинуть камень в их огород.&lt;/p&gt;
&lt;p&gt;Попробовав поработать с OS X 10.9 около двух недель, хочу поделиться своим впечатлением и настройками, вдруг когда-то еще пригодятся. Немного про мой беграунд и с какой позиции я буду судить :), 12 лет я прожил под Windows (начиная с 95 и до 7-ки, на семерку меня хватило около 2-х недель ... мигрировал на ubuntu) и 6 лет под Linux (RedHat, Mandriva, Ubuntu, CentOS, ArchLinux).&lt;/p&gt;
&lt;p&gt;Пройдусь по пунктам что мне не понравилось:&lt;/p&gt;
&lt;ol class="nomargin"&gt;
&lt;li&gt;&lt;b&gt;Рендринг шрифтов&lt;/b&gt;. Мои глаза абсолютно его не восприняли и через полчаса начали пекти :(. А это, согласитесь, не совместимо с комфортной работой. Скриншот рендринга шрифтов в OS X и ArchLinux смотрите ниже, это веб-морда почты от i.ua в Chrome. Под арчем шрифты значительно четче и приятней для чтения! Попытка играться с командой &lt;code&gt;defaults -currentHost write -globalDomain AppleFontSmoothing -int 1&lt;/code&gt; не дала результата. Насколько я понял в OS X вообще нельзя отключить/ослабить &lt;i&gt;сглаживание шрифтов&lt;/i&gt; (anti-aliasing)  или можно? если кто-то знает напишите плиз в комментариях. В iTerm2, Sublime Text, PyCharm это дело отключается и можно комфортно работать, в остальных приложениях, что я использую, такой опции нет :(.
&lt;ul class="gallery"&gt;
    &lt;li&gt;&lt;a title="" rel="prettyPhoto[osx]" href="http://proft.me/media/mac/archlinux.png"&gt;&lt;img alt="ArchLinux" src="http://proft.me/media/mac/archlinux.png" /&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a title="" rel="prettyPhoto[osx]" href="http://proft.me/media/mac/macosx.png"&gt;&lt;img alt="OS X 10.9.4" src="http://proft.me/media/mac/macosx.png" /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="clear:both;padding-bottom: 10px"&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Отваливается wifi&lt;/b&gt; с одним из dlink-роутеров, хотя остальные 7 девайсов работает нормально. Позабавила встроенная утилита для просмотра сетевых интерфейсов &lt;code&gt;networksetup -listallnetworkservices&lt;/code&gt;, в частности, её любовь к длинным ключам :) ... не сравнимо с &lt;code&gt;ip a&lt;/code&gt; под Linux.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Finder&lt;/b&gt; не умеет (!) отображать сначала директории а потом файлы. Нету быстрого способа показать/скрыть скрытые файлы. Нету возможности вырезать файл. Нужно либо искать другой файловый менеджер или ставить надстройки над Finder, я нашел бесплатный &lt;a href="http://www.trankynam.com/xtrafinder/"&gt;xtrafinder&lt;/a&gt;. Если &lt;i&gt;xtrafinder&lt;/i&gt; и добавит в Finder возможность отображать сначала директории а потом файлы то в диалоговых окнах Открыть/Сохранить по-прежнему будет каша (!).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Клавиши Home/End&lt;/b&gt; не везде работают как на PC. Нужно искать что-то, что вернет этим клавишам привычный функционал, например, &lt;a href="https://pqrs.org/macosx/keyremap4macbook/"&gt;KeyRemap4MacBook&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Если приложение минимизировать&lt;/b&gt; через кнопки управления в заголовке, то это приложение по &lt;code&gt;cmd+tab&lt;/code&gt; развернуть нельзя уже. Зачем так сделано?&lt;/li&gt;
&lt;li&gt;Есть под OS X такой популярный и бесплатный &lt;b&gt;менеджер буферов обменов&lt;/b&gt; как &lt;a href="http://www.clipmenu.com/"&gt;ClipMenu&lt;/a&gt;. Вроде и богат функционалом и красив, но зачем разбивать элементы по группам? Кроме того что не понятно в какой группе текст так еще лишнее движение на выбор группы (или поиск группы, в которой находиться нужный текст). Неудобно!&lt;/li&gt;
&lt;li&gt;Позабавил &lt;b&gt;принцип установки&lt;/b&gt; некоторых приложений: drug-n-drop в папку Applications, шикарно :) (сарказм).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Теперь с чистой совестью можно перейти к установке джентльменского набора приложений.&lt;/p&gt;
&lt;p&gt;Для начала нам понадобиться Xcode, это можно сделать либо с помощью команды в терминале&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
xcode-select --install
&lt;/pre&gt;

&lt;p&gt;или поискав в &lt;a href="http://itunes.apple.com/us/app/xcode/id497799835?ls=1&amp;amp;mt=12"&gt;App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b class="lb"&gt;Менеджер пакетов brew&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Для установки дополнительного софта воспользуемся &lt;a href="http://brew.sh/"&gt;brew&lt;/a&gt;&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go/install)"
&lt;/pre&gt;

&lt;p&gt;Проверим наше окружение для совместимости с &lt;em&gt;brew&lt;/em&gt;&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
brew doctor
&lt;/pre&gt;

&lt;p&gt;Следующая команда обновит &lt;em&gt;brew&lt;/em&gt; и список пакетов&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
brew update
&lt;/pre&gt;

&lt;p&gt;Список команд brew&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;brew install python&lt;/code&gt; - установка пакета python&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew uninstall python&lt;/code&gt; - удаление пакета python&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew update&lt;/code&gt; - обновить brew&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew upgrade python&lt;/code&gt; - обновление пакета python&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew info python&lt;/code&gt; - информация о пакете python&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew list&lt;/code&gt; - список установленных пакетов&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Все пакеты устанавливаются в /usr/local/Cellar/.&lt;/p&gt;
&lt;p&gt;Дополнительные приложения можно устанавливать не только с помощью &lt;em&gt;brew&lt;/em&gt;, есть еще много разных других менеджеров пакетов: &lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt;, &lt;a href="http://www.finkproject.org/"&gt;Fink&lt;/a&gt; и &lt;a href="http://rudix.org/"&gt;Rudix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b class="lb"&gt;Установка zsh&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Установим zsh с помощью brew&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
brew install zsh zsh-completions
&lt;/pre&gt;

&lt;p&gt;Подключим oh-my-zsh&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
cd $HOME
git clone https://github.com/robbyrussell/oh-my-zsh.git .oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
&lt;/pre&gt;

&lt;p&gt;Сделаем zsh шелом по-умолчанию для текущего пользователя&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
chsh -s /usr/local/bin/zsh
&lt;/pre&gt;

&lt;p&gt;Активируем установленные дополнения, добавив следующие в ~/.zshrc&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
fpath=(/usr/local/share/zsh-completions $fpath)
&lt;/pre&gt;

&lt;p&gt;Активируем полезные плагины от oh-my-zsh, добавив следующею строчку в ~/.zshrc&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
plugins=(git pip django postgres history history-substring-search brew sublime)
&lt;/pre&gt;

&lt;p&gt;Можем поменять тему, полный список тем смотрим &lt;a href="https://github.com/robbyrussell/oh-my-zsh/wiki/themes"&gt;тут&lt;/a&gt;&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
ZSH_THEME="bobbyrussell"
&lt;/pre&gt;

&lt;p&gt;Еще можем подключить тёмный &lt;a href="http://www.iterm2.com/hostedcolors/Solarized%20Dark.itermcolors"&gt;Solarized&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b class="lb"&gt;Установка vim&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Установка&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
brew install vim macvim
&lt;/pre&gt;

&lt;p&gt;Установим Vundle&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/vundle
&lt;/pre&gt;

&lt;p&gt;Подтяним свой конфиг (пример &lt;a href="https://github.com/proft/dotfiles/blob/master/.vimrc"&gt;моего&lt;/a&gt;)&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
ln -s ~/reps/dotfiles/.vimrc ~/
&lt;/pre&gt;

&lt;p&gt;Запустим vim и обновим плагины &lt;code&gt;:PluginInstall&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b class="lb"&gt;Дополнительный софт&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Установим git, ack, wget, curl&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
brew install git ack wget curl
&lt;/pre&gt;

&lt;p&gt;Набор приложений, для установки которых надо будет скачать вручную и запустить&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.iterm2.com/#/section/home"&gt;iTerm2&lt;/a&gt; - терминал&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/intl/en/chrome/browser/"&gt;Chrome&lt;/a&gt; - браузер&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.mozilla.org/en-US/firefox/new/"&gt;Firefox&lt;/a&gt; - браузер&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.skype.com/en/download-skype/skype-for-mac/"&gt;Skype&lt;/a&gt; - сообщения, видео связь&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adium.im/"&gt;Adium&lt;/a&gt; - jabber клиент&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dropbox.com/downloading?os=mac"&gt;Dropbox&lt;/a&gt; - синхронизация файлов&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.transmissionbt.com/"&gt;Transmission&lt;/a&gt; - BitTorrent клиент&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.libreoffice.org/download/libreoffice-fresh/"&gt;LibreOffice&lt;/a&gt; - офис&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.sublimetext.com/3"&gt;Sublime Text 3&lt;/a&gt; - текстовый редактор, &lt;a href="http://proft.me/2013/02/2/oda-o-sublime-text/"&gt;настройка Sublime&lt;/a&gt;. Если не работают клавиши Home и End то читать &lt;a href="https://coderwall.com/p/upolqw"&gt;тут&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.jetbrains.com/pycharm/download/"&gt;PyCharm&lt;/a&gt; - IDE для python/django/flask/etc&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.clipmenu.com/"&gt;ClipMenu&lt;/a&gt; - менеджер буферов обмена&lt;/li&gt;
&lt;li&gt;&lt;a href="http://bluezbox.com/mlswitcher2r.html"&gt;MLSwitcher&lt;/a&gt; - менеджер переключения языков&lt;/li&gt;
&lt;li&gt;&lt;a href="https://itunes.apple.com/ua/app/id425424353?mt=12"&gt;The Unarchiver&lt;/a&gt; - разархиватор, ссылка на App Store&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ragingmenace.com/software/menumeters/index.html#sshot"&gt;MenuMeters&lt;/a&gt; - мониторинг ресурсов системы&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.freemacsoft.net/appcleaner/"&gt;AppCleaner&lt;/a&gt; - для удаления приложений&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lightheadsw.com/caffeine/"&gt;Caffeine&lt;/a&gt; - иногда полезная вещь, блокирует засыпание&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.videolan.org/vlc/"&gt;VLC Video player&lt;/a&gt; - видео-плейер&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mstarke/MacPass/releases"&gt;MacPass&lt;/a&gt; - менеджер паролей (умеет читать базу от keepassx)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://macilove.com/news/review-of-the-best-file-manager-for-os-x/"&gt;Обзор лучших файловых менеджеров для OS X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b class="lb"&gt;Разное&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Включение выделения текста в окнах быстрого просмотра&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
defaults write com.apple.finder QLEnableTextSelection -bool TRUE;killall Finder

# для возврата к начальным установкам
defaults write com.apple.finder QLEnableTextSelection -bool FALSE;killall Finder
&lt;/pre&gt;

&lt;p&gt;Отображение скрытых файлов в Finder&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
defaults write com.apple.finder AppleShowAllFiles -bool YES; killall Finder

# для возврата к начальным установкам
defaults write com.apple.finder AppleShowAllFiles -bool NO; killall Finder
&lt;/pre&gt;

&lt;p&gt;Отключаем звук при старте системы&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
sudo nvram SystemAudioVolume=%80
&lt;/pre&gt;

&lt;p&gt;&lt;b class="lb"&gt;Итог&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;03.01.2015&lt;/strong&gt; Каждый для себя сам решает стоит ли мигрировать под OsX или оставаться под Linux. Для пользователей Windows лучше выбрать Linux или OsX :). Все выше описанное личный опыт, на основе которо я принял решение - OsX не для меня. Основная причина: вроде как бы и есть нужный функционал, но он сделан как-то не по-людкси, а если хочешь что-то поменять - изволь ... 'мыши плакали, кололись... но продолжали есть кактус' это не про меня :). &lt;/p&gt;
&lt;p&gt;Mac mini продан и я снова рад настроенному под меня Archlinux + XFCE - все минималистично, ничего лишнего и главное функционально! &lt;/p&gt;</description><pubDate>Mon, 07 Jul 2014 00:00:00 +0300</pubDate><guid>http://proft.me/2014/07/7/probuyu-mac-os-x-kislovato/</guid></item></channel></rss>