これはおそらく最も包括的な Java 面接のステレオタイプです

Javaの特徴

Java はオブジェクト指向プログラミング言語ですオブジェクト指向とプロセス指向の違いについては、次の質問を参照してください。

Java はプラットフォームに依存せず、移植可能です

  • Java には、「一度書けばどこでも実行できる」というスローガンがありますWrite once, run anywhereこれもJavaの魅力です。そしてこの機能を実現するのがJava仮想マシンJVMです。コンパイルされた Java プログラムは、JVM を備えた任意のプラットフォームで実行できます。Windows プラットフォームでコードを作成し、それを Linux で実行することができます。コードを記述した後にそのコードを .class ファイルにコンパイルし、そのクラス ファイルを Java パッケージにパッケージ化する限り、jar パッケージはさまざまなプラットフォームで実行できます。

Java は堅牢です

  • Java は厳密に型指定された言語であり、潜在的な型の不一致をコンパイル時に拡張してチェックできます。Java では明示的なメソッド宣言が必要ですが、C スタイルの暗黙的な宣言はサポートされていません。これらの厳格な要件により、コンパイラーが呼び出しエラーを確実に捕捉し、プログラムの信頼性が高まります。最も包括的な Java インタビュー サイト
  • 例外処理は、プログラムをより堅牢にする Java のもう 1 つの機能です。例外は、エラーに似た、ある種の異常な状態の信号です。try/catch/finallyプログラマはステートメントを使用してエラー処理コードを見つけることができ、これによりエラー処理と回復のタスクが簡素化されます。

Java はどのようにしてクロスプラットフォームを実現するのでしょうか?

Java は、JVM (Java Virtual Machine) を通じてクロスプラットフォームです。

JVM はソフトウェアの一部として理解でき、プラットフォームが異なればバージョンも異なります。作成した Java コードは、コンパイル後に .class ファイル (バイトコード ファイル) を生成します。Java 仮想マシンは、特定のプラットフォームでバイトコード ファイルをマシン コードに変換する役割を果たします。これは、JVM によってマシン コードに変換された後でのみ実行できます。異なるプラットフォームでコンパイルおよび生成されたバイトコードは同じですが、JVM によって変換されたマシンコードは異なります。

対応する JVM がさまざまなプラットフォームにインストールされている限り、バイトコード ファイルを実行して、作成した Java プログラムを実行できます。

したがって、Java プログラムを実行するには、JVM のサポートが必要です。コンパイルされた結果はマシンコードではなく、実行前に JVM によって変換される必要があるためです。

この記事は Github ウェアハウスに含まれています。これには、コンピューター基盤、Java 基盤、マルチスレッド、JVM、データベース、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散、マイクロサービス、デザイン パターン、アーキテクチャ、学校採用およびソーシャル採用の共有が含まれます。などコア知識ポイント、スターへようこそ~

Githubアドレス

Github にアクセスできない場合は、コード クラウド アドレスにアクセスできます。

コードクラウドアドレス

Java と C++ の違い

  • Java は純粋なオブジェクト指向言語であり、すべてのオブジェクトは java.lang.Object を継承し、C++ は C と互換性があり、オブジェクト指向だけでなくプロセス指向もサポートします。
  • Java は仮想マシンを通じてクロスプラットフォーム機能を実現しますが、C++ は特定のプラットフォームに依存します。
  • Java にはポインターがなく、その参照は安全なポインターとして理解できますが、C++ には C と同じポインターがあります。
  • Java では自動ガベージ コレクションがサポートされていますが、C++ では手動コレクションが必要です。
  • Java は多重継承をサポートしておらず、複数のインターフェイスを実装することによってのみ同じ目的を達成できますが、C++ は多重継承をサポートしています。

JDK/JRE/JVMの関係

JVM

英語名(Java Virtual Machine)はおなじみのJava仮想マシンです。プラットフォーム間で実行できる Java の機能の中核は、JVM にあります。

すべての Java プログラムは、最初に .class クラス ファイルにコンパイルされ、仮想マシン上で実行できます。つまり、クラス ファイルはマシンのオペレーティング システムと直接対話せず、仮想マシンを介して間接的にオペレーティング システムと対話し、仮想マシンはプログラムをローカル システムに解釈して実行します。

Linux バージョンや Windows バージョンなど、システムごとに異なる jvm 実装がありますが、同じコード部分のコンパイルされたバイトコードは同じです。これが、Java がクロスプラットフォームであり、一度作成すれば複数の場所で実行できる理由です。

JRE

英語名(Java RuntimeEnvironment)はJava実行環境です。私たちが作成する Java プログラムは JRE 上で実行する必要があります。これは主に、JVM と Java コア クラス ライブラリの 2 つの部分で構成されます。

JRE は Java ランタイム環境であり、開発環境ではないため、コンパイラやデバッガなどの開発ツールは含まれません。

Java プログラムを開発するのではなく、Java プログラムを実行するだけの場合は、JRE をインストールするだけで済みます。

最後に、Github ウェアハウスを共有したいと思います。そこには、C 言語、C++、Java、Python、フロントエンド、データベース、オペレーティング システム、コンピュータ ネットワーク、データ構造を含む、Dabin によってコンパイルされた300 以上の古典的なコンピュータ ブック PDF が保存されています。アルゴリズム、機械学習、プログラミング生活など、スターを付けると、次回書籍を直接検索するときに、倉庫が継続的に更新されます~

Githubアドレス

JDK

英語名(Java Development Kit)はJava Development Kitです。

Java を学習した学生は、JDK をインストールしている必要があります。JDKをインストールした後のディレクトリ構造は次のようになります。

JDK ディレクトリに JRE があることがわかります。つまり、JRE は JDK に統合されており、JRE を別途インストールする必要はありません。

さらに、JDK には jinfo、jps、jstack などの便利なツールがいくつかあります。

最後に、JDK/JRE/JVM とその 3 つの関係をまとめます。

JRE = JVM + Javaコアクラスライブラリ

JDK = JRE + Javaツール + コンパイラ + デバッガ

Java プログラムはコンパイルされて実行されますか、それとも解釈されますか?

まずコンパイル言語とインタプリタ言語とは何かを見てみましょう。

コンパイル言語

プログラムを実行する前に、ソースプログラムはコンパイラによって実行可能な機械語コードのバイナリにコンパイルされ、後でプログラムを実行するときに再度コンパイルする必要はありません。

利点: コンパイラには通常、コードを最適化するためのプリコンパイル プロセスがあります。コンパイルが一度だけ行われるため、実行時にコンパイルが不要なため、プログラムの実行効率が高く、言語環境に依存せずに実行できます。

欠点: コンパイル後に変更する必要がある場合は、モジュール全体を再コンパイルする必要があります。コンパイル時に、対応するオペレーティング システム環境に応じてマシン コードが生成されます。異なるオペレーティング システム間での移植には問題が発生します。実行中のオペレーティング システム環境に応じて、異なる実行可能ファイルをコンパイルする必要があります。

概要: 実行速度が速く、効率が高いため、コンパイラに依存しており、クロスプラットフォームのパフォーマンスが低い。

代表的な言語:C、C++、Pascal、Object-C、Swift。

通訳された言語

定義: インタープリター言語のソース コードは直接マシン コードに変換されず、最初に中間コードに変換され、その後インタープリターが中間コードを解釈して実行します。ソースプログラムは実行時に機械語に翻訳され、1文が翻訳され、最後まで1文が実行されます。

アドバンテージ:

  1. プラットフォーム互換性が高く、インタープリター (仮想マシンなど) がインストールされていれば、どの環境でも実行できます。
  2. 柔軟性があり、コードを変更するときに直接変更でき、メンテナンスのためのダウンタイムなしで迅速に展開できます。

欠点: 実行するたびに説明する必要があり、パフォーマンスはコンパイル言語ほど良くありません。

概要: インタープリター型言語は実行速度が遅く、効率が低いため、インタープリターに依存しており、クロスプラットフォームのパフォーマンスが優れています。

代表的な言語: JavaScript、Python、Erlang、PHP、Perl、Ruby。

Javaのような言語の場合、ソースコードはまず javac によってバイトコードにコンパイルされ、次にjvm によってマシンコードに変換されて実行されます。つまり、インタープリタとコンパイルが併用されるため、ハイブリッドまたはセミコンパイル型と呼ぶことができます。 。

オブジェクト指向とプロセス指向の違いは何ですか?

オブジェクト指向とプロセス指向は、ソフトウェア開発思想の一種です。

  • プロセス指向とは、問題を解決するために必要な手順を分析し、関数を使用してこれらの手順を実装し、使用時にそれらの手順を順番に呼び出すことです。

  • オブジェクト指向とは、問題を構成する事柄を個々のオブジェクトに分解し、それらを個別に設計し、完全な機能を備えたシステムに組み立てることです。プロセス指向は関数のみで実装されますが、オブジェクト指向は各機能モジュールを実装するクラスで実装されます。

バックギャモンを例に取ると、プロセス指向の設計アイデアが問題を分析するための最初のステップとなります。

1. ゲームを開始する、2. 黒点が先になる、3. 絵を描く、4. 勝ち負けを決める、5. バイの番だ、6. 絵を描く、7. 勝ち負けを決める、 8. 手順 2 に戻り、9. 最終結果を出力します。
上記の各ステップを個別の関数で実装すると、問題は解決されます。

