平时我们用javac 或者 java执行程序可能比较少,入门时候用到的也是简单的类,没有package或者没有依赖关系或者没有用其他.jar包的,所以执行起来没啥问题。在Algorithems Froth Edition中,经常要用命令行模式来Test 算法性能。
在排序算法-初级排序算法这一章,执行java SortCompare Insertion Selection 1000 100就遇到问题:找不到外部.jar包或者找不到引用的类。网上答案大体表达清楚了意思,但是不够简洁,思路不够清晰。这里就用这个例子梳理一下。
先看下我的SortCompare包结构:
└── com
└── chm
├── algorithms
│ ├── Example.java
│ ├── Insertion.java
│ ├── Selection.java
│ └── SortCompare.java
我们目的是,流畅的执行以下命令
- javac SortCompare.java
- java SotrCompare Insertion Selection 1000 100
-
介绍下类之间依赖关系,SortCompare是关键。
SortCompare.java的头部信息是这样的
package com.chm.algorithms;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.Stopwatch;
这表明SortCompare这个类用到了外部jar包(算法提供的基础包algs4.jar);同时该类直接调用了Insertion.sort()、Selection.sort()方法,Insertion.sort()和Selection.sort()调用了Example中封装好的方法。
一言以蔽之,他们都在com.chm.algorithms这个package下(最核心部分)
最终结果:
3288 0.9993866420331129 0.9998753497357498
For 1000 random Doubles
Insertion is 1.0 times faster than Selection
如果你对javac cp 或者 java -cp 理解不透,那么过程自然不顺利,这里我们先结论先行:
知道这2点就够了,这2点不是凭空而来,执行 man javac 后的cp介绍很清楚,后文在回头细说。
没有package的类自然不用说,就在当前目录执行javac java即可;过程像小溪一样顺利;
我们这里的例子是比较复杂的:
我们应该在com这个级别执行javac 或者执行java,而不是跑到SortCompare.java所在的目录.这点最核心的,知道这点+结论中的(2)+(3),你的一切疑问都会烟消云散。
在这个目录执行命令,一会介绍为什么:
/Users/myIdeaTest/my2020/202002/src/test/java
第一步:执行javac命令:
Mac下:
javac -cp ~/Downloads/algs4.jar: com/chm/algorithms/SortCompare.java
Windows下:
javac -cp ~/Downloads/algs4.jar; com/chm/algorithms/SortCompare.java(未亲尝试)
注意:
1)algs4.jar包就是SortCompare.java用到的外部.jar包,放在了家目录下;
2)指定多个路径时,Mac和Window区别是:
1、多个.jar包或者目录Mac用:分开,Wind用;分开;
2、表达当前目录的方式,Mac用空格或者./;Winddow下大家知道这里不一样即可。
比如我这里的命令
javac -cp ~/Downloads/algs4.jar: com/chm/algorithms/SortCompare.java
或者
javac -cp ~/Downloads/algs4.jar:./ com/chm/algorithms/SortCompare.java
在Mac下都是可以的。网上CSDN部分博客不介绍清楚上下文,导致用;总是出问题。
你会发现与SortCompare相关的类自动都被编译了,因为它们的包相同。需要提前先编译好Example.class Insertion.class Selection.class嘛?NO!
javac后的包结构:
.
└── com
└── chm
├── algorithms
│ ├── Example.class
│ ├── Example.java
│ ├── Insertion.class
│ ├── Insertion.java
│ ├── Selection.class
│ ├── Selection.java
│ ├── SortCompare.class
│ └── SortCompare.java
第二步:执行java命令
Mac下:
java -cp ~/Downloads/algs4.jar: com/chm/algorithms/SortCompare Insertion Selection 1000 100
搞定!
现在详细解释下结论1)2)3)。如果你想知道我是怎么得出结论的,请耐心往下看:
1)这里的SortCompare有package,它用到的类(Example.java Insertion.java Selection.java)和它在一个包下,所以不用import;外部的.jar包自然需要import。执行javac编译它的时候,它所依赖的自然要能找到。javac怎么知道去哪里找呢?当然是当前目录。这个当前目录就是指的package。也就是SortCompare.java这个类,人家头部已经标明,我在这个包下面。如果我们跑到com/chm/algorithms这个目录去javac SortCompare.java,会提示找不到外部包,类似这样:
[@MacBook-Air:algorithms (develop)]$ pwd
/Users/myIdeaTest/my2020/202002/src/test/java/com/chm/algorithms
[@deMacBook-Air:algorithms (develop)]$ ls
Example.java Insertion.java Selection.java SortCompare.java
[@deMacBook-Air:algorithms (develop)]$ javac SortCompare.java
SortCompare.java:3: 错误: 程序包edu.princeton.cs.algs4不存在
import edu.princeton.cs.algs4.StdOut;
^
SortCompare.java:4: 错误: 程序包edu.princeton.cs.algs4不存在
import edu.princeton.cs.algs4.StdRandom;
^
SortCompare.java:5: 错误: 程序包edu.princeton.cs.algs4不存在
import edu.princeton.cs.algs4.Stopwatch;
^
SortCompare.java:12: 错误: 找不到符号
Stopwatch timer = new Stopwatch();
^
符号: 类 Stopwatch
位置: 类 SortCompare
好,找不到外部包还不简单,加上就可以了
javac -cp ~/Downloads/algs4.jar SortCompare.java
SortCompare.java:14: 错误: 找不到符号
Insertion.sort(a);
^
符号: 变量 Insertion
位置: 类 SortCompare
SortCompare.java:17: 错误: 找不到符号
Selection.sort(a);
^
符号: 变量 Selection
位置: 类 SortCompare
2 个错误
这里提示找不到Insertion 和Selection,他们不是和SortCompare在一个包下嘛?怎么会找不到?让我们再次回头大声朗读边结论(1),还是不大懂吧?没关系。我们在这个目录下执行javac的时候,编译器发现SortCompare.java所依赖的类在com.chm.algorithm包下面,所以就从SortCompare.java所在的目录往下寻找com.chm.algorithm.Insertion.java,这个目录往下哪里还有这个包呢?恍然大悟了吧?
因此,这就是SortCompare.java所"认识"的包的真实含义。所以我们需要到com包所在的这个层级执行,也就是在package的层级执行javac 或者java.
好的,那我们就回到package所在的目录去执行上面命令:
[@deMacBook-Air:java (develop)]$ pwd
/Users/myIdeaTest/my2020/202002/src/test/java
[@deMacBook-Air:java (develop)]$ ls
com
[@deMacBook-Air:java (develop)]$ tree
.
└── com
└── chm
├── algorithms
│ ├── Example.java
│ ├── Insertion.java
│ ├── Selection.java
│ └── SortCompare.java
6 directories, 11 files
[@dzjdeMacBook-Air:java (develop)]$ javac ~/Downloads/algs4.jar com/chm/algorithms/SortCompare.java
javac: 无效的标记: /Users/Downloads/algs4.jar
用法: javac <options> <source files>
-help 用于列出可能的选项 //这里没报错了
[@deMacBook-Air:java (develop)]$ tree
.
└── com
└── chm
├── algorithms
│ ├── Example.java
│ ├── Insertion.java
│ ├── Selection.java
│ └── SortCompare.java
没报错了,但是没编译成功!没有.class文件。为什么?
这就涉及到结论(2)了,请返回结论,大声朗读几遍结论(2)
使用-cp 意味着指定了java 或者javac去哪里找所编译的类依赖的.jar包,同时也意味着需要指定所编译的类所以来的类的路径。看一下这个
-cp path or -classpath path
Specifies where to find user class files, and (optionally) annotation processors
and source files. This class path overrides the user class path in the CLASSPATH
environment variable. If neither CLASSPATH, -cp nor -classpath is specified, then
the user class path is the current directory. See Setting the Class Path.
这里的class files表明去哪里找.java文件;
这里的source files表明去哪里找.jar文件
所以
javac -cp ~/Downloads/algs4.jar SortCompare.java
是不够的,还需要显式的指定被编译的类所在的包路径。因为-cp 表明你要手动指定所有目录,而不是让编译命令自动用”当前“的路径。
好了。介绍结束了,过程有点啰嗦,结论很简洁。