序文: 今日は、Ye Qiu 先輩が最適化のトピックについて話しますので、Java でコード最適化を実装する方法について話しましょう. ここでいくつかの実用的なヒントを共有してください.
ブロガー ポータル:
推奨列:
目次
4.リソースがなくなったら、時間内に閉じることを忘れないでください
1. String.format で文字列を連結する
特に複数のパラメーターと長い文字列の場合、文字列をつなぎ合わせたかどうかはわかりません。
たとえば、現在要件があります。get リクエストを使用してサードパーティのインターフェイスを呼び出すには、URL の後に複数のパラメーターをスプライスする必要があります。
以前は、リクエスト アドレスは次のようにつなぎ合わせられていました。
String url = "http://susan.sc.cn?userName="+userName+"&age="+age+"&address="+address+"&sex="+sex+"&roledId="+roleId;
文字+
列は数字と連結されるため、エラーが発生しやすくなります。
StringBuilder
後で最適化され、連結された文字列が代わりに使用されました。
StringBuilder urlBuilder = new StringBuilder("http://susan.sc.cn?");
urlBuilder.append("userName=")
.append(userName)
.append("&age=")
.append(age)
.append("&address=")
.append(address)
.append("&sex=")
.append(sex)
.append("&roledId=")
.append(roledId);
コードが最適化された後は、もう少し直感的です。
しかし、それはまだ厄介に見えます。
この時点で、String.format
メソッドの最適化を使用できます。
String requestUrl = "http://susan.sc.cn?userName=%s&age=%s&address=%s&sex=%s&roledId=%s";
String url = String.format(requestUrl,userName,age,address,sex,roledId);
コードの可読性が一気に向上しました。
通常、String.format
メソッドを使用して、URL 要求パラメーター、ログ印刷、およびその他の文字列を連結できます。
ただし、+ 記号を使用して文字列を結合したり、StringBuilder を使用して文字列を結合したりするよりも実行効率が低下するため、for ループで文字列を結合するために使用することはお勧めしません。
2. バッファ可能な IO ストリームを作成する
IO流
おそらく誰もが頻繁に使用しており、データを写入
特定のファイルに転送したり、特定のファイルからデータを転送し读取
たり、ファイル a に転送したり内存
、ディレクトリ b复制
からディレクトリ c に転送したりする必要があることがよくあります。
JDK は、IO ストリームを操作するための非常に豊富な API を提供します。
例えば:
public class IoTest1 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");
File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
int len;
while ((len = fis.read()) != -1) {
fos.write(len);
}
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
この例の主な機能は、1.txt ファイルの内容を 2.txt ファイルにコピーすることです。この例では、機能的な観点から要件を満たすことができる通常の IO ストリームを使用していますが、パフォーマンスはあまり良くありません。
この例では、1.txt ファイルから 1 バイトのデータを読み取ると、すぐに 2.txt ファイルに書き込まれるため、ファイルの読み取りと書き込みを頻繁に行う必要があります。
最適化:
public class IoTest {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");
File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
この例では、入力ストリームと出力ストリームを使用BufferedInputStream
してBufferedOutputStream
作成します。可缓冲
最も重要な点は、バッファ バイト配列を定義し、1.txt ファイルから読み取ったデータを一時的に保存してから、バッファ バイト配列のデータを一度に 2.txt にバッチで書き込むことです。
これの利点は、ファイルの読み取りと書き込みの回数が減ることです。ファイルの読み取りと書き込みが非常に時間のかかる操作であることは誰もが知っています。つまり、キャッシュ可能な入力ストリームと出力ストリームを使用すると、IO のパフォーマンスを向上させることができます。特に、ファイルが非常に大きい場合、効率が大幅に向上します。
3.サイクル数を減らす
日々の開発において、コレクションをループすることは不可欠な操作です。
ただし、ループ階層が深い場合、ループ内のループがコードの実行効率に影響を与える可能性があります。
反例
:
for(User user: userList) {
for(Role role: roleList) {
if(user.getRoleId().equals(role.getId())) {
user.setRoleName(role.getName());
}
}
}
この例では 2 層のループがあり、userList と roleList のデータが大量にある場合、必要なデータを取得するために何度も走査する必要があり、多くの CPU リソースを消費します。
正例
:
Map<Long, List<Role>> roleMap = roleList.stream().collect(Collectors.groupingBy(Role::getId));
for (User user : userList) {
List<Role> roles = roleMap.get(user.getRoleId());
if(CollectionUtils.isNotEmpty(roles)) {
user.setRoleName(roles.get(0).getName());
}
}
ループの数を減らす最も簡単な方法は、2 番目のレイヤーのループのセットを に変更して、map
直接通過しkey
て目的のvalue
データを取得できるようにすることです。
マップのキーは存在しますが、hash冲突
格納されたデータをトラバースすることは、リスト コレクション全体をトラバースすることよりもはるかに小さくなります。链表
红黑树
时间复杂度
4.リソースがなくなったら、時間内に閉じることを忘れないでください
资源
私たちの日常の開発では、データベース接続の取得、ファイルの読み取りなど、頻繁にアクセスされる可能性があります。
例として、データベース接続を取得してみましょう。
反例
:
//1. 加载驱动类
Class.forName("com.mysql.jdbc.Driver");
//2. 创建连接
Connection connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");
//3.编写sql
String sql ="select * from user";
//4.创建PreparedStatement
PreparedStatement pstmt = conn.prepareStatement(sql);
//5.获取查询结果
ResultSet rs = pstmt.execteQuery();
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
}
上記のコードは正常に実行できますが、大きな誤りがあります。つまり、ResultSet、PreparedStatement、および Connection オブジェクトのリソースが使用後に閉じられません。
データベース接続が非常に貴重なリソースであることは誰もが知っています。常に接続を作成することは不可能であり、使用された後はリサイクルされず、データベース リソースが無駄に浪費されます。
正例
:
//1. 加载驱动类
Class.forName("com.mysql.jdbc.Driver");
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//2. 创建连接
connection = DriverManager.getConnection("jdbc:mysql//localhost:3306/db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8","root","123456");
//3.编写sql
String sql ="select * from user";
//4.创建PreparedStatement
pstmt = conn.prepareStatement(sql);
//5.获取查询结果
rs = pstmt.execteQuery();
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
}
} catch(Exception e) {
log.error(e.getMessage(),e);
} finally {
if(rs != null) {
rs.close();
}
if(pstmt != null) {
pstmt.close();
}
if(connection != null) {
connection.close();
}
}
この例では、ResultSet、PreparedStatement、または Connection オブジェクトのいずれであっても、使用後にclose
メソッドが呼び出されてリソースが閉じられます。
ここで注意していただきたいのは、ResultSet、PreparedStatement、または Connection オブジェクトの 3 つのクローズ リソースの順序を元に戻すことはできません。そうしないと、例外が発生する可能性があることです。
5. プール技術を利用する
データベースからデータを照会するには、まずデータベースに接続してConnection
リソースを取得する必要があることは誰もが知っています。
プログラムを複数のスレッドで実行するには、Thread
クラスを使用してスレッドを作成する必要があり、スレッドもリソースです。
通常、データベース操作のプロセスは次のとおりです。
-
接続を作成する
-
データベース操作を実行する
-
接続を閉じる
接続の作成と接続のクローズは、非常に時間のかかる操作です. 接続の作成には、同時にいくつかのリソースの作成が必要です. 接続をクローズする場合、それらのリソースをリサイクルする必要があります.
ユーザーがデータベース要求を行うたびに、プログラムが接続を作成して閉じる必要がある場合、多くの時間を浪費する可能性があります。
さらに、過剰なデータベース接続が発生する可能性があります。
データベース最大连接数
が限られていることは誰もが知っています. mysql を例にとると、接続の最大数は:100
ですが、この数はパラメータで調整できます.
ユーザーが要求した接続数が最大接続数を超えるとtoo many connections
、例外が報告されます。新しいリクエストが来ると、データベースが利用できなくなります。
この時点で、次のコマンドを渡すことができます。
show variables like max_connections
最大接続数を確認してください。
次に、コマンドを渡します。
set GLOBAL max_connections=1000
接続の最大数を手動で変更します。
このアプローチは問題を一時的に緩和するだけであり、良い解決策ではなく、問題を根本的に解決することはできません。
最大の問題は、データベース接続の数が無限に制御不能に増加する可能性があることです。
この時点で使用できます数据库连接池
。
現在の Java オープン ソース データベース接続プールは次のとおりです。
-
DBCP: Jakarta commons-pool オブジェクト プール メカニズムに依存するデータベース接続プールです。
-
C3P0: はオープン ソースの JDBC 接続プールであり、lib ディレクトリで Hibernate と共にリリースされます。これには、jdbc3 および jdbc2 拡張仕様で指定された接続およびステートメント プールを実装する DataSources オブジェクトが含まれます。
-
Druid: Alibaba の Druid は、データベース接続プールであるだけでなく、ProxyDriver、一連の組み込み JDBC コンポーネント ライブラリ、および SQL パーサーも含まれています。
-
Proxool: これは Java SQL ドライバー ドライバーであり、既存のコードに簡単に移植できる他の種類の選択されたドライバーに接続プールのカプセル化を提供します。
最もよく使用されるデータベース接続プールは次のとおりDruid
です。
この問題はここで終わります。ブロガーに注意して、道に迷わないでください。Ye Qiu 先輩があなたをハイウェイに連れて行きます~~