amp@java

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  99 随笔 :: 0 文章 :: 228 评论 :: 0 Trackbacks

2012年4月8日 #

当年是从CSDN博客迁过来的,因为那里很不稳定,那时候这里很火的;
前段时间发现,Blogjava的登录页面居然没有验证码了,图片显示错误,无法登录,预感这个地方要凉,只好到处找替代,但没找到好用的博客,github是一个选项,但似乎不是很适合做博客。
现在终于又可以登录了,不过首页居然只剩一篇文章,我以为数据都没了,登录发现还是有的,不过太让人不放心了。
posted @ 2018-08-18 22:14 amp@java 阅读(143) | 评论 (0)编辑 收藏

 昨天下午开始,之前用得好好的USB鼠标,突然不能用了,找到个PS/2鼠标,却发现主板没有鼠标的PS/2口,只有键盘的PS/2口,真是奇葩,幸好在用的键盘是PS/2口的,费了九牛二虎之力,用键盘操作,Win+R,compmgmt.msc进入计算机管理,上下箭头移到设备管理器,发现右边的USB控制器前全是黄色叹号,尝试卸载再安装,却怎么也装不上,重新启动发现鼠标在启动Windows前还是亮灯的,但到了显示Windows徽标的时候灯就灭了,应该不是硬件问题,而是驱动问题。
为了进一步证实硬件没问题,找了个安装系统的U盘,插进去启动,能够正常使用鼠标,于是目标就聚焦在找回驱动上。
开始折腾:
1、首先是找官方驱动啊,我这电脑是老机,AMD7系列主板,找了半天,这个主板驱动并没有包含USB控制器,因为USB控制器都是Windows自带的驱动,下载了一个南桥驱动,安装后并没有效果;
2、Windows自带的驱动原来都是放在C:\Windows\System32\DriverStore\FileRepository下,USB控制器相关的驱动,就在usbport.inf_amd64_xxxxxxxxxx文件夹里,xxxxxxxxx是一串16进制数字,悲催的是,安装这个驱动时要不提示找不到指定文件,要不说第三方INF没有签名;
3、自己折腾搞不定,找个软件吧,第一个想到的是驱动之家官方的驱动精灵,下载下来发现是个全家桶啊,什么腾讯管家,金山毒霸,浏览器首页修改一应俱全,而且没有鼠标点击,用TAB键根本移动不到取消框,只好默认全部安装了,装完启动,检测,提示系统自带驱动缺失,于是回车修复,但每次修复完,重新检测还是那样,而且没有提示USB控制器驱动安装有问题,有些功能用键盘无法操作,不知道是不是还有哪里可以操作一下,于是又搜了一下,如何用键盘代替鼠标,居然真的找到了!
4、按WIN键,输入设置,回车,打开设置主页:

移到“轻松使用”,进去后左边选择“鼠标”,在右边启用“使用数字小键盘在屏幕上移动鼠标”(按空格键开关),最好把三个开关都打开,如果没有启用CTRL键加速功能,鼠标移动非常慢:

好了,现在可以用小键盘移动鼠标了;
5、继续回到驱动精灵,再次修复,还是不行啊,这个东西除了带来一堆垃圾,什么作用都没有!于是把它带来的垃圾以及它自己卸载了。
6、似乎360也有一个驱动大师,于是就下载了一个,这个倒是很纯洁,但是功能太弱,完全没发现问题;
7、刚才搜索“安装驱动 找不到指定的文件”时,发现一个论坛提到了这个,是驱动人生的论坛,好像还有解决方案,但要注册才能下载,难道驱动人生可以解决?于是就下载了一个驱动人生,安装的时候还是附带全家桶,不过现在可以用键盘移动鼠标取消了,只安装了驱动人生自己,跟刚才两个软件不同的是,它提示USB外设驱动没有安装,于是点修复,结果反反复复出现等待光标,就是无法完成;
8、之前又搜索到,驱动安装的日志在C:\Windows\INF\setupapi.dev.log文件里,于是打开这个文件,发现Driver package failed signature verification. Error = 0xE000022F,驱动程序签名有问题,所以不能安装成功;
9、Windows10有个高级启动选项是禁用强制驱动签名,如何进入高级启动选项呢?以前是按F8,现在不行了,要在设置里面,更新和安全,恢复,高级启动,立即重启,然后设置疑难解答,高级启动,再重启,就可以进入高级启动菜单,按7进入禁止强制驱动签名模式,重启后再用驱动人生修复,果然成功了;
10、打开驱动人生下载目录,DTLFolder\DriversDownLoad,发现它下载了USB驱动目录是USB_10.0.10240.16384_WHQL_107049,里面文件如下:

除了第一个xml文件是程序自己用的外,其他都是USB驱动用到的文件,点右键发现那几个sys文件,除了usbohci.sys和usbuhci.sys外,其他都有数字签名,而usbuhci.sys我这里没用到,问题就出在usbohci.sys上:


11、难道是驱动人生替换了未签名的文件?图谋不轨?为了验证一下,我又下载了一个Windows10安装光盘(版本是当前使用的1703版):
cn_windows_10_multiple_editions_version_1703_updated_march_2017_x64_dvd_10194190.iso
12、怎么提取安装光盘中的内置驱动?找了一下,原来Windows的安装盘从VISTA起,不再使用XP以前的I386目录和Drivers.cab文件存放驱动,而是打包在一个Install.wim镜像文件中,要找到驱动文件,必须用工具提取,这个工具就是Imagex.exe,微软自己做的命令行工具,但是我的电脑上没有,于是下载了一个64位的,放在C盘根目录,通过如下命令即可提取:
c:\IMAGEX_x64 /mount f:\sources\install.wim 5 i:\1703
其中F盘是在iso文件上点右键,打开方式选“Windows资源管理器”打开后虚拟出来的盘符,其实就相当于系统自带的虚拟光驱,I盘是硬盘,用来存放挂载的镜像文件,5是选择挂载哪一个版本的Windows(多合一版),如果不知道要挂哪个,把这个数字改成100,会显示xml文件内容,并提示找不到这个索引号,从xml文件内容就能找到各版本的信息,然后再重新挂载正确的即可。这个挂载其实是个解压缩过程,时间很长,提取完之后就跟安装好了Windows一样,目录都列好了。
今天又发现另一个图形化的工具,Dism++,比这个操作更简单。Dism是PowerShell内置的命令,也是与镜像有关,也能挂载提取,但用了一下似乎提示权限有问题,Dism++是国内开源爱好者自己开发的图形化工具,与Dism没有关系。
13、好了,原版的Windows已经准备好,进入Windows\System32\DriverStore\FileRepository目录,搜索usbohci.sys,在usbport.inf_amd64_8e5f608c0111283d目录下,点右键一看,也是没签名的:
这不是坑爹吗?你自己带的东西都没签名,然后又不给用!!!!
14、有点怀疑是Windows自己更新的时候修改了一些策略,导致之前可以用的不能用了,为了再次验证,又继续下载了两个版本的Windows10安装光盘,分别是早期的1607和最新的1709,找到usbohci.sys,如下所示:
从左到右依次为1607,1703,1709,均未签名,基本可以判断是Windows自己抽风了。
15、昨天晚上搞到12点多,搞定鼠标后没有重启测试,今天早上开机,果然发现鼠标又不能用了,因为我没有选择禁用强制签名选项来启动,系统发现那个没签名的驱动,就把它停了,尝试卸载,结果再装也装不上,于是只好又设置高级启动,重新禁止强制签名,进入系统,装上驱动,恰好这时Windows又在后台偷偷摸摸地更新,不知道更新了啥,让我重启。
16、重启之后,奇迹出现,刚才明明提示没有签名强制安装的驱动,现在居然正常启动也没问题了,而且查看驱动详情的时候出现了矛盾的一幕:
外面显示数字签名者:未经数字签名,里面的sys文件又显示数字签名者是Microsoft Windows,然而进入C:\Windows\System32\drivers目录,找到usbohci.sys,点右键,却发现并没有数字签名:
好吧,你开心就好,反正不要再禁我的鼠标就行……
感谢这次蛋疼的折腾之旅,让我知道了驱动程序来自哪里,安装日志在哪里,哪个软件坑爹又没用,怎么玩安装盘,怎么用键盘操作鼠标,怎么进入高级启动界面……
我为什么要知道这些??????????????为微软的疏忽买单啊!!!!!
啥都不说了,它又提示我重启了,不知道又有什么奇迹会发生……
posted @ 2017-11-19 12:43 amp@java 阅读(1142) | 评论 (0)编辑 收藏

 每次换手机,把旧手机的数据迁移到新手机就是个很麻烦的事情,幸好最近华为的“手机克隆”APP越来越强大,居然能够把微信的聊天记录包括图片原封不动地迁移到新手机上,以前用微信自带的聊天记录转移功能只能转移文字信息,图片视频全部丢失,不知道现在的怎么样。手机克隆还能把SD卡的内容也转移过来,基本满足了需要。
