Firefox 58 でも、Shift + マウスホイールで「戻る/進む」をできるようにする方法

今日リリースされた Firefox 58 で、Shift + マウスホイールで横スクロールができないという有名な16年物のバグ (?) が修正されました。

ただ、もともと WindowsFirefox では Shift + マウスホイールは「戻る/進む」に割り当てられていました。これが、上記の対応によりできなくなりました。
自分はこれを使っていたので、超困ります…。


そこで調べてみたら、Alt + マウスホイールで同じことができるようになっているそうです。
(ただし、Alt を離すタイミングでメニューバーが出てきます)

もしくは、about:config で mousewheel.with_shift.action を 2 にすれば今までどおりの挙動に戻すことも可能
どーしても今までどおりがいいという人はこちらを試してみるのもありかと。

なんで挙動変えたの

Bug 143038 Make users can scroll contents horizontally with vertical wheel operation with a modifier r?smaug | Diff Viewer | Review Board
このコミットコメントによれば、Webアプリケーション開発者が macOS かどうかをチェックす必要がなくなる(もともと macOS だと、OS標準に合わせて Shift + マウスホイールの横スクロールに対応していた)こと、Chromium と同じ動作になることが理由だそうです。
ようするに、互換性を重視した結果ということ。

「Alt+Click」だけでテキストをコピーできるようにするブラウザ拡張機能を作りました。

テキストをコピーするのがものすごく簡単になる Google Chrome / Firefox 用ブラウザ拡張機能を作りました!

Copy Text with Alt+Click


この拡張機能を導入後は、Alt + Click でテキストをコピーできるようになります!
範囲選択も Ctrl + C を押す必要もありません!Alt + Click だけです!


Google Chrome用: Copy text with Alt-Click - Chrome ウェブストア
Firefox 用: Copy Text with Alt-Click – Firefox 向けアドオン

単機能ですが、自分自身ほぼ毎日使っているぐらい超便利な拡張機能です。
ぜひご利用ください!