この問題を別の考え方から解決するのがオブジェクト指向設計です。バックギャモン全体は次のように分類できます。

  1. 黒と白
  2. 絵を描くチェス盤システム
  3. ルールシステムは、ファウル、勝敗などを決定する責任があります。

黒側と白側は、ユーザーの入力を受け入れ、チェス駒のレイアウトが変更されたことをチェス盤システムに通知する責任を負います。チェス盤システムは、チェス駒の変更に関する情報を受信した後、画面に変更を表示する責任があります。 . 同時に、チェスのゲームを判断するためにルールシステムを使用します。

オブジェクト指向の特徴は何ですか?

オブジェクト指向の 4 つの特徴: カプセル化、継承、ポリモーフィズム、抽象化

1. カプセル化とは、クラスの情報をクラス内に隠し、外部プログラムから直接アクセスすることを許さず、このクラスのメソッドを通じて操作や隠し情報へのアクセスを実現します。適切なパッケージングにより結合が軽減されます。

2. 継承とは、既存のクラスから新しいクラスを派生させることであり、新しいクラスは親クラスの属性や動作を継承し、新しい機能を拡張できるため、プログラムの再利用性や保守性が大幅に向上します。Java では、これは単一継承です。つまり、サブクラスには 1 つの親クラスしかありません。

3. ポリモーフィズムとは、同じ動作を複数の異なる表現で表現できる機能です。プログラム コードを変更せずに、プログラムの実行時にバインドされたコードを変更します。ポリモーフィズムの 3 つの要素: 継承、書き換え、サブクラス オブジェクトを指す親クラス参照。

  • 静的ポリモーフィズム: オーバーロードによって実装され、同じメソッドに異なるパラメーター リストがあり、異なるパラメーターに従って異なる処理を実行できます。
  • 動的ポリモーフィズム: 子クラスで親クラスのメソッドをオーバーライドします。動作中に、参照されるオブジェクトの実際の型が判断され、その実際の型に応じて対応するメソッドが呼び出されます。

4. 抽象化。客観的なものをコードで抽象化します。

オブジェクト指向プログラミングの 6 つの原則

  • オブジェクトの単一責任: 私たちが設計および作成するオブジェクトには、商品クラスなどの明確な責任が必要であり、そのオブジェクト内の関連する属性とメソッドは商品に関連している必要があり、注文などの無関係なコンテンツは表示できません。ここでのクラスは、単なるクラスではなく、モジュール、クラス ライブラリ、アセンブリにすることができます。
  • Li スタイルの置換原則: サブクラスは親クラスを完全に置換できますが、その逆はできません。通常、インターフェイスを実装するときに使用されます。サブクラスは基本 (親) クラスを完全に置き換えることができるため、親クラスには多くのサブクラスがあり、後続のプログラム拡張での拡張が容易になり、プログラムをまったく変更せずに拡張できます。たとえば、IA の実装は A です。プロジェクトの要件が変更され、新しい実装が必要になったので、コンテナーの注入ポイントでインターフェイスを直接置き換えるだけです。
  • 迪米特法则,也叫最小原则,或者说最小耦合。通常在设计程序或开发程序的时候,尽量要高内聚,低耦合。当两个类进行交互的时候,会产生依赖。而迪米特法则就是建议这种依赖越少越好。就像构造函数注入父类对象时一样,当需要依赖某个对象时,并不在意其内部是怎么实现的,而是在容器中注入相应的实现,既符合里式替换原则,又起到了解耦的作用。
  • 开闭原则:开放扩展,封闭修改。当项目需求发生变更时,要尽可能的不去对原有的代码进行修改,而在原有的基础上进行扩展。
  • 依赖倒置原则:高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。
  • 接口隔离原则:一个对象和另外一个对象交互的过程中,依赖的内容最小。也就是说在接口设计的时候,在遵循对象单一职责的情况下,尽量减少接口的内容。

简洁版

  • 单一职责:对象设计要求独立,不能设计万能对象。
  • 开闭原则:对象修改最小化。
  • 里式替换:程序扩展中抽象被具体可以替换(接口、父类、可以被实现类对象、子类替换对象)
  • 迪米特:高内聚,低耦合。尽量不要依赖细节。
  • 依赖倒置:面向抽象编程。也就是参数传递,或者返回值,可以使用父类类型或者接口类型。从广义上讲:基于接口编程,提前设计好接口框架。
  • 接口隔离:接口设计大小要适中。过大导致污染,过小,导致调用麻烦。

数组到底是不是对象?

先说说对象的概念。对象是根据某个类创建出来的一个实例,表示某类事物中一个具体的个体。

对象具有各种属性,并且具有一些特定的行为。站在计算机的角度,对象就是内存中的一个内存块,在这个内存块封装了一些数据,也就是类中定义的各个属性。

所以,对象是用来封装数据的。

java中的数组具有java中其他对象的一些基本特点。比如封装了一些数据,可以访问属性,也可以调用方法。

因此,可以说,数组是对象。

也可以通过代码验证数组是对象的事实。比如以下的代码,输出结果为java.lang.Object。

Class clz = int[].class;
System.out.println(clz.getSuperclass().getName());

由此,可以看出,数组类的父类就是Object类,那么可以推断出数组就是对象。

Java的基本数据类型有哪些?

  • byte,8bit
  • char,16bit
  • short,16bit
  • int,32bit
  • float,32bit
  • long,64bit
  • double,64bit
  • boolean,只有两个值:true、false,可以使⽤用 1 bit 来存储
简单类型 boolean byte char short Int long float double
二进制位数 1 8 16 16 32 64 32 64
包装类 Boolean Byte Character Short Integer Long Float Double

在Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 还要看虚拟机实现是否按照规范来,因此boolean占用1个字节或者4个字节都是有可能的。

为什么不能用浮点型表示金额?

由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。

建议使用BigDecimal或者Long来表示金额。

什么是值传递和引用传递?

  • 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
  • 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。

java中不存在引用传递,只有值传递。即不存在变量a指向变量b,变量b指向对象的这种情况。

了解Java的包装类型吗?为什么需要包装类?

Java はオブジェクト指向言語であり、プリミティブ データ型の代わりにオブジェクトを使用する必要がある場所が数多くあります。たとえば、コレクション クラスには、int、double、その他の型を含めることはできません。コレクションのコンテナーでは要素の型が Object である必要があるためです。

基本タイプにもオブジェクトの特性を持たせるためにパッケージタイプが登場します。これは、基本型をラップしてオブジェクトの性質を持たせ、それにプロパティとメソッドを追加して、基本型の操作を強化することに相当します。

自動ボックス化とボックス化解除

Java の基本データ型とそれに対応するラッパー クラスを次の表に示します。

プリミティブ型 梱包の種類
ブール値 ブール値
バイト バイト
文字 キャラクター
浮く 浮く
整数 整数
長さ 長さ
短い 短い
ダブル ダブル

ボクシング: 基になる型をラップされた型に変換します。

アンボックス化: ラップされた型を基になる型に変換します。

基本型とそのパッケージ化クラスに次の状況がある場合、コンパイラーは自動的にボックス化またはボックス化解除します。

  • 割り当て操作 (ボックス化またはアンボックス化)
  • 加算、減算、乗算、除算の混合演算を実行する (アンボックス化)
  • >、<、== 比較演算を実行する (アンボックス化)
  • 比較のために等しいものを呼び出します (ボクシング)
  • ArrayListやHashMapなどのコレクションクラスに基本型データを追加する場合(ボクシング)

サンプルコード:

Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()

面接でよくある質問は次のとおりです。

Integer a = 100;
Integer b = 100;
System.out.println(a == b);

Integer c = 200;
Integer d = 200;
System.out.println(c == d);

出力:

true
false

3 番目の出力が false になるのはなぜですか? Integer クラスのソース コードを見てください。

public static Integer valueOf(int i) {
    
    
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Integer c = 200;電話をかけますInteger.valueOf(200)Integer の valueOf() のソース コードからわかるように、ここでの実装は単純な新しい Integer ではなく、IntegerCache をキャッシュとして使用します。

private static class IntegerCache {
    
    
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
    
    
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
    
    
            try {
    
    
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
    
    
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;
    }
    ...
}

这是IntegerCache静态代码块中的一段,默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好在这个范围内,所以从cache中取对应的Integer并返回,所以a和b返回的是同一个对象,所以==比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==比较的结果是不相等的。

String 为什么不可变?

先看看什么是不可变的对象。

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

接着来看Java8 String类的源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}

从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。

value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。

String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。

所以,String是不可变的。

那为什么String要设计成不可变的?

主要有以下几点原因:

  1. 线程安全。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
  2. 支持hash映射和缓存。因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。
  3. セキュリティ上の理由からネットワークアドレスのURL、ファイルパスのパス、パスワードは通常String型で格納されますが、String型を固定しないと様々なセキュリティリスクが発生します。たとえば、パスワードが String 型で保存された場合、ガベージ コレクターがパスワードをクリアするまでメモリ内に残ります。String クラスが固定されていない場合、このパスワードが変更される可能性があり、セキュリティ リスクが発生します。
  4. 文字列定数プールの最適化String オブジェクトが作成されると、文字列定数プールにキャッシュされ、次回同じオブジェクトを作成する必要があるときに、キャッシュされた参照を直接返すことができます。