但是要把手机上的东西传到电脑就没那么简单了,现在已经没有了以前的大容量存储模式,只能选择MTP模式,这种模式其实不是一个完整的文件系统,有很多限制,所以一些传统的软件读取不到,例如FastCopy是用不了的,用Windows自带的文件管理器来复制,开始计算时间就要等很久,中间出了个错就前功尽弃;还有通过手机上的APP访问电脑共享的方式,在手机上复制也可以,但是同样会莫名其妙卡死,FTP同理,折腾了好久,还是觉得自己动手比较好。
MTP协议在维基百科里解释得比较清楚:https://en.wikipedia.org/wiki/Media_Transfer_Protocol ,简单点说就是:
1、不是以块设备的形式访问,跟U盘不同;
2、只能单线程访问,不能同时进行多个操作,只能一个接一个;
3、控制权在设备上,对外展示的内容由设备决定;
4、默认不能直接对文件进行部分修改,只能复制过来修改完再复制回去,但Android对协议做了扩展,能够修改部分文件内容;
5、在Linux上有些软件能够把它挂载为文件系统,这样其他软件就能像访问普通文件系统一样访问了,但是Windows下似乎没有。

不过有人开发了一个在Windows下通过JNI实现的Java库jmtp,项目托管在Google Code,被墙了,但是GitHub有人fork了一个,可以下载下来,我下载的是https://github.com/reindahl/jmtp
里面包含了C++的代码和Java的代码,以及两个已经编译好的dll文件,分别用于Win32和Win64,把其中一个dll文件放在工程目录下,再把Java源代码加入工程中即可使用,文档比较简陋,但是看test目录下的MtpTest.java,基本可以摸到如何使用了,这个协议比较简单,其实没什么功能,我要的只是把文件复制到电脑上。
根据MtpTest.java,稍微修改一下,做个递归复制即可把手机上的所有文件复制到电脑上:
package test;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.rmi.server.SocketSecurityException;
import java.util.ArrayList;


import jmtp.PortableDevice;
import jmtp.PortableDeviceFolderObject;
import jmtp.PortableDeviceManager;
import jmtp.PortableDeviceObject;
import jmtp.PortableDeviceStorageObject;


public class TestApp {

   

    
public static void main(String[] args) {
        
// TODO Auto-generated method stub
        
        ArrayList
<PortableDeviceStorageObject> devices = new ArrayList<>();

        PortableDeviceManager manager 
= new PortableDeviceManager();

        
for (PortableDevice device : manager) {
            System.out.println(device);
            device.open();
            
            
// Iterate over deviceObjects
            for (PortableDeviceObject object : device.getRootObjects()) {
                String storageName
=object.getName();
                System.out.println(storageName);

                
// If the object is a storage object
                if (object instanceof PortableDeviceStorageObject) {
                    PortableDeviceStorageObject storage 
= (PortableDeviceStorageObject) object;
                    System.out.println(storage.getChildObjects().length);
                    
for (PortableDeviceObject child : storage.getChildObjects()) {
                            copyall(child,
"E:\\手机备份\\"+object.getName());
                    }
                }
            }

            device.close();
            System.out.println(size);
        }

  
  
    }
    
    
public static void copyall(PortableDeviceObject obj,String path) {

        if(obj instanceof PortableDeviceFolderObject) {
          

            String objName=obj.getName();
            
if(objName.contains(":")) {
                objName
=objName.replace(':''');
            }
            String newPath 
= path+"\\"+objName;
            System.out.println(
"创建文件夹:"+newPath);
            
            File file = new File(newPath);
            if(!file.exists()) {
                file.mkdirs();
            }
            for(PortableDeviceObject subObj:((PortableDeviceFolderObject) obj).getChildObjects()) {             
                copyall(subObj,newPath);
            }
        }
        
else {      
            
if(obj.getName().contains(":"))
                
return;
            System.out.println(
"开始复制文件到:"+path+"\\"+obj.getName());
            File file 
= new File(path);
            obj.copy(file.toPath());                     
            System.out.println(
"文件复制完成!");
        }
    }

}
其中发现有点问题:
1、Android设备文件名里是可以包含冒号(:)的,但Windows是不可以的,所以复制到这些文件的时候会有问题,于是遇到目录名这样就把它改为中文的冒号(:),但是遇到文件名这样就不行了,因为这个库的copy函数只需要指定目标目录,不需要指定目标文件名,所以这些文件只能放弃;
2、Android手机的MTP协议是由“媒体存储”这个系统APP控制的,有时候手机上可以看到的文件,通过MTP访问却怎么也看不到,重启手机也不行,应该就是这个APP没有更新数据,需要把它的系统数据清除掉,等它重建完重新访问就可以看到了,不过这个重建时间非常长,可以查看它数据占用的空间,刚清除之后会发现它占用的空间会不断增长,到了不增长的时候就是重建完了,就可以正常访问了;
3、这个库有时候还有点bug,有一次发现它读取到的文件和文件夹都没有了最后一个.后面的部分,所以总是卡住,重新插拔一下手机数据线又没问题了;
4、为了避免复制了半天结果发现不完整,又要重来,最好在复制前先统计一下文件大小,看看跟手机上看到的占用存储空间是不是一致,对于MTP设备上的文件,可以通过getSize函数得到大小,把上面复制操作改为大小累加即可,速度比复制快一些,不过由于小文件太多,也不会快很多。

把手机里的文件复制到电脑后,通过一些简单的分析,发现有很多其实是垃圾来的,也可以为手机空间清理提供参考,因为在电脑上分析起来比在手机上方便一些。例如一些视频APP的缓存,居然超过1G,占用了宝贵的内部存储空间,之前一直都没发现,通过电脑里的按文件大小搜索才发现。
posted @ 2017-11-17 14:54 amp@java 阅读(2211) | 评论 (0)编辑 收藏

