2012年9月6日
上篇博文中说明了如何通过命令编译及运行自己的Java文件。但是当前都是以项目为单位的,如何组织项目中的文件以及对项目的编译运行,是命令行编译主要解决的问题。
1. 项目组织 我们以下面的项目作为样例来说明:
Test/
|-- Test.jar
|-- classes
| |-- Main.class
| |-- OutterTest.class
| `-- inner
| `-- InnerTest.class
|-- run.py
|-- run.sh
`-- src
|-- Main.java
|-- OutterTest.java
`-- inner
`-- InnerTest.java
上面是一个项目:Test。其中,有两个文件夹,src用来放置所有的源代码,也就是.java文件;classes用来放置相应的.class文件。Test.jar是最终生成的jar文件,run.py和run.sh是项目的脚本文件。下面列出三个.java文件,只是简单的显示一句话:
// Main.java
import inner.*;
public class Main
{
public static void main(String[] args)
{
System.out.println("main: hello word!");
OutterTest out1 = new OutterTest();
out1.hello();
InnerTest in1 = new InnerTest();
in1.hello();
}
}
// OutterTest.java
public class OutterTest
{
public void hello()
{
System.out.println("Hello OutterTest!");
}
}
// InnerTest.java
package inner;
public class InnerTest
{
public void hello()
{
System.out.println("Hello InnerTest!");
}
}
注意上面的InnerTest类,它在package inner中,所以将InnerTest.java放在inner文件夹下,这样可以保证统一。在Eclipse中,新建一个类时,会让你填写package名,然后Eclipse会为你新新建一个相应的文件夹。
2. 项目编译 javac -d classes src/*.java src/inner/*.java 由上一篇知道,javac中-d表示”指定存放生成的类文件的位置“,也就是将生成的.class文件放在-d指定的文件夹中。需要指出的是,classes文件夹是手动建立的。
另外,javac还可以批量编译.java文件,上面的命令表示编译src目录下的所以.java文件、编译src/inner目录下的所有.java文件。这样就可以批量编译.java文件,并将生成的.class文件放在classes文件夹中。这里同样要指出一点,因为package inner的关系,会自动建立inner文件夹,并将所有package inner下的类文件.class放在classes/inner下。
3.项目打包 jar -cvf Test.jar -C classes/ . 这个命令将会把classes下的所有文件(包括.class和文件夹等)打包为Test.jar文件。
上篇博客中,介绍了参数-C的意义:-C 更改为指定的目录并包含其中的文件,如果有任何目录文件, 则对其进行递归处理。它相当于使用 cd 命令转指定目录下。
注意后面的"."的用法,jar可以递归的打包文件夹,"."表示是当前文件夹。如果执行命令“jar -cvf Test.jar .”,表示将当前目录下的所有文件及文件夹打包。所以上面的命令的意思就是“将classes下的所有文件打包为Test.jar”。
4.项目运行 java -cp Test.jar Main 通过上面的命令就可以执行Test.jar中的Main.class。其中,cp指定了jar文件的位置。
5. 脚本文件 通过上面的几步,我们就可以完成整个项目的编译和运行。而且,通过src和classes两个文件夹将源文件和目标文件分开,使项目比较整洁。但是如果每次编译、运行都要输入上面一系列命令,还是比较繁琐的,尤其当项目文件较多时,这时通过脚本文件管理整个项目是明智的选择。
下面是项目的脚本文件run.py
import os
import sys
if __name__ == "__main__":
ProjectJar = "Test.jar"
if sys.argv[1] == "c":
print("Compile program.")
src = "src/*.java src/inner/*.java"
os.system("javac -d classes " + src)
os.system("jar -cvf " + ProjectJar + " -C classes/ .")
if sys.argv[1] == "r":
print("Run program.")
os.system("java -cp " + ProjectJar + " Main")
print("Over!")
通过这个脚本文件,可以使用"python run.py c"完成项目的编译、打包;使用"python run.py r"运行项目。
通过这篇文章,我们已经了解了Java项目的管理方法,以及编译、打包、运行的命令行,最后介绍了使用脚本文件有效管理项目。
附件中包含整个项目,同时还包括一个run.sh,方便不熟悉python的人查看。
附件:
Test.tar
2012年8月22日
当前大部分开发者在开发Java程序时使用Eclipse,它可以方便的进行程序的编译、打包和运行。但是不使用IDE,在完全的命令行下进行Java开发者从未用过的。在命令行下进行开发不是用来在展现自己有多牛,而是通过命令行开发,可以对Java的编译、jar包等各个部分有一个深入了解。
下面的几篇博客将会对Java的编译、打包和运行方法由浅入深的进行介绍。
在这里使用的操作系统是Linux,并提供相应的shell和python脚本。
首先介绍一下三个常用的命令:javac、jar、java。每个命令都有不同的参数,这些参数的用法会详细介绍。
1. javac javac的功能是对java源代码进行编译,将后缀为.java的文件编译为.class的文件。javac的一般格式是
javac <选项> <源文件>
例如:
javac Main.java
会产生Main.class文件。
javac的常用选项有:
-classpath <路径> 指定查找用户类文件的位置
-cp <路径> 指定查找用户类文件的位置(与上面的选项一样,cp是classpath的简写)
-d <目录> 指定存放生成的类文件的位置
2.jar jar的功能是根据选项将指定的一些.class文件打包为一个jar包。jar的一般格式是
jar {ctxui}[vfm0Me] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...
例如,
jar cvf Test.jar Main.class Bar.class
它将Main.class和Bar.class打包为一个文件Test.jar。
jar命令的选项比较多,用到选项包括:
-c 创建新的归档文件
-t 列出归档目录
-x 从档案中提取指定的 (或所有) 文件
-u 更新现有的归档文件
-v 在标准输出中生成详细输出
-f 指定归档文件名
-m 包含指定清单文件中的清单信息
-e 为捆绑到可执行 jar 文件的独立应用程序,指定应用程序入口点
-C 更改为指定的目录并包含其中的文件,如果有任何目录文件, 则对其进行递归处理。
例如,
上例中的cvf参数,分别表示创建新的jar文件、创建时显示jar包的信息(可以执行看一下)、指定jar包名为Test.jar。
jar tf Test.jar 查看Test.jar的内容,其中t表示列出jar包内容,f指定jar包名
jar xf Test.jar 解压Test.jar文件
jar xf Test.jar Main.class 仅解压Test.jar中的Main.class文件
这里要指出的是,f/m/e都指定一个名称(jar包名, 清单文件名和入口点名称),相应的名称顺序与参数的顺序要一致。
3.java java的功能是执行应用程序。java的一般格式是
执行一个类: java [ options ] class [ argument ... ]
执行一个jar包:java [ options ] -jar file.jar [ argument ... ]
例如:
java Main
执行Main.class,注意上面没有.class后缀
java Test.jar
执行一个jar包,这个jar包中要指定了程序入口点(通过在MANIFEST.MF文件中指定)。
常用的java的选项:
-classpath<类搜索路径> 指定用户类文件的位置,可能为文件夹、zip、jar文件
总结
通过这篇内容,我们应用学会了如何使用javac编译自己的类,并使用java执行自己的类。但是关于打包的操作及jar的执行比较复杂,将在以后继续介绍。
2012年8月17日
Java中计算中文的MD5值
前几天的工作中,需要计算中文的MD5值,计算的函数接口及调用方式如下:
public static String getMD5(byte[] source);
String s = "中文编码";
String md5_value = getMD5(s.getBytes());
其中getBytes函数使用平台默认的字符集将string编码为byte序列。由于平台的中文编码方式可能不同,所以同一中文经过getBytes得到的二进制是不一样的。为保证每次得到的结果一致,或者使用指定的编码方式得到byte序列,应该在getBytes中使用参数。
String md5_value = getMD5(s.getBytes("utf-8"));
这样得到的值就是一样的。
2012年5月14日
crontab命令是Unix/Linux中的一个常用命令,用于设置周期性被执行的指令。没有用过的可以查一下,在运行服务端程序时会经常用到。 程序使用Java读取一个含有中文的文件,进行处理后,将结果写到一个结果文件中。在运行的程序时,出现了这样一个问题:在本地环境下,运行正确;但是当使用crontab定时执行时,得到的却是错误的结果。 经过一定的调研发现原来是对crontab的机制没有弄明白导致的。crontab运行程序时,会使用它自己的环境变量,这个环境变量与你本地的环境变量可能会不同。比如,在你本地情况下,环境变量的语言为en_US.UTF-8,而在crontab中,环境变量可能是zh_CN.GBK,这样会导致读写文件时——尤其是读写中文文件时内容编码错误,进而导致结果出错。 所以在使用Java读写文件时,一定要指定编码格式,而不是使用环境变量的格式。例如下面的语句用于读取utf-8格式的文件:
String encodeType = "utf-8";
File in_file = new File("test.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(in_file), encodeType));
网上也有关于修改crontab环境变量的方法,但是我不建议使用,因为一个系统可能是很多人共用的,修改crontab环境变量可能会引起他人程序的问题,最好修改自己的程序,保证它不依赖具体的环境变量。
2011年7月20日
很多同学和朋友经常向我抱怨说,算法已经成为他们学习和工作中的一个负担。在他们看来,算法只能应付一下考试,在学习中没有任何用处;也有人说,求职过程中考算法纯粹是浪费精力,在实际工作中没有用到任何算法。
在我的本科四年中,花了大量时间学习算法,参加算法比赛——成绩很滥,朋友们都说,和我的付出严重不成比例,但是我一点也不后悔花费时间学习算法。算法让我在平时的学习中应对各个科目都从容自如,学习起来事半功倍。
在平时的学习交流中,有时看其他人的代码相当别扭,不是看不懂(有时真看不懂),而是很纳闷,为什么要这么写呢,明明有很明显的顺畅的思路,干吗要写这么绕呢?我将我觉得正确的思路写出来的时候,对方也会觉得这样写顺畅了很多,代码也小了很多。
在一般应用中,不会说让你写一个KMP算法,或者给你设计一个动态规划的题目让你来解。但是如果你会这些算法,掌握了这种优化的思想,在平时的编写中会不知不觉的写出比较高效的代码。
有人说,现在中国的软件现状就是这样,不在乎你实现的有多快,只会在乎你有没有实现所有的功能点,随着硬件条件的改善,一般情况下都能满足要求。这样说也是没错,如果你甘愿只做一名码工,因为对于相当大的一部分软件,效率并不是最重要的,甚至第二、第三重要都排不上。但是对于企业级的应用和软件的核心部分,往往效率是非常重要的。
如果将编程和写作类比的话,可以进行这个的比较。编程必须会语言C/C++、Java、Python等,写作也一定要会一个语言汉语、英语、法语,当然计算机语言有效率的优劣以及适合的应用的场景,而自然语言没有这种属性。算法修养之于编程就像文学修养之于创作,没有一定的文学修养就写不出好的作品,同样没有良好的算法修养也创造不出好的代码。数据结构就像写作中的排比句等,它们是骨架,结构化编程就像是写作中的段落,它们使得层次分明。
在静静的夜晚,明月透过窗子,照进小屋,顿时一种异样的情怀涌上心头。这时有下面几种人,第一种,嚎啕大哭,“妈妈……”;第二种,潸然泪下,“我嚓,想家了是不”;第三种,沉思状,“有首诗可以表达这个情怀”,拿起全唐诗,查找了整晚,夜尽天明时,惊呼“找到了,原来是‘举头望明月,底头思故乡’啊,我记得有的。”;第四种,徘徊低吟“举头望明月,底头思故乡”;第五种,心中油然而生,“独在异乡为异客,明月千里寄乡思”。
同样地,这也反映了一个程序员算法修养的几种境界。第一种,菜鸟,不会表达自己的思想,这种多见于初学语言的人,只会写hello world之类程序的人;第二种,入门,可以表达出自己的想法,但是不懂什么算法,只是凭自己对计算机语言的理解写程序,当然写出的程序功能点可能不少,但是不完善,潜在的bug很多;第三种,有一定的算法基础,但是没有掌握完全,写程序时有高效的意识,对一些核心程序,看着资料也能写出相应的代码,但是不能活学活用;第四种,有不错的算法修养,写出的代码清晰高效,可胜任关键代码的编写;第五种,有很高的算法修养,理解了算法的精髓,自己可以得出算法供世人学习,一般是大师级人物。国内有相当一部分程序员就是处于第二阶段,他们也有稳定的工作,甚至不错的收入,但是总感觉遇到了瓶颈,自己的能力踌躇不前。
有人说,现在各种库都有,那些代码都非常高效,我只要熟练使用这些库就行了,不用学习算法。可以达到熟练使用几个库的程序员也算是第三阶段,但是要想熟练驾驭更多的库,将库函数熟练的使用,没有一定的算法基础是不行的。比如,如果你想熟练使用STL库,那么对各种容器和算法都应该有一定的认识,什么时候使用list,为什么快排不能应用于list?要想回答这些问题,必须掌握初步的算法。
对于想要在技术上有一定作为的程序员,没有算法修养,就如同金庸小说中的学武之人不学习内功,它可以将外功学的炉火纯清,凭着这份功力可以走遍半个江湖,但是一旦遇到高手就挂了;反之如果有内功修为,学习外功可学一知十,触类旁通,内功修为越高将来的成就越大。
学习算法吧!
2011年1月21日
最近做数据时,使用神经网络建模。在神经网络中,会用到激发函数(activation function)。
典型的激发函数有Sigmod函数:
双曲正切函数:
这两个都涉及到指数函数,在C中,为求指数函数,使用exp()函数。
在数次出错后找到问题,原来是我的指数值过大,数据中有时会出现超过1000的数字,这导致在求值过程中,即使使用double型,也使得结果溢出。
解决方法是定义一个指数函数,当指数值超过一定界限便指定一个相对无穷大的值,这样也符合数学定义。在我的处理中,将界限设定为15,当该值大于15时,返回3000000;当界限值小于-15时,返回0。
2010年4月11日
在配置文件共享时,对没有经验的用户会出现很多问题,这里介绍三点注意事项:
1. 首先打开菜单中“工具”->“文件夹选项”->“查看”,确认“使用简单文件共享”没有被选上;
2. 右键“我的电脑”,“管理”->“系统工具”->“本地用户和组”->“用户”,右键“Guest”,“属性”,在“常规”选项卡中,取消“账户已停用”;
3. 打开“控制面板”->“管理工具”->“本地安全策略”,打开“本地策略”->“用户权利指派”,双击“拒绝从网络访问这台计算机”,确认其中的“guest”已删除。
本文针对XP用户,其它用户可参照。
2009年4月29日
赋值运算符和复制构造函数都是用已存在A的对象来创建另一个对象B。不同之处在于:赋值运算符处理两个已有对象,即赋值前B应该是存在的;复制构造函数是生成一个全新的对象,即调用复制构造函数之前B不存在。
CTemp a(b); //复制构造函数,C++风格的初始化
CTemp a=b; //仍然是复制构造函数,不过这种风格只是为了与C兼容,与上面的效果一样
在这之前a不存在,或者说还未构造好。
CTemp a;
a=b; //赋值运算符
在这之前a已经通过默认构造函数构造完成。
我觉得将赋值运算符称为“
赋值构造函数”是错误的,构造函数发生在对象创建时期,而赋值是运算符,发生在“运算”时期,赋值运算前,对象已经构造完成,所以不能称之为“构造函数”。
一家之言!!