String は不変であるため、その中には多くの substring、replace、replaceAll メソッドがあります。これらのメソッドは String オブジェクトを変更するようですか? どうやって説明すればいいでしょうか?

実際にはそうではなく、replace や他のメソッドを呼び出すたびに、実際にはヒープ メモリに新しいオブジェクトが作成されます。次に、その値配列参照は別のオブジェクトを指します。

JDK9 が String の基礎となる実装を char[] から byte[] に変更するのはなぜですか?

主にString が占有するメモリを節約するためです

ほとんどの Java プログラムのヒープ メモリでは、String が最大のスペースを占め、ほとんどの String には Latin-1 文字のみが含まれており、これらの Latin-1 文字には 1 バイトしか必要としません。

JDK9 より前では、JVM は String に char 配列ストレージを使用し、各 char は 2 バイトを占有するため、文字列に 1 バイトしか必要ない場合でも、2 バイトに従って割り当てる必要があり、メモリ領域の半分が無駄になります。

JDK9以降では、文字列ごとにLatin-1文字のみかどうかを判定し、ラテン1文字のみの場合は1バイトの指定に従ってメモリを確保し、そうでない場合は1バイトの指定に従ってメモリを確保するようになりました。 2 バイト このようにすると、メモリ使用量が増加すると同時に、GC の数が減って効率が向上します。

ただし、Latin-1 コード セットでサポートされる文字は限られています。たとえば、中国語の文字はサポートされていません。そのため、中国語の文字列の場合は、UTF16 エンコード (2 バイト) が使用されるため、byte[] と char の間に違いはありません[]。

String, StringBuffer 和 StringBuilder区别

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

什么是StringJoiner?

StringJoiner是 Java 8 新增的一个 API,它基于 StringBuilder 实现,用于实现对字符串之间通过分隔符拼接的场景。

StringJoiner 有两个构造方法,第一个构造要求依次传入分隔符、前缀和后缀。第二个构造则只要求传入分隔符即可(前缀和后缀默认为空字符串)。

StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner(CharSequence delimiter)

有些字符串拼接场景,使用 StringBuffer 或 StringBuilder 则显得比较繁琐。

比如下面的例子:

List<Integer> values = Arrays.asList(1, 3, 5);
StringBuilder sb = new StringBuilder("(");

for (int i = 0; i < values.size(); i++) {
    
    
	sb.append(values.get(i));
	if (i != values.size() -1) {
    
    
		sb.append(",");
	}
}

sb.append(")");

而通过StringJoiner来实现拼接List的各个元素,代码看起来更加简洁。

List<Integer> values = Arrays.asList(1, 3, 5);
StringJoiner sj = new StringJoiner(",", "(", ")");

for (Integer value : values) {
    
    
	sj.add(value.toString());
}

另外,像平时经常使用的Collectors.joining(“,”),底层就是通过StringJoiner实现的。

源码如下:

public static Collector<CharSequence, ?, String> joining(
    CharSequence delimiter,CharSequence prefix,CharSequence suffix) {
    
    
    return new CollectorImpl<>(
            () -> new StringJoiner(delimiter, prefix, suffix),
            StringJoiner::add, StringJoiner::merge,
            StringJoiner::toString, CH_NOID);
}

String 类的常用方法有哪些?

  • indexOf():返回指定字符的索引。
  • charAt():返回指定索引处的字符。
  • replace():字符串替换。
  • trim():去除字符串两端空白。
  • split():分割字符串,返回一个分割后的字符串数组。
  • getBytes():返回字符串的 byte 类型数组。
  • length():返回字符串长度。
  • toLowerCase():将字符串转成小写字母。
  • toUpperCase():将字符串转成大写字符。
  • substring():截取字符串。
  • equals():字符串比较。

new String(“dabin”)会创建几个对象?

使用这种方式会创建两个字符串对象(前提是字符串常量池中没有 “dabin” 这个字符串对象)。

  • “dabin” 属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个 “dabin” 字符串字面量;
  • 使用 new 的方式会在堆中创建一个字符串对象。

什么是字符串常量池?

字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。

String最大长度是多少?

String类提供了一个length方法,返回值为int类型,而int的取值上限为2^31 -1。

所以理论上String的最大长度为2^31 -1。

达到这个长度的话需要多大的内存吗

String内部是使用一个char数组来维护字符序列的,一个char占用两个字节。如果说String最大长度是2^31 -1的话,那么最大的字符串占用内存空间约等于4GB。

也就是说,我们需要有大于4GB的JVM运行内存才行。

那String一般都存储在JVM的哪块区域呢

字符串在JVM中的存储分两种情况,一种是String对象,存储在JVM的堆栈中。一种是字符串常量,存储在常量池里面。

什么情况下字符串会存储在常量池呢

当通过字面量进行字符串声明时,比如String s = “程序新大彬”;,这个字符串在编译之后会以常量的形式进入到常量池。

那常量池中的字符串最大长度是2^31-1吗

不是的,常量池对String的长度是有另外限制的。。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。

CONSTANT_Utf8_info {
    
    
    u1 tag;
    u2 length;
    u1 bytes[length];
}

ここでの length は文字列の長さを表し、長さのタイプは u2 で、u2 は符号なし 16 ビット整数です。つまり、最大長は 2^16-1 または 65535 になります。

ただし、javac コンパイラには制限があり、65535 未満の長さが必要です。したがって、定数プール内の文字列定数の最大長は 65535 - 1 = 65534 です。

最後に要約すると、次のようになります。

文字列の長さ制限は状態ごとに異なります。

  • 文字列定数の長さは 65534 を超えることはできません
  • ヒープ内の文字列の長さは 2^31-1 を超えません

Object の一般的なメソッドは何ですか?

Javaの面接でよく出てくる質問、Objectの一般的なメソッド。整理させてください。

オブジェクトの共通メソッドはtoString()equals()hashCode()clone()などです。

デフォルトでオブジェクトアドレスを出力します。

public class Person {
    
    
    private int age;
    private String name;

    public Person(int age, String name) {
    
    
        this.age = age;
        this.name = name;
    }

    public static void main(String[] args) {
    
    
        System.out.println(new Person(18, "程序员大彬").toString());
    }
    //output
    //me.tyson.java.core.Person@4554617c
}

toString メソッドは、書き換えロジックに従ってオブジェクト値を出力するように書き換えることができます。

public class Person {
    
    
    private int age;
    private String name;

    public Person(int age, String name) {
    
    
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return name + ":" + age;
    }

    public static void main(String[] args) {
    
    
        System.out.println(new Person(18, "程序员大彬").toString());
    }
    //output
    //程序员大彬:18
}

等しい

デフォルトでは、2 つの参照変数が同じオブジェクト (メモリ アドレス) を指しているかどうかを比較します。

public class Person {
    
    
    private int age;
    private String name;

    public Person(int age, String name) {
    
    
       this.age = age;
       this.name = name;
    }

    public static void main(String[] args) {
    
    
        String name = "程序员大彬";
        Person p1 = new Person(18, name);
        Person p2 = new Person(18, name);

        System.out.println(p1.equals(p2));
    }
    //output
    //false
}

年齢と名前が等しいかどうかを判断するために、equals メソッドを書き直すことができます。

public class Person {
    
    
    private int age;
    private String name;

    public Person(int age, String name) {
    
    
        this.age = age;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
    
    
        if (o instanceof Person) {
    
    
            Person p = (Person) o;
            return age == p.age && name.equals(p.name);
        }
        return false;
    }

    public static void main(String[] args) {
    
    
        String name = "程序员大彬";
        Person p1 = new Person(18, name);
        Person p2 = new Person(18, name);

        System.out.println(p1.equals(p2));
    }
    //output
    //true
}

ハッシュコード

オブジェクトに関する情報をハッシュ値にマッピングします。デフォルト実装の hashCode 値はメモリアドレスに基づいて変換されます。

public class Cat {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(new Cat().hashCode());
    }
    //out
    //1349277854
}

クローン

Java の代入はオブジェクト参照をコピーすることですが、オブジェクトのコピーを取得したい場合、代入操作を使用することはできません。Object オブジェクトには clone() メソッドがあり、

画像内の各属性のコピーですが、その表示範囲は保護されています。

protected native Object clone() throws CloneNotSupportedException;

したがって、エンティティ クラスのクローン作成を使用する前提は次のとおりです。

  • Cloneable インターフェイスを実装します。これはマーカー インターフェイスであり、独自のメソッドはありません。これは規則である必要があります。clone メソッドを呼び出すと、Cloneable インターフェイスが実装されているかどうかが判断され、Cloneable が実装されていない場合は、例外 CloneNotSupportedException がスローされます。
  • clone() メソッドをオーバーライドし、可視性をパブリックに上げます。
public class Cat implements Cloneable {
    
    
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        Cat c = new Cat();
        c.name = "程序员大彬";
        Cat cloneCat = (Cat) c.clone();
        c.name = "大彬";
        System.out.println(cloneCat.name);
    }
    //output
    //程序员大彬
}

getクラス

Java リフレクション メカニズムでよく使用される、このオブジェクトのランタイム クラスを返します。

public class Person {
    
    
    private String name;

    public Person(String name) {
    
    
        this.name = name;
    }