09款老速腾,不支持USB和AUX,要听歌除了CD以外,就是刻录在CD上的MP3了,以前不知道用什么软件刻录了一张碟,能够完美地显示中文文件名和ID3信息,前几天用ImgBurn刻了一张,发现中文是乱码,开始以为是ID3信息显示乱码,于是下载了一个Mp3Tag,把所有ID3信息都清除,结果显示文件名依然乱码,重新写入ID3信息,发现ID3可以正常显示,但文件名还是乱码。
于是就把原来那张可以正常显示中文的碟拿来研究一下,发现它的ID3标签只是ID3v1,而后来重新写入的ID3是ID3v2.3,两个都可以正常显示中文,说明ID3信息是正常的,文件名乱码不是这个问题。
但是不知道用什么软件来显示光盘的文件系统,只能一次次摸索。
幸好有一张CD-RW可以反复尝试。
ImgBurn默认是使用ISO9660+UDF,而ISO 9660则使用最老的ISO 9660文件系统,也就是1988版本,文件名默认是不支持中文的,不知道是不是这个原因,于是就把文件系统改为ISO 9660+Joliet,如下图:

并且把ISO 9660标准改为1999:

可能是因为字符编码那里改为了ASCII,所以就好了。
后来又尝试只使用UDF文件系统,结果认不出碟。



所以,目前能够使用中文的环境其实就是:
ImgBurn使用ISO 9660 1999标准;
ID3使用v1或v2.3都可以。

posted @ 2017-02-10 09:17 amp@java 阅读(381) | 评论 (0)编辑 收藏

最近新部署了一个信息系统,厂家居然没有升级方案,所有数据都要重新输入,包括用户、角色等都要重新配置,真是操蛋。要是一个个录入简直是日狗了,这些用户在其他信息系统早已存在,但是每个都复制粘贴提交一遍也不是办法,于是就想用程序自动完成这些操作。步骤如下:
1、从其他信息系统的数据库导出用户信息,也可以直接从其他信息系统的界面把所有用户信息复制下来放在一个文本文件里,反正就是准备好数据源;
2、在需要录入用户信息的系统中,用人工操作的方式登录系统,并录入一个用户,同时用Wireshark抓包,查看整个过程要提交一些什么样的表单数据;
3、在程序中用httpclient提交同样的数据,完成登录,并从第1步的数据源中读取用户信息,然后循环提交录入用户所需的数据,完成用户的录入;
4、新系统没有默认的用户角色,是需要一个个修改的,是根据用户的ID来确定当前修改的用户,并且提交一个角色ID来进行设定,因此需要首先获取用户的ID,然后根据该ID来提交角色ID,而用户ID是通过用户列表页面获取到的,因此还需要通过正则表达式来获取所有用户的ID,然后循环提交角色ID,完成角色设定。

花了不少时间才搞定,有几个地方需要注意:
1、如果表单数据不包含中文,直接把表单的Name和Value加在HttpPost的URL的?后面即可,不需要专门建立NameValuePair,如下所示:
HttpPost httppost = new HttpPost("http://1.1.1.1/test/adduser?userid=abc&username=efg");
httpclient.execute(httppost);

但是,如果表单数据包含中文,例如姓名,用这种方式提交的表单数据会出现乱码,即使通过URLEncoder进行编码后再发也不行,必须建立NameValuePair,再加到HttpPost的Entity里面,如下所示:
HttpPost httppost = new HttpPost("http://1.1.1.1/test/adduser?userid=abc");
List
<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(
new BasicNameValuePair("username","张三"));
httppost.setEntity(
new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
httpclient.execute(httppost);

2、用于网页内容查找的正则表达式的使用方式一般为:
Pattern p = Pattern.compile(".*?abc(whattoget)123.*?");
Matcher m = p.matcher(line);
if(m.matches()){
     String whattoget 
= m.group(1);
}
".*?abc(whattoget)123.*?"就是一个正则表达式,如果用于匹配一行的时候,由于要查找的内容是在行中间,所以前后需要加上.*?,表示前后可以是任意字符,也可以什么都没有,而表达式中间的(whattoget)就表示一个group,编号为1,编号为0的group是整个匹配的字符串,找到之后提取group(1)即可得到想要的内容。
测试正则表达式是一项很麻烦的工作,不过有个很好的软件可以完成此工作:RegexBuddy,支持各种语言的正则表达式的调试。
学习正则表达式的宝典是《Mastering Regular Expression》。
posted @ 2016-01-12 15:54 amp@java 阅读(3567) | 评论 (1)编辑 收藏

今天遇到一个非常奇怪的问题,有台装XP的电脑,插上USB键盘没反应,还以为是键盘坏了,又找来两个不同型号的键盘,依然不行,又以为是USB接口坏了,结果在电脑启动的时候又可以按F2进入BIOS,这样就只有一个原因,Windows的驱动没装上了。
幸好这电脑还有传统的PS/2口,而且插上就能识别,否则连Windows都进不去,因为按Ctrl+Alt+Del没反应。
进去之后提示安装USB键盘驱动,到最后一步提示安装失败,原因是拒绝访问。
上网搜了一下,安装驱动拒绝访问的其中一个原因是注册表有个键的权限设置有问题,改过来即可,但是我打开注册表,连那个键都没找到,不是这个原因。
不过从这个解决方案中也知道了驱动安装的日志是在Windows目录下的setupapi.log文件里面,于是打开那个文件,发现每次安装都有两个拒绝访问的错误,但并没有说是注册表拒绝访问,在拒绝访问之前,还提到一个叫MlCoInst.dll的文件没有签名。
上网搜MlCoInst.dll,没有找到任何结果,在System32目录下找到它,看属性,果然没有签名,是个三无文件,不知道为什么每次安装驱动都要调用它。
日志里面还提到了“共同安装程序”,似乎和CoInst有点关联,于是又查了一下,原来安装驱动的时候可以通过调用“共同安装程序”来实现某些目的,例如修改驱动程序的签名状态,欺骗操作系统,这样就可以只安装一次驱动即可,不用每次插入都安装一次。
于是尝试把MlCoInst.dll删除,提示删除失败。
在注册表里面搜索MlCoInst.dll,把所有找到的键值都删除,再次插拔键盘,顺利安装完毕,删除MlCoInst.dll,也成功了,果然是它的问题。

这个应该是某个USB设备的驱动引进来的,而且修改了usb.inf,每次安装任何USB设备都要调用它,但它可能与Windows的签名机制有冲突,所以导致安装失败,真是坑爹!
posted @ 2015-04-02 17:04 amp@java 阅读(3576) | 评论 (0)编辑 收藏

做GUI程序的时候,通常有个后台工作线程在努力工作,但是中间又需要一些暂停,而关闭程序的时候,必须立即结束那个线程,退出程序,也有的时候需要停止后台线程,但不关闭程序。例如,做一个目录监控程序,发现目录中有文件的时候,执行一定的操作,执行完之后没有文件了,就要暂停一下,过几秒或几分钟再次检测,这时候就要对线程进行暂停操作,如果在暂停的时候,用户要关闭程序,就必须马上停止线程,如果用户需要暂停检测,按下某个按钮后,需要让线程马上停止,但再次按下某个按钮,线程又必须马上开始。

以前我都是通过检测停止标记和用Thread.sleep(time)来完成的,后台线程的每次循环都要检查停止标记,如果发现停止标记已设定,就不再循环,退出线程,在线程内部,如果需要暂停,就执行Thread.sleep(time)。通过把线程的setDaemon(true)方法,还可以让线程作为后台线程,当图形界面关闭后,线程也自动退出。

但是,这种方式有个问题,如果我需要在图形界面上点击按钮来停止线程,但并不退出程序,而点击按钮的时候线程正处于sleep状态,就对它没有任何办法,只能让它醒过来再操作,如果sleep的时间比较长,例如1分钟,那么点击按钮之后,用户最多要等1分钟才能把线程停下来。当然,Thread对象有个interrupt方法,但是已经被标记为过期,一般不建议使用了。感谢评论中watchzerg的提醒,Thread的interrupt()并没有标记为过期,可以按照他的说法来操作,更为简单。

怎么让线程能暂停,又能随时叫醒呢?原来Java里最原始的对象Object就自带此功能。

每个Object都有wait(time)和notify()方法,前者就是让拥有该Obejct的线程处于暂停状态,后者则让线程马上唤醒,通过这两个方法,就能够满足上述的所有要求。

首先,建立一个同步对象:
Object syncObj = new Object();

然后在线程中需要暂停的地方,调用该对象的wait(time)方法:
synchronized (syncObj) {
       syncObj.wait(60*1000);
}

在图形界面的按钮监听事件中,对该对象执行notify()方法:
        button_1.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {

                thread.setStop(true);
                synchronized (syncObj) {
                        syncObj.notify();
                 }
                //为了等待线程退出,还可以加上以下语句:
                thread.join();

            }
        });

