技術メモ

神奈川在住のITエンジニアの備忘録。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

iOSのアップデートに失敗してリンゴループになった話

iphone SEiOS を 11 から 13 にアップデートしたら、アップデートに失敗し、いわゆるリンゴループ (リンゴマークの表示画面から遷移しない状態) になってしまった。何とか回復できたので、その時に行った対処方法についてここにメモしておく。

  1. iphoneリカバリモードにして、iTunes に接続する。リカバリモードにするには、ホームボタンと電源ボタンをリカバリモードの画面になるまで長押しし続ける。
  2. iTunes経由で iOS のアップデートを行う・・・が失敗。。何度か試しても失敗し、iTunes 上で「初期化しないとダメです。」と表示されたので、やむなく初期化&アップデート。
  3. 無事、iOS の初期化&アップデートが終わり、アプリの入れ直し。なお、連絡先やメモは iCloud と連携していたので、消えずに済んだ。助かった・・。

以下、補足。

  • LINEは、初期化&アップデートした後にアプリを入れてログインしたら、問題なくデータを引き継げた。
  • 以前、iTunesiphoneデータのバックアップを取っていたが、パスワードが分からず、そこから復元できなかった(´・ω・`)
  • ただ、上述の通り、iCloud 上にあったバックアップのお陰で、そんなにダメージはなかった。
  • リンゴループになった後、何度か強制再起動を行ったが、回復しなかった。(リンゴループ状態から抜け出せない。)
  • iOS のアップデートに失敗した理由は不明。おそらくアップデート中に無線ネットワークが一時的に切れたとかかな。。あと、充電しながらやっていなかったので、途中で iphone の電池が切れた可能性もある。

 

今回の教訓としては、iOS のアップデートを行う前に、例えば、バックアップを取得したり、LINE については他のPC端末などからアクセスできるようにしておく。(iphone以外の端末で連絡が取れる状態にしておく。) つまり、アップデートに失敗してにっちもさっちも行かない状態になっても、ある程度は大丈夫な状態にしてから実施する。そうすれば、アップデートに失敗しても焦らずにリカバリ作業を実施できる。

あと、上述の通り、なくして困るデータについては、iCloud 連携するよう設定しておいた方が良さそうだ。

perl の正規表現の最小マッチ

perl で、文字列の前後のスペースをトリムする時は、正規表現を利用して、

$str =~ s/^\s*(.*?)\s*$/$1/;

というコードを書く。

上記の (.*?) の中の「?」が何のために必要なのか、よく忘れてしまうので、ここにメモしておく。

結論から言うと、perl正規表現のマッチはデフォルトでは最長マッチなので、
ここでの用途に合わせて最短マッチにするために「?」は必要になるということ。


簡単な例で見ていこう。

my $str = '   abc   ';
$str =~ s/^\s*(.*)\s*$/$1/;
print $str;

上記のコードの出力は、「abc△△△」(△はスペース) となり、後ろ部分のスペースがトリムできていない。
これは、上述の通り、perl正規表現はデフォルトでは最長マッチなので、(.*) が後ろ部分のスペースまで含んでしまうためである。

そこで、「?」の出番である。以下のように「?」を付与すると、最小マッチになり、
.*? がマッチするのは、「abc」だけになる。

my $str = '   abc   ';
$str =~ s/^\s*(.*?)\s*$/$1/;
print $str;

上記のコードの出力は「abc」となり、意図通り、前後のスペースがトリムされている。

深さ優先探索と幅優先探索

深さ優先探索(DFS)と幅優先探索(BFS)について、実装方法をよく忘れてしまうので、ここに簡単な例題を解く際の実装例としてメモしておく。

今回は、以下のようなツリーをDFSとBFSで探索することにする。

f:id:akrad:20191216225204j:plain

深さ優先探索(DFS)では、以下の順番で探索する。
0 -> 1 -> 3 -> 4 -> 2 -> 5 ->6

幅優先探索(BFS)では、以下の順番で探索する。
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6

上記のように探索して、探索結果を表示するプログラムをJavaで作成した。

import java.util.ArrayDeque;
import java.util.Deque;

public class Dfs {

    public void execDfs(TreeNode root) {
        if (root == null) {
            throw new IllegalArgumentException("Nothing to print because input tree is null.");
        }
        Deque<TreeNode> stack = new ArrayDeque<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            System.out.println(node.val);
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
    }

    private static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(0);

        root.left = new TreeNode(1);
        root.left.left = new TreeNode(3);
        root.left.right = new TreeNode(4);

        root.right = new TreeNode(2);
        root.right.left = new TreeNode(5);
        root.right.right = new TreeNode(6);

        new Dfs().execDfs(root);
    }
}

これを実行すると、
0 -> 1 -> 3 -> 4 -> 2 -> 5 ->6
と表示される。
深さ優先探索では、スタックを使用するため、ArrayDeque をスタックとして使用している。

import java.util.ArrayDeque;
import java.util.Deque;

public class Bfs {

    public void execBfs(TreeNode root) {
        if (root == null) {
            throw new IllegalArgumentException("Nothing to print because input tree is null.");
        }
        Deque<TreeNode> queue = new ArrayDeque<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.println(node.val);
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
    }

    private static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(0);

        root.left = new TreeNode(1);
        root.left.left = new TreeNode(3);
        root.left.right = new TreeNode(4);

        root.right = new TreeNode(2);
        root.right.left = new TreeNode(5);
        root.right.right = new TreeNode(6);

        new Bfs().execBfs(root);
    }
}

これを実行すると、
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6
と表示される。
幅優先探索は、キューを使用するため、ArrayDeque をキューとして使用している。

RHELでホスト名を変更する

これまで、RHELのホスト名を変更する時は、/etc/sysconfig/network ファイルの HOSTNAME 変数を変更して、RHEL上のネットワークサービスを再起動していた。

しかし、先日、RHELのマニュアルを見ていたら、以下の文言を見つけた。

/etc/sysconfig/network ファイルでの HOSTNAME 変数の使用は非推奨になりました。

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/networking_guide/sec-configuring_host_names_using_the_text_user_interface_nmtui

 

どうやら、RHEL7以降は、ホスト名を変更する方法が変わったらしい。では、どうやって変更したら良いのかについて、同じくRHELのマニュアルを探したところ、以下の方法があるようだ。

  • nmtui コマンド
  • hostnamectl コマンド
  • nmcli コマンド

なんで3通りもあるのか分かっていないが、マニュアルを見た感じ、hostnamectl コマンドが一番いろんな機能があるっぽいので、今後はホスト名を変更する時はこれを使っていこうと思っている。

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/networking_guide/sec_configuring_host_names_using_hostnamectl#sec_Changing_Host_Names_Remotely

Java で wait() する時は synchronized が必要な理由

Java で wait() を呼ぶ時は、該当スレッドで synchronized していることが必要となる。これをしておかないと、wait() を読んだ時に、IllegalMonitorStateException が発生する。なので、これまでは無条件に synchronized してから wait() していた。

しかし、なぜ synchronized が必要なのかについては分かっていなかったので、ちょっと調べてみたところ、以下の記事を見つけた。

https://code-examples.net/ja/q/2a695c

上記より、synchronized してから wait() しないと、そのスレッドが意図せず wait し続けてしまう可能性があり、そうなってしまうのを防ぐ目的があると理解した。つまり、プログラマがマルチスレッドの処理でバグを作り込まないようにするための、Java 側の配慮であると思っている。他にも理由がありそうなので、また何か分かったら、ここに追記したいと思う。

Javaのプリミティブ型のラッパーオブジェクトはイミュータブル

整数の入ったリストを加算する場合、普段、以下のようなコードを書いている。

        // リストから一個一個取り出して、加算して戻す。
        List<Integer> numlist = new ArrayList<>(Arrays.asList(1, 2, 3));
        for (int i = 0; i < numlist.size(); i++) {
            int num = numlist.get(i);
            numlist.set(i, num + 1);
        }
        for (Integer num : numlist) {
            System.out.print(num + " "); // -> 2, 3, 4
        }

もっと簡単に書けないものかと思い、以下を試してみたが、意図通りにはならなかった。(加算されなかった。)

        // リストの要素から取り出して、そのまま加算する。
        List<Integer> numlist1 = new ArrayList<>(Arrays.asList(1, 2, 3));
        for (Integer num : numlist1) {
            num += 1;
        }
        for (Integer num : numlist1) {
            System.out.print(num + " "); // -> 1, 2, 3
        }

デバッガで見てみると分かるが、加算した時点で新しい Integer オブジェクトが自動的に作成されている。つまり、元のリストの Integer オブジェクトは何も変更されていない。
これは、Integer のような Javaのプリミティブ型のラッパーオブジェクトはイミュータブル(不変)なので、そのものは変更できないためである。

〇参考
https://www.javaworld.com/article/2077343/java-s-primitive-wrappers-are-written-in-stone.html
http://www.kab-studio.biz/Programing/JavaA2Z/Word/00001115.html

Listを走査しながら要素を削除する。

Java で、List を走査しながら要素を削除すると、ConcurrentModificationException が発生することがある。例えば、以下のようなケース。

        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(-1);
        nums.add(4);
        nums.add(-2);
        nums.add(5);

        for (Integer num : nums) {
            if (num < 0) {
                nums.remove(num);
            }
        }

なので、これまでは、削除対象を別の List に入れてから、その削除対象の List で for 文を回しながら対応していた。例えば、以下のような感じ。

        List<Integer> removeList = new ArrayList<>();
        for (Integer num : nums) {
            if (num < 0) {
                removeList.add(num);
            }
        }
        for (Integer remove : removeList) {
            nums.remove(remove);
        }

ただ、このやり方だと、2回も for 文を回しているし冗長だな~と思って調べてみたら、以下のように Iterator を使うことでやりたいことができるようだ。

        Iterator<Integer> iter = nums.iterator();
        while (iter.hasNext()) {
            if (iter.next() < 0) {
                iter.remove();
                break;
            }
        }

今後は、この Iterator を使う書き方でいきたいと思う。