为什么Java的InputStream.read函数要返回int型而实际上它只读一个byte

本文通过一次因混淆DataInputStream中的read与readInt方法引发的问题,深入解析这两种读取方法的区别及应用场景。阐述了read方法用于读取单个字节并返回int类型的原因,以及readInt方法遇到文件末尾时的异常处理机制。

今天为了一个低级错误查了一个小时,这个低级错误就是把DataInputStream的read和readInt当作一个函数用。两个都叫read的函数返回值也都是int,Bill Joy(Java之父)应该为我的错误负部分责任!

 

DataInputStream的read()正如我们所知道是继承自InputStream的read(),作用是读取下一个byte。那为什么读byte却要返回int呢?我想这种“大材小用”的设计的最主要的考虑是-1,即正常的byte值返回为一个0-255之间的int,而当字节流结尾以-1表示。如果返回类型是byte就没法表示-1。

 

那如果readInt()读到结尾了怎么办?答案是抛出一个EOFException异常。

 

希望读了本日志的童鞋们不要再犯跟我一样的错误...

package com.example.skin; import android.content.Context; import android.graphics.Bitmap; import android.util.Log; import org.tensorflow.lite.DataType; import org.tensorflow.lite.Interpreter; import org.tensorflow.lite.support.common.ops.NormalizeOp; import org.tensorflow.lite.support.image.ImageProcessor; import org.tensorflow.lite.support.image.TensorImage; import org.tensorflow.lite.support.image.ops.ResizeOp; import org.tensorflow.lite.support.image.ops.ResizeWithCropOrPadOp; import org.tensorflow.lite.support.tensorbuffer.TensorBuffer; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; public class SkinClassifier { private static final String TAG = "SkinClassifier"; private static final int INPUT_SIZE = 224; // 模输入尺寸 private static final int NUM_CHANNELS = 3; // RGB通道 private static final int NUM_CLASSES = 3; // 分类类别数 private final Interpreter tflite; private final ImageProcessor imageProcessor; public SkinClassifier(Context context) throws IOException { // 1. 从ml文件夹加载模文件 ByteBuffer modelBuffer = loadModelFile(context, "skin_classifier.tflite"); // 2. 初始化Interpreter Interpreter.Options options = new Interpreter.Options(); options.setNumThreads(4); // 使用4个线程加速推理 tflite = new Interpreter(modelBuffer, options); // 3. 初始化图像处理器 imageProcessor = new ImageProcessor.Builder() .add(new ResizeWithCropOrPadOp(INPUT_SIZE, INPUT_SIZE)) .add(new ResizeOp(INPUT_SIZE, INPUT_SIZE, ResizeOp.ResizeMethod.BILINEAR)) .add(new NormalizeOp(0f, 255f)) // 归一化处理 .build(); // 打印输入输出张量信息 Log.i(TAG, "输入张量: " + tflite.getInputTensor(0).shape()[1] + "x" + tflite.getInputTensor(0).shape()[2] + "x" + tflite.getInputTensor(0).shape()[3]); Log.i(TAG, "输出张量: " + tflite.getOutputTensor(0).shape()[1]); } // 从ml文件夹加载模 private ByteBuffer loadModelFile(Context context, String modelName) throws IOException { String resourceName = modelName.replace(".tflite", "").toLowerCase(); int resId = context.getResources().getIdentifier( resourceName, "raw", context.getPackageName()); if (resId == 0) { throw new IOException("模资源未找到: " + resourceName); } try (InputStream inputStream = context.getResources().openRawResource(resId)) { byte[] modelBytes = new byte[inputStream.available()]; inputStream.read(modelBytes); // 修复方案:直接创建只读缓冲区 return (ByteBuffer) ByteBuffer.allocateDirect(modelBytes.length) .order(ByteOrder.nativeOrder()) .put(modelBytes) .asReadOnlyBuffer() // 在ByteBuffer上调用 .rewind(); // 重置位置指针 } } // 执行分类 public List<Category> classify(Bitmap bitmap) { if (bitmap == null) { Log.w(TAG, "输入Bitmap为空"); return new ArrayList<>(); } try { // 使用TensorBuffer直接处理 TensorImage tensorImage = new TensorImage(DataType.FLOAT32); tensorImage.load(bitmap); TensorImage processedImage = imageProcessor.process(tensorImage); // 自动处理输入输出 TensorBuffer inputBuffer = processedImage.getTensorBuffer(); TensorBuffer outputBuffer = TensorBuffer.createFixedSize( new int[]{1, NUM_CLASSES}, DataType.FLOAT32); // 执行推理 tflite.run(inputBuffer.getBuffer(), outputBuffer.getBuffer()); // 解析结果 return parseOutput(outputBuffer.getFloatArray()); } catch (Exception e) { Log.e(TAG, "分类错误", e); return new ArrayList<>(); } } public void close() { if (tflite != null) { tflite.close(); Log.i(TAG, "模资源已释放"); } } // 解析模输出 private List<Category> parseOutput(float[] probabilities) { List<Category> results = new ArrayList<>(); // 假设类别顺序: [0]油性, [1]干性, [2]中性 String[] labels = {"Dry", "Normal", "Oily"}; for (int i = 0; i < probabilities.length; i++) { results.add(new Category(labels[i], probabilities[i])); } // 按置信度排序 results.sort((c1, c2) -> Float.compare(c2.confidence, c1.confidence)); return results; } // 自定义分类结果类 public static class Category { public final String label; public final float confidence; public Category(String label, float confidence) { this.label = label; this.confidence = confidence; } } }上述为SkinClassifier的代码package com.example.skin; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class MainActivity extends AppCompatActivity { private static final int CAMERA_PERMISSION_CODE = 100; private static final int CAMERA_REQUEST_CODE = 101; private static final int PICK_IMAGE_REQUEST = 102; private String currentPhotoPath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 设置点击监听器 findViewById(R.id.takePhotoBtn).setOnClickListener(v -> checkCameraPermission()); findViewById(R.id.selectPhotoBtn).setOnClickListener(v -> { // 这里可以添加从相册选择照片的功能 Toast.makeText(this, "相册功能待实现", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, PICK_IMAGE_REQUEST); }); } private void checkCameraPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, CAMERA_PERMISSION_CODE); } else { openCamera(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == CAMERA_PERMISSION_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openCamera(); } else { Toast.makeText(this, "需要摄像头权限才能拍照", Toast.LENGTH_SHORT).show(); } } } private void openCamera() { Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (cameraIntent.resolveActivity(getPackageManager()) != null) { File photoFile = createImageFile(); if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, "com.example.skin.fileprovider", photoFile); cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(cameraIntent, CAMERA_REQUEST_CODE); } } } private File createImageFile() { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "SKIN_ANALYSIS_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = null; try { image = File.createTempFile( imageFileName, ".jpg", storageDir ); currentPhotoPath = image.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); } return image; } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) return; if (requestCode == CAMERA_REQUEST_CODE) { // 相机拍照处理 Intent analysisIntent = new Intent(this, AnalysisActivity.class); analysisIntent.putExtra("image_path", currentPhotoPath); startActivity(analysisIntent); } else if (requestCode == PICK_IMAGE_REQUEST && data != null) { // 相册选择处理 Uri selectedImage = data.getData(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query( selectedImage, filePathColumn, null, null, null); if (cursor != null && cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String imagePath = cursor.getString(columnIndex); cursor.close(); Intent analysisIntent = new Intent(this, AnalysisActivity.class); analysisIntent.putExtra("image_path", imagePath); startActivity(analysisIntent); //跳转到皮肤分析界面后可点击分享按钮,实现分享功能 } } } }上述为mainactivity的代码package com.example.skin; import android.annotation.SuppressLint; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; import com.example.skin.SkinClassifier; import java.io.File; import java.util.List; public class AnalysisActivity extends AppCompatActivity { private ImageView previewImage; private TextView resultText; private Button retryButton; private SkinClassifier classifier; private String imagePath; private Button homeButton; private Button shareButton; private String skinTypeResult = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_analysis); // 初始化UI previewImage = findViewById(R.id.previewImage); resultText = findViewById(R.id.resultText); retryButton = findViewById(R.id.retryButton); // 获取传递的图片路径 imagePath = getIntent().getStringExtra("image_path"); // 显示图片 Bitmap bitmap = BitmapFactory.decodeFile(imagePath); previewImage.setImageBitmap(bitmap); // 初始化分类器 initClassifier(); // 重拍按钮事件 retryButton.setOnClickListener(v -> finish()); // 绑定新按钮 homeButton = findViewById(R.id.homeButton); shareButton = findViewById(R.id.shareButton); // 设置监听器 homeButton.setOnClickListener(v -> returnToMain()); shareButton.setOnClickListener(v -> shareResults()); } private void initClassifier() { new Thread(() -> { try { classifier = new SkinClassifier(this); runOnUiThread(this::performAnalysis); } catch (Exception e) { runOnUiThread(() -> { resultText.setText("模加载失败"); resultText.setTextColor(Color.RED); }); } }).start(); } // 返回主界面功能 private void returnToMain() { Intent mainIntent = new Intent(this, MainActivity.class); mainIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清除Activity栈 startActivity(mainIntent); finish(); // 结束当前Activity } // 分享功能实现 private void shareResults() { // 1. 获取分享内容 TextView resultView = findViewById(R.id.resultText); TextView adviceView = findViewById(R.id.adviceText); String resultText = skinTypeResult + "\n\n" + resultView.getText().toString() + "\n\n" + adviceView.getText().toString(); // 2. 创建分享图片的URI Uri imageUri = FileProvider.getUriForFile( this, "com.example.skin.fileprovider", new File(imagePath) ); // 3. 创建分享Intent Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("image/*"); shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri); shareIntent.putExtra(Intent.EXTRA_TEXT, resultText); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 4. 启动分享选择器 startActivity(Intent.createChooser( shareIntent, "分享皮肤分析结果" )); } @SuppressLint("SetTextI18n") private void performAnalysis() { new Thread(() -> { try { Bitmap bitmap = BitmapFactory.decodeFile(imagePath); if (bitmap == null) return; // 执行预测 long start = System.currentTimeMillis(); List<SkinClassifier.Category> results = classifier.classify(bitmap); long duration = System.currentTimeMillis() - start; runOnUiThread(() -> { if (results != null && !results.isEmpty()) { SkinClassifier.Category top = results.get(0); String skinType = getSkinTypeDisplayName(top.label); @SuppressLint("DefaultLocale") String resultMsg = String.format( "✅ 预测结果: %s\n\n" + "🔬 置信度: %.1f%%\n" + "⏱️ 耗时: %dms", skinType, top.confidence * 100, duration ); resultText.setText(resultMsg); resultText.setTextColor(getConfidenceColor(top.confidence)); // 添加个性化建议 addSkinCareAdvice(skinType); // 记录分享用的文本 skinTypeResult = "我的皮肤类分析结果:" + skinType; // 启用分享按钮 shareButton.setEnabled(true); } else { resultText.setText("⚠️ 无法识别皮肤类"); resultText.setTextColor(Color.YELLOW); // 禁用分享按钮 shareButton.setEnabled(false); } }); } catch (Exception e) { runOnUiThread(() -> { resultText.setText("❌ 分析失败: " + e.getMessage()); resultText.setTextColor(Color.RED); }); } }).start(); } private String getSkinTypeDisplayName(String type) { switch (type.toLowerCase()) { case "oily": return "油性肌肤"; case "dry": return "干性肌肤"; case "normal": return "中性肌肤"; default: return type; } } private int getConfidenceColor(float confidence) { if (confidence > 0.8f) return Color.parseColor("#4CAF50"); // 绿色 if (confidence > 0.6f) return Color.parseColor("#FFC107"); // 黄色 return Color.parseColor("#F44336"); // 红色 } private void addSkinCareAdvice(String skinType) { TextView adviceText = findViewById(R.id.adviceText); switch (skinType) { case "油性肌肤": adviceText.setText("✨ 护理建议:\n- 使用控油洁面产品\n- 每周1-2次深层清洁\n- 选择轻薄无油配方护肤品"); break; case "干性肌肤": adviceText.setText("✨ 护理建议:\n- 使用滋润洁面乳\n- 早晚使用高效保湿霜\n- 每周2-3次保湿面膜"); break; default: adviceText.setText("✨ 护理建议:\n- 保持日常清洁保湿\n- 注意防晒\n- 定期皮肤检查"); } adviceText.setTextColor(Color.parseColor("#2196F3")); // 蓝色 } }上述为analysisactivity的代码<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures/" /> <external-path name="external_files" path="."/> <external-files-path name="images" path="Pictures" /> </paths>上述为file_paths的代码仔细分析所给代码,用Android Studio运行该APP时拍照后出现跳转皮肤分析界面,显示tflite模加载失败,分析问题,并给出解决办法,tflite模是在Python环境下tflite-support为0.1.0a1版本下训练的,但在Android中打开模时没有元数据显示
10-25
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值