posted @ 2015-03-10 16:52 amp@java 阅读(7679) | 评论 (2)编辑 收藏

Eclipse有个功能就是把整个项目打包成一个可执行的Jar文件,里面包含了所有项目引用了的库,如果电脑上安装了JRE,直接双击就可以启动,看起来很方便,如下图所示:

可以选择把所有类库打包进去,也就是Jar里面还包含一堆Jar:

还可以生成ant脚本:


生成的jar文件,可以直接通过java -jar xx.jar启动,简单快捷。


然而,这样导出来的可执行jar,启动速度却非常慢,这跟包的大小有关。有个项目导出来的包有40MB,结果启动需要近一分钟,在性能差的电脑上,甚至要几分钟,就是你执行完命令后,没有任何界面显示,但是查看任务管理器发现java进程的CPU占用率在浮动,说明正在努力启动,过了一段时间之后界面突然显示出来,简直让人崩溃。可是在Eclipse里面运行,却是一点就开。

我开始尝试缩小导出的包。但是Eclipse的导出对话框并没有提供需要打包哪些库的选项,都是默认把所有库都打包进去,但是它可以生成ant脚本,可以通过编辑ant脚本的方式来减少不必要的库。经过多次尝试,终于把40MB的包缩成了17MB,启动速度快了一些,但是依然需要半分钟以上。

后来觉得,能不能不打包直接运行呢?于是把导出来的包用压缩软件解压,再把里面包含的jar包继续解压,最后得到一堆没有压缩的class文件,再通过指定classpath的方式,直接运行程序入口class,发现启动速度和在Eclipse里面一样,一点就开。

打包的好处是部署简单,只需要一个文件,但带来的缺点实在不能忍,打散的方式部署起来稍微难一点,但是启动速度够快,对普通用户来说,这个才是最重要的。

当然,还有一种方式是像Eclipse那样,启动时显示一个图片,底下一个进度条显示启动进度,不过这样也加大了工作量,而且每次都要等那进度条,实际上也很烦。
posted @ 2015-03-10 15:07 amp@java 阅读(5134) | 评论 (0)编辑 收藏

     摘要: JMF太老了,各种问题得不到解决,Oracle也没再升级过,如果能找到新东西,最好能把它扔掉。最近OpenCV比较火,还有人用Java封装了OpenCV,成立了JavaCV项目,通过改造VideoInput这个基于C语言的项目,能够用Java来调用摄像头,JMF可以扔掉了。如果想测试,非常简单,把那些编译好的jar文件放入Build Path即可,如果是在Windows X86环境下,则只需要把带...  阅读全文
posted @ 2015-02-15 11:41 amp@java 阅读(10417) | 评论 (7)编辑 收藏

其实我觉得用java结合SQL来做同样的事情更简单,但是EXCEL的好处是不用编程,虽然里面有好多陷阱,但是掌握之后能够熟练运用还是比每次都编个程序更简单。VLOOKUP函数的使用比较复杂,搞了好久都不明白,终于找到了下面这篇文章,解决了很多问题。

以下是转载文章,原来的出处不知道是哪里,应该是来自

http://www.excelpx.com
但具体的地址不详。



VLOOKUP函数是Excel中几个最重函数之一,为了方便大家学习,兰色幻想特针对VLOOKUP函数的使用和扩展应用,进行一次全面综合的说明。本文为入门部分

     一、入门级

      VLOOKUP是一个查找函数,给定一个查找的目标,它就能从指定的查找区域中查找返回想要查找到的值。它的基本语法为:

      VLOOKUP(查找目标查找范围返回值的列数精确OR模糊查找)