    public static void main(String[] args) {
    
    
        Person p = new Person("程序员大彬");
        Class clz = p.getClass();
        System.out.println(clz);
        //获取类名
        System.out.println(clz.getName());
    }
    /**
     * class com.tyson.basic.Person
     * com.tyson.basic.Person
     */
}

待って

現在のスレッドがオブジェクトの wait() メソッドを呼び出した後、現在のスレッドはオブジェクトのロックを解放し、待機状態に入ります。他のスレッドがこのオブジェクトのnotify()/notifyAll()を呼び出してウェイクアップするのを待つか、タイムアウト待機(ロングタイムアウト)が自動的にウェイクアップするのを待ちます。スレッドは、obj.wait() を呼び出す前に、obj オブジェクトのロックを取得する必要があります。

通知する

obj.notify() は、このオブジェクトを待機している単一のスレッドを起動します。選択は任意です。NoticeAll() は、このオブジェクトを待機しているすべてのスレッドを起動します。

ディープ コピーとシャロー コピーについて話しますか?

浅いコピー: コピー オブジェクトと元のオブジェクトの参照型は同じオブジェクトを参照します。

次の例では、Cat オブジェクトに person オブジェクトがあり、clone を呼び出した後、複製されたオブジェクトと元のオブジェクトの person は同じオブジェクト (浅いコピー) を参照します。

public class Cat implements Cloneable {
    
    
    private String name;
    private Person owner;

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        Cat c = new Cat();
        Person p = new Person(18, "程序员大彬");
        c.owner = p;

        Cat cloneCat = (Cat) c.clone();
        p.setName("大彬");
        System.out.println(cloneCat.owner.getName());
    }
    //output
    //大彬
}

ディープ コピー: コピー オブジェクトと元のオブジェクトの参照型は異なるオブジェクトを参照します。

以下の例では、clone関数内でsuper.cloneを呼び出すだけでなく、Personオブジェクトのcloneメソッドも呼び出す(personもCloneableインタフェースを実装し、cloneメソッドをオーバーライドする)ことでディープコピーを実現しています。ご覧のとおり、コピーされたオブジェクトの値は元のオブジェクトの影響を受けません。

public class Cat implements Cloneable {
    
    
    private String name;
    private Person owner;

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        Cat c = null;
        c = (Cat) super.clone();
        c.owner = (Person) owner.clone();//拷贝Person对象
        return c;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        Cat c = new Cat();
        Person p = new Person(18, "程序员大彬");
        c.owner = p;

        Cat cloneCat = (Cat) c.clone();
        p.setName("大彬");
        System.out.println(cloneCat.owner.getName());
    }
    //output
    //程序员大彬
}

2 つのオブジェクトの hashCode() が同じ場合、equals() は必ず true になりますか?

イコールとハッシュコードの関係:

  1. 2 つのオブジェクトが、equals を呼び出して比較し、true を返す場合、それらの hashCode 値は同じである必要があります。
  2. 2 つのオブジェクトが同じ hashCode を持つ場合、それらは必ずしも同じであるとは限りません。

hashcode メソッドは、主にオブジェクト比較の効率を向上させるために使用されます。まず hashcode() を比較します。同じでない場合は、等号比較をする必要がありません。これにより、等号比較の回数が大幅に削減されます。オブジェクトに時間がかかると効率が向上します。

イコールを書き換えるときに hashCode を書き換える必要があるのはなぜですか?

equals()書き換えが書き換えである理由は、メソッドが true を返すときにハッシュコード値も一貫していることhashcode()を確認するためです。書き換えを書き換えない場合、2 つのオブジェクトは等しくなりますが、等しくありません。このように、オブジェクトの 1 つをキーとして使用して hashMap、hashTable、または hashSet に保存し、別のオブジェクトをキーとして使用してそれらを検索すると、そのオブジェクトは見つかりません。equals()equals()hashcode()hashcode()

Java がオブジェクトを作成する方法は何通りありますか?

Java でオブジェクトを作成するには、いくつかの方法があります。

  • オブジェクトは new ステートメントを使用して作成されます。
  • リフレクションを使用すると、オブジェクトは Class.newInstance() を使用して作成されます。
  • オブジェクトの clone() メソッドを呼び出します。
  • 逆シリアル化メソッドを使用して、java.io.ObjectInputStream オブジェクトの readObject() メソッドを呼び出します。

クラスのインスタンス化の順序についての話

Java でのクラスのインスタンス化の順序は次のとおりです。

  1. 静的属性、静的コード ブロック。
  2. 普通の属性、普通のコードブロック。
  3. 施工方法。
public class LifeCycle {
    
    
    // 静态属性
    private static String staticField = getStaticField();

    // 静态代码块
    static {
    
    
        System.out.println(staticField);
        System.out.println("静态代码块初始化");
    }

    // 普通属性
    private String field = getField();

    // 普通代码块
    {
    
    
        System.out.println(field);
        System.out.println("普通代码块初始化");
    }

    // 构造方法
    public LifeCycle() {
    
    
        System.out.println("构造方法初始化");
    }

    // 静态方法
    public static String getStaticField() {
    
    
        String statiFiled = "静态属性初始化";
        return statiFiled;
    }

    // 普通方法
    public String getField() {
    
    
        String filed = "普通属性初始化";
        return filed;
    }

    public static void main(String[] argc) {
    
    
        new LifeCycle();
    }

    /**
     *      静态属性初始化
     *      静态代码块初始化
     *      普通属性初始化
     *      普通代码块初始化
     *      构造方法初始化
     */
}

等しいと == はどう違いますか?

  • プリミティブ データ型の場合、== はそれらの値を比較します。基本的なデータ型には同等のメソッドはありません。

  • 複合データ型の場合、== はそれらのストレージ アドレス (それらが同じオブジェクトであるかどうか) を比較します。equals()デフォルトではアドレス値を比較しますが、書き換える場合は書き換えロジックに従って比較します。

よくあるキーワードは何ですか?

静的

static を使用して、クラスのメンバー メソッドとメンバー変数を変更できます。

静的変数は、静的変数 とも呼ばれます。静的変数と非静的変数の違いは、静的変数はすべてのオブジェクトによって共有され、メモリ内にコピーが 1 つだけ存在することです。静的変数は、クラスが最初にロードされたときにのみ初期化されます。非静的変数はオブジェクトによって所有され、オブジェクトの作成時に初期化されます。複数のコピーがあり、各オブジェクトが所有するコピーは相互に影響しません。

次の例では、age が非静的変数の場合、p1 の出力結果は次のようになります。age がstatic で変更された場合、メモリ内に静的変数のName:zhangsan, Age:10コピーが 1 つだけあるため、p1 の出力結果は次のようになります。Name:zhangsan, Age:12

public class Person {
    
    
    String name;
    int age;
    
    public String toString() {
    
    
        return "Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
    
    
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 10;
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 12;
        System.out.println(p1);
        System.out.println(p2);
    }
    /**Output
     * Name:zhangsan, Age:10
     * Name:lisi, Age:12
     *///~
}

静的メソッドは一般的に静的メソッドと呼ばれます。静的メソッドはオブジェクトに依存せずにアクセスでき、クラス名を通じて呼び出すことができます。

public class Utils {
    
    
    public static void print(String s) {
    
    
        System.out.println("hello world: " + s);
    }

    public static void main(String[] args) {
    
    
        Utils.print("程序员大彬");
    }
}

静的コード ブロックは、クラスのロード時に 1 回だけ実行されます。次の例では、クラスのロード時に startDate と endDate が割り当てられます。

class Person  {
    
    
    private Date birthDate;
    private static Date startDate, endDate;
    static{
    
    
        startDate = Date.valueOf("2008");
        endDate = Date.valueOf("2021");
    }

    public Person(Date birthDate) {
    
    
        this.birthDate = birthDate;
    }
}

静的内部クラス

静的メソッドでは、非静的内部クラスの使用は外部クラスのインスタンスに依存します。つまり、非静的内部クラスの作成に使用する前に、外部クラスのインスタンスを作成する必要があります。静的内部クラスはそうではありません。

public class OuterClass {
    
    
    class InnerClass {
    
    
    }
    static class StaticInnerClass {
    
    
    }
    public static void main(String[] args) {
    
    
        // 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例
        // 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例
        // InnerClass innerClass = new InnerClass();
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();

        outerClass.test();
    }
    
    public void nonStaticMethod() {
    
    
        InnerClass innerClass = new InnerClass();
        System.out.println("nonStaticMethod...");
    }
}

最後の

  1. 基本データ型が Final で変更された場合、それは変更できず、定数になります。オブジェクト参照がFinal で変更された場合、参照はそのオブジェクトのみを指すことができ、他のオブジェクトを指すことはできませんが、オブジェクト自体は変更できます。

  2. 最終的に変更されたメソッドはサブクラスによってオーバーライドできません

  3. 最終的に変更されたクラスは継承できません。

これ

this.属性名称クラス内のメンバー変数にアクセスすることを指します。メンバー変数とローカル変数を区別するために使用できます。次のコードに示すように、クラス Person の現在のインスタンスの変数にアクセスしますthis.name

/**
 * @description:
 * @author: 程序员大彬
 * @time: 2021-08-17 00:29
 */
public class Person {
    
    
    String name;
    int age;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }
}