JJUG CCC 2017 Fall ( #jjug_ccc ) - セッション資料の一覧

JJUG CCC 2017 Fall に行ってきました!


今回は自分好みな「普段使ってるアレ、深掘りするとこうなってるんだぜ」的なセッションをいろいろ聞けて大満足でした。


印象に残っているのが「DBのTCPプロトコルJDBC / yohei yamana さん」
「かっちりした PostgreSQL とやんちゃな MySQL という感じがする」という話があって、自分が普段使ってる印象がそのまま JDBC にも現れているんだなーと思いました。なかなかこういうものは深掘りできないので、面白い!と思ったところをポイント抑えて紹介してもらえたのは嬉しかったです。


あと、今回のメインセッションで唯一の難易度 ★★★ だった「CPUから見たG1GC / 数村憲治さん」
難しすぎて理解できていない*1ですが、なんでこうなるの!?という不可思議現象に迫るというお話は聞いていてワクワクしました。これを理解できるレベルまで、JVM のこともっと知りたいです…!


あとは業務関係で聞いたのが「Spring Security にできること・できないこと / opengl-8080 さん」
セッションでは、独自に作られていたサンプルで実際に Spring Security の有効・無効の動きを比較しながら丁寧に解説していただいたので、とてもスルスルと理解できました。実際に使用することになったら、今回のセッションで聞いたことがすぐに役に立ちそうです。ありがたや。



さて、最後にいつものを。
今回、残念ながら満席だったり時間がかぶってしまって参加できなかったセッションもあったので、あとで読むために現時点で発表者の方が公開されている資料一覧をまとめしました。*2
(あとで JJUG CCC 2017 Fall のページにもリンクが載るかもしれませんが、とりあえず自分の方で調べました)

追記:JJUGGitHub ページで公式な資料一覧が公開されました!
jjug-ccc/slides-articles-2017fall: JJUG CCC 2017 Fallの発表資料およびブログ記事まとめ

10:00-10:45

15:45-16:30

17:45-18:30

ちなみに

勉強会スライドbot (@tech_slideshare) を運用しています。
こちらには、JJUG CCC だけでなく他の勉強会の資料も流していますので、ぜひご利用ください!

*1:懇親会で他の人と話をしていて、色々誤解しまくっていたことを知りました…

*2:発表者のお名前は敬称略とさせていただきました。

今回も、JJUG CCC 2017 Fall の時間別タイムテーブル(セッション一覧)を作りました


11月18日(土) に開催されるJJUG CCC 2017 Fall「時間別」&「カテゴリ別」タイムテーブル(セッション一覧)が欲しかったので、前回同様にページを作りました!
JJUG CCC 2017 Fall - Timetable (非公式)
http://yujisoftware.github.io/jjug-ccc/2017-fall/

今回の改良点はお気に入りボタンがわかりやすくなったぐらいですが、時間があればさらに改良する予定です。

ぜひご利用ください!!

Java9 で String クラスがリファクタリングされていました。(replace メソッド編)

前々からこの実装はどうなの!?と言われていた String クラスの CharSequence を引数に取る replace メソッド。
これがついに Java 9 でリファクタリングされていました!

今まであった問題

String クラスの replace (CharSequence target, CharSequence replacement) メソッドは、文字列を置換する処理です。
これが、Java 8 までのバージョンでは正規表現を使って実装していました。

public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

正規表現は様々な処理ができる反面、かなり重たい処理です。
そのため、このメソッドは大した仕様ではないにも関わらず、あまり速くありませんでした。

Java 9 の実装

Java 9 では、正規表現を使うのをやめて StringBuilder を使うように書き直されていました!

public String replace(CharSequence target, CharSequence replacement) {
    String tgtStr = target.toString();
    String replStr = replacement.toString();
    int j = indexOf(tgtStr);
    if (j < 0) {
        return this;
    }
    int tgtLen = tgtStr.length();
    int tgtLen1 = Math.max(tgtLen, 1);
    int thisLen = length();

    int newLenHint = thisLen - tgtLen + replStr.length();
    if (newLenHint < 0) {
        throw new OutOfMemoryError();
    }
    StringBuilder sb = new StringBuilder(newLenHint);
    int i = 0;
    do {
        sb.append(this, i, j).append(replStr);
        i = j + tgtLen;
    } while (j < thisLen && (j = indexOf(tgtStr, j + tgtLen1)) > 0);
    return sb.append(this, i, thisLen).toString();
}

indexOf で文字列を探して、StringBuilder でちょっとずつ結合していくという実装です。
Apache Commons Lang の実装とよく似ています。
たしかにこれらなら速そうです。


念のため、簡単なコードで試してみたところ、文字列の長さや検索する文字列にもよりますが、だいたい2.5倍以上速くなっていました。
特に、文字列がヒットしなかった場合は10倍以上速くなっていました。


これなら安心して使えますね!

Java9 でも String クラスがリファクタリングされていました (JEP 254: Compact Strings 編)

本日、ついに JavaSE 9 がリリースされました!
そこで、かねてから噂になっていた JEP 254: Compact Strings がどのように実装されているのか調べてみました。

Compact Strings の概要

これまで String クラスや StringBuilder クラスなどの内部では、文字列を UTF-16エンコードして char 配列で保持していました。
つまり、一文字あたり*1常に char ひとつ = 2バイト分のメモリを使っていました。
しかし、これだと 1 バイトで表せる LATIN1(ASCII コード + ラテン文字)の文字列の場合、その半分が 0x00 になるという無駄がありました。


そこで、内部表現を変更し、文字列が LATIN1 のみで構成されるときは 1 文字を 1 バイトで保持するようにリファクタリングされました。
ちなみに、LATIN1 以外の文字(日本語など)があるときは、これまで通り 1 文字 2 バイトの UTF-16 で保持します。

これを踏まえて、ソースコードを追ってみました。

文字列の中身

String クラスでは、各オブジェクトごとに以下の3つのフィールド変数 (value, coder, hash) を持つようになっていました。
このうち、value の型が char 配列から byte 配列に変更されたところがポイント。
この配列を、LATIN1 なら 1byte ずつ、 UTF-16 なら 2byte ずつ使用しています。

// 文字列の中身を保持する配列
// Java 8 までは byte[] ではなく char[] だった
private final byte[] value;

// value のエンコード方式(Java 9 で追加)
// 0 なら LATIN1, 1 なら UTF-16
private final byte coder;

// この文字列のハッシュコードのキャッシュ(既存)
// 最初に hashCode メソッドが呼び出されたときに遅延初期化される。
private int hash; // Default to 0


それと、 定数 COMPACT_STRINGS (boolean) が追加されていました。
この値が true だと LATIN1/UTF16 の切り替えが有効になり、false だと無効(常に UTF16)になります。
デフォルトは有効 (true) です。

static final boolean COMPACT_STRINGS;

static {
    COMPACT_STRINGS = true;
}

この値は static final な定数ですが、VMオプションで変更が可能です*2

-XX:-CompactStrings


ちなみに、このオプションはドキュメント化されていないっぽいです。
ググったのですが、JDK のテストにしか記述が見つかりませんでした。
jdk9/jdk9/jdk: 4f6e52f9dc79 test/java/lang/String/CompactString/VMOptionsTest.java

String クラスの処理

さて、String クラスの処理がどうなっているかというと…。
一部の比較処理を除いて、大体の処理が StringLatin1 クラスと StringUTF16 クラスに移譲されていました。
たとえば、charAt メソッドがやっているのは Latin1 かどうか判定してそれぞれのクラスに処理を投げるだけになっていました。

public char charAt(int index) {
    if (isLatin1()) {
        return StringLatin1.charAt(value, index);
    } else {
        return StringUTF16.charAt(value, index);
    }
}

StringUTF16 クラスの実装は、だいたいこれまでの String クラスの実装と同じでした。
一方、StringLatin1 クラスの方はサローゲートペアを考える必要がなく、codePointAt などのメソッドの実装がシンプルになっていました。
そのため、Latin1 の文字列のときはこれまでよりも少しパフォーマンスが良くなっていそうです。
(具体的にどのぐらい良くなったかまでは分からず…。何処かに資料あるのかな…。)

char 配列の取り扱い

読んでいて気になったになったのは、従来からある char 配列を受け取るコンストラクタの処理。
以前はただ単に char 配列をコピーするだけだったのですが、今回 LATIN1/UTF16 を切り替えるために処理が増えていました。

  • char の長さが0なら、空配列をコピーする
  • そうでなければ、LATIN1 前提で byte 配列を作成し、LATIN1 かどうかを確認しながら 1 文字ずつコピーする
    • もし、すべての文字が LATIN1 ならば、その byte 配列を使う
    • LATIN1 の範囲外の文字が見つかったら、その byte 配列を破棄し UTF16 の byte 配列を新たに作成する
String(char[] value, int off, int len, Void sig) {
    if (len == 0) {
        this.value = "".value;
        this.coder = "".coder;
        return;
    }
    if (COMPACT_STRINGS) {
        // ↓ byte 配列に1文字ずつコピーしている。
        // コピー中に LATIN1 範囲外の文字があった場合は null が返ってくる。
        byte[] val = StringUTF16.compress(value, off, len);
        if (val != null) {
            this.value = val;
            this.coder = LATIN1;
            return;
        }
    }
    this.coder = UTF16;
    this.value = StringUTF16.toBytes(value, off, len);
}

ポイントは、投機的に LATIN1 で配列作って処理しているところ。
このような処理は、StringDecoderUTF8#decode にもあります。


このコンストラクタはそれなりに使われている(少なくとも、BufferedReader#readLine から使われているのを確認)ので、すぐに破棄しているとは言え余分な配列生成のオーバーヘッドが大丈夫なのか気になりました。
もし、ほとんどの文字列で LATIN1 範囲外を使うことが確定している場合は、前述の VM オプションで Compact Strings を無効化してパフォーマンスを比較してみたほうがいいかもしれません。

StringBuilder での取り扱い

この Compact Strings​ は String クラスだけではなく、StringBuilder クラスでも実装されています。
例えば、 new StringBuilder().append("abc") だと、内部的には LATIN1 で文字列を保持しています。


では、 new StringBuilder().appned("abc").append("あいう"); のように、LATIN1 文字列のあとに UTF-16 文字列を追加したらどうなるのか。
ソースコードを確認したとろ、この場合は LATIN1 を UTF-16 に変換して新たな配列に格納し、その後に文字を付け足すようになっていました。

最初から UTF-16 にしておく方法はないかなーと思ったんですが、Compact Strings​ を無効化するしか方法はありませんでした。

AbstractStringBuilder(int capacity) {
    if (COMPACT_STRINGS) {
        value = new byte[capacity];
        coder = LATIN1;
    } else {
        value = StringUTF16.newBytesFor(capacity);
        coder = UTF16;
    }
}


また、たとえ new StringBuilder("あいう"); であっても配列を作り直す処理が発生します。

これは、内部的には new StringBuilder() + append("あいう") という処理になっており、コンストラクタを呼んだ時点では LATIN1、その後の append で追加する文字列が UTF16 になるので上記と同じフローになってしまうためです。
この場合は中身が空なので、そのまま使ってくれてもいいのになーと思いました。
この点は、将来のバージョンアップでまたリファクタリングされるのでしょうか…。

まとめ

全体としては、英語圏であれば確実にパフォーマンスが良くなりそうだなーと思った一方、日本語圏だとパフォーマンスが大丈夫なのか気になりました。
日本語が含まれた文字列は思っているほど多くないと思いますが、もし徹底的にチューニングしたいということであれば Compact Strings​ の有効/無効を切り替えて、どちらがパフォーマンスが良いか調べてみるのもありかもしれません。


ちなみに、Java 9 では JEP 250: Store Interned Strings in CDS Archives や JEP 280: Indify String Concatenation と言った文字列関連の改善が他にもあります。
それらの実装についても、またの機会に紹介できたらなと思います!

*1:サロゲートペアなどは考慮しない

*2:2017/10/06 編集。当初は "-XX:-CompactStrings -DCompactStringEnabled=false" としていましたが、後者はテスト用のプロパティのため、VMの動作には影響がなかったため削除しました。(コメントで hana さんから教えていただきました)

Java 6, 7 の API ドキュメントを開いた時に、自動で Java 8 のドキュメントにリダイレクトしてくれるツール "JavaAPI-Redirector" を作りました!

たまに検索エンジンなどからリンクを辿って Java API ドキュメント (Javadoc) のページを開くと、古い Java 6 や Java 7 のページが表示されて (´・ω・`) となることがあります。
自分は Java 8 の API ドキュメントが見たいのに…。


そんなときに、自動で Java 8 のページへリダイレクトしてくれるツールを作りました!

JavaAPI-Redirector

Java 6, 7 の API ドキュメント (Javadoc) のページを開いた時に、自動で Java 8 の API ドキュメントヘリダイレクトしてくれるツール(ブラウザの拡張機能)です。

例:
https://docs.oracle.com/javase/jp/6/api/java/lang/String.html
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html


設定等は特になし。
ブラウザにインストールするだけですぐに使えます!

      • -

Firefox 版: JavaAPI-Redirector :: Add-ons for Firefox
Google Chrome 版: JavaAPI-Redirector - Chrome ウェブストア

      • -

単機能なツールですが、自分はこれのおかげで Java API を調べるのがとても楽になりました*1
日頃 Java で開発をされている方におすすめです。


ぜひ、ご利用ください!

*1:今まで手動でURLを書き換えていました。