下面以一个实例来介绍一下这四个参数的使用

     例1:如下图所示,要求根据表二中的姓名,查找姓名所对应的年龄。  

   公式:B13 =VLOOKUP(A13,$B$2:$D$8,3,0)  

   参数说明:

       1 查找目标:就是你指定的查找的内容或单元格引用。本例中表二A列的姓名就是查找目标。我们要根据表二的“姓名”在表一中A列进行查找。

        公式:B13 =VLOOKUP(A13,$B$2:$D$8,3,0)   

       2 查找范围(VLOOKUP(A13,$B$2:$D$8,3,0) : 指定了查找目标,如果没有说从哪里查找,EXCEL肯定会很为难。所以下一步我们就要指定从哪个范围中进行查找。VLOOKUP的这第二个参数可以从一个 单元格区域中查找,也可以从一个常量数组或内存数组中查找。本例中要从表一中进行查找,那么范围我们要怎么指定呢?这里也是极易出错的地方。大家一定要注 意,给定的第二个参数查找范围要符合以下条件才不会出错:

        A 查找目标一定要在该区域的第一列。本例中查找表二的姓名,那么姓名所对应的表一的姓名列,那么表一的姓名列(列)一定要是查找区域的第一列。象本例中,给定的区域要从第二列开始,即$B$2:$D$8,而不能是$A$2:$D$8。因为查找的“姓名”不在$A$2:$D$8区域的第一列。

        B 该区域中一定要包含要返回值所在的列,本例中要返回的值是年龄。年龄列(表一的D列)一定要包括在这个范围内,即:$B$2:$D$8,如果写成$B$2:$C$8就是错的。

       3 返回值的列数(B13 =VLOOKUP(A13,$B$2:$D$8,3,0))。这是VLOOKUP第3个参数。它是一个整数值。它怎么得来的呢。它是“返回值”在第二个参数给定的区域中的列数。本例中我们要返回的是“年龄”,它是第二个参数查找范围$B$2:$D$8的第3列。这里一定要注意,列数不是在工作表中的列数(不是第4列),而是在查找范围区域的第几列。如果本例中要是查找姓名所对应的性别,第3个参数的值应该设置为多少呢。答案是2。因为性别在$B$2:$D$8的第2列中。

       4 精确OR模糊查找(VLOOKUP(A13,$B$2:$D$8,3,0)  ),最后一个参数是决定函数精确和模糊查找的关键。精确即完全一样,模糊即包含的意思。第4个参数如果指定值是0或FALSE就表示精确查找,而值为1 或TRUE时则表示模糊。这里兰色提醒大家切记切记,在使用VLOOKUP时千万不要把这个参数给漏掉了,如果缺少这个参数默为值为模糊查找,我们就无法精确查找到结果了。  

      好了,关于VLOOKUP函数的入门级应用就说到这里,VLOOKUP函数可不只是这么简单的查找,我们讲的还只是1/10的用法。其他的没法在一篇文章中说明。敬请期待“VLOOKUP的使用方法-进阶篇”吧。

 

上一讲咱们学习了VLOOKUP的基本用法和示例,本讲将介绍VLOOKUP在使用中的一些小技巧。

Excel函数速成教程全系列(包括VLOOKUP函数,IF函数,offset函数,sumif函数等66个函数)预计6月初全部录制完成,现已在淘宝开始预订(8折优惠),地址:http://item.taobao.com/item.htm?id=17500884347

一、VLOOKUP多行查找时复制公式的问题

    VLOOKUP函数的第三个参数是查找返回值所在的列数,如果我们需要查找返回多列时,这个列数值需要一个个的更改,比如返回第2列的,参数设置为2,如 果需要返回第3列的,就需要把值改为3。。。如果有十几列会很麻烦的。那么能不能让第3个参数自动变呢?向后复制时自动变为2,3,4,5。。。   

    在EXCEL中有一个函数COLUMN,它可以返回指定单元格的列数,比如

         =COLUMNS(A1) 返回值1

         =COLUMNS(B1) 返回值2

   而单元格引用复制时会自动发生变化,即A1随公式向右复制时会变成B1,C1,D1。。这样我们用COLUMN函数就可以转换成数字1,2,3,4。。。 

    例:下例中需要同时查找性别,年龄,身高,体重。

   

     公式:=VLOOKUP($A13,$B$2:$F$8,COLUMN(B1),0)

  公式说明:这里就是使用COLUMN(B1)转化成可以自动递增的数字。

二、VLOOKUP查找出现错误值的问题。

    1、如何避免出现错误值。

     EXCEL2003 在VLOOKUP查找不到,就#N/A的错误值,我们可以利用错误处理函数把错误值转换成0或空值。

      即:=IF(ISERROR(VLOOKUP(参数略)),"",VLOOKUP(参数略)

     EXCEL2007,EXCEL2010中提供了一个新函数IFERROR,处理起来比EXCEL2003简单多了。

     IFERROR(VLOOKUP(),"") 

    2、VLOOKUP函数查找时出现错误值的几个原因

      A、实在是没有所要查找到的值

      B、查找的字符串或被查找的字符中含有空格或看不见的空字符,验证方法是用=号对比一下,如果结果是FALSE,就表示两个单元格看上去相同,其实结果不同。

      C、参数设置错误。VLOOKUP的最后一个参数没有设置成1或者是没有设置掉。第二个参数数据源区域,查找的值不是区域的第一列,或者需要反回的字段不在区域里,参数设置在入门讲里已注明,请参阅。

     D、数值格式不同,如果查找值是文本,被查找的是数字类型,就会查找不到。解决方法是把查找的转换成文本或数值,转换方法如下:

     文本转换成数值:*1或--或/1

     数值转抱成文本:&""  

     VLOOKUP函数的初级篇就说到这里了,咱们下一讲将介绍VLOOKUP的模糊查找有、反向查找等。

 

 在学习了VLOOKUP的入门和初级篇后,本文将带将大家学习VLOOKUP的进阶篇:VLOOKUP的模糊查找。

    一、字符的模糊查找    

        在A列我们知道如何查找型号为“AAA”的产品所对应的B列价格,即:

    =VLOOKUP(C1,A:B,2,0)

       如果我们需要查找包含“AAA”的产品名称怎么表示呢?如下图表中所示。

     公式=VLOOKUP("*"&A10&"*",A2:B6,2,0)  

    公式说明:VLOOKUP的第一个参数允许使用通配符“*”来表示包含的意思,把*放在字符的两边,即"*" & 字符 & "*"。

   二、数字的区间查找

      数字的区间查找即给定多个区间,指定一个数就可以查找出它在哪个区间并返回这个区间所对应的值。

    在VLOOKUP入门中我们提示VLOOKUP的第4个参数,如果为0或FALSE是精确查找,如果是1或TRUE或省略则为模糊查找,那么实现区间查找正是第4个参数的模糊查找应用。

    首先我们需要了解一下VLOOKUP函数模糊查找的两个重要规则:

    1、引用的数字区域一定要从小到大排序。杂乱的数字是无法准确查找到的。如下面A列符合模糊查找的前题,B列则不符合 

    

    2、模糊查找的原理是给一定个数,它会找到和它最接近,但比它小的那个数。详见下图说明。

    

   最后看一个实例: 

    例:如下图所示,要求根据上面的提成比率表,在提成表计算表中计算每个销售额的提成比率和提成额。

    

   公式:=VLOOKUP(A11,$A$3:$B$7,2)

   公式说明:

    1、上述公式省略了VLOOKUP最后一个参数,相当于把第四个参数设置成1或TRUE。这表示VLOOKUP要进行数字的区间查找。

    2、图中公式中在查找5000时返回比率表0所对应的比率1%,原因是0和10000与5000最接近,但VLOOKUP只选比查找值小的那一个,所以公式会返回0所对应的比率1%。

 

 前言:前面我们分别学习了VLOOKUP函数的入门、初级和进阶篇。今天我们学习VLOOKUP函数的高级应用部分-VLOOKUP函数的数组应用。(本文由兰色幻想原创,转载请注明转自excel精英培训

 一、VLOOKUP的反向查找。

    一般情况下,VLOOKUP函数只能从左向右查找。但如果需要从右向右查找,则需要把区域进行“乾坤大挪移”,把列的位置用数组互换一下。

    例1:要求在如下图所示表中的姓名反查工号。

     

    公式:=VLOOKUP(A9,IF({1,0},B2:B5,A2:A5),2,0)

    公式剖析:

        1、这里其实不是VLOOKUP可以实现从右至右的查找,而是利用IF函数的数组效应把两列换位重新组合后,再按正常的从左至右查找。

        2、IF({1,0},B2:B5,A2:A5)这是本公式中最重要的组成部分。在EXCEL函数中使用数组时(前提时该函数的参数支持数组),返回的结 果也会是一个数组。这里1和0不是实际意义上的数字,而是1相关于TRUE,0相当于FALSE,当为1时,它会返回IF的第二个参数(B列),为0时返 回第二个参数(A列)。根据数组运算返回数组,所以使用IF后的结果返回一个数组(非单元格区域):{"张一","A001";"赵 三","A002";"杨五","A003";"孙二","A004"}

 二、VLOOKUP函数的多条件查找。

      VLOOKUP函数需要借用数组才能实现多条件查找。

     例2:要求根据部门和姓名查找C列的加班时间。

     分析:我们可以延用例1的思路,我们的努力方向不是让VLOOKUP本身实现多条件查找,而是想办法重构一个数组。多个条件我们可以用&连接在一起,同样两列我们也可以连接成一列数据,然后用IF函数进行组合。

    公式:{=VLOOKUP(A9&B9,IF({1,0},A2:A5&B2:B5,C2:C5),2,0)}

    公式剖析:

       1、A9&B9 把两个条件连接在一起。把他们做为一个整体进行查找。

       2、A2:A5&B2:B5,和条件连接相对应,把部分和姓名列也连接在一起,作为一个待查找的整体。

       3、IF({1,0},A2:A5&B2:B5,C2:C5) 用IF({1,0}把连接后的两列与C列数据合并成一个两列的内存数组。按F9后可以查看的结果为:

       {"销售张一",1;"销售赵三",5;"人事杨五",3;"销售赵三",6}

       4、完成了数组的重构后,接下来就是VLOOKUP的基本查找功能了,另外公式中含有多个数据与多个数据运算(A2:A5&B2:B5),,所以必须以数组形式输入,即按ctrl+shift后按ENTER结束输入。

     三、VLOOKUP函数的批量查找。

     VLOOKUP一般情况下只能查找一个,那么多项该怎么查找呢?

     例3 要求把如图表中所有张一的消费金额全列出来

     分析:经过前面的学习,我们也有这样一个思路,我们在实现复杂的查找时,努力的方向是怎么重构一个查找内容和查找的区域。要想实现多项查找,我们可以对查找的内容进行编号,第一个出现的是后面连接1,第二个出现的连接2。。。

     公式:{=VLOOKUP(B$9&ROW(A1),IF({1,0},$B$2:$B$6&COUNTIF(INDIRECT("b2:b"&ROW($2:$6)),B$9),$C$2:$C$6),2,)}

     公式剖析:

        1、B$9&ROW(A1) 连接序号,公式向下复制时会变成B$9连接1,2,3

        2、给所有的张一进行编号。要想生成编号,就需要生成一个不断扩充的区域(INDIRECT("b2:b"&ROW($2:$6)),然后在这个逐行扩充的区域内统计“张一”的个数,在连接上$B$2:$B$6后就可以对所有的张一进行编号了。

       3、IF({1,0}把编号后的B列和C组重构成一个两列数组

     通过以上的讲解,我们需要知道,VLOOKUP函数的基本用法是固定的,要实现高级查找,就需要借助其他函数来重构查找内容和查找数组。

     至此VLOOKUP函数从入门到高级的四篇VLOOKUP函数使用教程全部结束了,VLOOKUP函数在数组运算中还有着其他应用,但只是配角了,所以本系列不再介绍。由于笔者水平有限,不免有错漏之处,请大家多多指点。

posted @ 2014-12-16 09:10 amp@java 阅读(510) | 评论 (0)编辑 收藏

JMF(Java Media Framework)是Java平台使用摄像头、麦克风等媒体设备的应用程序框架,但到了2.1.1e就不再更新,在Windows 7 X64上还能正常运行,只是安装的界面让你感觉回到了Windows98的时代。


不过年代久远的东西,虽然还能用,但可能会遇到一些奇怪的问题,折腾了两个月,发现了两个比较大的问题:

第一个是在Windows 7 x64上提示摄像头初始化失败的问题。这个问题很奇怪,电脑刚开机的时候可以顺利找到一次摄像头并正常操作,但是第二次就会提示摄像头初始化失败。有人提出的解决方法是安装一个叫ManyCamera的程序,这个程序可以把一个摄像头供多个程序同时使用,其实就等于中间加了一层转换,效果会有点差别,免费版还会加上水印,要求比较高的人可能会不爽,但是目前找不到其他办法。

第二个是在程序中找不到摄像头,不光找不到摄像头,其他媒体设备通通找不到,使用以下语句:
vector = CaptureDeviceManager.getDeviceList ( null )
按照文档说明是返回所有媒体设备,但每次vector都是null。
在Eclipse中运行又能正常,导出成jar之后运行就会找不到摄像头。
原因在于找不到jmf.properities文件,该文件包含所有检测到的媒体设备的信息,最简单的解决方法就是把JMF安装目录下lib子目录中的jmf.properties文件复制到最后运行的jar所在的目录,不过如果摄像头改过的话,重新检测后要把新的文件复制到jar目录,因为检测到的媒体设备信息都会存放在JMF安装目录里面的jmf.properties文件里。

StackOverflow里面有个人对这个问题解释得比较清楚:
http://stackoverflow.com/questions/8768142/java-capturedevicemanagergetdevicelist-is-empty


虽然问题解决了,但是还是不明白为何在Eclipse中可以正常运行,导出jar后运行却找不到摄像头,即使把JMF所有jar和lib目录都加入系统的CLASSPATH环境变量里还是不行。


另外,JMF安装程序会自动把它的jar和lib目录加入系统的%CLASSPATH%环境变量,但是如果你卸载了再重新安装到其他目录,并不会改变%CLASSPATH%的值,需要手动修改。不过这个环境变量似乎没啥用处。
最好不要把JMF安装到默认的Program Files目录,可能会运行不了,安装到短目录会比较保险,它似乎还是只认Dos时代的8.3目录结构,但偏偏又默认安装到Program Files里面。
posted @ 2014-07-01 10:33 amp@java 阅读(7267) | 评论 (0)编辑 收藏

其实我也搞不懂Windows的域,反正能用就行了。
但是最近有一台客户端的时间改不了,总是提示特权级不够,按理说应该是组策略限制了,但是我把那台计算机从包含该组策略的OU中移出来,还是不行,这就奇怪了,难道组策略不是指针对OU里面的成员的吗?百思不得其解啊,最后只能在BIOS里面把时间改了。
今天发现我自己的电脑设置不了屏幕保护,也是组策略限制了,然后把我的用户和计算机都移出了组策略应用的OU,结果发现还是设置不了,这下肯定是组策略的应用上有问题了。
搜索了半天发现有个叫rsop.msc的管理工具,可以看到某用户在某计算机上应用的组策略,结果发现我还是应用之前的组策略,但是计算机配置和用户配置前面都有个红叉,右键-属性-错误信息里面显示:
Title
由于下面列出的错误,组策略结构 失败。

系统找不到指定的路径。

注意: 由于 GP Core 失败,其它组策略组件没有一个处理了它们的策略。因此,其它组件的状态信息不可用。
好像是有某个组策略找不到,所以不能应用的意思。
然后想起来在域名下面有个“新建组策略”,但是没有做过任何配置,应该是有人手贱点了一下新建按钮加进去的,于是把它删除,还是不行。
重启了一下客户端,居然好了,时间也可以改了,屏幕保护也可以改了,一切都按计划进行。


莫名其妙~~
posted @ 2014-06-23 16:11 amp@java 阅读(1336) | 评论 (0)编辑 收藏

现在的手机摄像头动辄几百万上千万像素,如果电脑需要用到摄像头又没有的话,不妨用手机的摄像头代替。

我是在做一个电脑二维码识别器的时候,因为原来的摄像头太差,从而想到用一台淘汰的Android手机来代替。

这类应用应该不少,我首先找到的是一个叫DraoidCam的应用,装好之后发现免费版没法调整分辨率,于是放弃之。

然后又找到了一个国内做的免费软件,叫魅色,非常简单,支持USB和WiFi连接方式,如果是USB连接的话,打开USB调试模式之后,运行电脑的客户端,手机上就自动装上了App并且自动运行,可以调整分辨率,不过最高只有640*480,帧率不到10,不过已经能够满足我的需求了。

然后就可以像普通PC摄像头一样使用了,在JMF里面也能找到,于是就可以被Java调用了。经测试,效果比原来的PC摄像头好多了。

软件主页:http://www.libfetion.org/meise/
posted @ 2014-06-05 15:35 amp@java 阅读(1714) | 评论 (4)编辑 收藏

     摘要: TabActivity在API 13(Android 3.2)被标记为过期,需要使用Fragment来实现,Fragment是Android 3.0引入的一个概念,主要就是为了适应各种不同的屏幕大小(手机、平板电脑)。Android 4.1发布时,google还发布了一个Android Support v4的包,用于Android 1.6以上的系统兼容新的特性,其中包括Fragment。为了在低于...  阅读全文
posted @ 2012-12-27 19:07 amp@java 阅读(15628) | 评论 (0)编辑 收藏

SQL Server 2000的导入导出功能还是不错的,支持各种各样的数据库,但是却有好多奇怪的bug,不能直接操作,几乎每一步都要上网搜索,最后搞定了,一定要记下来:
1、在同一台电脑上装好SQL Server 2000的客户端和Oracle 10g的客户端,并分别设置好到源数据库(SQL Server 2000数据库)和目标数据库(Oracle 数据库)的连接,两个数据库都有图形界面的企业管理器,很容易设置好;
2、在控制面板-管理工具-数据源里添加一个DSN,驱动程序选择类似“Oracle in OraClient10g_home1”的,确定之后输入Data Source Name(随意),Description(随意),TNS Service Name(在企业管理器里设置好的连接名),User ID(用户名),然后按Test Connection测试是否连接成功,成功之后点OK;
3、在SQL Server 2000的企业管理器里,在任意一个表上点右键,所有任务,导出数据,在目的里选择“Oracle in OraClient10g_home1”,用户/系统DSN里面就会出现刚才设置好的DSN名字,选中,然后输入用户名密码,点两次下一步就会出现选择源表和视图对话框
4、这里要注意的是,勾上源中的某个表,在目的里面就会出现"用户名"."表名"的默认选项,如果你刚才使用的Oracle用户名是小写的话,这里也会是小写,一定要改成大写,否则会提示该用户名不存在

目的也可以使用Microsoft OLE DB Provider for Oracle,在属性里面设置服务器名称为Oracle的TNS名称,用户名和密码输入Oracle用户名和密码,测试连接通过即可,后面的步骤都一样。

如果出现以下错误:

OLE DB 提供程序 'MSDAORA' 报错。

[OLE/DB provider returned message: 未找到 Oracle 客户端和网络组件。这些组件是由 Oracle 公司提供的,是 Oracle 8i (或更高) 客户软件安装的一部分。

 

在安装这些组件之前,将无法使用此提供程序。]

OLE DB 错误跟踪[OLE/DB Provider 'MSDAORA' IDBInitialize::Initialize returned 0x80004005:  


就要修改注册表,有人已经作出了详细的修改说明,在这里可以看到:
http://www.cnblogs.com/autumn/articles/splinkedserver.html

我把那个表也贴在这里:
Oracle Client  Microsoft Windows NT、
Oracle Microsoft Windows 95、
Client Windows 98 和 Windows 98 SE
Microsoft Windows 2000/XP/2003
7.x [HKEY_LOCAL_MACHINE\SOFTWARE
\Microsoft\TransactionServer\Local Computer\My Computer]
"OracleXaLib"="xa73.dll"
"OracleSqlLib"="SQLLib18.dll"
"OracleOciLib"="ociw32.dll

 

[HKEY_LOCAL_MACHINE\SOFTWARE
Microsoft\MSDTC\MTxOCI]
"OracleXaLib"="xa73.dll"
 "OracleSqlLib"="SQLLib18.dll"
 "OracleOciLib"="ociw32.dll"
 
8.0 [HKEY_LOCAL_MACHINE\SOFTWARE
\Microsoft\Transaction Server
\Local Computer\My Computer]
"OracleXaLib"="xa80.dll"
"OracleSqlLib"="sqllib80.dll"
"OracleOciLib"="oci.dll"
 
[HKEY_LOCAL_MACHINE\SOFTWARE
 \Microsoft\MSDTC\MTxOCI]
 "OracleXaLib"="xa80.dll"
 "OracleSqlLib"="sqllib80.dll"
"OracleOciLib"="oci.dll"
8.1 [HKEY_LOCAL_MACHINE\SOFTWARE
\Microsoft\Transaction Server
\Local Computer\My Computer]
"OracleXaLib"="oraclient8.dll"
"OracleSqlLib"="orasql8.dll"
"OracleOciLib"="oci.dll"
 
[HKEY_LOCAL_MACHINE\SOFTWARE
 \Microsoft\MSDTC\MTxOCI]
"OracleXaLib"="oraclient8.dll"
"OracleSqlLib"="orasql8.dll"
"OracleOciLib"="oci.dll"
 
9.0 [HKEY_LOCAL_MACHINE\SOFTWARE
\Microsoft\Transaction Server
\Local Computer\My Computer]
"OracleXaLib"="oraclient9.dll"
"OracleSqlLib"="orasql9.dll"
"OracleOciLib"="oci.dll"
[HKEY_LOCAL_MACHINE\SOFTWARE
 \Microsoft\MSDTC\MTxOCI]
"OracleXaLib"="oraclient9.dll"
"OracleSqlLib"="orasql9.dll"
"OracleOciLib"="oci.dll"
 
10.0 [HKEY_LOCAL_MACHINE\SOFTWARE
\Microsoft\Transaction Server
\Local Computer\My Computer]
"OracleXaLib"="oraclient10.dll"
"OracleSqlLib"="orasql10.dll"
"OracleOciLib"="oci.dll"
[HKEY_LOCAL_MACHINE\SOFTWARE
 \Microsoft\MSDTC\MTxOCI]
"OracleXaLib"="oraclient10.dll"
"OracleSqlLib"="orasql10.dll"
"OracleOciLib"="oci.dll"
 
posted @ 2012-04-28 10:22 amp@java 阅读(2562) | 评论 (0)编辑 收藏

当读写二进制文件,或者要把非标准长度的整数与标准长度的整数互相转换时,就要用到大量的位操作,虽然看起来很简单,实际上里面却有很多细节很容易出错。

首先,Java有些标准跟C/C++是不同的:

1、Java采用高字节在前的方式读写数据,例如要把一个4字节的int数值写入文件时,它是按照从高字节到低字节的顺序写入的,读取的时候也是这样读出来。
而C/C++则采用平台相关的方式,在Windows平台采用低字节在前的方式,在Linux/Unix平台则采用高字节在前的方式。
如果Java要读取C/C++创建的二进制文件,就要注意这个问题,最好先搞清楚原来的文件是采用哪种方式创建的。网络通信也要注意。

2、Java没有无符号数,无论byte,short,int,long都是有符号整数,而C/C++有个unsigned关键字可以设置一个数值为无符号数。

3、Java的整数基本数据类型就是byte,short,int,long这几个,长度分别为1,2,4,8字节,C/C++可以用typedef定义各种数据类型。

第二,Java是采用补码来存放整数的。
有时候觉得补码的定义有些奇怪,实际上可以这样理解:

把一个整数从0一直往上加1,加到溢出就变成了负数的最小值,然后再继续加1,最后又能回到0,实际上就是一个轮回。
例如一个byte类型的整数,一共有8位,能表示256个数值,采用补码的话数值范围就是-128~127,表示方法如下:
0        0000 0000
1        0000 0001
.
.
126    0111 1110
127    0111 1111
-128   1000 0000
-127   1000 0001
.
.
-1       1111 1111
0         0000 0000

第三、不同长度的整数转换。
如果是从较短的数转成较长的数,很简单,如果是正数就在高字节补0,如果是负数就在高字节补1。
例如byte的127转为short的127:
byte:0111 1111
short:0000 0000 0111 0111
byte的-127转为short的-127
byte:1000 0001
short:1111 1111 1000 0001
如果是从较长的数转成较短的数,实际上就是把高位都截断了,所以转出来的数值可能完全不是一回事了。
例如short的256转为byte:
short:0000 0001 0000 0000
byte: 0000 0000
把256变成了0
short的-255转成byte:
short:1111 1111 0000 0001
byte:0000 0001
把-255变成了1

第四、位运算操作符及它们的优先级
Java的位运算操作符包括:~非,|按位或,&按位与,^按位异或,<<左移,>>右移,>>>右移左侧补0
各种运算符的优先级如下表所示:
优先级
运算符
结合性
1
() [] .
从左到右
2
! +(正) -(负) ~ ++ --
从右向左
3
* / %
从左向右
4
+(加) -(减)
从左向右
5
<< >> >>>
从左向右
6
< <= > >= instanceof
从左向右
7
== !=
从左向右
8
&(按位与)
从左向右
9
^
从左向右
10
|
从左向右
11
&&
从左向右
12
||
从左向右
13
?:
从右向左
14
= += -= *= /= %= &= |= ^= ~= <<= >>= >>>=
从右向左
根据该表可以看到,位运算操作符的优先级各有不同,分别为:
1、~
2、>> << >>>
3、&
4、^
5、|
另外需要特别注意的是,除了~,其他位运算操作的优先级都低于加减,所以要记得以下语句是返回32而不是7!
1<<2+3
还有就是&、^、|的优先级都是低于逻辑操作符的,因此下面的语句会编译出错,幸好Java不像C那样对所有大于1的值都认为是真,否则下面的语句也能编译通过,但可能与你的意图不太一样,可能调试半天才发现。
if(3&1>0)
如果记不清楚,还是按照你的意图加上括号最保险。

第五、字节数组与整数之间的转换
为了把一个整数存入文件,或者从文件中读取一个整数,需要经常在字节数组和整数之间转换,这个过程要用到大量的位运算。
首先需要记住的是,在参与所有运算前,Java都会把byte、short类型的值都转换成int,然后再对转换后的int进行操作。例如下面的语句会编译出错:
byte a=10,b=20,c;
c=a+b;

因为a和b在相加前都被转成了int,最后得到的结果是个int类型的值,如果要赋给byte类型的c,必须显式地进行类型转换,即把第二句改为:
c=(byte)(a+b)

这一点很关键,因为对于一个最高位为1的byte类型的整数(负数),在运算之前它会被强制转换成int类型,根据上面所说的第三点,其实就是往前面的三个高字节补上1,这样一来,它在参与位运算的过程中,就不仅仅是它本身的8个bit参与了,实际上连前3个字节的24个bit(均为1)也参与了。例如有一个整数i=1082163328,它的二进制表示为:
01000000 10000000 10000000 10000000
分为4个字节存储,除了第一个字节是正数外,其余3个字节均为负数。假如用a代表最高字节的值,用b代表其他三个字节的值,如果按照通常的理解,你可能会这样得到i的值:
i=(a<<24)+(b<<16)+(b<<8)+b

如果a和b都是正数,上面的等式是成立的,但是在这个例子里,却是错的,因为上式中的a和b都已经被强制转换成了int类型再参加运算,实际上
a=00000000 00000000 00000000 01000000
b=11111111 11111111 11111111 10000000
i=01000000 00000000 00000000 00000000+11111111 10000000 00000000 00000000+11111111 11111111 10000000 00000000+11111111 11111111 11111111 10000000
最后得到的结果是1065320320,不是原来的值了。
为了不让byte在强制转换成int的过程加入了我们不想要的高位1,我们需要把它跟0xff进行与操作,i的值应该这样运算:
= ( ( a& 0xff ) << 24 ) +( ( b & 0xff ) << 16 ) + ( ( b & 0xff ) << 8 ) + ( b & 0xff )

注意,因为&和<<的优先级都低于+,所以上面的括号是不能少的。不过由于跟0xff与操作之后,其余24位都变成了0,因此可以把+改为|操作,因为任何值与0进行或操作都得到本身:
= ( a & 0xff ) << 24 | ( b & 0xff ) << 16 | ( b & 0xff ) << 8 | ( b & 0xff )

由于<<的优先级高于|,所以省了一些括号。最高字节可以不与0xff进行与操作,因为它转换成int后左边增加的3个字节都在左移24位时被去掉了:
= a << 24 | ( b & 0xff ) << 16 | ( b & 0xff ) << 8 | ( b & 0xff )


把int转为字节数组的时候比较简单,直接右移截断即可:
byte[] b = new byte[4];
b[0= (byte) (i >> 24);
b[1= (byte) (i >> 16);
b[2= (byte) (i >> 8);
b[3= (byte) i;


第六、非标准长度整数的存储和读取
假如有两个变量,他们的值可以用12个bit来表示,如果我们用16bit的short类型来表示一个变量,那么两个变量就需要4个字节,而实际上它们只需要3个字节就能表示出来,如果存储空间比较有限,写入文件时可以把它们存放在3个字节里面,但是读写过程就需要进行转换。
在内存里,它们都是标准的数据类型:
short a,b;

写入文件时,我们用第一个字节和第二个字节的前半部分来表示a,把第二个字节的后半部分和第三个字节来表示b,即:
1:xxxx xxxx
2:xxxx yyyy
3:yyyy yyyy
x和y都表示一个bit,分别用来存放a和b。写入时先把a和b转为字节数组:
byte[] out = new byte[3];
out[
0= (byte) ( a >> 4 );//把a的高8位放在第一个字节
out[1= (byte) ( a << 4 );//先把a左移四位,在右边补上4个0,第二个字节的高4位就是a的低4位了,第二个字节的高4位已经生成,低4位还是0
out[1|= (byte) ( b >> 8 & 0x0f );//b右移8位,并与0x0f进行与操作,实际上就只保留了b的高4位,并且是在字节的低4位上,跟第二步得到的字节进行或操作,就生成了第二个字节
out[2= (byte) b;//把b的高4位截断就得到了低8位
然后再把这个字节数组写入文件,就可以用3个字节表示两个整数了。
读取:
=(short)( (out[0& 0xff<< 4 | ( out[1& 0xf0 )>>4);
= (short)((out[1& 0x0f<< 8 | ( out[2& 0xff));
posted @ 2012-04-08 16:56 amp@java 阅读(1817) | 评论 (2)编辑 收藏