this.方法名称このクラスのメソッドにアクセスするために使用されます。次のコードでは、this.born()クラス Person の現在のインスタンスのメソッドが呼び出されます。

/**
 * @description:
 * @author: 程序员大彬
 * @time: 2021-08-17 00:29
 */
public class Person {
    
    
    String name;
    int age;

    public Person(String name, int age) {
    
    
        this.born();
        this.name = name;
        this.age = age;
    }

    void born() {
    
    
    }
}

素晴らしい

super キーワードは、サブクラス内の親クラスの変数とメソッドにアクセスするために使用されます。

class A {
    
    
    protected String name = "大彬";

    public void getName() {
    
    
        System.out.println("父类:" + name);
    }
}

public class B extends A {
    
    
    @Override
    public void getName() {
    
    
        System.out.println(super.name);
        super.getName();
    }

    public static void main(String[] args) {
    
    
        B b = new B();
        b.getName();
    }
    /**
     * 大彬
     * 父类:大彬
     */
}

サブクラス B では、親クラスのメソッドを書き換えていますが、getName()書き換えたメソッドgetName()内で親クラスの同じメソッドを呼び出したい場合は、 super キーワードで明示的に指定する必要があります。

ファイナル、ファイナル、ファイナライズの違い

  • Final は属性、メソッド、クラスを変更するために使用され、それぞれ属性の再割り当て、メソッドのオーバーライド、クラスの継承ができないことを示します。
  • Final は例外処理ステートメント構造の一部であり、通常はtry-catch-finallyに表示され、finallyコード ブロックは常に実行されることを意味します。
  • finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法,回收垃圾,JVM并不保证此方法总被调用。

final关键字的作用?

  • final 修饰的类不能被继承。
  • final 修饰的方法不能被重写。
  • final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

方法重载和重写的区别?

同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。

重载是面向对象的一个基本特性。

public class OverrideTest {
    
    
    void setPerson() {
    
     }
    
    void setPerson(String name) {
    
    
        //set name
    }
    
    void setPerson(String name, int age) {
    
    
        //set name and age
    }
}

方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。方法重写时, 方法名与形参列表必须一致。

如下代码,Person为父类,Student为子类,在Student中重写了dailyTask方法。

public class Person {
    
    
    private String name;
    
    public void dailyTask() {
    
    
        System.out.println("work eat sleep");
    }
}


public class Student extends Person {
    
    
    @Override
    public void dailyTask() {
    
    
        System.out.println("study eat sleep");
    }
}

接口与抽象类区别?

1、语法层面上的区别

  • 抽象类可以有方法实现,而接口的方法中只能是抽象方法(Java 8 之后接口方法可以有默认实现);
  • 抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型;
  • 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法(Java 8之后接口可以有静态方法);
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2、设计层面上的区别

  • 抽象化にはさまざまなレベルがあります。抽象クラスは属性や動作を含むクラス全体を抽象化しますが、インターフェイスはクラスの動作のみを抽象化します。抽象クラスの継承は「かどうか」の関係ですが、インターフェイスの実装は「存在するかどうか」の関係です。クラスが抽象クラスを継承する場合、サブクラスは抽象クラスの型である必要があり、インターフェイスの実装には、鳥が飛べるかどうかなど、そのクラスにはない関係があります。
  • 抽象クラスを継承するクラスは同様の特性を持ちますが、インターフェイスを実装するクラスは異なる場合があります。

ドアと警報器の例:

class AlarmDoor extends Door implements Alarm {
    
    
    //code
}

class BMWCar extends Car implements Alarm {
    
    
    //code
}

一般的な例外は何ですか?

一般的なランタイム例外:

  1. ClassCastException// 型変換例外
  2. IndexOutOfBoundsException//配列の範囲外の例外
  3. NullPointerException//ヌルポインタ
  4. ArrayStoreException//配列ストレージ例外
  5. NumberFormatException//数値フォーマットの例外
  6. ArithmeticException//数学演算例外

チェック済み 例外:

  1. NoSuchFieldException//リフレクション例外、対応するフィールドなし
  2. ClassNotFoundException// クラスが見つからない例外
  3. IllegalAccessException//セキュリティ権限が異常です。リフレクション中にプライベートメソッドが呼び出された可能性があります

エラーと例外の違いは何ですか?

エラーStackOverflowError: スタック オーバーフロー、メモリ オーバーフローOOMなど、JVM が解決できない重大な問題。プログラムが処理できないエラー。

例外: プログラミングエラーまたは偶発的な外部要因によって引き起こされるその他の一般的な問題。コードで扱える。例: null ポインター例外、配列の添字が範囲外など。

実行時例外と非実行時例外 (チェック済み) の違いは何ですか?

unchecked exceptionRuntimeExceptionおよびクラスを含むError他のすべての例外は、チェック例外と呼ばれます。

  1. RuntimeExceptionプログラムエラーが原因である場合、そのような例外を回避するにはプログラムを修正する必要があります。
  2. checked Exception特定の環境に起因する例外(読み込んだファイルが存在しない、ファイルが空、または SQL 例外)。処理する必要があります。処理しないとコンパイルが失敗します。キャッチまたはスローできます。

投げる と 投げる はどう違いますか?

  • throw : 特定の例外オブジェクトをスローするために使用されます。

  • throws : メソッドがスローする可能性のある例外を宣言するためにメソッド シグネチャで使用されます。サブクラス メソッドによってスローされる例外の範囲はさらに小さいか、例外がまったくスローされません。

ストーリーを通じて NIO を明確に伝える

例を挙げて説明しましょう。

銀行の従業員が 10 人しかいないとします。銀行の業務プロセスは次の 4 つのステップに分かれています。

1) お客様は申請フォームに記入します (5 分)。

2) スタッフによるレビュー (1 分)。

3) スタッフは警備員に金庫からお金を引き出すように頼みます (3 分)。

4) 店員はレシートを印刷し、お金とレシートを顧客に返します (1 分)。

銀行のさまざまな作業方法が業務効率にどのような影響を与えるかを見てみましょう。

1つ目はBIO方式です。

顧客が来ると、すぐにスタッフが受け取り、処理します。このスタッフは、上記の 4 つの完全なプロセスを担当する必要があります。10名以上のお客様がいらっしゃる場合は、残りのお客様も並んでお待ちいただくことになります。

店員が顧客を処理するのに 10 分 (5+1+3+1) かかります。1時間(60分)で6人のお客様、合計10人のスタッフ、つまり60人のお客様しか対応できません。

最初のステップでは実際に待っているなど、銀行員の勤務状況は飽和していないことがわかります。

実はこのような作業がBIOであり、リクエスト(顧客)が来るたびにスレッドプールに割り当てられ、スレッド(スタッフ)で処理され、スレッドプールの上限(10)を超えるとスローされる列に並んで待ちます。

では、銀行のスループットを向上させるにはどうすればよいでしょうか?

その考え方は、「分割統治」、タスクを分割し、専門の担当者に専門のタスクを担当させることです。

具体的には、銀行は行員 A を特別に配置し、顧客が銀行に来るたびに顧客に記入してもらうための用紙を渡すのが仕事です。顧客がフォームに記入するたびに、A は次のステップを完了するために残りの 9 人のスタッフ メンバーにそのフォームをランダムに割り当てます。

このように、顧客が多くなり、従業員 A の仕事が飽和状態になったと仮定すると、従業員 A は、フォームに記入した顧客を処理のためにカウンターに連れて行き続けます。

カウンターの 1 人の従業員は 5 分で 1 人の顧客に対応でき、9 人の従業員が 1 時間に対応できます: 9*(60/5)=108。

働き方の変革により、大幅な効率向上が期待できることがわかります。

この働き方は、実はNIOのアイデアなのです。

次の図は、非常に古典的な NIO の図です。mainReactorスレッドは、サーバー ソケットをリッスンし、新しい接続を受信し、確立されたソケットをサーバーに割り当てる役割を果たします。subReactor

subReactorこれは、接続されたソケットの逆多重化とネットワーク データの読み取りと書き込みを担当するスレッドまたはスレッド プールです。ここでのネットワーク データの読み取りと書き込みは、顧客がフォームに記入する時間のかかるアクションにたとえることができ、特定のビジネス処理機能については、完了するためにワーカー スレッド プールにスローされます。

mainReactor一般的な NIO には、スレッド、subReactorスレッド、スレッドという 3 種類のスレッドがあることがわかりますwork

それぞれのスレッドが専門的な処理を行うため、最終的には各スレッドが空になることがなくなり、システムのスループットが自然に向上します。

このプロセスに改善の余地はあるでしょうか?

このビジネス プロセスの 3 番目のステップで、スタッフが警備員に金庫からお金を引き出すように依頼したことがわかります (3 分)。この3分はカウンタースタッフが待っている間に費やしており、この3分を利用することができます。

3番目のステップを担当するスタッフBを配置する、分割統治の考えは依然として残っています。

カウンターのスタッフがステップ 2 を完了するたびに、お金を引き出すために警備員と連絡を取る責任をスタッフ B に通知します。このとき、カウンタースタッフは次の顧客の対応を続けることができます。

従業員Bがお金を受け取った後、カウンターにお金が到着したことを顧客に伝え、再度列に並んでもらい、再びカウンターの店員が接客したところ、顧客は最初の3ステップを完了しており、ステップ 4 を直接実行できます。

今日の Web サービスでは、多くの場合、RPC または Http を介してサードパーティ サービスを呼び出す必要があります。これはステップ 3 に相当します。このステップに時間がかかる場合は、非同期メソッドを使用してリソース使用率を大幅に削減できます。

NIO+ 非同期アプローチにより、少数のスレッドで多数の処理を実行できるようになります。これは、プロキシ サービス、API サービス、長期接続サービスなど、多くのアプリケーション シナリオに当てはまります。これらのアプリケーションは同期すると、大量のマシン リソースを消費します。

ただし、NIO+ 非同期によりシステムのスループットは向上しますが、リクエストの待ち時間を短縮することはできず、逆に待ち時間が増加する可能性があります。

結局のところ、NIO の基本的な考え方は次のように要約できます。分割統治、タスクを分割し、特定のタスクを担当する専任の担当者を配置する

BIO/NIO/AIO の違いは何ですか?

同期ブロッキング IO : ユーザー プロセスは IO 操作を開始した後、実行を続行する前に IO 操作が実際に完了するまで待つ必要があります。

同期ノンブロッキング IO : クライアントとサーバーは、マルチプレクサー ポーリングによって登録されたチャネルを通じて接続されますChannelスループットと信頼性を向上させます。ユーザー プロセスは IO 操作を開始した後、他のことを行うことができますが、ユーザー プロセスは IO 操作が完了したかどうかをポーリングする必要があるため、CPU リソースが不必要に浪費されます。

非同期ノンブロッキング IO : NIO のアップグレード版であるノンブロッキング非同期通信モードは、非同期チャネルを使用して非同期通信を実現し、その読み取りおよび書き込みメソッドは非同期メソッドです。ユーザー プロセスは IO 操作を開始してすぐに戻り、実際に IO 操作が完了すると、アプリケーションに IO 操作が完了したことが通知されます。Future パターンに似ています。

デーモンスレッドとは何ですか?

  • デーモン スレッドは、バックグラウンドで実行される特別なプロセスです。
  • これは制御端末から独立しており、定期的に何らかのタスクを実行するか、何らかのイベントが発生するのを待ちます。
  • ガベージ コレクション スレッドは、Java の特別なデーモン スレッドです。

Java は多重継承をサポートしていますか?

Java では、クラスは多重継承をサポートしません。インターフェイスは多重継承をサポートします。インターフェイスの役割は、オブジェクトの機能を拡張することです。サブインターフェースが複数の親インターフェースを継承するということは、サブインターフェースが複数の機能を拡張することを意味します。クラスがこのインターフェイスを実装すると、複数の関数が拡張されます。

Java が多重継承をサポートしない理由:

  • セキュリティ上の理由から、サブクラスによって継承された複数の親クラスが同じメソッドまたはプロパティを持つ場合、サブクラスはどれを継承するかを認識できません。
  • Java は、多重継承を実現し、単一継承の欠点を補うためのインターフェースと内部クラスを提供します。

オブジェクトの複製を実現するにはどうすればよいですか?

  • インターフェースを実装し、メソッドをオーバーライドします。このメソッドは浅いコピーです。つまり、クラス内の属性にカスタム参照型がある場合、参照のみがコピーされ、参照が指すオブジェクトはコピーされません。オブジェクトのプロパティのクラスがインターフェイス、オブジェクトのクローン作成時にプロパティもクローン化されます (ディープ コピー)。Cloneableclone()Cloneable
  • 連載を組み合わせたディープコピー。
  • オブジェクトのコピーは、およびorg.apache.commonsのユーティリティ クラスを通じて行われます。BeanUtilsPropertyUtils

同期と非同期の違いは何ですか?

同期: 呼び出しが行われると、結果が取得されるまで呼び出しは戻りません。

非同期: 呼び出しが発行された後、呼び出し先は結果を返した後で呼び出し元に通知するか、コールバック関数を通じて呼び出しを処理します。

ブロッキングとノンブロッキングの違いは何ですか?

ブロッキングとノンブロッキングはスレッドの状態に焦点を当てます。

ブロッキング呼び出しとは、呼び出しの結果が返される前に現在のスレッドが一時停止されることを意味します。呼び出しスレッドは、結果が得られるまで実行を再開しません。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

举个例子,理解下同步、阻塞、异步、非阻塞的区别:

同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。

Java8的新特性有哪些?

  • Lambda 表达式:Lambda允许把函数作为一个方法的参数
  • Stream API :新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中
  • 默认方法:默认方法就是一个在接口里面有了一个实现的方法。
  • Optional 类 :Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Date Time API :加强对日期与时间的处理。

Java8 新特性总结

序列化和反序列化

  • 序列化:把对象转换为字节序列的过程称为对象的序列化.
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化.

什么时候需要用到序列化和反序列化呢?

当我们只在本地 JVM 里运行下 Java 实例,这个时候是不需要什么序列化和反序列化的,但当我们需要将内存中的对象持久化到磁盘,数据库中时,当我们需要与浏览器进行交互时,当我们需要实现 RPC 时,这个时候就需要序列化和反序列化了.

前两个需要用到序列化和反序列化的场景,是不是让我们有一个很大的疑问? 我们在与浏览器交互时,还有将内存中的对象持久化到数据库中时,好像都没有去进行序列化和反序列化,因为我们都没有实现 Serializable 接口,但一直正常运行.

下面先给出结论:

只要我们对内存中的对象进行持久化或网络传输,这个时候都需要序列化和反序列化.

理由:

服务器与浏览器交互时真的没有用到 Serializable 接口吗? JSON 格式实际上就是将一个对象转化为字符串,所以服务器与浏览器交互时的数据格式其实是字符串,我们来看来 String 类型的源码:

public final class String
    implements java.io.Serializable,Comparable<String>,CharSequence {
    /\*\* The value is used for character storage. \*/
    private final char value\[\];

    /\*\* Cache the hash code for the string \*/
    private int hash; // Default to 0

    /\*\* use serialVersionUID from JDK 1.0.2 for interoperability \*/
    private static final long serialVersionUID = -6849794470754667710L;

    ......
}

String 型は Serializable インターフェイスを実装し、指定された SerialVersionUID の値を表示します。

次に、オブジェクトがデータベースに永続化されるときの状況、Mybatis データベース マッピング ファイルにコードを挿入する状況を見てみましょう。

<insert id="insertUser" parameterType="org.tyshawn.bean.User">
    INSERT INTO t\_user(name,age) VALUES (#{name},#{age})
</insert>

実際、オブジェクト全体をデータベースに永続化するのではなく、オブジェクト内のプロパティをデータベースに永続化します。これらのプロパティ (Date/String など) は Serializable インターフェイスを実装します。

シリアル化と逆シリアル化のために Serializable インターフェイスを実装するのはなぜですか?

Java で Serializable インターフェイスが実装されると、JVM はクラスがロードされたときにこのインターフェイスが実装されたことを認識し、インスタンス オブジェクトの初期化時に底部でシリアル化と逆シリアル化を実装するのに役立ちます。

書き込まれるオブジェクトの型が String、array、または Enum ではなく、Serializable インターフェイスが実装されていない場合、シリアル化中に NotSerializableException がスローされます。ソースコードは次のとおりです。

// remaining cases
if (obj instanceof String) {
    
    
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    
    
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    
    
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    
    
    writeOrdinaryObject(obj, desc, unshared);
} else {
    
    
    if (extendedDebugInfo) {
    
    
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
    
    
        throw new NotSerializableException(cl.getName());
    }
}

Serializable インターフェイスを実装した後、指定された SerialVersionUID の値が表示されるのはなぜですか?

指定されたserialVersionUIDが表示されない場合、JVMはシリアル化時に属性に応じてserialVersionUIDを自動生成し、その属性とともにシリアル化し、永続化またはネットワーク送信を行います。デシリアライズ時には、JVMは自動的に新しいバージョンの属性に従ってserialVersionUIDを取得し、この新しいserialVersionUIDとシリアル化中に生成された古いserialVersionUIDを比較します。それらが同じであれば、逆シリアル化は成功します。そうでない場合は、エラーが報告されます。

SerialVersionUID が指定されている場合、JVM はシリアル化および逆シリアル化中にも SerialVersionUID を生成しますが、その値は表示される値であるため、逆シリアル化中に古いバージョンと新しいバージョンの SerialVersionUID が一致します。

クラスを書いた後に変更しない場合は、serialVersionUID を指定しなくても問題ありませんが、実際の開発ではこれは不可能です。クラスは反復処理を継続します。クラスが変更されると、古いオブジェクトは逆シリアル化されます。エラーが報告されます。そのため、実際の開発では明示的にserialVersionUIDを指定することになります。

静的プロパティがシリアル化されないのはなぜですか?

シリアル化はオブジェクトに対して行われるため、静的プロパティはオブジェクトよりも優先され、クラスのロードでロードされるため、シリアル化されません。

この結論を見て、serialVersionUID も静的に変更されるのに、なぜ SerialVersionUID がシリアル化されるのかと疑問に思う人もいるかもしれませんが、実際には、serialVersionUID 属性はシリアル化されておらず、オブジェクトをシリアル化するときに JVM が自動的に SerialVersionUID を生成し、指定された自動生成されたserialVersionUIDには、serialVersionUID属性値が割り当てられます。

一時的なキーワードの役割は何ですか?

Java 言語のキーワード、変数修飾子、インスタンス変数が transient で宣言されている場合、オブジェクトの格納時にその値を維持する必要はありません。

つまり、transient によって変更されたメンバー変数の値はシリアル化中は無視され、逆シリアル化後、一時変数の値は初期値 (int 型の場合は 0、オブジェクト型の場合は null など) に設定されます。

反省とは何ですか?

動的に情報を取得したり、オブジェクトのメソッドを動的に呼び出したりする機能をJava言語のリフレクション機構と呼びます。

実行状態では、どのクラスでも、このクラスのすべてのプロパティとメソッドを知ることができます。どのオブジェクトでも、そのメソッドとプロパティを呼び出すことができます。

リフレクションの適用シナリオは何ですか?

  1. JDBC がデータベースに接続するときは、Class.forName()リフレクションを通じてデータベースをロードするドライバーを使用します。
  2. Eclipse や IDEA などの開発ツールは、リフレクションを使用してオブジェクトのタイプと構造を動的に分析し、オブジェクトのプロパティとメソッドを動的にプロンプ​​トします。
  3. serviceサーブレットのメソッドはWebサーバーでのリフレクションによって呼び出されます。
  4. JDK 動的プロキシの最下層はリフレクション実装に依存します

ジェネリックとは何か教えてください。

Java ジェネリックは JDK 5 で導入された新機能で、クラスとインターフェイスを定義するときに型パラメーターの使用を可能にします。宣言された型パラメータは、使用時に具体的な型に置き換えられます。

ジェネリックの最大の利点は、コードの再利用性を向上できることです。List インターフェイスを例にとると、String、Integer、およびその他の型を List に入れることができます。ジェネリックスを使用しない場合は、String 型を格納する List インターフェイスを作成し、String 型を格納する別の List インターフェイスを作成する必要があります。この問題を解決するには、整数型。ジェネリックでも問題ありません。

実行中のスレッドを停止するにはどうすればよいですか?

いくつかの方法があります。

1.スレッドの stop メソッドを使用します

スレッドを強制終了するには stop() メソッドを使用します。ただし、stop は時代遅れの方法であるため、お勧めできません。

Stop メソッドを使用すると、常に ThreadDeath 例外が上方に伝播されるため、ターゲット スレッドはロックされているすべてのモニターのロックを解除します。つまり、すべてのオブジェクト ロックが解放されます。以前にロックされたオブジェクトは同期的に処理できないため、データの不整合が発生する可能性があります。

2.スレッドを中断するには、interrupt メソッドを使用します。このメソッドはスレッドに終了を指示するだけですが、スレッドが最終的にいつ終了するかはコンピュータによって異なります。割り込みメソッドの呼び出しは、現在のスレッドの停止マークにすぎず、実際にスレッドを停止するわけではありません。

次に、Thread.currentThread().isInterrupted() メソッドを呼び出します。このメソッドは、現在のスレッドが終了したかどうかを判断するために使用できます。この判断を通じて、ビジネス ロジックの処理を行うことができます。通常、isInterrupted が true を返すと、割り込み例外が発生します。スローされ、try -catch Capture を介して実行されます。

3.フラグビットをセットする

フラグビットを設定し、フラグビットが特定の値になるとスレッドは正常に終了します。フラグを設定することは、共有変数を使用する方法です。メモリ内で共有変数の可視性を確保するには、volatile を使用して変更できます。このようにして、変数の値は常にメイン メモリから最新の値を取得します。

ただし、共有変数をマークするこの揮発的な方法では、スレッドがブロックされている場合は応答を完了できません。たとえば、Thread.sleep() メソッドを呼び出した後、スレッドは非実行状態にあり、メインスレッドが共有変数の値を変更したとしても、この時点ではスレッドはループ フラグをまったくチェックできません。スレッドの中断は実現できません。

したがって、現時点では、実行中のスレッドを中断する最も正しい方法は、interrupt() と手動例外スローです。

クロスドメインとは何ですか?

簡単に言うと、クロスドメインとは、あるドメイン名の Web ページから別のドメイン名のリソースを要求することを指します。同一生成元ポリシーにより、このような直接アクセスは通常許可されません。ただし、多くのシナリオではクロスドメイン アクセスが必要になることが多く、たとえば、フロント エンドとバック エンドを分離するモードでは、フロント エンドとバック エンドのドメイン名が一致せず、このときにクロスドメインの問題が発生します。

では、同一生成元ポリシーとは何でしょうか?

いわゆる相同性とは、「プロトコル + ドメイン名 + ポート」が同じであることを意味します。2 つの異なるドメイン名が同じ IP アドレスを指している場合でも、それらは同じ起源のものではありません。

同一生成元ポリシーは、次の動作を制限します。

1. Cookie、LocalStorage 和 IndexDB 无法读取
2. DOM 和 Js对象无法获得
3. AJAX 请求不能发送

同一生成元ポリシーがあるのはなぜですか?

たとえば、オンライン バンキングでアカウントのパスワードを入力し、残高を確認し、その後他の色付きの Web サイトにアクセスした場合、この Web サイトは今オンライン バンキング サイトにアクセスしてアカウントのパスワードを取得でき、その結果は想像できます。したがって、セキュリティの観点から見ると、同一生成元ポリシーは Web サイト情報の保護に役立ちます。

クロスドメインの問題を解決するにはどうすればよいでしょうか?

まあ、いくつかの方法があります:

CORS、クロスオリジンリソース共有

CORS (クロスオリジン リソース共有)、クロスオリジン リソース共有。CORS は、実際にはブラウザによって策定された仕様です。ブラウザは自動的に CORS と通信します。その実装は主にサーバー側で行われます。一部の HTTP ヘッダーは、アクセスできるドメインを制限するために使用されます。たとえば、ページ A は、 B サーバー上のデータ B サーバーが A のドメイン名へのアクセスが許可されていると宣言した場合、A から B へのクロスドメイン要求を完了できます。

@CrossOrigin アノテーション

プロジェクトで Springboot を使用している場合は、@CrossOrigin(origins="*") アノテーションを Controller クラスに追加して、現在のコントローラーへのクロスドメイン アクセスを実現できます。もちろん、このタグはメソッドに追加することも、直接追加することもできます。クロスドメイン処理は、エントリ クラスのすべてのインターフェイスで実行されます。SpringMVC のバージョンはバージョン 4.2 以降でのみ @CrossOrigin をサポートすることに注意してください。

Nginx リバースプロキシインターフェイスのクロスドメイン

クロスドメイン nginx リバース プロキシの原理は次のとおりです。 まず、同一生成元ポリシーはブラウザのセキュリティ ポリシーであり、HTTP プロトコルの一部ではありません。サーバーは HTTP プロトコルを使用してのみ HTTP インターフェイスを呼び出し、JS スクリプトを実行せず、同一生成元ポリシーを必要とせず、交差の問題はありません。

nginx リバース プロキシ インターフェイスのクロスドメイン実装は次のとおりです。nginx を基点としてプロキシ サーバー (ドメイン名はドメイン 1 と同じ、ポートは異なります) を構成し、リバース プロキシはドメイン 2 インターフェイスにアクセスします。ちなみに、Cookie 内のドメイン情報を変更することもできます。これは、現在のドメイン Cookie を書き込み、クロスドメイン ログインを実現するのに便利です。

// proxy服务器
server {
    
    
    listen       81;
    server_name  www.domain1.com;
    location / {
    
    
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
        
        add_header Access-Control-Allow-Origin http://www.domain1.com;
    }
}

このようにして、フロントエンド エージェントは http:www.domain1.com:81/* にアクセスするだけで済みます。

jsonp を介したクロスドメイン

通常、Web サーバーの負荷を軽減するために、js、css、img などの静的リソースを独立したドメイン名の別のサーバーに分離し、HTML ページ内の対応するタグを通じて異なるドメイン名の静的リソースを読み込みます。許可される操作は、この原則に基づいて、スクリプトを動的に作成し、パラメーターを含む URL をリクエストして、クロスドメイン通信を実現できます。

インターフェイスを設計する際に注意すべき点は何ですか?

  1. インターフェイスパラメータの検証インターフェイスは、入力パラメータが空であることが許可されるかどうか、入力パラメータの長さが期待を満たしているかどうかなど、パラメータを検証する必要があります。
  2. インターフェースを設計するときは、インターフェースの拡張性を十分に考慮してください。インターフェイスを再利用できるかどうか、およびインターフェイスのスケーラビリティを維持する方法を検討してください。
  3. シリアル呼び出しをパラレル呼び出しに変更することを検討してくださいたとえば、モールのホームページのインターフェースを設計するには、商品情報、マーケティング情報、ユーザー情報などを確認する必要があります。一つ一つ順番にチェックしていたら、かなりの時間がかかってしまいます。このシナリオを並列呼び出しに変更して、時間のかかるインターフェイスを削減できます。
  4. 接口是否需要防重处理。涉及到数据库修改的,要考虑防重处理,可以使用数据库防重表,以唯一流水号作为唯一索引。
  5. 日志打印全面,入参出参,接口耗时,记录好日志,方便甩锅。
  6. 修改旧接口时,注意兼容性设计
  7. 异常处理得当。使用finally关闭流资源、使用log打印而不是e.printStackTrace()、不要吞异常等等
  8. 是否需要考虑限流。限流为了保护系统,防止流量洪峰超过系统的承载能力。

过滤器和拦截器有什么区别?

1、实现原理不同

过滤器和拦截器底层实现不同。过滤器是基于函数回调的,拦截器是基于Java的反射机制(动态代理)实现的。一般自定义的过滤器中都会实现一个doFilter()方法,这个方法有一个FilterChain参数,而实际上它是一个回调接口。

2、使用范围不同

过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。而拦截器是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。拦截器不仅能应用在web程序中,也可以用于Application、Swing等程序中。

3、使用的场景不同

インターセプターはビジネスシステムに近いため、主にロギング、権限判断、その他のビジネスなど、プロジェクト内のビジネス判断を実現するために使用されます。フィルターは通常、機密単語のフィルター処理、応答データ圧縮、その他の機能などの汎用機能フィルター処理を実装するために使用されます。

4.トリガータイミングが異なります

フィルタ フィルタは、リクエストがコンテナに入った後、サーブレットに入る前に前処理され、リクエストの終了はサーブレットの処理後です。

インターセプタは、リクエストがサーブレットに入った後、コントローラに入る前に前処理され、リクエストは対応するビューがコントローラでレンダリングされた後に終了します。

5.インターセプトされるリクエストの範囲が異なります

リクエストの実行順序は、リクエストがコンテナに入る -> フィルタに入る -> サーブレットに入る -> インターセプタに入る -> コントローラを実行する。フィルタとインターセプタの実行タイミングも異なり、フィルタが先に実行され、次にインターセプタが実行され、最後に実際に呼び出されるメソッドが入力されることがわかります。

参考リンク:https://segmentfault.com/a/1190000022833940

サードパーティのインターフェイスに接続するときに考慮すべきことは何ですか?

さて、考慮すべき点がいくつかあります。

  1. インターフェイスに接続されているネットワーク プロトコルがhttps/http またはカスタム プライベート プロトコルであることを確認します。
  2. データ転送パラメータと応答形式(application/json など)について合意し、弱い型を強く型付けされた言語に接続する場合には特別な注意を払う
  3. インターフェースセキュリティの観点からは、本人確認方法、使用トークン、証明書検証などを決定する必要があります。
  4. データ送信の最終的な一貫性を確保するためにインターフェイス呼び出しが失敗した後、再試行メカニズムが必要かどうかを確認します。
  5. ロギングは包括的である必要がありますインターフェイスの入力パラメータと出力パラメータ、および分析後のパラメータ値は、問題の特定(問題解決)を容易にするためにログに記録する必要があります。

参考:https://blog.csdn.net/gzt19881123/article/details/108791034

バックエンドインターフェースのパフォーマンスを最適化する方法にはどのようなものがありますか?

以下のような方法があります。

1.インデックスを最適化しますwhere 条件のキー フィールド、またはorder by後続の並べ替えフィールドにインデックスを追加します。

2. SQL ステートメントを最適化しますたとえば、select *、バッチ操作の使用を避け、深いページングを避け、group by の効率を向上させます。

3.大きなことは避ける@Transactional を使用してこの宣言型トランザクションに注釈を付けてトランザクション機能を提供すると、簡単に大規模なトランザクションが発生し、その他の問題が発生する可能性があります。トランザクション内で一度に大量のデータを処理することを避け、トランザクションと関係のないロジックをトランザクションの外に配置する必要があります。

4.非同期処理主ロジックと補助ロジックを分離し、補助ロジックを非同期で実行したり、ライブラリを非同期で記述したりできます。例えば、ユーザが購入した商品が配送された場合、テキストメッセージ通知を送信する必要があるが、テキストメッセージ通知は二次的な処理であり、メイン処理の実行に影響を与えないように非同期で実行することができる。

5.ロックの粒度を減らします同時シナリオでは、複数のスレッドが同時にデータを変更するため、データの不整合が発生します。この場合、通常はロックすることで解決します。ただし、ロックが適切に追加されていない場合、ロックの粒度が粗くなりすぎ、インターフェイスのパフォーマンスに大きな影響を与えます。

6.キャッシュを追加しますテーブル内のデータの量が非常に多い場合、データベースからデータを直接クエリするとパフォーマンスが非常に低下します。Redis memcached を使用すると、クエリのパフォーマンスが向上し、それによってインターフェイスのパフォーマンスが向上します。

7.サブデータベースとサブテーブルシステムが一定の段階まで発展すると、同時ユーザー数が多くなり、多数のデータベース要求が発生し、多数のデータベース接続が占有され、ディスク IO パフォーマンスのボトルネックも発生します。または、データベース テーブルのデータが非常に大きいため、SQL クエリでインデックスを使用する場合でも時間がかかります。この時、サブデータベースのサブテーブルで解決できます。サブライブラリは、データベース接続リソースの不足とディスク IO のパフォーマンスのボトルネックの問題を解決するために使用されます。テーブル分割は、インデックスを使用しても 1 つのテーブル内のデータ量が多すぎて、SQL ステートメントでデータをクエリするのに非常に時間がかかるという問題を解決するために使用されます。

8.ループ内でデータベースをクエリすることは避けてくださいループでデータベースにクエリを実行すると非常に時間がかかるため、必要なデータをすべて 1 回のクエリで取得するのが最善です。

Alibaba Java 開発マニュアルで属性を定義するためにラッパー タイプを使用することが必須なのはなぜですか?

さて、例として Boolean フィールドを取り上げると、オブジェクトのフィールドの値を設定しない場合、Boolean 型の変数はデフォルト値を設定し、booleannull型の変数はデフォルト値を設定しますfalse

つまり、パッケージ型のデフォルト値は null ですが、基本データ型のデフォルト値は固定値です (ブール型の場合は false、バイト型の場合は 0、short、int、long、float 型の場合は 0.0f)。 。

たとえば、手数料を差し引くときに外部の価格設定システムからレート値を読み取る必要がある手数料控除システムがあり、このインターフェイスの戻り値には浮動小数点レート フィールドが含まれることが予想されます。この値を取得したら、「金額 * 料金 = 手数料」という計算式を使用して計算し、計算結果が差し引かれます。

課金システムが異常な場合、デフォルト値が返される場合があります。フィールドが Double 型の場合、デフォルト値は null、フィールドが double 型の場合、デフォルト値は 0.0 です。

控除システムがレートの戻り値に対して特別な処理を行わない場合、計算のために NULL 値を取得すると、直接エラーが報告され、プログラムがブロックされます。0.0 が出た場合は直接計算して、インターフェースが 0 になった後は手数料を差し引きます。この異常は認識できません。

そうすると、0.0で特別な判定をして、0ならエラー報告をブロックするのですが、これは可能でしょうか?

いや、この時に問題が発生するのですが、許容レートが0の場面をどうするか?

使用基本数据类型只会让方案越来越复杂,坑越来越多。

这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。

因此,建议在POJO和RPC的返回值中使用包装类型。

参考链接:https://mp.weixin.qq.com/s/O_jCxZWtTTkFZ9FlaZgOCg

8招让接口性能提升100倍

池化思想

如果你每次需要用到线程,都去创建,就会有增加一定的耗时,而线程池可以重复利用线程,避免不必要的耗时。

比如TCP三次握手,它为了减少性能损耗,引入了Keep-Alive长连接,避免频繁的创建和销毁连接。

拒绝阻塞等待

如果你调用一个系统B的接口,但是它处理业务逻辑,耗时需要10s甚至更多。然后你是一直阻塞等待,直到系统B的下游接口返回,再继续你的下一步操作吗?这样显然不合理

参考IO多路复用模型。即我们不用阻塞等待系统B的接口,而是先去做别的操作。等系统B的接口处理完,通过事件回调通知,我们接口收到通知再进行对应的业务操作即可。

远程调用由串行改为并行

比如设计一个商城首页接口,需要查商品信息、营销信息、用户信息等等。如果是串行一个一个查,那耗时就比较大了。这种场景是可以改为并行调用的,降低接口耗时。

锁粒度避免过粗

在高并发场景,为了防止超卖等情况,我们经常需要加锁来保护共享资源。但是,如果加锁的粒度过粗,是很影响接口性能的。

不管你是synchronized加锁还是redis分布式锁,只需要在共享临界资源加锁即可,不涉及共享资源的,就不必要加锁。

耗时操作,考虑放到异步执行

耗时操作,考虑用异步处理,这样可以降低接口耗时。比如用户注册成功后,短信邮件通知,是可以异步处理的。

使用缓存

把要查的数据,提前放好到缓存里面,需要时,直接查缓存,而避免去查数据库或者计算的过程

提前初始化到缓存

预取思想很容易理解,就是提前把要计算查询的数据,初始化到缓存。如果你在未来某个时间需要用到某个经过复杂计算的数据,才实时去计算的话,可能耗时比较大。这时候,我们可以采取预取思想,提前把将来可能需要的数据计算好,放到缓存中,等需要的时候,去缓存取就行。这将大幅度提高接口性能。

压缩传输内容

压缩传输内容,传输报文变得更小,因此传输会更快。

最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~

Github地址

おすすめ

転載: blog.csdn.net/Tyson0314/article/details/130836561